PageSourceSearch

https://soci-316-w4.netlify.app/

html soci-316-w4.netlify.app collected 2026-10-03 11:25:26 UTC 306,776 bytes, 7,423 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en"><head>
3<script src="index_files/libs/clipboard/clipboard.min.js"></script>
vendor: 1 bytes, line 3
3
4<script src="index_files/libs/quarto-html/tabby.min.js"></script>
vendor: 1 bytes, line 4
4
5<script src="index_files/libs/quarto-html/popper.min.js"></script>
vendor: 1 bytes, line 5
5
6<script src="index_files/libs/quarto-html/tippy.umd.min.js"></script>
6
7<link href="index_files/libs/quarto-html/tippy.css" rel="stylesheet">
8<link href="index_files/libs/quarto-html/light-border.css" rel="stylesheet">
9<link href="index_files/libs/quarto-html/quarto-syntax-highlighting-bb62276ab45c84d9efd712f51ff97b5a.css" rel="stylesheet" id="quarto-text-highlighting-styles">
10<link href="index_files/libs/quarto-contrib/fontawesome6-6.7.2/all.min.css" rel="stylesheet">
11<link href="index_files/libs/quarto-contrib/fontawesome6-6.7.2/latex-fontsize.css" rel="stylesheet">
12<script src="index_files/libs/quarto-contrib/glightbox/glightbox.min.js"></script>
12
13<link href="index_files/libs/quarto-contrib/glightbox/glightbox.min.css" rel="stylesheet">
14<link href="index_files/libs/quarto-contrib/glightbox/lightbox.css" rel="stylesheet"><meta charset="utf-8">
15  <meta name="generator" content="quarto-1.10.18">
16
17  <meta name="author" content="Sakeef M. Karim  Amherst College     SOCIAL RESEARCH">
18  <title>Lecture 4 for SOCI—316 at Amherst College</title>
19  <meta name="apple-mobile-web-app-capable" content="yes">
20  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
21  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui">
22  <link rel="stylesheet" href="index_files/libs/revealjs/dist/reset.css">
23  <link rel="stylesheet" href="index_files/libs/revealjs/dist/reveal.css">
24  <style>
25    /* Default styles provided by pandoc.
26    ** See https://pandoc.org/MANUAL.html#variables-for-html for config info.
27    */
28    span.smallcaps{font-variant: small-caps;}
29    div.columns{display: flex; gap: 1.5em;}
30    div.column{flex: auto;}
31    @media screen {
32    div.columns{gap: min(4vw, 1.5em);}
33    div.column{overflow-x: auto;}
34    }
35    div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
36    ul.task-list{list-style: none;}
37    ul.task-list li input[type="checkbox"] {
38      width: 0.8em;
39      margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */ 
40      vertical-align: middle;
41    }
42    /* CSS for citations */
43    div.csl-bib-body { }
44    div.csl-entry {
45      clear: both;
46    }
47    .hanging-indent div.csl-entry {
48      margin-left:2em;
49      text-indent:-2em;
50    }
51    div.csl-left-margin {
52      min-width:2em;
53      float:left;
54    }
55    div.csl-right-inline {
56      margin-left:2em;
57      padding-left:1em;
58    }
59    div.csl-indent {
60      margin-left: 2em;
61    }  </style>
62  <link rel="stylesheet" href="index_files/libs/revealjs/dist/theme/quarto-b7529e71c7438fb8fba5788ff2fe02e1.css">
63  <link rel="stylesheet" href="logo.css">
64  
64<script>window.backupDefine = window.define; window.define = undefined;</script>
64<script src="https://cdn.jsdelivr.net/npm/katex@latest/dist/katex.min.js"></script>
64
65  
65<script>document.addEventListener("DOMContentLoaded", function () {
66 var mathElements = document.getElementsByClassName("math");
67 var macros = [];
68 for (var i = 0; i < mathElements.length; i++) {
69  var texText = mathElements[i].firstChild;
70  if (mathElements[i].tagName == "SPAN") {
71   katex.render(texText.data, mathElements[i], {
72    displayMode: mathElements[i].classList.contains('display'),
73    throwOnError: false,
74    macros: macros,
75    fleqn: false
76   });
77}}});
78  </script>
78
79  
79<script>window.define = window.backupDefine; window.backupDefine = undefined;</script>
79<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@latest/dist/katex.min.css">
80  <link href="index_files/libs/revealjs/plugin/quarto-line-highlight/line-highlight.css" rel="stylesheet">
81  <link href="index_files/libs/revealjs/plugin/reveal-menu/menu.css" rel="stylesheet">
82  <link href="index_files/libs/revealjs/plugin/reveal-menu/quarto-menu.css" rel="stylesheet">
83  <link href="index_files/libs/revealjs/plugin/reveal-chalkboard/font-awesome/css/all.css" rel="stylesheet">
84  <link href="index_files/libs/revealjs/plugin/reveal-chalkboard/style.css" rel="stylesheet">
85  <link href="index_files/libs/revealjs/plugin/quarto-support/footer.css" rel="stylesheet">
86  <style type="text/css">
87    .reveal div.sourceCode {
88      margin: 0;
89      overflow: auto;
90    }
91    .reveal div.hanging-indent {
92      margin-left: 1em;
93      text-indent: -1em;
94    }
95    .reveal .slide:not(.center) {
96      height: 100%;
97      overflow-y: auto;
98    }
99    .reveal .slide.scrollable {
100      overflow-y: auto;
101    }
102    .reveal .footnotes {
103      height: 100%;
104      overflow-y: auto;
105    }
106    .reveal .slide .absolute {
107      position: absolute;
108      display: block;
109    }
110    .reveal .footnotes ol {
111      counter-reset: ol;
112      list-style-type: none; 
113      margin-left: 0;
114    }
115    .reveal .footnotes ol li:before {
116      counter-increment: ol;
117      content: counter(ol) ". "; 
118    }
119    .reveal .footnotes ol li > p:first-child {
120      display: inline-block;
121    }
122    .reveal .slide ul,
123    .reveal .slide ol {
124      margin-bottom: 0.5em;
125    }
126    .reveal .slide ul li,
127    .reveal .slide ol li {
128      margin-top: 0.4em;
129      margin-bottom: 0.2em;
130    }
131    .reveal .slide ul[role="tablist"] li {
132      margin-bottom: 0;
133    }
134    .reveal .slide ul li > *:first-child,
135    .reveal .slide ol li > *:first-child {
136      margin-block-start: 0;
137    }
138    .reveal .slide ul li > *:last-child,
139    .reveal .slide ol li > *:last-child {
140      margin-block-end: 0;
141    }
142    .reveal .slide .columns:nth-child(3) {
143      margin-block-start: 0.8em;
144    }
145    .reveal blockquote {
146      box-shadow: none;
147    }
148    .reveal .tippy-content>* {
149      margin-top: 0.2em;
150      margin-bottom: 0.7em;
151    }
152    .reveal .tippy-content>*:last-child {
153      margin-bottom: 0.2em;
154    }
155    .reveal .slide > img.stretch.quarto-figure-center,
156    .reveal .slide > img.r-stretch.quarto-figure-center {
157      display: block;
158      margin-left: auto;
159      margin-right: auto; 
160    }
161    .reveal .slide > img.stretch.quarto-figure-left,
162    .reveal .slide > img.r-stretch.quarto-figure-left  {
163      display: block;
164      margin-left: 0;
165      margin-right: auto; 
166    }
167    .reveal .slide > img.stretch.quarto-figure-right,
168    .reveal .slide > img.r-stretch.quarto-figure-right  {
169      display: block;
170      margin-left: auto;
171      margin-right: 0; 
172    }
173  </style>
174  <link rel="icon" type="media/png" href="media/network.png">
175                 <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
176    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/min/vs/editor/editor.main.css">
177    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
178    
179  <style type="text/css">
180  .monaco-editor pre {
181    background-color: unset !important;
182  }
183
184  .qwebr-editor-toolbar {
185    width: 100%;
186    display: flex;
187    justify-content: space-between;
188    box-sizing: border-box;
189  }
190
191  .qwebr-editor-toolbar-left-buttons, .qwebr-editor-toolbar-right-buttons {
192    display: flex;
193  }
194
195  .qwebr-non-interactive-loading-container.qwebr-cell-needs-evaluation, .qwebr-non-interactive-loading-container.qwebr-cell-evaluated {
196    justify-content: center;
197    display: flex;
198    background-color: rgba(250, 250, 250, 0.65);
199    border: 1px solid rgba(233, 236, 239, 0.65);
200    border-radius: 0.5rem;
201    margin-top: 15px;
202    margin-bottom: 15px;
203  }
204
205  .qwebr-r-project-logo {
206    color: #2767B0; /* R Project's blue color */
207  }
208
209  .qwebr-icon-status-spinner {
210    color: #7894c4;
211  }
212
213  .qwebr-icon-run-code {
214    color: #0d9c29
215  }
216
217  body.quarto-light .qwebr-output-code-stdout {
218    color: #111;
219  }
220
221  body.quarto-dark .qwebr-output-code-stdout {
222    color: #EEE;
223  }
224
225  .qwebr-output-code-stderr {
226    color: #db4133;
227  }
228
229  body.quarto-light .qwebr-editor {
230    border: 1px solid #EEEEEE;
231  }
232
233  body.quarto-light .qwebr-editor-toolbar {
234    background-color: #EEEEEE;
235    padding: 0.2rem 0.5rem;
236  }
237
238  body.quarto-dark .qwebr-editor {
239    border: 1px solid #111;
240  }
241
242  body.quarto-dark .qwebr-editor-toolbar {
243    background-color: #111;
244    padding: 0.2rem 0.5rem;
245  }
246
247  .qwebr-button {
248    display: inline-block;
249    font-weight: 400;
250    line-height: 1;
251    text-decoration: none;
252    text-align: center;
253    padding: 0.375rem 0.75rem;
254    font-size: .9rem;
255    border-radius: 0.25rem;
256    transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
257  }
258
259  body.quarto-light .qwebr-button {
260    background-color: #EEEEEE;
261    color: #000;
262    border-color: #dee2e6;
263    border: 1px solid rgba(0,0,0,0);
264  }
265
266  body.quarto-dark .qwebr-button {
267    background-color: #111;
268    color: #EEE;
269    border-color: #dee2e6;
270    border: 1px solid rgba(0,0,0,0);
271  }
272
273  body.quarto-light .qwebr-button:hover {
274    color: #000;
275    background-color: #d9dce0;
276    border-color: #c8ccd0;
277  }
278
279  body.quarto-dark .qwebr-button:hover {
280    color: #d9dce0;
281    background-color: #323232;
282    border-color: #d9dce0;
283  }
284
285  .qwebr-button:disabled,.qwebr-button.disabled,fieldset:disabled .qwebr-button {
286    pointer-events: none;
287    opacity: .65
288  }
289
290  .qwebr-button-reset {
291    color: #696969; /*#4682b4;*/
292  }
293
294  .qwebr-button-copy {
295    color: #696969;
296  }
297
298  /* Style the code highlight lines */
299  body.quarto-light .qwebr-editor-highlight-line {
300    background-color: lightblue;
301  }
302
303  body.quarto-dark .qwebr-editor-highlight-line {
304    background-color: darkblue;
305  }
306
307  /* Style the modal pop-up */
308
309  /* The Modal (background) */
310  .qwebr-modal {
311    display: none; /* Hidden by default */
312    position: fixed; /* Stay in place */
313    z-index: 1; /* Sit on top */
314    left: 0;
315    top: 0;
316    width: 100%; /* Full width */
317    height: 100%; /* Full height */
318    overflow: auto; /* Enable scroll if needed */
319    background-color: rgb(0,0,0); /* Fallback color */
320    background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
321    padding-top: 60px;
322  }
323
324  /* Modal Content */
325  .qwebr-modal-content {
326    background-color: #fefefe;
327    margin: 5% auto; /* 15% from the top and centered */
328    padding: 20px;
329    border: 1px solid #888;
330    width: 80%; /* Could be more or less, depending on screen size */
331  }
332
333  .qwebr-modal-content-code {
334    max-height: 50vh;
335    min-height: 5vh;
336    overflow: scroll;
337    border: 1px solid #888;
338  }
339
340  /* The Close Button */
341  .qwebr-modal-close {
342    color: #aaa;
343    float: right;
344    font-size: 28px;
345    font-weight: bold;
346  }
347
348  .qwebr-modal-close:hover,
349  .qwebr-modal-close:focus {
350    color: black;
351    text-decoration: none;
352    cursor: pointer;
353  }
354
355  .qwebr-download-btn {
356    margin-top: 10px;
357    text-decoration: none !important;
358  }
359
360  /* Styling to download image that is created */
361
362  .qwebr-canvas-image {
363    position: relative;
364    display: inline-block;
365    margin: 10px;
366  }
367
368  .qwebr-canvas-image-download-btn {
369    position: absolute;
370    top: 10px;
371    right: 10px;
372    padding: 5px 10px;
373    background-color: #007BFF;
374    color: white;
375    border: none;
376    cursor: pointer;
377    display: none;
378  }
379
380  figure:hover .qwebr-canvas-image-download-btn {
381    display: block;
382  }
383
384  /* Custom styling for RevealJS Presentations*/
385
386  /* Reset the style of the interactive area */
387  .reveal div.qwebr-interactive-area {
388    display: block;
389    box-shadow: none;
390    max-width: 100%;
391    max-height: 100%;
392    margin: 0;
393    padding: 0;
394  }
395
396  /* Provide space to entries */
397  .reveal div.qwebr-output-code-area pre div {
398    margin: 1px 2px 1px 10px;
399  }
400
401  /* Collapse the inside code tags to avoid extra space between line outputs */
402  .reveal pre div code.qwebr-output-code-stdout, .reveal pre div code.qwebr-output-code-stderr {
403    padding: 0;
404    display: contents;
405  }
406
407  body.reveal.quarto-light pre div code.qwebr-output-code-stdout {
408    color: #111;
409  }
410
411  body.reveal.quarto-dark pre div code.qwebr-output-code-stdout {
412    color: #EEEEEE;
413  }
414
415  .reveal pre div code.qwebr-output-code-stderr {
416    color: #db4133;
417  }
418
419
420  /* Create a border around console and output (does not effect graphs) */
421  body.reveal.quarto-light div.qwebr-console-area {
422    border: 1px solid #EEEEEE;
423    box-shadow: 2px 2px 10px #EEEEEE;
424  }
425
426  body.reveal.quarto-dark div.qwebr-console-area {
427    border: 1px solid #111;
428    box-shadow: 2px 2px 10px #111;
429  }
430
431
432  /* Cap output height and allow text to scroll */
433  /* TODO: Is there a better way to fit contents/max it parallel to the monaco editor size? */
434  .reveal div.qwebr-output-code-area pre {
435    max-height: 400px;
436    overflow: scroll;
437  }
438
439  iframe.qwebr-output-code-browse {
440    width: 100%;
441
442    /*
443      TODO: How to make the height automatic according to the widget size,
444      or respect the quarto code block options?
445    */
446    min-height: 500px;
447  }
448
449  </style>
450  
450<script type="module">
451  // Document level settings ----
452
453  // Determine if we need to install R packages
454  globalThis.qwebrInstallRPackagesList = ['tidyverse', 'nycflights13', 'gapminder', 'palmerpenguins'];
455
456  // Specify possible locations to search for the repository
457  globalThis.qwebrPackageRepoURLS = ['https://repo.r-wasm.org/'];
458
459  // Check to see if we have an empty array, if we do set to skip the installation.
460  globalThis.qwebrSetupRPackages = !(qwebrInstallRPackagesList.indexOf("") !== -1);
461  globalThis.qwebrAutoloadRPackages = true;
462
463  // Display a startup message?
464  globalThis.qwebrShowStartupMessage = false;
465  globalThis.qwebrShowHeaderMessage = false;
466
467  // Describe the webR settings that should be used
468  globalThis.qwebrCustomizedWebROptions = {
469    "baseURL": "https://webr.r-wasm.org/v0.6.0/",
470    "serviceWorkerUrl": "",
471    "homedir": "/home/web_user", 
472    "channelType": "ChannelType.Automatic"
473  };
474
475  // Store cell data
476  globalThis.qwebrCellDetails = [{"code":"soci316 <- tibble(initials = c(\"SA\", \"CB\", \"AB\", \"AC\", \"SCL\", \"RK\", \"AL\", \"NM\", \"SO\", \"NT\", \"PV\", \"TW\", \"SX\"))\n\nset.seed(2026)\n\ntreatment <- soci316 |> slice_sample(prop = 0.5) |> pull(1)\n\nsoci316 |> \nmutate(condition = if_else(initials %in% treatment, \"Treatment\", \"Control\") |> fct_rev()) |> \narrange(condition, initials)","id":1,"options":{"autorun":"false","classes":"styled-output","comment":"","context":"interactive","dpi":72,"editor-font-scale":"0.5","editor-max-height":"","editor-quick-suggestions":"false","editor-word-wrap":"true","fig-cap":"","fig-height":5,"fig-width":7,"label":"unnamed-chunk-1","message":"true","out-height":"","out-width":"700px","output":"true","read-only":"false","results":"markup","warning":"true"}}];
477
478  </script>
478
479  
479<script type="module">
480  // Declare startupMessageQWebR globally
481  globalThis.qwebrStartupMessage = document.createElement("p");
482
483  // Verify if OffScreenCanvas is supported
484  globalThis.qwebrOffScreenCanvasSupport = function() {
485    return typeof OffscreenCanvas !== 'undefined'
486  }
487
488  // Function to set the button text
489  globalThis.qwebrSetInteractiveButtonState = function(buttonText, enableCodeButton = true) {
490    document.querySelectorAll(".qwebr-button-run").forEach((btn) => {
491      btn.innerHTML = buttonText;
492      btn.disabled = !enableCodeButton;
493    });
494  }
495
496  // Function to update the status message in non-interactive cells
497  globalThis.qwebrUpdateStatusMessage = function(message) {
498    document.querySelectorAll(".qwebr-status-text.qwebr-cell-needs-evaluation").forEach((elem) => {
499      elem.innerText = message;
500    });
501  }
502
503  // Function to update the status message
504  globalThis.qwebrUpdateStatusHeader = function(message) {
505    qwebrStartupMessage.innerHTML = `
506      <i class="fa-solid fa-spinner fa-spin qwebr-icon-status-spinner"></i>
507      <span>${message}</span>`;
508  }
509
510  // Function to return true if element is found, false if not
511  globalThis.qwebrCheckHTMLElementExists = function(selector) {
512    const element = document.querySelector(selector);
513    return !!element;
514  }
515
516  // Function that detects whether reveal.js slides are present
517  globalThis.qwebrIsRevealJS = function() {
518    // If the '.reveal .slides' selector exists, RevealJS is likely present
519    return qwebrCheckHTMLElementExists('.reveal .slides');
520  }
521
522  // Initialize the Quarto sidebar element
523  function qwebrSetupQuartoSidebar() {
524    var newSideBarDiv = document.createElement('div');
525    newSideBarDiv.id = 'quarto-margin-sidebar';
526    newSideBarDiv.className = 'sidebar margin-sidebar';
527    newSideBarDiv.style.top = '0px';
528    newSideBarDiv.style.maxHeight = 'calc(0px + 100vh)';
529
530    return newSideBarDiv;
531  }
532
533  // Position the sidebar in the document
534  function qwebrPlaceQuartoSidebar() {
535    // Get the reference to the element with id 'quarto-document-content'
536    var referenceNode = document.getElementById('quarto-document-content');
537
538    // Create the new div element
539    var newSideBarDiv = qwebrSetupQuartoSidebar();
540
541    // Insert the new div before the 'quarto-document-content' element
542    referenceNode.parentNode.insertBefore(newSideBarDiv, referenceNode);
543  }
544
545  function qwebrPlaceMessageContents(content, html_location = "title-block-header", revealjs_location = "title-slide") {
546
547    // Get references to header elements
548    const headerHTML = document.getElementById(html_location);
549    const headerRevealJS = document.getElementById(revealjs_location);
550
551    // Determine where to insert the quartoTitleMeta element
552    if (headerHTML || headerRevealJS) {
553      // Append to the existing "title-block-header" element or "title-slide" div
554      (headerHTML || headerRevealJS).appendChild(content);
555    } else {
556      // If neither headerHTML nor headerRevealJS is found, insert after "webr-monaco-editor-init" script
557      const monacoScript = document.getElementById("qwebr-monaco-editor-init");
558      const header = document.createElement("header");
559      header.setAttribute("id", "title-block-header");
560      header.appendChild(content);
561      monacoScript.after(header);
562    }
563  }
564
565
566
567  function qwebrOffScreenCanvasSupportWarningMessage() {
568    
569    // Verify canvas is supported.
570    if(qwebrOffScreenCanvasSupport()) return;
571
572    // Create the main container div
573    var calloutContainer = document.createElement('div');
574    calloutContainer.classList.add('callout', 'callout-style-default', 'callout-warning', 'callout-titled');
575
576    // Create the header div
577    var headerDiv = document.createElement('div');
578    headerDiv.classList.add('callout-header', 'd-flex', 'align-content-center');
579
580    // Create the icon container div
581    var iconContainer = document.createElement('div');
582    iconContainer.classList.add('callout-icon-container');
583
584    // Create the icon element
585    var iconElement = document.createElement('i');
586    iconElement.classList.add('callout-icon');
587
588    // Append the icon element to the icon container
589    iconContainer.appendChild(iconElement);
590
591    // Create the title container div
592    var titleContainer = document.createElement('div');
593    titleContainer.classList.add('callout-title-container', 'flex-fill');
594    titleContainer.innerText = 'Warning: Web Browser Does Not Support Graphing!';
595
596    // Append the icon container and title container to the header div
597    headerDiv.appendChild(iconContainer);
598    headerDiv.appendChild(titleContainer);
599
600    // Create the body container div
601    var bodyContainer = document.createElement('div');
602    bodyContainer.classList.add('callout-body-container', 'callout-body');
603
604    // Create the paragraph element for the body content
605    var paragraphElement = document.createElement('p');
606    paragraphElement.innerHTML = 'This web browser does not have support for displaying graphs through the <code>quarto-webr</code> extension since it lacks an <code>OffScreenCanvas</code>. Please upgrade your web browser to one that supports <code>OffScreenCanvas</code>.';
607
608    // Append the paragraph element to the body container
609    bodyContainer.appendChild(paragraphElement);
610
611    // Append the header div and body container to the main container div
612    calloutContainer.appendChild(headerDiv);
613    calloutContainer.appendChild(bodyContainer);
614
615    // Append the main container div to the document depending on format
616    qwebrPlaceMessageContents(calloutContainer, "title-block-header"); 
617
618  }
619
620
621  // Function that attaches the document status message and diagnostics
622  function displayStartupMessage(showStartupMessage, showHeaderMessage) {
623    if (!showStartupMessage) {
624      return;
625    }
626
627    // Create the outermost div element for metadata
628    const quartoTitleMeta = document.createElement("div");
629    quartoTitleMeta.classList.add("quarto-title-meta");
630
631    // Create the first inner div element
632    const firstInnerDiv = document.createElement("div");
633    firstInnerDiv.setAttribute("id", "qwebr-status-message-area");
634
635    // Create the second inner div element for "WebR Status" heading and contents
636    const secondInnerDiv = document.createElement("div");
637    secondInnerDiv.setAttribute("id", "qwebr-status-message-title");
638    secondInnerDiv.classList.add("quarto-title-meta-heading");
639    secondInnerDiv.innerText = "WebR Status";
640
641    // Create another inner div for contents
642    const secondInnerDivContents = document.createElement("div");
643    secondInnerDivContents.setAttribute("id", "qwebr-status-message-body");
644    secondInnerDivContents.classList.add("quarto-title-meta-contents");
645
646    // Describe the WebR state
647    qwebrStartupMessage.innerText = "🟡 Loading...";
648    qwebrStartupMessage.setAttribute("id", "qwebr-status-message-text");
649    // Add `aria-live` to auto-announce the startup status to screen readers
650    qwebrStartupMessage.setAttribute("aria-live", "assertive");
651
652    // Append the startup message to the contents
653    secondInnerDivContents.appendChild(qwebrStartupMessage);
654
655    // Add a status indicator for COOP and COEP Headers if needed
656    if (showHeaderMessage) {
657      const crossOriginMessage = document.createElement("p");
658      crossOriginMessage.innerText = `${crossOriginIsolated ? '🟢' : '🟡'} COOP & COEP Headers`;
659      crossOriginMessage.setAttribute("id", "qwebr-coop-coep-header");
660      secondInnerDivContents.appendChild(crossOriginMessage);
661    }
662
663    // Combine the inner divs and contents
664    firstInnerDiv.appendChild(secondInnerDiv);
665    firstInnerDiv.appendChild(secondInnerDivContents);
666    quartoTitleMeta.appendChild(firstInnerDiv);
667
668    // Place message on webpage
669    qwebrPlaceMessageContents(quartoTitleMeta); 
670  }
671
672  function qwebrAddCommandHistoryModal() {
673    // Create the modal div
674    var modalDiv = document.createElement('div');
675    modalDiv.id = 'qwebr-history-modal';
676    modalDiv.className = 'qwebr-modal';
677
678    // Create the modal content div
679    var modalContentDiv = document.createElement('div');
680    modalContentDiv.className = 'qwebr-modal-content';
681
682    // Create the span for closing the modal
683    var closeSpan = document.createElement('span');
684    closeSpan.id = 'qwebr-command-history-close-btn';
685    closeSpan.className = 'qwebr-modal-close';
686    closeSpan.innerHTML = '&times;';
687
688    // Create the h1 element for the modal
689    var modalH1 = document.createElement('h1');
690    modalH1.textContent = 'R History Command Contents';
691
692    // Create an anchor element for downloading the Rhistory file 
693    var downloadLink = document.createElement('a');
694    downloadLink.href = '#';
695    downloadLink.id = 'qwebr-download-history-btn';
696    downloadLink.className = 'qwebr-download-btn';
697
698    // Create an 'i' element for the icon
699    var icon = document.createElement('i');
700    icon.className = 'bi bi-file-code';
701
702    // Append the icon to the anchor element
703    downloadLink.appendChild(icon);
704
705    // Add the text 'Download R History' to the anchor element
706    downloadLink.appendChild(document.createTextNode(' Download R History File'));
707
708    // Create the pre for command history contents
709    var commandContentsPre = document.createElement('pre');
710    commandContentsPre.id = 'qwebr-command-history-contents';
711    commandContentsPre.className = 'qwebr-modal-content-code';
712
713    // Append the close span, h1, and history contents pre to the modal content div
714    modalContentDiv.appendChild(closeSpan);
715    modalContentDiv.appendChild(modalH1);
716    modalContentDiv.appendChild(downloadLink);
717    modalContentDiv.appendChild(commandContentsPre);
718
719    // Append the modal content div to the modal div
720    modalDiv.appendChild(modalContentDiv);
721
722    // Append the modal div to the body
723    document.body.appendChild(modalDiv);
724  }
725
726  function qwebrRegisterRevealJSCommandHistoryModal() {
727    // Select the <ul> element inside the <div> with data-panel="Custom0"
728    let ulElement = document.querySelector('div[data-panel="Custom0"] > ul.slide-menu-items');
729
730    // Find the last <li> element with class slide-tool-item
731    let lastItem = ulElement.querySelector('li.slide-tool-item:last-child');
732
733    // Calculate the next data-item value
734    let nextItemValue = 0;
735    if (lastItem) {
736        nextItemValue = parseInt(lastItem.dataset.item) + 1;
737    }
738
739    // Create a new <li> element
740    let newListItem = document.createElement('li');
741    newListItem.className = 'slide-tool-item';
742    newListItem.dataset.item = nextItemValue.toString(); // Set the next available data-item value
743
744    // Create the <a> element inside the <li>
745    let newLink = document.createElement('a');
746    newLink.href = '#';
747    newLink.id = 'qwebrRHistoryButton'; // Set the ID for the new link
748    
749    // Create the <kbd> element inside the <a>
750    let newKbd = document.createElement('kbd');
751    newKbd.textContent = ' '; // Set to empty as we are not registering a keyboard shortcut
752
753    // Create text node for the link text
754    let newText = document.createTextNode(' View R History');
755
756    // Append <kbd> and text node to <a>
757    newLink.appendChild(newKbd);
758    newLink.appendChild(newText);
759
760    // Append <a> to <li>
761    newListItem.appendChild(newLink);
762
763    // Append <li> to <ul>
764    ulElement.appendChild(newListItem);
765  }
766
767  // Handle setting up the R history modal
768  function qwebrCodeLinks() {
769
770    if (qwebrIsRevealJS()) {
771      qwebrRegisterRevealJSCommandHistoryModal();
772      return;
773    }
774
775    // Create the container div
776    var containerDiv = document.createElement('div');
777    containerDiv.className = 'quarto-code-links';
778
779    // Create the h2 element
780    var h2 = document.createElement('h2');
781    h2.textContent = 'webR Code Links';
782
783    // Create the ul element
784    var ul = document.createElement('ul');
785
786    // Create the li element
787    var li = document.createElement('li');
788
789    // Create the a_history_btn element
790    var a_history_btn = document.createElement('a');
791    a_history_btn.href = 'javascript:void(0)';
792    a_history_btn.setAttribute('id', 'qwebrRHistoryButton');
793
794    // Create the i_history_btn element
795    var i_history_btn = document.createElement('i');
796    i_history_btn.className = 'bi bi-file-code';
797
798    // Create the text node for the link text
799    var text_history_btn = document.createTextNode('View R History');
800
801    // Append the icon element and link text to the a element
802    a_history_btn.appendChild(i_history_btn);
803    a_history_btn.appendChild(text_history_btn);
804
805    // Append the a element to the li element
806    li.appendChild(a_history_btn);
807
808    // Append the li element to the ul element
809    ul.appendChild(li);
810
811    // Append the h2 and ul elements to the container div
812    containerDiv.appendChild(h2);
813    containerDiv.appendChild(ul);
814
815    // Append the container div to the element with the ID 'quarto-margin-sidebar'
816    var sidebar = document.getElementById('quarto-margin-sidebar');
817      
818    // If the sidebar element is not found, create it
819    if(!sidebar) {
820      qwebrPlaceQuartoSidebar();
821    }
822    
823    // Re-select the sidebar element (if it was just created)
824    sidebar = document.getElementById('quarto-margin-sidebar');   
825
826
827    // If the sidebar element exists, append the container div to it
828    if(sidebar) {
829      // Append the container div to the sidebar
830      sidebar.appendChild(containerDiv);
831      // Force the sidebar to be clickable by removing the 'zindex-bottom' class
832      // added in pre-release: https://github.com/quarto-dev/quarto-cli
832/commit/f0c53a1ffcaa1de4eccbf07803b096898248adcc
833      sidebar.className = 'sidebar margin-sidebar';
834    } else {
835      // Get a debugger ...
836      console.warn('Element with ID "quarto-margin-sidebar" not found.');
837    }
838  }
839
840  // Call the function to append the code links for qwebR into the right sidebar
841  qwebrCodeLinks();
842
843  // Add the command history modal
844  qwebrAddCommandHistoryModal();
845
846  displayStartupMessage(qwebrShowStartupMessage, qwebrShowHeaderMessage);
847  qwebrOffScreenCanvasSupportWarningMessage();
848  </script>
848
849  
849<script type="module">
850  // Define a global storage and retrieval solution ----
851
852  // Store commands executed in R
853  globalThis.qwebrRCommandHistory = [];
854
855  // Function to retrieve the command history
856  globalThis.qwebrFormatRHistory = function() {
857     return qwebrRCommandHistory.join("\n\n");
858  }
859
860  // Retrieve HTML Elements ----
861
862  // Get the command modal
863  const command_history_modal = document.getElementById("qwebr-history-modal");
864
865  // Get the button that opens the command modal
866  const command_history_btn = document.getElementById("qwebrRHistoryButton");
867
868  // Get the <span> element that closes the command modal
869  const command_history_close_span = document.getElementById("qwebr-command-history-close-btn");
870
871  // Get the download button for r history information
872  const command_history_download_btn = document.getElementById("qwebr-download-history-btn");
873
874  // Plug in command history into modal/download button ----
875
876  // Function to populate the modal with command history
877  function populateCommandHistoryModal() {
878      document.getElementById("qwebr-command-history-contents").innerHTML = qwebrFormatRHistory() || "No commands have been executed yet.";
879  }
880
881  // Function to format the current date and time to
882  // a string with the format YYYY-MM-DD-HH-MM-SS
883  function formatDateTime() {
884      const now = new Date();
885
886      const year = now.getFullYear();
887      const day = String(now.getDate()).padStart(2, '0');
888      const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are zero-based
889      const hours = String(now.getHours()).padStart(2, '0');
890      const minutes = String(now.getMinutes()).padStart(2, '0');
891      const seconds = String(now.getSeconds()).padStart(2, '0');
892
893      return `${year}-${month}-${day}-${hours}-${minutes}-${seconds}`;
894  }
895
896
897  // Function to convert document title with datetime to a safe filename
898  function safeFileName() {
899      // Get the current page title
900      let pageTitle = document.title;
901
902      // Combine the current page title with the current date and time
903      let pageNameWithDateTime = `Rhistory-${pageTitle}-${formatDateTime()}`;
904
905      // Replace unsafe characters with safe alternatives
906      let safeFilename = pageNameWithDateTime.replace(/[\\/:\*\?! "<>\|]/g, '-');
907
908      return safeFilename;
909  }
910
911
912  // Function to download list contents as text file
913  function downloadRHistory() {
914      // Get the current page title + datetime and use it as the filename
915      const filename = `${safeFileName()}.R`;
916
917      // Get the text contents of the R History list
918      const text = qwebrFormatRHistory();
919
920      // Create a new Blob object with the text contents
921      const blob = new Blob([text], { type: 'text/plain' });
922    
923      // Create a new anchor element for the download
924      const a = document.createElement('a');
925      a.style.display = 'none';
926      a.href = URL.createObjectURL(blob);
927      a.download = filename;
928
929      // Append the anchor to the body, click it, and remove it
930      document.body.appendChild(a);
931      a.click();
932      document.body.removeChild(a);
933  }
934
935  // Register event handlers ----
936
937  // When the user clicks the View R History button, open the command modal
938  command_history_btn.onclick = function() {
939      populateCommandHistoryModal();
940      command_history_modal.style.display = "block";
941  }
942
943  // When the user clicks on <span> (x), close the command modal
944  command_history_close_span.onclick = function() {
945      command_history_modal.style.display = "none";
946  }
947
948  // When the user clicks anywhere outside of the command modal, close it
949  window.onclick = function(event) {
950      if (event.target == command_history_modal) {
951          command_history_modal.style.display = "none";
952      }
953  }
954
955  // Add an onclick event listener to the download button so that
956  // the user can download the R history as a text file
957  command_history_download_btn.onclick = function() {
958      downloadRHistory();
959  };
960  </script>
960
961  
961<script type="module">
962  // Supported Evaluation Types for Context
963  globalThis.EvalTypes = Object.freeze({
964    Interactive: 'interactive',
965    Setup: 'setup',
966    Output: 'output',
967  });
968
969  // Function that obtains the font size for a given element 
970  globalThis.qwebrCurrentFontSizeOnElement = function(element, cssProperty = 'font-size') {
971
972    const currentFontSize = parseFloat(
973      window
974      .getComputedStyle(element)
975      .getPropertyValue(cssProperty)
976    );
977    
978    return currentFontSize;
979  }
980
981  // Function to determine font scaling
982  globalThis.qwebrScaledFontSize = function(div, qwebrOptions) {
983    // Determine if we should compute font-size using RevealJS's `--r-main-font-size` 
984    // or if we can directly use the document's `font-size`.
985    const cssProperty = document.body.classList.contains('reveal') ? 
986      "--r-main-font-size" : "font-size";
987    
988    // Get the current font size on the div element
989    const elementFontSize = qwebrCurrentFontSizeOnElement(div, cssProperty);
990
991    // Determine the scaled font size value
992    const scaledFontSize = ((qwebrOptions['editor-font-scale'] ?? 1) * elementFontSize) ?? 17.5;
993
994    return scaledFontSize;
995  }
996
997
998  // Function that dispatches the creation request
999  globalThis.qwebrCreateHTMLElement = function (
1000    cellData
1001  ) {
1002
1003    // Extract key components
1004    const evalType = cellData.options.context;
1005    const qwebrCounter = cellData.id;
1006
1007    // We make an assumption that insertion points are defined by the Lua filter as:
1008    // qwebr-insertion-location-{qwebrCounter} 
1009    const elementLocator = document.getElementById(`qwebr-insertion-location-${qwebrCounter}`);
1010
1011    // Figure out the routine to use to insert the element.
1012    let qwebrElement;
1013    switch ( evalType ) {
1014      case EvalTypes.Interactive:
1015        qwebrElement = qwebrCreateInteractiveElement(qwebrCounter, cellData.options);
1016        break;
1017      case EvalTypes.Output: 
1018        qwebrElement = qwebrCreateNonInteractiveOutputElement(qwebrCounter, cellData.options);
1019        break;
1020      case EvalTypes.Setup: 
1021        qwebrElement = qwebrCreateNonInteractiveSetupElement(qwebrCounter, cellData.options);
1022        break;
1023      default: 
1024        qwebrElement = document.createElement('div');
1025        qwebrElement.textContent = 'Error creating `quarto-webr` element';
1026    }
1027
1028    // Insert the dynamically generated object at the document location.
1029    elementLocator.appendChild(qwebrElement);
1030  };
1031
1032  // Function that setups the interactive element creation
1033  globalThis.qwebrCreateInteractiveElement = function (qwebrCounter, qwebrOptions) {
1034
1035    // Create main div element
1036    var mainDiv = document.createElement('div');
1037    mainDiv.id = 'qwebr-interactive-area-' + qwebrCounter;
1038    mainDiv.className = `qwebr-interactive-area`;
1039    if (qwebrOptions.classes) {
1040      mainDiv.className += " " + qwebrOptions.classes
1041    }
1042
1043    // Add a unique cell identifier that users can customize
1044    if (qwebrOptions.label) {
1045      mainDiv.setAttribute('data-id', qwebrOptions.label);
1046    }
1047
1048    // Create toolbar div
1049    var toolbarDiv = document.createElement('div');
1050    toolbarDiv.className = 'qwebr-editor-toolbar';
1051    toolbarDiv.id = 'qwebr-editor-toolbar-' + qwebrCounter;
1052
1053    // Create a div to hold the left buttons
1054    var leftButtonsDiv = document.createElement('div');
1055    leftButtonsDiv.className = 'qwebr-editor-toolbar-left-buttons';
1056
1057    // Create a div to hold the right buttons
1058    var rightButtonsDiv = document.createElement('div');
1059    rightButtonsDiv.className = 'qwebr-editor-toolbar-right-buttons';
1060
1061    // Create Run Code button
1062    var runCodeButton = document.createElement('button');
1063    runCodeButton.className = 'btn btn-default qwebr-button qwebr-button-run';
1064    runCodeButton.disabled = true;
1065    runCodeButton.type = 'button';
1066    runCodeButton.id = 'qwebr-button-run-' + qwebrCounter;
1067    runCodeButton.textContent = '🟡 Loading webR...';
1068    runCodeButton.title = `Run code (Shift + Enter)`;
1069
1070    // Append buttons to the leftButtonsDiv
1071    leftButtonsDiv.appendChild(runCodeButton);
1072
1073    // Create Reset button
1074    var resetButton = document.createElement('button');
1075    resetButton.className = 'btn btn-light btn-xs qwebr-button qwebr-button-reset';
1076    resetButton.type = 'button';
1077    resetButton.id = 'qwebr-button-reset-' + qwebrCounter;
1078    resetButton.title = 'Start over';
1079    resetButton.innerHTML = '<i class="fa-solid fa-arrows-rotate"></i>';
1080
1081    // Create Copy button
1082    var copyButton = document.createElement('button');
1083    copyButton.className = 'btn btn-light btn-xs qwebr-button qwebr-button-copy';
1084    copyButton.type = 'button';
1085    copyButton.id = 'qwebr-button-copy-' + qwebrCounter;
1086    copyButton.title = 'Copy code';
1087    copyButton.innerHTML = '<i class="fa-regular fa-copy"></i>';
1088
1089    // Append buttons to the rightButtonsDiv
1090    rightButtonsDiv.appendChild(resetButton);
1091    rightButtonsDiv.appendChild(copyButton);
1092
1093    // Create console area div
1094    var consoleAreaDiv = document.createElement('div');
1095    consoleAreaDiv.id = 'qwebr-console-area-' + qwebrCounter;
1096    consoleAreaDiv.className = 'qwebr-console-area';
1097
1098    // Create editor div
1099    var editorDiv = document.createElement('div');
1100    editorDiv.id = 'qwebr-editor-' + qwebrCounter;
1101    editorDiv.className = 'qwebr-editor';
1102
1103    // Create output code area div
1104    var outputCodeAreaDiv = document.createElement('div');
1105    outputCodeAreaDiv.id = 'qwebr-output-code-area-' + qwebrCounter;
1106    outputCodeAreaDiv.className = 'qwebr-output-code-area';
1107    outputCodeAreaDiv.setAttribute('aria-live', 'assertive');
1108
1109    // Create pre element inside output code area
1110    var preElement = document.createElement('pre');
1111    preElement.style.visibility = 'hidden';
1112    outputCodeAreaDiv.appendChild(preElement);
1113
1114    // Create output graph area div
1115    var outputGraphAreaDiv = document.createElement('div');
1116    outputGraphAreaDiv.id = 'qwebr-output-graph-area-' + qwebrCounter;
1117    outputGraphAreaDiv.className = 'qwebr-output-graph-area';
1118
1119    // Append buttons to the toolbar
1120    toolbarDiv.appendChild(leftButtonsDiv);
1121    toolbarDiv.appendChild(rightButtonsDiv);
1122
1123    // Append all elements to the main div
1124    mainDiv.appendChild(toolbarDiv);
1125    consoleAreaDiv.appendChild(editorDiv);
1126    consoleAreaDiv.appendChild(outputCodeAreaDiv);
1127    mainDiv.appendChild(consoleAreaDiv);
1128    mainDiv.appendChild(outputGraphAreaDiv);
1129
1130    return mainDiv;
1131  }
1132
1133  // Function that adds output structure for non-interactive output
1134  globalThis.qwebrCreateNonInteractiveOutputElement = function(qwebrCounter, qwebrOptions) {
1135    // Create main div element
1136    var mainDiv = document.createElement('div');
1137    mainDiv.id = 'qwebr-noninteractive-area-' + qwebrCounter;
1138    mainDiv.className = `qwebr-noninteractive-area`;
1139    if (qwebrOptions.classes) {
1140      mainDiv.className += " " + qwebrOptions.classes
1141    }
1142    
1143    // Add a unique cell identifier that users can customize
1144    if (qwebrOptions.label) {
1145      mainDiv.setAttribute('data-id', qwebrOptions.label);
1146    }
1147    
1148    // Create a status container div
1149    var statusContainer = createLoadingContainer(qwebrCounter);
1150
1151    // Create output code area div
1152    var outputCodeAreaDiv = document.createElement('div');
1153    outputCodeAreaDiv.id = 'qwebr-output-code-area-' + qwebrCounter;
1154    outputCodeAreaDiv.className = 'qwebr-output-code-area';
1155    outputCodeAreaDiv.setAttribute('aria-live', 'assertive');
1156
1157    // Create pre element inside output code area
1158    var preElement = document.createElement('pre');
1159    preElement.style.visibility = 'hidden';
1160    outputCodeAreaDiv.appendChild(preElement);
1161
1162    // Create output graph area div
1163    var outputGraphAreaDiv = document.createElement('div');
1164    outputGraphAreaDiv.id = 'qwebr-output-graph-area-' + qwebrCounter;
1165    outputGraphAreaDiv.className = 'qwebr-output-graph-area';
1166
1167    // Append all elements to the main div
1168    mainDiv.appendChild(statusContainer);
1169    mainDiv.appendChild(outputCodeAreaDiv);
1170    mainDiv.appendChild(outputGraphAreaDiv);
1171
1172    return mainDiv;
1173  };
1174
1175  // Function that adds a stub in the page to indicate a setup cell was used.
1176  globalThis.qwebrCreateNonInteractiveSetupElement = function(qwebrCounter, qwebrOptions) {
1177    // Create main div element
1178    var mainDiv = document.createElement('div');
1179    mainDiv.id = `qwebr-noninteractive-setup-area-${qwebrCounter}`;
1180    mainDiv.className = `qwebr-noninteractive-setup-area`;
1181    if (qwebrOptions.classes) {
1182      mainDiv.className += " " + qwebrOptions.classes
1183    }
1184
1185
1186    // Add a unique cell identifier that users can customize
1187    if (qwebrOptions.label) {
1188      mainDiv.setAttribute('data-id', qwebrOptions.label);
1189    }
1190
1191    // Create a status container div
1192    var statusContainer = createLoadingContainer(qwebrCounter);
1193
1194    // Append status onto the main div
1195    mainDiv.appendChild(statusContainer);
1196
1197    return mainDiv;
1198  }
1199
1200
1201  // Function to create loading container with specified ID
1202  globalThis.createLoadingContainer = function(qwebrCounter) {
1203
1204    // Create a status container
1205    const container = document.createElement('div');
1206    container.id = `qwebr-non-interactive-loading-container-${qwebrCounter}`;
1207    container.className = 'qwebr-non-interactive-loading-container qwebr-cell-needs-evaluation';
1208
1209    // Create an R project logo to indicate its a code space
1210    const rProjectIcon = document.createElement('i');
1211    rProjectIcon.className = 'fa-brands fa-r-project fa-3x qwebr-r-project-logo';
1212
1213    // Setup a loading icon from font awesome
1214    const spinnerIcon = document.createElement('i');
1215    spinnerIcon.className = 'fa-solid fa-spinner fa-spin fa-1x qwebr-icon-status-spinner';
1216
1217    // Add a section for status text
1218    const statusText = document.createElement('p');
1219    statusText.id = `qwebr-status-text-${qwebrCounter}`;
1220    statusText.className = `qwebr-status-text qwebr-cell-needs-evaluation`;
1221    statusText.innerText = 'Loading webR...';
1222
1223    // Incorporate an inner container
1224    const innerContainer = document.createElement('div');
1225
1226    // Append elements to the inner container
1227    innerContainer.appendChild(spinnerIcon);
1228    innerContainer.appendChild(statusText);
1229
1230    // Append elements to the main container
1231    container.appendChild(rProjectIcon);
1232    container.appendChild(innerContainer);
1233
1234    return container;
1235  }
1236  </script>
1236
1237  
1237<script type="module">
1238  // Function to install a single package
1239  async function qwebrInstallRPackage(packageName) {
1240    await mainWebR.evalRVoid(`webr::install('${packageName}');`);
1241  }
1242
1243  // Function to load a single package
1244  async function qwebrLoadRPackage(packageName) {
1245    await mainWebR.evalRVoid(`require('${packageName}', quietly = TRUE)`);
1246  }
1247
1248  // Generic function to process R packages
1249  async function qwebrProcessRPackagesWithStatus(packages, processType, displayStatusMessageUpdate = true) {
1250    // Switch between contexts
1251    const messagePrefix = processType === 'install' ? 'Installing' : 'Loading';
1252
1253    // Modify button state
1254    qwebrSetInteractiveButtonState(`🟡 ${messagePrefix} package ...`, false);
1255
1256    // Iterate over packages
1257    for (let i = 0; i < packages.length; i++) {
1258      const activePackage = packages[i];
1259      const formattedMessage = `${messagePrefix} package ${i + 1} out of ${packages.length}: ${activePackage}`;
1260
1261      // Display the update in header
1262      if (displayStatusMessageUpdate) {
1263        qwebrUpdateStatusHeader(formattedMessage);
1264      }
1265
1266      // Display the update in non-active areas
1267      qwebrUpdateStatusMessage(formattedMessage);
1268
1269      // Run package installation
1270      if (processType === 'install') {
1271        await qwebrInstallRPackage(activePackage);
1272      } else {
1273        await qwebrLoadRPackage(activePackage);
1274      }
1275    }
1276
1277    // Clean slate
1278    if (processType === 'load') {
1279      await mainWebR.flush();
1280    }
1281  }
1282
1283  // Start a timer
1284  const initializeWebRTimerStart = performance.now();
1285
1286  // Encase with a dynamic import statement
1287  globalThis.qwebrInstance = import(qwebrCustomizedWebROptions.baseURL + "webr.mjs").then(
1288    async ({ WebR, ChannelType }) => {
1289      // Populate WebR options with defaults or new values based on `webr` meta
1290      globalThis.mainWebR = new WebR(qwebrCustomizedWebROptions);
1291
1292      // Initialization WebR
1293      await mainWebR.init();
1294
1295      // Setup a shelter
1296      globalThis.mainWebRCodeShelter = await new mainWebR.Shelter();
1297
1298      // Setup a pager to allow processing help documentation
1299      await mainWebR.evalRVoid('webr::pager_install()');
1300
1301      // Setup a viewer to allow processing htmlwidgets.
1302      // This might not be available in old webr version
1303      await mainWebR.evalRVoid('try({ webr::viewer_install() })');
1304
1305      // Override the existing install.packages() to use webr::install()
1306      await mainWebR.evalRVoid('webr::shim_install()');
1307
1308      // Specify the repositories to pull from
1309      // Note: webR does not use the `repos` option, but instead uses `webr_pkg_repos`
1310      // inside of `install()`. However, other R functions still pull from `repos`.
1311      await mainWebR.evalRVoid(`
1312        options(
1313          webr_pkg_repos = c(${qwebrPackageRepoURLS.map(repoURL => `'${repoURL}'`).join(',')}),
1314          repos = c(${qwebrPackageRepoURLS.map(repoURL => `'${repoURL}'`).join(',')})
1315        )
1316      `);
1317
1318      // Check to see if any packages need to be installed
1319      if (qwebrSetupRPackages) {
1320        // Obtain only a unique list of packages
1321        const uniqueRPackageList = Array.from(new Set(qwebrInstallRPackagesList));
1322
1323        // Install R packages one at a time (either silently or with a status update)
1324        await qwebrProcessRPackagesWithStatus(uniqueRPackageList, 'install', qwebrShowStartupMessage);
1325
1326        if (qwebrAutoloadRPackages) {
1327          // Load R packages one at a time (either silently or with a status update)
1328          await qwebrProcessRPackagesWithStatus(uniqueRPackageList, 'load', qwebrShowStartupMessage);
1329        }
1330      }
1331    }
1332  );
1333
1334  // Stop timer
1335  const initializeWebRTimerEnd = performance.now();
1336
1337  </script>
1337
1338  
1338<script type="module">
1339  // Function to verify a given JavaScript Object is empty
1340  globalThis.qwebrIsObjectEmpty = function (arr) {
1341      return Object.keys(arr).length === 0;
1342  }
1343
1344  // Global version of the Escape HTML function that converts HTML
1345  // characters to their HTML entities.
1346  globalThis.qwebrEscapeHTMLCharacters = function(unsafe) {
1347      return unsafe
1348        .replace(/&/g, "&amp;")
1349        .replace(/</g, "&lt;")
1350        .replace(/>/g, "&gt;")
1351        .replace(/"/g, "&quot;")
1352        .replace(/'/g, "&#039;");
1353  };
1354
1355  // Passthrough results
1356  globalThis.qwebrIdentity = function(x) {
1357      return x;
1358  };
1359
1360  // Append a comment
1361  globalThis.qwebrPrefixComment = function(x, comment) {
1362      return `${comment}${x}`;
1363  };
1364
1365  // Function to store the code in the history
1366  globalThis.qwebrLogCodeToHistory = function(codeToRun, options) {
1367      qwebrRCommandHistory.push(
1368          `# Ran code in ${options.label} at ${new Date().toLocaleString()} ----\n${codeToRun}`
1369      );
1370  };
1371
1372  // Function to attach a download button onto the canvas
1373  // allowing the user to download the image.
1374  function qwebrImageCanvasDownloadButton(canvas, canvasContainer) {
1375
1376      // Create the download button
1377      const downloadButton = document.createElement('button');
1378      downloadButton.className = 'qwebr-canvas-image-download-btn';
1379      downloadButton.textContent = 'Download Image';
1380      canvasContainer.appendChild(downloadButton);
1381
1382      // Trigger a download of the image when the button is clicked
1383      downloadButton.addEventListener('click', function() {
1384          const image = canvas.toDataURL('image/png');
1385          const link = document.createElement('a');
1386          link.href = image;
1387          link.download = 'qwebr-canvas-image.png';
1388          link.click();
1389      });
1390  }
1391
1392
1393  // Function to parse the pager results
1394  globalThis.qwebrParseTypePager = async function (msg) {
1395
1396      // Split out the event data
1397      const { path, title, deleteFile } = msg.data;
1398
1399      // Process the pager data by reading the information from disk
1400      const paged_data = await mainWebR.FS.readFile(path).then((data) => {
1401          // Obtain the file content
1402          let content = new TextDecoder().decode(data);
1403
1404          // Remove excessive backspace characters until none remain
1405          while(content.match(/.[\b]/)){
1406              content = content.replace(/.[\b]/g, '');
1407          }
1408
1409          // Returned cleaned data
1410          return content;
1411      });
1412
1413      // Unlink file if needed
1414      if (deleteFile) {
1415          await mainWebR.FS.unlink(path);
1416      }
1417
1418      // Return extracted data with spaces
1419      return paged_data;
1420  };
1421
1422
1423  // Function to parse the browse results
1424  globalThis.qwebrParseTypeBrowse = async function (msg) {
1425
1426      // msg.type === "browse"
1427      const path = msg.data.url;
1428
1429      // Process the browse data by reading the information from disk
1430      const browse_data = await mainWebR.FS.readFile(path).then((data) => {
1431          // Obtain the file content
1432          let content = new TextDecoder().decode(data);
1433
1434          return content;
1435      });
1436
1437      // Return extracted data as-is
1438      return browse_data;
1439  };
1440
1441  // Function to run the code using webR and parse the output
1442  globalThis.qwebrComputeEngine = async function(
1443      codeToRun,
1444      elements,
1445      options) {
1446
1447      // Call into the R compute engine that persists within the document scope.
1448      // To be prepared for all scenarios, the following happens:
1449      // 1. We setup a canvas device to write to by making a namespace call into the {webr} package
1450      // 2. We use values inside of the options array to set the figure size.
1451      // 3. We capture the output stream information (STDOUT and STERR)
1452      // 4. We disable the current device's image creation.
1453      // 5. Piece-wise parse the results into the different output areas
1454
1455      // Create a pager variable for help/file contents
1456      let pager = [];
1457
1458      // Handle how output is processed
1459      let showMarkup = options.results === "markup" && options.output !== "asis";
1460      let processOutput;
1461
1462      if (showMarkup) {
1463          processOutput = qwebrEscapeHTMLCharacters;
1464      } else {
1465          processOutput = qwebrIdentity;
1466      }
1467
1468      // ----
1469      // Convert from Inches to Pixels by using DPI (dots per inch)
1470      // for bitmap devices (dpi * inches = pixels)
1471      let fig_width = options["fig-width"] * options["dpi"];
1472      let fig_height = options["fig-height"] * options["dpi"];
1473
1474      // Initialize webR
1475      await mainWebR.init();
1476
1477      // Configure capture output
1478      let captureOutputOptions = {
1479          withAutoprint: true,
1480          captureStreams: true,
1481          captureConditions: false,
1482          // env: webR.objs.emptyEnv, // maintain a global environment for webR v0.2.0
1483      };
1484
1485      // Determine if the browser supports OffScreen
1486      if (qwebrOffScreenCanvasSupport()) {
1487          // Mirror default options of webr::canvas()
1488          // with changes to figure height and width.
1489          captureOutputOptions.captureGraphics = {
1490              width: fig_width,
1491              height: fig_height,
1492              bg: "white", // default: transparent
1493              pointsize: 12,
1494              capture: true
1495          };
1496      }  else {
1497          // Disable generating graphics
1498          captureOutputOptions.captureGraphics = false;
1499      }
1500
1501      // Store the code to run in history
1502      qwebrLogCodeToHistory(codeToRun, options);
1503
1504      // Setup a webR canvas by making a namespace call into the {webr} package
1505      // Evaluate the R code
1506      // Remove the active canvas silently
1507      const result = await mainWebRCodeShelter.captureR(
1508          `${codeToRun}`,
1509          captureOutputOptions
1510      );
1511
1512      // -----
1513
1514      // Start attempting to parse the result data
1515      processResultOutput:try {
1516
1517          // Avoid running through output processing
1518          if (options.results === "hide" || options.output === "false") {
1519              break processResultOutput;
1520          }
1521
1522          // Merge output streams of STDOUT and STDErr (messages and errors are combined.)
1523          // Require both `warning` and `message` to be true to display `STDErr`.
1524          const out = result.output
1525          .filter(
1526              evt => evt.type === "stdout" ||
1527              ( evt.type === "stderr" && (options.warning === "true" && options.message === "true"))
1528          )
1529          .map((evt, index) => {
1530              const className = `qwebr-output-code-${evt.type}`;
1531              const outputResult = qwebrPrefixComment(processOutput(evt.data), options.comment);
1532              return `<code id="${className}-editor-${elements.id}-result-${index + 1}" class="${className}">${outputResult}</code>`;
1533          })
1534          .join("\n");
1535
1536
1537          // Clean the state
1538          // We're now able to process pager events.
1539          // As a result, we cannot maintain a true 1-to-1 output order
1540          // without individually feeding each line
1541          const msgs = await mainWebR.flush();
1542
1543          // Use `map` to process the filtered "pager" events asynchronously
1544          const pager = [];
1545          const browse = [];
1546
1547          await Promise.all(
1548              msgs.map(
1549                  async (msg) => {
1550
1551                      const msgType = msg.type || "unknown";
1552
1553                      switch(msgType) {
1554                          case 'pager':
1555                              const pager_data = await qwebrParseTypePager(msg);
1556                              pager.push(pager_data);
1557                              break;
1558                          case 'browse':
1559                              const browse_data = await qwebrParseTypeBrowse(msg);
1560                              browse.push(browse_data);
1561                              break;
1562                      }
1563                      return;
1564                  }
1565              )
1566          );
1567
1568          // Nullify the output area of content
1569          elements.outputCodeDiv.innerHTML = "";
1570          elements.outputGraphDiv.innerHTML = "";
1571
1572          // Design an output object for messages
1573          const pre = document.createElement("pre");
1574          if (/\S/.test(out)) {
1575              // Display results as HTML elements to retain output styling
1576              const div = document.createElement("div");
1577              div.innerHTML = out;
1578
1579              // Calculate a scaled font-size value
1580              const scaledFontSize = qwebrScaledFontSize(
1581                  elements.outputCodeDiv, options);
1582
1583              // Override output code cell size
1584              pre.style.fontSize = `${scaledFontSize}px`;
1585              pre.appendChild(div);
1586          } else {
1587              // If nothing is present, hide the element.
1588              pre.style.visibility = "hidden";
1589          }
1590
1591          elements.outputCodeDiv.appendChild(pre);
1592
1593          // Determine if we have graphs to display
1594          if (result.images.length > 0) {
1595
1596              // Create figure element
1597              const figureElement = document.createElement("figure");
1598              figureElement.className = "qwebr-canvas-image";
1599
1600              // Place each rendered graphic onto a canvas element
1601              result.images.forEach((img) => {
1602
1603                  // Construct canvas for object
1604                  const canvas = document.createElement("canvas");
1605
1606                  // Add an image download button
1607                  qwebrImageCanvasDownloadButton(canvas, figureElement);
1608
1609                  // Set canvas size to image
1610                  canvas.width = img.width;
1611                  canvas.height = img.height;
1612
1613                  // Apply output truncations
1614                  canvas.style.width = options["out-width"] ? options["out-width"] : `${fig_width}px`;
1615                  if (options["out-height"]) {
1616                      canvas.style.height = options["out-height"];
1617                  }
1618
1619                  // Apply styling
1620                  canvas.style.display = "block";
1621                  canvas.style.margin = "auto";
1622
1623                  // Draw image onto Canvas
1624                  const ctx = canvas.getContext("2d");
1625                  ctx.drawImage(img, 0, 0, img.width, img.height);
1626
1627                  // Append canvas to figure output area
1628                  figureElement.appendChild(canvas);
1629
1630              });
1631
1632              if (options['fig-cap']) {
1633                  // Create figcaption element
1634                  const figcaptionElement = document.createElement('figcaption');
1635                  figcaptionElement.innerText = options['fig-cap'];
1636                  // Append figcaption to figure
1637                  figureElement.appendChild(figcaptionElement);
1638              }
1639
1640              elements.outputGraphDiv.appendChild(figureElement);
1641
1642          }
1643
1644          // Display the pager data
1645          if (pager.length > 0) {
1646              // Use the `pre` element to preserve whitespace.
1647              pager.forEach((paged_data, index) => {
1648                  const pre_pager = document.createElement("pre");
1649                  pre_pager.innerText = paged_data;
1650                  pre_pager.classList.add("qwebr-output-code-pager");
1651                  pre_pager.setAttribute("id", `qwebr-output-code-pager-editor-${elements.id}-result-${index + 1}`);
1652                  elements.outputCodeDiv.appendChild(pre_pager);
1653              });
1654          }
1655
1656          // Display the browse data
1657          if (browse.length > 0) {
1658              // Use the `pre` element to preserve whitespace.
1659              browse.forEach((browse_data, index) => {
1660                  const iframe_browse = document.createElement('iframe');
1661                  iframe_browse.classList.add("qwebr-output-code-browse");
1662                  iframe_browse.setAttribute("id", `qwebr-output-code-browse-editor-${elements.id}-result-${index + 1}`);
1663                  iframe_browse.style.width = "100%";
1664                  iframe_browse.style.minHeight = "500px";
1665                  elements.outputCodeDiv.appendChild(iframe_browse);
1666
1667                  iframe_browse.contentWindow.document.open();
1668                  iframe_browse.contentWindow.document.write(browse_data);
1669                  iframe_browse.contentWindow.document.close();
1670              });
1671          }
1672      } finally {
1673          // Clean up the remaining code
1674          mainWebRCodeShelter.purge();
1675      }
1676  };
1677
1678  // Function to execute the code (accepts code as an argument)
1679  globalThis.qwebrExecuteCode = async function (
1680      codeToRun,
1681      id,
1682      options = {}) {
1683
1684      // If options are not passed, we fall back on the bare minimum to handle the computation
1685      if (qwebrIsObjectEmpty(options)) {
1686          options = {
1687              "context": "interactive",
1688              "fig-width": 7, "fig-height": 5,
1689              "out-width": "700px", "out-height": "",
1690              "dpi": 72,
1691              "results": "markup",
1692              "warning": "true", "message": "true",
1693          };
1694      }
1695
1696      // Next, we access the compute areas values
1697      const elements = {
1698          runButton: document.getElementById(`qwebr-button-run-${id}`),
1699          outputCodeDiv: document.getElementById(`qwebr-output-code-area-${id}`),
1700          outputGraphDiv: document.getElementById(`qwebr-output-graph-area-${id}`),
1701          id: id,
1702      }
1703
1704      // Disallowing execution of other code cells
1705      document.querySelectorAll(".qwebr-button-run").forEach((btn) => {
1706          btn.disabled = true;
1707      });
1708
1709      if (options.context == EvalTypes.Interactive) {
1710          // Emphasize the active code cell
1711          elements.runButton.innerHTML = '<i class="fa-solid fa-spinner fa-spin qwebr-icon-status-spinner"></i> <span>
1711Run Code</span>';
1712      }
1713
1714      // Evaluate the code and parse the output into the document
1715      await qwebrComputeEngine(codeToRun, elements, options);
1716
1717      // Switch to allowing execution of code
1718      document.querySelectorAll(".qwebr-button-run").forEach((btn) => {
1719          btn.disabled = false;
1720      });
1721
1722      if (options.context == EvalTypes.Interactive) {
1723          // Revert to the initial code cell state
1724          elements.runButton.innerHTML = '<i class="fa-solid fa-play qwebr-icon-run-code"></i> <span>Run Code</span>';
1725      }
1726  }
1727
1728  </script>
1728
1729</head>
1730<body class="quarto-light">
1731<script src="https://cdn.jsdelivr.net/npm/[email protected]/min/vs/loader.js"></script>
vendor: 1 bytes, line 1731
1731
1732<script type="module" id="qwebr-monaco-editor-init">
1733
1734  // Configure the Monaco Editor's loader
1735  require.config({
1736    paths: {
1737      'vs': 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs'
1738    }
1739  });
1740</script>
vendor: 1 bytes, line 1740
1740
1741<script type="module">
1742// Function to update Monaco Editors when body class changes
1743function updateMonacoEditorsOnBodyClassChange() {
1744    // Select the body element
1745    const body = document.querySelector('body');
1746
1747    // Options for the observer (which mutations to observe)
1748    const observerOptions = {
1749        attributes: true,  // Observe changes to attributes
1750        attributeFilter: ['class'] // Only observe changes to the 'class' attribute
1751    };
1752
1753    // Callback function to execute when mutations are observed
1754    const bodyClassChangeCallback = function(mutationsList, observer) {
1755        for(let mutation of mutationsList) {
1756            if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
1757                // Class attribute has changed
1758                // Update all Monaco Editors on the page
1759                updateMonacoEditorTheme();
1760            }
1761        }
1762    };
1763
1764    // Create an observer instance linked to the callback function
1765    const observer = new MutationObserver(bodyClassChangeCallback);
1766
1767    // Start observing the target node for configured mutations
1768    observer.observe(body, observerOptions);
1769}
1770
1771// Function to update all instances of Monaco Editors on the page
1772function updateMonacoEditorTheme() {
1773    // Determine what VS Theme to use
1774    const vsThemeToUse = document.body.classList.contains("quarto-dark") ? 'vs-dark' : 'vs' ;
1775
1776    // Iterate through all initialized Monaco Editors
1777    qwebrEditorInstances.forEach( function(editorInstance) { 
1778        editorInstance.updateOptions({ theme: vsThemeToUse }); 
1779    });
1780}
1781
1782// Call the function to start observing changes to body class
1783updateMonacoEditorsOnBodyClassChange();
1784</script>
vendor: 1 bytes, line 1784
1784
1785<script type="module">
1786// Global array to store Monaco Editor instances
1787globalThis.qwebrEditorInstances = [];
1788
1789function isValidCodeLineNumbers(stringCodeLineNumbers) {
1790  // Regular expression to match valid input strings
1791  const regex = /^(\d+(-\d+)?)(,\d+(-\d+)?)*$/;
1792  return regex.test(stringCodeLineNumbers);
1793}
1794
1795// Function that builds and registers a Monaco Editor instance    
1796globalThis.qwebrCreateMonacoEditorInstance = function (cellData) {
1797
1798  const initialCode = cellData.code;
1799  const qwebrCounter = cellData.id;
1800  const qwebrOptions = cellData.options;
1801
1802  // Retrieve the previously created document elements
1803  let runButton = document.getElementById(`qwebr-button-run-${qwebrCounter}`);
1804  let resetButton = document.getElementById(`qwebr-button-reset-${qwebrCounter}`);
1805  let copyButton = document.getElementById(`qwebr-button-copy-${qwebrCounter}`);
1806  let editorDiv = document.getElementById(`qwebr-editor-${qwebrCounter}`);
1807
1808  // Load the Monaco Editor and create an instance
1809  let editor;
1810  require(['vs/editor/editor.main'], function () {
1811    editor = monaco.editor.create(editorDiv, {
1812      value: initialCode,
1813      language: 'r',
1814      theme: 'vs-light',
1815      automaticLayout: true,           // Works wonderfully with RevealJS
1816      scrollBeyondLastLine: false,
1817      minimap: {
1818        enabled: false
1819      },
1820      fontSize: qwebrScaledFontSize(editorDiv, qwebrOptions),         
1821      renderLineHighlight: "none",      // Disable current line highlighting
1822      hideCursorInOverviewRuler: true,  // Remove cursor indictor in right hand side scroll bar
1823      readOnly: qwebrOptions['read-only'] ?? false,
1824      quickSuggestions: qwebrOptions['editor-quick-suggestions'] ?? false,
1825      wordWrap: (qwebrOptions['editor-word-wrap'] == 'true' ? "on" : "off")
1826    });
1827
1828    // Store the official counter ID to be used in keyboard shortcuts
1829    editor.__qwebrCounter = qwebrCounter;
1830
1831    // Store the official div container ID
1832    editor.__qwebrEditorId = `qwebr-editor-${qwebrCounter}`;
1833
1834    // Store the initial code value and options
1835    editor.__qwebrinitialCode = initialCode;
1836    editor.__qwebrOptions = qwebrOptions;
1837
1838    // Set at the model level the preferred end of line (EOL) character to LF.
1839    // This prevent `\r\n` from being given to the webR engine if the user is on Windows.
1840    // See details in: https://github.com/coatless/quarto-webr/issues/94
1841    // Associated error text: 
1842    // Error: <text>:1:7 unexpected input
1843
1844    // Retrieve the underlying model
1845    const model = editor.getModel();
1846    // Set EOL for the model
1847    model.setEOL(monaco.editor.EndOfLineSequence.LF);
1848    
1849    // Dynamically modify the height of the editor window if new lines are added.
1850    let ignoreEvent = false;
1851    const updateHeight = () => {
1852      // Increment editor height by 2 to prevent vertical scroll bar from appearing
1853      const contentHeight = editor.getContentHeight() + 2;
1854
1855      // Retrieve editor-max-height option
1856      const maxEditorHeight = qwebrOptions['editor-max-height'];
1857
1858      // If editor-max-height is missing, allow infinite growth. Otherwise, threshold.
1859      const editorHeight = !maxEditorHeight ?  contentHeight : Math.min(contentHeight, maxEditorHeight);
1860
1861      // We're avoiding a width change
1862      //editorDiv.style.width = `${width}px`;
1863      editorDiv.style.height = `${editorHeight}px`;
1864      try {
1865        ignoreEvent = true;
1866
1867        // The key to resizing is this call
1868        editor.layout();
1869      } finally {
1870        ignoreEvent = false;
1871      }
1872    };
1873
1874    // Function to generate decorations to highlight lines
1875    // in the editor based on input string.
1876    function decoratorHighlightLines(codeLineNumbers) {
1877      // Store the lines to be lighlight
1878      let linesToHighlight = [];
1879      
1880      // Parse the codeLineNumbers string to get the line numbers to highlight
1881      // First, split the string by commas
1882      codeLineNumbers.split(',').forEach(part => {
1883        // Check if we have a range of lines
1884        if (part.includes('-')) {
1885            // Handle range of lines (e.g., "6-8")
1886            const [start, end] = part.split('-').map(Number);
1887            for (let i = start; i <= end; i++) {
1888                linesToHighlight.push(i);
1889            }
1890        } else {
1891            // Handle single line (e.g., "7")
1892            linesToHighlight.push(Number(part));
1893        }
1894      });
1895  
1896      // Create monaco decorations for the lines to highlight
1897      const decorations = linesToHighlight.map(lineNumber => ({
1898          range: new monaco.Range(lineNumber, 1, lineNumber, 1),
1899          options: {
1900              isWholeLine: true,
1901              className: 'qwebr-editor-highlight-line'
1902          }
1903      }));
1904  
1905      // Return decorations to be applied to the editor
1906      return decorations;
1907    }
1908
1909    // Ensure that the editor-code-line-numbers option is set and valid
1910    // then apply styling
1911    if (qwebrOptions['editor-code-line-numbers']) {
1912      // Remove all whitespace from the string
1913      const codeLineNumbers = qwebrOptions['editor-code-line-numbers'].replace(/\s/g,'');
1914      // Check if the string is valid for line numbers, e.g., "1,3-5,7"
1915      if (isValidCodeLineNumbers(codeLineNumbers)) {
1916        // Apply the decorations to the editor
1917        editor.createDecorationsCollection(decoratorHighlightLines(codeLineNumbers));
1918      } else {
1919        // Warn the user that the input is invalid
1920        console.warn(`Invalid "editor-code-line-numbers" value in code cell ${qwebrOptions['label']}: ${codeLineNumbers}`);
1921      }
1922    }
1923
1924    // Helper function to check if selected text is empty
1925    function isEmptyCodeText(selectedCodeText) {
1926      return (selectedCodeText === null || selectedCodeText === undefined || selectedCodeText === "");
1927    }
1928
1929    // Registry of keyboard shortcuts that should be re-added to each editor window
1930    // when focus changes.
1931    const addWebRKeyboardShortCutCommands = () => {
1932      // Add a keydown event listener for Shift+Enter to run all code in cell
1933      editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, () => {
1934
1935        // Retrieve all text inside the editor
1936        qwebrExecuteCode(editor.getValue(), editor.__qwebrCounter, editor.__qwebrOptions);
1937      });
1938
1939      // Add a keydown event listener for CMD/Ctrl+Enter to run selected code
1940      editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {
1941
1942        // Get the selected text from the editor
1943        const selectedText = editor.getModel().getValueInRange(editor.getSelection());
1944        // Check if no code is selected
1945        if (isEmptyCodeText(selectedText)) {
1946          // Obtain the current cursor position
1947          let currentPosition = editor.getPosition();
1948          // Retrieve the current line content
1949          let currentLine = editor.getModel().getLineContent(currentPosition.lineNumber);
1950
1951          // Propose a new position to move the cursor to
1952          let newPosition = new monaco.Position(currentPosition.lineNumber + 1, 1);
1953
1954          // Check if the new position is beyond the last line of the editor
1955          if (newPosition.lineNumber > editor.getModel().getLineCount()) {
1956            // Add a new line at the end of the editor
1957            editor.executeEdits("addNewLine", [{
1958            range: new monaco.Range(newPosition.lineNumber, 1, newPosition.lineNumber, 1),
1959            text: "\n", 
1960            forceMoveMarkers: true,
1961            }]);
1962          }
1963          
1964          // Run the entire line of code.
1965          qwebrExecuteCode(currentLine, editor.__qwebrCounter, editor.__qwebrOptions);
1966
1967          // Move cursor to new position
1968          editor.setPosition(newPosition);
1969        } else {
1970          // Code to run when Ctrl+Enter is pressed with selected code
1971          qwebrExecuteCode(selectedText, editor.__qwebrCounter, editor.__qwebrOptions);
1972        }
1973      });
1974    }
1975
1976    // Register an on focus event handler for when a code cell is selected to update
1977    // what keyboard shortcut commands should work.
1978    // This is a workaround to fix a regression that happened with multiple
1979    // editor windows since Monaco 0.32.0 
1980    // https://github.com/microsoft/monaco-editor/issues/2947
1981    editor.onDidFocusEditorText(addWebRKeyboardShortCutCommands);
1982
1983    // Register an on change event for when new code is added to the editor window
1984    editor.onDidContentSizeChange(updateHeight);
1985
1986    // Manually re-update height to account for the content we inserted into the call
1987    updateHeight();
1988
1989    // Store the editor instance in the global dictionary
1990    qwebrEditorInstances[editor.__qwebrCounter] = editor;
1991
1992  });
1993
1994  // Add a click event listener to the run button
1995  runButton.onclick = function () {
1996    qwebrExecuteCode(editor.getValue(), editor.__qwebrCounter, editor.__qwebrOptions);
1997  };
1998
1999  // Add a click event listener to the reset button
2000  copyButton.onclick = function () {
2001    // Retrieve current code data
2002    const data = editor.getValue();
2003    
2004    // Write code data onto the clipboard.
2005    navigator.clipboard.writeText(data || "");
2006  };
2007  
2008  // Add a click event listener to the copy button
2009  resetButton.onclick = function () {
2010    editor.setValue(editor.__qwebrinitialCode);
2011  };
2012  
2013}
2014</script>
2014
2015  <div class="reveal">
2016    <div class="slides">
2017
2018<section id="title-slide" class="quarto-title-block center">
2019  <h1 class="title"><p><span style="color:#a9a9a9;">Week 4</span><br>
2020Causal Inference</p></h1>
2021  <p class="subtitle"><span style="color:#9c9c9c; font-family: Dynalight; font-size: 130%;">Soci—316</span></p>
2022
2023<div class="quarto-title-authors">
2024<div class="quarto-title-author">
2025<div class="quarto-title-author-name">
2026Sakeef M. Karim <br><span style="font-size: 80%; color:#9c9c9c;"><em>Amherst College</em></span><br><a href="https://www.sakeefkarim.com" class="external" target="_blank"><svg aria-hidden="true" role="img" viewbox="0 0 512 512" style="height:1em;width:1em;vertical-align:-0.125em;margin-left:auto;margin-right:auto;font-size:inherit;fill:#856cb0;overflow:visible;position:relative;"><path d="M352 256c0 22.2-1.2 43.6-3.3 64H163.3c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64H348.7c2.2 20.4 3.3 41.8 3.3 64zm28.8-64H503.9c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64H380.8c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32H376.7c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0H167.7c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 20.9 58.2 27 94.7zm-209 0H18.6C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192H131.2c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64H8.1C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6H344.3c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352H135.3zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6H493.4z"></path></svg></a> <a href="mailto:[email protected]"><svg aria-hidden="true" role="img" viewbox="0 0 512 512" style="height:1em;width:1em;vertical-align:-0.125em;margin-left:auto;margin-right:auto;font-size:inherit;fill:#856cb0;overflow:visible;position:relative;"><path d="M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48H48zM0 176V384c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V176L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z"></path></svg></a> <br> <span style="font-weight: 300; font-size: 60%; color: #9c9c9c;">SOCIAL RESEARCH</span>
2027</div>
2028</div>
2029</div>
2030
2031</section>
2032<section>
2033<section id="does-x-to-y-september-22nd" class="title-slide slide level1 center" data-background-color="#43464B">
2034<h1>Does <span style="font-size:90%"><span class="math inline">X</span></span> <span class="math inline">\to</span> <span style="font-size:90%"><span class="math inline">Y</span></span>?<br>September 22<span style="font-size: 70%;"><sup>nd</sup></span></h1>
2035
2036</section>
2037<section id="an-update" class="slide level2">
2038<h2>An Update</h2>
2039<h4 id="qualtrics-survey-assignment"><span style="color:#a9a9a9;">Qualtrics Survey Assignment</span></h4>
2040<div style="text-align: center; margin-top: 25px;">
2041<p><span style="font-size: 85%;">Assignment instructions are live.</span></p>
2042<p align="center">
2043<iframe class="iframe" src="https://soci-316-qualtrics.netlify.app/" height="420" width="1000" style="border: 1px solid #2e3846;">
2044</iframe>
2045</p>
2046<div style="text-align: center; font-size: 70%; margin-top:-40px">
2047<p><a href="https://soci-316-qualtrics.netlify.app/" data-preview-link="true">Full Page</a></p>
2048</div>
2049</div>
2050</section></section>
2051<section>
2052<section id="advancing-causal-claims" class="title-slide slide level1 center" data-background-color="#43464B">
2053<h1>Advancing Causal Claims</h1>
2054
2055</section>
2056<section id="cause-and-effect" class="slide level2">
2057<h2>Cause and Effect</h2>
2058<div style="text-align: center; margin-top: 80px;">
2059<div class="res-block">
2060<p>Social scientists are often interested in drawing <span style="color: white; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><strong>causal</strong></span> inferences.</p>
2061<p><span class="math inline">X \to  Y</span></p>
2062</div>
2063</div>
2064</section>
2065<section id="cause-and-effect-1" class="slide level2 scrollable">
2066<h2>Cause and Effect</h2>
2067<div style="text-align: center; margin-top: 50px;">
2068<p>About what, exactly?</p>
2069<div class="fragment">
2070<p>There are a dizzying array of examples.</p>
2071</div>
2072<div class="fragment">
2073<p>Here are three questions that invite causal claims:</p>
2074</div>
2075<div class="fragment">
2076<details class="reveal-detail">
2077<summary>
2078Three Causal Questions
2079</summary>
2080<div class="detail-content" style="font-size: 75%; text-align: center;">
2081<ol type="1">
2082<li class="fragment"><p>
2082Do smaller class sizes improve pedagogical outcomes?</p></li>
2083<li class="fragment"><p>Will investments in family planning programs improve economic and health outcomes for women in low-income settings?</p></li>
2084<li class="fragment"><p>Did the cultural grievances of the “middle class” trigger the rise of fascist politics in interwar Europe?</p></li>
2085</ol>
2086</div>
2087</details>
2088</div>
2089</div>
2090</section>
2091<section id="cause-and-effect-2" class="slide level2">
2092<h2>Cause and Effect</h2>
2093<div style="text-align: center; margin-top: 60px; font-size: 95%;">
2094<p>Let’s pause for a second.</p>
2095<div class="fragment">
2096<p>Try to frame your research question in <span style="color: white; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><strong>causal</strong></span> terms.</p>
2097</div>
2098<div style="margin-top: 30px;">
2099
2100</div>
2101<div class="fragment">
2102<p>Rearrange the text in the box below to arrive at your question.</p>
2103<div style="margin-top: 30px;">
2104
2105</div>
2106
2107<!--
2108  ═══════════════════════════════════════════════════════════════════
2109  QUESTION PAD · Quarto RevealJS drop-in
2110  Space Grotesk / JetBrains Mono · purple ramp (#856cb0)
2111  ───────────────────────────────────────────────────────────────────
2112  Notepad idiom, with the card's border as the clock: the stroke
2113  drains clockwise from top centre, turns red in the warning window
2114  (10% of duration, capped at 60s), and the card pulses at 0:00.
2115
2116  Per-instance attributes on .oe-pad:
2117    data-minutes  duration (fractions OK)   default 10
2118    data-file     export filename           default research_question.txt
2119  Master size control: --oe-size.
2120  ═══════════════════════════════════════════════════════════════════
2121-->
2122
2123<style>
2124.oe-pad {
2125  --oe-accent: #856cb0;
2126  --oe-deep:   #6f5a96;
2127  --oe-pale:   #e9e4ff;
2128  --oe-warn:   #c75b5b;
2129  --oe-ink:    #2a2632;
2130  --oe-mute:   #857f93;
2131  --oe-sans: "Space Grotesk", -apple-system, sans-serif;
2132  --oe-mono: "JetBrains Mono", ui-monospace, monospace;
2133  --oe-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38);
2134
2135  /* ▸▸ MASTER SIZE CONTROL ◂◂  17px compact · 20px default · 24px large */
2136  --oe-size: 20px;
2137
2138  position: relative;
2139  box-sizing: border-box;
2140  max-width: 1000px;
2141  margin: 0 auto;
2142  padding: 1em 1.15em 1.05em;
2143  border-radius: 14px;
2144  background: #fff;
2145  box-shadow: var(--oe-shadow);
2146  font-family: var(--oe-sans);
2147  font-size: var(--oe-size);
2148  color: var(--oe-ink);
2149  text-align: left;
2150}
2151.oe-pad *, .oe-pad *::before, .oe-pad *::after { box-sizing: border-box; }
2152
2153/* ── perimeter clock ────────────────────────────────────────────── */
2154.oe-ring { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }
2155.oe-track { fill: none; stroke: rgba(133, 108, 176, .14); stroke-width: 2.5; }
2156.oe-prog {
2157  fill: none;
2158  stroke: var(--oe-accent);
2159  stroke-width: 2.5;
2160  stroke-linecap: round;
2161  stroke-dasharray: 1 2;
2162  opacity: .55;
2163  transition: stroke .3s ease, opacity .3s ease, filter .3s ease;
2164}
2165.oe-pad[data-state="running"] .oe-prog { opacity: 1; filter: drop-shadow(0 0 4px rgba(133, 108, 176, .55)); }
2166.oe-pad[data-state="paused"]  .oe-prog { opacity: .75; }
2167.oe-pad.is-warn .oe-prog { stroke: var(--oe-warn); filter: drop-shadow(0 0 4px rgba(199, 91, 91, .45)); }
2168.oe-pad[data-state="done"]    .oe-prog { opacity: 0; }
2169
2170@keyframes oe-ring-pulse {
2171  0%   { box-shadow: var(--oe-shadow), 0 0 0 0    rgba(199, 91, 91, .4); }
2172  100% { box-shadow: var(--oe-shadow), 0 0 0 14px rgba(199, 91, 91, 0); }
2173}
2174.oe-pad.is-pulse { animation: oe-ring-pulse 1.4s ease-out 3; }
2175
2176/* ── header ─────────────────────────────────────────────────────── */
2177.oe-head {
2178  display: flex;
2179  align-items: center;
2180  justify-content: space-between;
2181  gap: 1em;
2182  margin-bottom: .6em;
2183}
2184.oe-title {
2185  font-size: 1.15em;
2186  font-weight: 600;
2187  letter-spacing: -.015em;
2188  line-height: 1.2;
2189}
2190
2191.oe-clock { display: flex; align-items: center; gap: .35em; }
2192.oe-time {
2193  font-family: var(--oe-mono);
2194  font-size: 1.05em;
2195  font-weight: 500;
2196  color: var(--oe-accent);
2197  min-width: 3.2em;
2198  text-align: right;
2199  letter-spacing: .02em;
2200  font-variant-numeric: tabular-nums;
2201  transition: color .3s ease;
2202}
2203.oe-pad.is-warn .oe-time { color: var(--oe-warn); }
2204.oe-pad[data-state="done"] .oe-time { color: var(--oe-warn); animation: oe-blink 1.4s ease-in-out infinite; }
2205@keyframes oe-blink { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
2206
2207.oe-tbtn {
2208  width: 1.5em; height: 1.5em;
2209  display: grid; place-items: center;
2210  margin: 0; padding: 0;
2211  border: 0; border-radius: 50%;
2212  background: rgba(133, 108, 176, .08);
2213  color: var(--oe-accent);
2214  cursor: pointer;
2215  transition: background-color .2s ease, color .2s ease, transform .15s ease;
2216}
2217.oe-tbtn:hover { background: rgba(133, 108, 176, .16); }
2218.oe-tbtn:active { transform: scale(.94); }
2219.oe-tbtn:focus-visible { outline: 2px solid var(--oe-accent); outline-offset: 2px; }
2220.oe-tbtn.is-on { background: var(--oe-accent); color: #fff; }
2221.oe-tbtn svg { width: .65em; height: .65em; }
2222
2223/* ── editor ─────────────────────────────────────────────────────── */
2224.oe-editor {
2225  display: block;
2226  width: 100%;
2227  min-height: 6.5em;
2228  padding: .8em 1em;
2229  font-family: var(--oe-sans);
2230  font-size: .95em;
2231  font-weight: 300;
2232  line-height: 1.7;
2233  color: var(--oe-ink);
2234  background: #fff;
2235  border: 1px solid rgba(133, 108, 176, .18);
2236  border-radius: 8px;
2237  resize: vertical;
2238  outline: none;
2239  transition: border-color .2s ease, box-shadow .2s ease;
2240}
2241.oe-editor::placeholder { color: var(--oe-mute); font-style: italic; }
2242.oe-editor:focus {
2243  border-color: rgba(133, 108, 176, .45);
2244  box-shadow: 0 0 0 3px rgba(133, 108, 176, .1);
2245}
2246
2247/* ── actions ────────────────────────────────────────────────────── */
2248.oe-actions {
2249  display: flex;
2250  align-items: center;
2251  gap: .35em;
2252  margin-top: .5em;
2253}
2254.oe-btn {
2255  display: inline-flex;
2256  align-items: center;
2257  gap: .4em;
2258  margin: 0;
2259  border: 0;
2260  border-radius: 6px;
2261  padding: .35em 1em;
2262  font-family: var(--oe-sans);
2263  font-size: .78em;
2264  font-weight: 500;
2265  cursor: pointer;
2266  transition: background-color .2s ease, color .2s ease, transform .15s ease;
2267}
2268.oe-btn:active { transform: scale(.98); }
2269.oe-btn:focus-visible { outline: 2px solid var(--oe-accent); outline-offset: 2px; }
2270.oe-btn svg { width: .85em; height: .85em; }
2271
2272.oe-primary { background: var(--oe-accent); color: #fff; }
2273.oe-primary:hover { background: var(--oe-deep); }
2274
2275.oe-secondary { background: rgba(133, 108, 176, .09); color: rgba(133, 108, 176, .75); }
2276.oe-secondary:hover { background: rgba(133, 108, 176, .16); color: var(--oe-accent); }
2277.oe-secondary.is-done { background: var(--oe-pale); color: var(--oe-deep); }
2278
2279.oe-count {
2280  margin-left: auto;
2281  font-family: var(--oe-mono);
2282  font-size: .62em;
2283  letter-spacing: .06em;
2284  text-transform: uppercase;
2285  color: var(--oe-mute);
2286  font-variant-numeric: tabular-nums;
2287}
2288
2289.oe-clear {
2290  background: rgba(199, 91, 91, .09);
2291  color: var(--oe-warn);
2292  margin-left: .35em;
2293}
2294.oe-clear:hover { background: rgba(199, 91, 91, .16); }
2295.oe-clear.is-arming { background: var(--oe-warn); color: #fff; }
2296
2297@media (prefers-reduced-motion: reduce) {
2298  .oe-pad.is-pulse, .oe-pad[data-state="done"] .oe-time { animation: none; }
2299  .oe-prog, .oe-btn, .oe-tbtn { transition: none; }
2300}
2301</style>
2302
2303<div class="oe-pad" data-minutes="10" data-file="research_question.txt">
2304
2305  <svg class="oe-ring" aria-hidden="true">
2306    <path class="oe-track"></path>
2307    <path class="oe-prog" pathlength="1"></path>
2308  </svg>
2309
2310  <div class="oe-head">
2311    <span class="oe-title">Your Research Question</span>
2312    <div class="oe-clock">
2313      <span class="oe-time" role="timer">10:00</span>
2314      <button class="oe-tbtn oe-toggle" type="button" aria-label="Start or pause the timer">
2315        <svg class="oe-i-play" viewbox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M6 4l14 8-14 8z"></path></svg>
2316        <svg class="oe-i-pause" viewbox="0 0 24 24" fill="currentColor" style="display:none" aria-hidden="true"><rect x="6" y="4" width="4" height="16" rx="1"></rect><rect x="14" y="4" width="4" height="16" rx="1"></rect></svg>
2317      </button>
2318      <button class="oe-tbtn oe-reset" type="button" aria-label="Reset the timer">
2319        <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>
2320      </button>
2321    </div>
2322  </div>
2323
2324  <textarea class="oe-editor" rows="4" placeholder="How does X shape variation in Y?"></textarea>
2325
2326  <div class="oe-actions">
2327    <button class="oe-btn oe-primary oe-export" type="button">
2328      <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
2329      Export .txt
2330    </button>
2331    <button class="oe-btn oe-secondary oe-copy" type="button">
2332      <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
2333      <span class="oe-copy-label">Copy</span>
2334    </button>
2335    <span class="oe-count">0 words</span>
2336    <button class="oe-btn oe-clear" type="button">
2337      <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path></svg>
2338      <span class="oe-clear-label">Clear</span>
2339    </button>
2340  </div>
2341</div>
2342
2343<script>
2344(function () {
2345  if (window.__oePadReady) { window.__oePadScan(); return; }
2346  window.__oePadReady = true;
2347
2348  window.__oePadScan = function () {
2349    document.querySelectorAll('.oe-pad').forEach(function (pad) {
2350      if (pad.dataset.oeBound) return;
2351      pad.dataset.oeBound = '1';
2352
2353      var TOTAL   = (parseFloat(pad.dataset.minutes) || 10) * 60000,
2354          WARN_AT = Math.min(60000, TOTAL * 0.1);
2355
2356      var q = function (s) { return pad.querySelector(s); };
2357      var elRing   = q('.oe-ring'),   elTrack  = q('.oe-track'),  elProg  = q('.oe-prog'),
2358          elTime   = q('.oe-time'),   elToggle = q('.oe-toggle'), elPlay  = q('.oe-i-play'),
2359          elPause  = q('.oe-i-pause'), elReset = q('.oe-reset'),  elText  = q('.oe-editor'),
2360          elCount  = q('.oe-count'),  elCopy   = q('.oe-copy'),   elCopyL = q('.oe-copy-label'),
2361          elClear  = q('.oe-clear'),  elClearL = q('.oe-clear-label');
2362
2363      var left = TOTAL, deadline = 0, raf = 0, shown = -1, armed = null, flash = null;
2364
2365      /* ── perimeter path, starting at top centre ── */
2366      function draw() {
2367        var w = pad.offsetWidth, h = pad.offsetHeight;
2368        if (!w || !h) return;
2369        var s = 1.25,
2370            r = Math.max(0, Math.min(parseFloat(getComputedStyle(pad).borderTopLeftRadius) || 0, w / 2, h / 2) - s),
2371            x0 = s, y0 = s, x1 = w - s, y1 = h - s;
2372        var d = 'M' + w / 2 + ' ' + y0 + 'H' + (x1 - r) + 'A' + r + ' ' + r + ' 0 0 1 ' + x1 + ' ' + (y0 + r) +
2373                'V' + (y1 - r) + 'A' + r + ' ' + r + ' 0 0 1 ' + (x1 - r) + ' ' + y1 +
2374                'H' + (x0 + r) + 'A' + r + ' ' + r + ' 0 0 1 ' + x0 + ' ' + (y1 - r) +
2375                'V' + (y0 + r) + 'A' + r + ' ' + r + ' 0 0 1 ' + (x0 + r) + ' ' + y0 + 'Z';
2376        elRing.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
2377        elTrack.setAttribute('d', d);
2378        elProg.setAttribute('d', d);
2379      }
2380      if ('ResizeObserver' in window) new ResizeObserver(draw).observe(pad);
2381      else window.addEventListener('resize', draw);
2382
2383      /* ── timer ── */
2384      function fmt(s) { return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); }
2385
2386      function setState(st) {
2387        pad.dataset.state = st;
2388        var on = st === 'running';
2389        elToggle.classList.toggle('is-on', on);
2390        elPlay.style.display  = on ? 'none' : '';
2391        elPause.style.display = on ? '' : 'none';
2392      }
2393
2394      function paint() {
2395        elProg.style.strokeDashoffset = -(1 - left / TOTAL);
2396        var secs = Math.ceil(left / 1000);
2397        if (secs !== shown) { shown = secs; elTime.textContent = fmt(secs); }
2398        pad.classList.toggle('is-warn', left <= WARN_AT);
2399      }
2400
2401      function tick(now) {
2402        left = Math.max(0, deadline - now);
2403        paint();
2404        if (left === 0) { finish(); return; }
2405        raf = requestAnimationFrame(tick);
2406      }
2407
2408      function finish() {
2409        cancelAnimationFrame(raf);
2410        setState('done');
2411        pad.classList.remove('is-pulse');
2412        void pad.offsetWidth;
2413        pad.classList.add('is-pulse');
2414      }
2415
2416      elToggle.addEventListener('click', function () {
2417        if (pad.dataset.state === 'running') {
2418          cancelAnimationFrame(raf);
2419          left = Math.max(0, deadline - performance.now());
2420          setState('paused');
2421          paint();
2422          return;
2423        }
2424        if (left <= 0) { left = TOTAL; shown = -1; pad.classList.remove('is-pulse'); }
2425        deadline = performance.now() + left;
2426        setState('running');
2427        raf = requestAnimationFrame(tick);
2428        elText.focus();
2429      });
2430
2431      elReset.addEventListener('click', function () {
2432        cancelAnimationFrame(raf);
2433        left = TOTAL; shown = -1;
2434        pad.classList.remove('is-pulse');
2435        setState('idle');
2436        paint();
2437      });
2438
2439      /* ── word count ── */
2440      function count() {
2441        var w = elText.value.trim();
2442        var n = w ? w.split(/\s+/).length : 0;
2443        elCount.textContent = n + (n === 1 ? ' word' : ' words');
2444      }
2445      elText.addEventListener('input', count);
2446
2447      /* Reveal listens on document — keep every keystroke out of its hands */
2448      ['keydown', 'keypress', 'keyup'].forEach(function (ev) {
2449        elText.addEventListener(ev, function (e) { e.stopPropagation(); });
2450      });
2451      pad.addEventListener('keydown', function (e) {
2452        if (e.key === ' ' || e.key === 'Spacebar') e.stopPropagation();
2453      });
2454
2455      /* ── export / copy / clear ── */
2456      q('.oe-export').addEventListener('click', function () {
2457        var t = elText.value;
2458        if (!t.trim()) return;
2459        var a = document.createElement('a');
2460        a.href = URL.createObjectURL(new Blob([t], { type: 'text/plain;charset=utf-8' }));
2461        a.download = pad.dataset.file || 'research_question.txt';
2462        a.click();
2463        setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000);
2464      });
2465
2466      elCopy.addEventListener('click', function () {
2467        var t = elText.value;
2468        if (!t.trim()) return;
2469        function done() {
2470          clearTimeout(flash);
2471          elCopy.classList.add('is-done');
2472          elCopyL.textContent = 'Copied';
2473          flash = setTimeout(function () {
2474            elCopy.classList.remove('is-done');
2475            elCopyL.textContent = 'Copy';
2476          }, 1600);
2477        }
2478        if (navigator.clipboard && navigator.clipboard.writeText) {
2479          navigator.clipboard.writeText(t).then(done, function () { elText.select(); });
2480        } else { elText.select(); document.execCommand('copy'); done(); }
2481      });
2482
2483      elClear.addEventListener('click', function () {
2484        if (!elText.value.trim()) return;
2485        if (armed) {
2486          clearTimeout(armed); armed = null;
2487          elClear.classList.remove('is-arming');
2488          elClearL.textContent = 'Clear';
2489          elText.value = '';
2490          count();
2491          elText.focus();
2492          return;
2493        }
2494        elClear.classList.add('is-arming');
2495        elClearL.textContent = 'Confirm';
2496        armed = setTimeout(function () {
2497          armed = null;
2498          elClear.classList.remove('is-arming');
2499          elClearL.textContent = 'Clear';
2500        }, 3000);
2501      });
2502
2503      setState('idle');
2504      paint();
2505      count();
2506      draw();
2507    });
2508  };
2509
2510  if (document.readyState === 'loading') {
2511    document.addEventListener('DOMContentLoaded', window.__oePadScan);
2512  } else { window.__oePadScan(); }
2513  if (window.Reveal && Reveal.on) Reveal.on('ready', window.__oePadScan);
2514})();
2515</script>
2515
2516
2517</div>
2518</div>
2519</section>
2520<section id="cause-and-effect-3" class="slide level2">
2521<h2>Cause and Effect</h2>
2522<div style="text-align: center; margin-top: 80px;">
2523<div class="res-block">
2524<p>We can, of course, deploy quantitative tools (e.g., estimators, weighting, causal diagrams, experiments) to “resolve” causal questions—or at least adduce evidence grounded in statistical reasoning.</p>
2525</div>
2526<div style="text-align: center;">
2527<div class="scrollable-note">
2528<p><span style="font-size: 85%;">For an overview of how causality “works” in ethnographic research, see Small <span class="citation" data-cites="small_causal_2013">(<a href="#/references" role="doc-biblioref" onclick="">2013</a>)</span>.</span></p>
2529</div>
2530</div>
2531</div>
2532</section>
2533<section id="cause-and-effect-4" class="slide level2">
2534<h2>Cause and Effect</h2>
2535<div style="text-align: center; margin-top: 100px; margin-bottom: 30px;">
2536<p>Let’s assume that our outcomes of substantive interest are approximately linear. Can we estimate linear regression models to “resolve” our <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><strong>causal</strong></span> questions?</p>
2537<p><span class="math inline">y = \beta_0 + \beta_1 x + \epsilon</span></p>
2538</div>
2539<div class="fragment">
2540<details class="reveal-detail">
2541<summary>
2542The Answer
2543</summary>
2544<div class="detail-content" style="font-size: 75%; text-align: center;">
2545<p>It <span style="text-decoration: underline; text-decoration-color: #856cb0;"><em>depends</em></span>.</p>
2546</div>
2547</details>
2548</div>
2549</section>
2550<section id="cause-and-effect-5" class="slide level2">
2551<h2>Cause and Effect</h2>
2552<h4 id="the-class-size-example"><span style="color:#a9a9a9;">The Class Size Example</span></h4>
2553
2554<!--
2555  ═══════════════════════════════════════════════════════════════════
2556  DAG · two-step confounder reveal · Quarto RevealJS drop-in
2557  Space Grotesk · purple ramp (#856cb0)
2558  ───────────────────────────────────────────────────────────────────
2559  Step 1: "Confounder?" on a hollow, dashed node.
2560  Step 2: label crossfades to "Field of Study"; node fills.
2561  Advance with the normal Reveal key (it's a fragment) or click the
2562  figure. Click again, or step back, to return. Size: --dag-size.
2563  ═══════════════════════════════════════════════════════════════════
2564-->
2565
2566<style>
2567.dag2 {
2568  --dag-ink:    #2a2632;
2569  --dag-accent: #856cb0;
2570  --dag-deep:   #6f5a96;
2571  --dag-pale:   #f3effa;
2572  --dag-lilac:  #c9bce2;
2573  --dag-size:   22px;
2574
2575  position: relative;
2576  max-width: 960px;
2577  margin: 0 auto;
2578  font-family: "Space Grotesk", -apple-system, sans-serif;
2579  font-size: var(--dag-size);
2580  line-height: 1;
2581  cursor: pointer;
2582  -webkit-tap-highlight-color: transparent;
2583}
2584.dag2 svg { display: block; width: 100%; height: auto; overflow: visible; }
2585
2586/* edges */
2587.dag2 .e { fill: none; stroke: var(--dag-ink); stroke-width: 2; }
2588.dag2 .e-dash { stroke-dasharray: 9 6 2 6; }
2589
2590/* nodes */
2591.dag2 .ring { fill: none; stroke-width: 1.5; }
2592.dag2 .n-x .core { fill: var(--dag-ink); }   .dag2 .n-x .ring { stroke: var(--dag-ink); }
2593.dag2 .n-y .core { fill: var(--dag-lilac); } .dag2 .n-y .ring { stroke: var(--dag-lilac); }
2594
2595.dag2 .n-z .core { fill: var(--dag-pale); transition: fill .5s ease; }
2596.dag2 .n-z .ring {
2597  stroke: var(--dag-accent);
2598  stroke-dasharray: 4 4;
2599  transition: stroke-dasharray .5s ease;
2600}
2601.dag2.is-on .n-z .core { fill: var(--dag-accent); }
2602.dag2.is-on .n-z .ring { stroke-dasharray: 0 0; }
2603
2604/* labels */
2605.dag2 .lab {
2606  position: absolute;
2607  transform: translate(-50%, -50%);
2608  display: grid;
2609  padding: .22em .45em .26em;
2610  border-radius: 5px;
2611  font-weight: 500;
2612  letter-spacing: -.005em;
2613  white-space: nowrap;
2614}
2615.dag2 .lab-x { left: 9.06%;  top: 96.3%; background: var(--dag-ink);   color: #fff; }
2616.dag2 .lab-y { left: 90.94%; top: 96.3%; background: var(--dag-lilac); color: var(--dag-ink); }
2617.dag2 .lab-z {
2618  left: 50%; top: 18.9%;
2619  background: var(--dag-accent); color: #fff;
2620  transition: background-color .5s ease;
2621}
2622.dag2.is-on .lab-z { background: var(--dag-deep); }
2623
2624/* crossfade: both states share one grid cell, so the box never jumps */
2625.dag2 .lab-z >
2625 span {
2626  grid-area: 1 / 1;
2627  justify-self: center;
2628  transition: opacity .35s ease, transform .35s ease, filter .35s ease;
2629}
2630.dag2 .lab-z .s2 { opacity: 0; transform: translateY(.3em); filter: blur(3px); }
2631.dag2.is-on .lab-z .s1 { opacity: 0; transform: translateY(-.3em); filter: blur(3px); }
2632.dag2.is-on .lab-z .s2 { opacity: 1; transform: none; filter: none; transition-delay: .1s; }
2633
2634.dag2 .dag2-frag { position: absolute; width: 0; height: 0; overflow: hidden; }
2635
2636@media (prefers-reduced-motion: reduce) {
2637  .dag2 *, .dag2 .lab-z > span { transition: none !important; filter: none !important; }
2638}
2639</style>
2640
2641<div class="dag2" role="img" aria-label="DAG: a confounder, later identified as field of study, causes both Amherst class size and income at 30; class size to income is the path of interest.">
2642  <svg viewbox="0 0 960 470" aria-hidden="true">
2643    <defs>
2644      <marker id="dag2-arr" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse">
2645        <path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path>
2646      </marker>
2647    </defs>
2648
2649    <path class="e" marker-end="url(#dag2-arr)" d="M470 106 C440 175 300 210 250 236 S100 322 90 382"></path>
2650    <path class="e" marker-end="url(#dag2-arr)" d="M490 106 C520 175 660 210 710 236 S860 322 870 382"></path>
2651    <path class="e e-dash" marker-end="url(#dag2-arr)" d="M117 413 H838"></path>
2652
2653    <g class="n-z"><circle class="core" cx="480" cy="47" r="20"></circle><circle class="ring" cx="480" cy="47" r="23"></circle></g>
2654    <g class="n-x"><circle class="core" cx="87" cy="413" r="20"></circle><circle class="ring" cx="87" cy="413" r="23"></circle></g>
2655    <g class="n-y"><circle class="core" cx="873" cy="413" r="20"></circle><circle class="ring" cx="873" cy="413" r="23"></circle></g>
2656  </svg>
2657
2658  <span class="lab lab-z"><span class="s1">Confounder?</span><span class="s2">Field of Study</span></span>
2659  <span class="lab lab-x">Amherst Class Size</span>
2660  <span class="lab lab-y">Income at 30</span>
2661
2662  <span class="fragment dag2-frag"></span>
2663</div>
2664
2665<script>
2666(function () {
2667  document.querySelectorAll('.dag2').forEach(function (d) {
2668    if (d.dataset.bound) return;
2669    d.dataset.bound = '1';
2670
2671    var f = d.querySelector('.dag2-frag');
2672    var R = window.Reveal && Reveal.on ? Reveal : null;
2673    function set(on) { d.classList.toggle('is-on', on); }
2674    function sync() { set(f.classList.contains('visible')); }
2675
2676    if (R) {
2677      R.on('fragmentshown',  function (e) { if (e.fragment === f) set(true);  });
2678      R.on('fragmenthidden', function (e) { if (e.fragment === f) set(false); });
2679      R.on('slidechanged', sync);
2680      R.on('ready', sync);
2681    }
2682
2683    /* click toggles; with Reveal, it moves the fragment state itself so
2684       arrow keys and clicks never disagree */
2685    d.addEventListener('click', function () {
2686      var on = !d.classList.contains('is-on');
2687      var i  = parseInt(f.getAttribute('data-fragment-index'), 10);
2688      if (R && R.navigateFragment && !isNaN(i)) R.navigateFragment(on ? i : i - 1);
2689      else set(on);
2690    });
2691  });
2692})();
2693</script>
2693
2694
2695</section>
2696<section id="cause-and-effect-6" class="slide level2">
2697<h2>Cause and Effect</h2>
2698<h4 id="the-class-size-example-1"><span style="color:#a9a9a9;">The Class Size Example</span></h4>
2699<div style="margin-top: 100px; text-align: center;">
2700<p>How can we deal with <em>confounding</em>—i.e., to estimate the <span style="color: white; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><strong>causal effect</strong></span> of class size at Amherst<br>on post-graduate outcomes?</p>
2701<div style="margin-top: 60px;">
2702
2703</div>
2704<div class="fragment">
2705<div class="rq-block">
2706<p><span style="color: white; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><em>Randomization</em></span> is the gold standard.</p>
2707<p>We will return to this point in a second.</p>
2708</div>
2709</div>
2710</div>
2711</section></section>
2712<section>
2713<section id="some-technical-details" class="title-slide slide level1 center" data-background-color="#43464B">
2714<h1>Some Technical Details</h1>
2715
2716</section>
2717<section id="treatments-and-outcomes" class="slide level2">
2718<h2>Treatments and Outcomes</h2>
2719<div style="margin-top: 40px;">
2720
2721</div>
2722<div class="elegant-quote">
2723<div class="shrinker">
2724<p><span style="color: #856cb0;"><strong>Causal relationships</strong></span> refer to <span style="color: white; background-color: #856cb0; padding: 1px 5px;">the cause-and-effect connection between two variables</span> … We refer to the variable where the change originates as the <span style="color: #856cb0;"><strong>treatment variable</strong></span>. We refer to the variable that may change in response to the change in the treatment variable as the <span style="color: #856cb0;"><strong>outcome variable</strong></span>.</p>
2725<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:28</a>
2725, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p>
2726</div>
2727</div>
2728</section>
2729<section id="treatments-and-outcomes-1" class="slide level2">
2730<h2>Treatments and Outcomes</h2>
2731<h4 id="under-the-assumption-of-a-binary-treatment"><span style="color:#a9a9a9;">Under the Assumption of a Binary Treatment</span></h4>
2732<div style="margin-top: 110px; font-size: 80%;">
2733<span class="math display">
2734X_i=
2735\begin{cases}
27361 &amp; \text{if individual } i \text{ receives the treatment}\\
27370 &amp; \text{if individual } i \text{ does not receive the treatment}
2738\end{cases}
2739</span>
2740<div class="fragment" style="margin-top: 50px;">
2741<p><span class="math display">
2742small_i=
2743\begin{cases}
27441 &amp; \text{if individual } i \text{ assigned to small classes at Amherst}\\
27450 &amp; \text{if individual } i \text{ was not assigned to small classes at Amherst}
2746\end{cases}
2747</span></p>
2748</div>
2749</div>
2750</section>
2751<section id="treatments-and-outcomes-2" class="slide level2">
2752<h2>Treatments and Outcomes</h2>
2753<div style="margin-top: 40px;">
2754
2755</div>
2756<div class="elegant-quote">
2757<div class="shrinker-plus">
2758<p>When estimating the <span style="color: #856cb0;"><strong>causal effect of <span class="math inline">X</span> on <span class="math inline">Y</span></strong></span>, <span style="color: white; background-color: #856cb0; padding: 1px 5px;">we attempt to quantify the change in the outcome variable <span class="math inline">Y</span> that is caused by a change in the treatment variable <span class="math inline">X</span></span> … In mathematical notation, we represent change with <span class="math inline">\Delta</span> (the Greek letter Delta), and thus, we represent a change in the outcome as <span class="math inline">\Delta Y</span>. To measure this change in the outcome <span class="math inline">Y</span>, ideally we would compare two potential outcomes: <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">the outcome when the treatment is present and the outcome when the treatment is absent</span>.</p>
2759<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:29–30</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p>
2760</div>
2761</div>
2762</section>
2763<section id="treatments-and-outcomes-3" class="slide level2">
2764<h2>Treatments and Outcomes</h2>
2765<div style="margin-top: 55px;">
2766
2767</div>
2768<div class="elegant-quote">
2769<div class="shrinker">
2770<ul>
2771<li><p><span style="color: #856cb0;"><span class="math inline">Y_{i}(X_{i}=1)</span></span> is the <span style="color: #856cb0;"><strong>potential outcome under the <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">treatment</span> condition</strong></span> for individual <span class="math inline">i</span> (the value of <span class="math inline">Y_{i}</span> if <span class="math inline">X_{i}=1</span>).</p></li>
2772<li><p><span style="color: #856cb0;"><span class="math inline">Y_{i}(X_{i}=0)</span></span> is the <span style="color: #856cb0;"><strong>potential outcome under the <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">control</span> condition</strong></span> for individual <span class="math inline">i</span> (the value of <span class="math inline">Y_{i}</span> if <span class="math inline">X_{i}=0</span>).</p></li>
2773</ul>
2774<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:30</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p>
2775</div>
2776</div>
2777<div class="fragment" style="margin-top: 10px; font-size: 80%;">
2778<p><span class="math display">
2779\Delta income_{i}
2780=
2781\underbrace{income_{i}\!\left(small_{i}=1\right)}_{\text{Treatment}}
2782-
2783\underbrace{income_{i}\!\left(small_{i}=0\right)}_{\text{Control}}
2784</span></p>
2785</div>
2786</section>
2787<section id="treatments-and-outcomes-4" class="slide level2">
2788<h2>Treatments and Outcomes</h2>
2789<div style="margin-top: 60px;">
2790
2791</div>
2792<div class="po-card">
2793<p><span class="po-title">If we could observe both potential outcomes</span></p>
2794<div class="po-eq">
2795<p><span class="math display">
2796\textit{Individual effect}_i \;=\; \Delta Y_i \;=\; {\color{#856cb0}
2796{Y_i(X_i{=}1)}} \;-\; {\color{#8a8494}{Y_i(X_i{=}0)}}
2797</span></p>
2798</div>
2799<div class="po-defs">
2800<p><span class="po-where">where</span></p>
2801<p><span class="po-term"><span class="math inline">\Delta Y_i</span></span></p>
2802<p><span class="po-desc">The change in the outcome individual <span class="math inline">i</span> would have experienced by receiving the treatment, compared to not receiving it.</span></p>
2803<p><span class="po-term"><span class="math inline">{\color{#856cb0}{Y_i(X_i{=}1)}}</span><br><span class="math inline">{\color{#8a8494}{Y_i(X_i{=}0)}}</span></span></p>
2804<p><span class="po-desc">The two potential outcomes for the same individual <span class="math inline">i</span>, under the <span class="po-t">treatment</span> and <span class="po-c">control</span> conditions, respectively.</span></p>
2805</div>
2806</div>
2807<p><span class="po-source">Adaptation of summary box in Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:30</a>)</span>.</span></p>
2808<style>
2809.po-card {
2810  --po-accent: #856cb0;
2811  --po-ink:    #2a2632;
2812  --po-mute:   #8a8494;
2813  --po-tint:   #f7f4fb;
2814
2815  max-width: 900px;
2816  margin: 1.3em auto 0;
2817  padding: 1em 1.5em 1.15em;
2818  border-radius: 14px;
2819  background: #fff;
2820  border: 1px solid rgba(133, 108, 176, .16);
2821  box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38);
2822  color: var(--po-ink);
2823  text-align: left;
2824}
2825.po-card p { margin: 0; }
2826
2827.po-title {
2828  display: block;
2829  text-align: center;
2830  font-size: .78em;
2831  font-weight: 600;
2832  letter-spacing: -.01em;
2833  color: var(--po-accent);
2834}
2835
2836.po-eq {
2837  margin: .5em 0 .8em;
2838  padding: .1em 1em;
2839  border-radius: 10px;
2840  background: var(--po-tint);
2841  font-size: .7em;
2842  text-align: center;
2843  overflow: hidden;
2844}
2845.po-eq p { margin: 0; }
2846.po-eq mjx-container[display="true"] { margin: .45em 0 !important; }
2847
2848.po-defs {
2849  display: grid;
2850  grid-template-columns: max-content 1fr;
2851  align-items: baseline;
2852  column-gap: 1.2em;
2853  row-gap: .7em;
2854  font-size: .6em;
2855  line-height: 1.5;
2856}
2857.po-defs > p:first-child { grid-column: 1 / -1; }
2858.po-where { font-style: italic; color: var(--po-mute); }
2859.po-term  { display: block; text-align: right; color: var(--po-ink); line-height: 1.7; }
2860.po-desc  { display: block; font-weight: 300; color: rgba(42, 38, 50, .85); text-wrap: pretty; }
2861.po-t { color: var(--po-accent); font-weight: 500; }
2862.po-c { color: var(--po-mute);   font-weight: 500; }
2863
2864.po-source {
2865  display: block;
2866  margin-top: 1em;
2867  text-align: center;
2868  font-size: .5em;
2869  color: #a9a9a9;
2870}
2871</style>
2872</section>
2873<section id="alternate-worlds" class="slide level2">
2874<h2>Alternate Worlds</h2>
2875<div style="margin-top: 40px;">
2876
2877</div>
2878<div class="elegant-quote">
2879<div class="shrinker">
2880<p><span style="color: white; background-color: #856cb0; padding: 1px 5px;">Unfortunately, this kind of analysis is not possible.</span> In the real world, we never observe both potential outcomes for the same individual. Instead, we observe only the <span style="color: #856cb0;"><strong>factual outcome</strong></span>, which is the potential outcome under whichever condition (treatment or control) was received in reality. We can never observe the <span style="color: #856cb0;"><strong>counterfactual outcome</strong></span>, which is the potential outcome that would have occurred under whichever condition (treatment or control) was not received in reality. <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">As a result, we cannot compute causal effects at the individual level</span>.</p>
2881<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:32</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p>
2882</div>
2883</div>
2884</section>
2885<section id="alternate-worlds-1" class="slide level2">
2886<h2>Alternate Worlds</h2>
2887<div style="margin-top: 100px;">
2888
2889</div>
2890<div class="fpci-card">
2891  <div class="fpci-title">The fundamental problem of causal inference</div>
2892
2893  <div class="fpci-body">
2894    <p>To measure causal effects, we need to compare the factual outcome with the counterfactual outcome,</p>
2895    <p class="fpci-key"><span>but we can never observe the counterfactual outcome.</span></p>
2896  </div>
2897</div>
2898
2899<style>
2900.fpci-card {
2901  --fp-accent: #856cb0;
2902  --fp-deep:   #5e4a86;
2903  --fp-ink:    #2a2632;
2904  --fp-tint:   #f5f1fb;
2905
2906  display: grid;
2907  grid-template-columns: 30% 1fr;
2908  max-width: 960px;
2909  margin: 1.3em auto 0;
2910  border-radius: 14px;
2911  overflow: hidden;
2912  background: #fff;
2913  border: 1px solid rgba(133, 108, 176, .16);
2914  box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38);
2915  color: var(--fp-ink);
2916  text-align: left;
2917}
2918
2919/* title panel */
2920.fpci-title {
2921  display: flex;
2922  align-items: center;
2923  padding: 1em 1.1em 1em 1.2em;
2924  background: var(--fp-tint);
2925  border-right: 1px solid rgba(133, 108, 176, .14);
2926  font-size: 1em;
2927  font-weight: 600;
2928  line-height: 1.15;
2929  letter-spacing: -.025em;
2930  color: var(--fp-deep);
2931  text-wrap: balance;
2932}
2933
2934/* statement */
2935.fpci-body {
2936  align-self: center;
2937  padding: 1em 1.3em 1em 1.35em;
2938  font-size: .8em;
2939  line-height: 1.6;
2940}
2941.fpci-body p { margin: 0 !important; }
2942.fpci-body p:first-child {
2943  font-weight: 400;
2944  color: rgba(42, 38, 50, .78);
2945  text-wrap: pretty;
2946}
2947
2948/* the punchline: its own line, one emphasis.
2949   inline + clone, so if it ever wraps each line gets its own pill */
2950.fpci-key { margin-top: .55em !important; line-height: 1.95; }
2951.fpci-key span {
2952  padding: .14em .55em .18em;
2953  -webkit-box-decoration-break: clone;
2954  box-decoration-break: clone;
2955  border-radius: 7px;
2956  background: var(--fp-accent);
2957  line-height:1.5;
2958  color: #fff;
2959  font-weight: 500;
2960  box-shadow: 0 6px 16px -8px rgba(94, 74, 134, .7);
2961}
2962</style>
2963<p><span class="fpci-source">Adaptation of summary box in Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:33</a>)</span>.</span></p>
2964<style>
2965.fpci-source { display: block; margin-top: 1em; text-align: center; font-size: .5em; color: #a9a9a9; }
2966</style>
2967</section>
2968<section id="alternate-worlds-2" class="slide level2">
2969<h2>Alternate Worlds</h2>
2970<div style="text-align: center; margin-top: 30px; font-size: 60%; color: grey; margin-bottom: -10px;">
2971<p><em>Alas, this can’t happen in real life for person <span class="math inline">i</span></em></p>
2972</div>
2973<div class="quarto-figure quarto-figure-center">
2974<figure>
2975<p><a href="./media/spiderman.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img data-src="./media/spiderman.jpg" class="quarto-figure quarto-figure-center" style="width:63.0%"></a></p>
2976</figure>
2977</div>
2978<div style="font-size: 50%; text-align: center; margin-top: -30px;">
2979<p>Image can be retrieved <a href="https://i.redd.it/bekphnqftcb41.jpg" class="external" target="_blank">here</a>.</p>
2980</div>
2981</section></section>
2982<section>
2983<section id="approximating-counterfactuals" class="title-slide slide level1 center" data-background-color="#43464B">
2984<h1>Approximating Counterfactuals</h1>
2985
2986</section>
2987<section id="average-treatment-effects-ates" class="slide level2">
2988<h2>Average Treatment Effects <span style="font-size: 80%;">(ATEs)</span></h2>
2989<div style="margin-top: 40px; font-size: 95.25%;">
2990<div class="elegant-quote">
2991<div class="shrinker-plus">
2992<p>To get around the fundamental problem of causal inference, we must find good approximations for the counterfactual outcomes. To accomplish this, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">we move away from individual-level effects and focus on the <em>average</em> causal effect across a <em>group of individuals</em>
2992.</span></p>
2993<p>The <span style="color: #856cb0;"><strong>average causal effect</strong></span> of the treatment <span class="math inline">X</span> on the outcome <span class="math inline">Y</span>, also known as the <span style="color: #856cb0;"><strong>average treatment effect</strong></span>, is the average of all the individual causal effects of <span class="math inline">X</span> on <span class="math inline">Y</span> within a group. Since each individual causal effect is the change in <span class="math inline">Y</span> caused by a change in <span class="math inline">X</span> for a particular individual, <span style="color: white; background-color: #856cb0; padding: 1px 5px;">the average causal effect of <span class="math inline">X</span> on <span class="math inline">Y</span> is the <em>average</em> change in <span class="math inline">Y</span> caused by a change in <span class="math inline">X</span> for a <em>group of individuals</em></span>.</p>
2994<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:33</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p>
2995</div>
2996</div>
2997</div>
2998</section>
2999<section id="average-treatment-effects-ates-1" class="slide level2">
3000<h2>Average Treatment Effects <span style="font-size: 80%;">(ATEs)</span></h2>
3001<div class="po-card">
3002<p><span class="po-title">If we could observe both potential outcomes</span></p>
3003<div class="po-eq">
3004<p><span class="math display">
3005{\color{#856cb0}{\overline{\textit{Individual effects}}}} \;=\; \frac{\sum_{i=1}^{n} \textit{Individual effects}_{i}}{n}
3006</span></p>
3007</div>
3008<div class="po-defs">
3009<p><span class="po-where">where</span></p>
3010<p><span class="po-term"><span class="math inline">{\color{#856cb0}{\overline{\textit{Individual effects}}}}</span></span></p>
3011<p><span class="po-desc">The <span class="po-t">average treatment effect</span> for the observations in the study.</span></p>
3012<p><span class="po-term"><span class="math inline">\textit{Individual effects}_{i}</span></span></p>
3013<p><span class="po-desc">The individual treatment effect for observation <span class="math inline">i</span>.</span></p>
3014<p><span class="po-term"><span class="math inline">\sum_{i=1}^{n} \textit{Individual effects}_{i}</span></span></p>
3015<p><span class="po-desc">The sum of all <span class="math inline">\textit{Individual effects}_{i}</span>, from <span class="math inline">i=1</span> to <span class="math inline">i=n</span>.</span></p>
3016<p><span class="po-term"><span class="math inline">n</span></span></p>
3017<p><span class="po-desc">The number of observations in the study.</span></p>
3018</div>
3019</div>
3020<p><span class="po-source">Adaptation of summary box in Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:34</a>)</span>.</span></p>
3021<style>
3022.po-card {
3023  --po-accent: #856cb0;
3024  --po-ink:    #2a2632;
3025  --po-mute:   #8a8494;
3026  --po-tint:   #f7f4fb;
3027
3028  max-width: 980px;
3029  margin: 1.3em auto 0;
3030  padding: 1em 1.5em 1.15em;
3031  border-radius: 14px;
3032  background: #fff;
3033  border: 1px solid rgba(133, 108, 176, .16);
3034  box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38);
3035  color: var(--po-ink);
3036  text-align: left;
3037}
3038.po-card p { margin: 0; }
3039
3040.po-title {
3041  display: block;
3042  text-align: center;
3043  font-size: .78em;
3044  font-weight: 600;
3045  letter-spacing: -.01em;
3046  color: var(--po-accent);
3047}
3048
3049.po-eq {
3050  margin: .5em 0 .8em;
3051  padding: .1em 1em;
3052  border-radius: 10px;
3053  background: var(--po-tint);
3054  font-size: .7em;
3055  text-align: center;
3056  overflow: hidden;
3057}
3058.po-eq p { margin: 0; }
3059.po-eq mjx-container[display="true"] { margin: .45em 0 !important; }
3060
3061.po-defs {
3062  display: grid;
3063  grid-template-columns: max-content 1fr;
3064  align-items: baseline;
3065  column-gap: 1.2em;
3066  row-gap: .7em;
3067  font-size: .6em;
3068  line-height: 1.5;
3069}
3070.po-defs > p:first-child { grid-column: 1 / -1; }
3071.po-where { font-style: italic; color: var(--po-mute); }
3072.po-term  { display: block; text-align: right; color: var(--po-ink); line-height: 1.7; }
3073.po-desc  { display: block; font-weight: 300; color: rgba(42, 38, 50, .85); text-wrap: pretty; }
3074.po-t { color: var(--po-accent); font-weight: 500; }
3075.po-c { color: var(--po-mute);   font-weight: 500; }
3076
3077.po-source {
3078  display: block;
3079  margin-top: 1em;
3080  text-align: center;
3081  font-size: .5em;
3082  color: #a9a9a9;
3083}
3084</style>
3085</section>
3086<section id="average-treatment-effects-ates-2" class="slide level2">
3087<h2>Average Treatment Effects <span style="font-size: 80%;">(ATEs)</span></h2>
3088<div style="margin-top: 40px;">
3089
3090</div>
3091<div class="elegant-quote">
3092<div class="shrinker">
3093<p>How can we obtain good approximations for the counterfactual outcomes, which by definition cannot be observed? As we will see in detail soon, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">we must find or create a situation in which the treated observations and the untreated observations are similar with respect to all the variables that might affect the outcome other than the treatment variable itself</span>. The best way to accomplish this is by conducting a <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">randomized experiment</span>.</p>
3094<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:34</a>
3094, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p>
3095</div>
3096</div>
3097</section>
3098<section id="random-assignment" class="slide level2">
3099<h2>Random Assignment</h2>
3100<div style="margin-top: 40px;">
3101
3102</div>
3103<div class="elegant-quote">
3104<div class="shrinker-super-plus">
3105<p>In a <span style="color: #856cb0;"><strong>randomized experiment</strong></span>, also known as a randomized controlled trial (RCT), researchers decide who receives the treatment based on <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">a random process</span> …</p>
3106<ul>
3107<li>the <span style="color: #856cb0;"><strong>treatment group</strong></span> consists of the individuals who <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">received the treatment</span> (the group of observations for which <span class="math inline">X_{i}=1</span>)</li>
3108<li>the <span style="color: #856cb0;"><strong>control group</strong></span> consists of the individuals who <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">did not receive the treatment</span> (the group of observations for which <span class="math inline">X_{i}=0</span>)</li>
3109</ul>
3110<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:34</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p>
3111</div>
3112</div>
3113</section>
3114<section id="random-assignment-1" class="slide level2">
3115<h2>Random Assignment</h2>
3116<h4 id="in-soci316"><span style="font-family: Dynalight; font-size: 130%; color:#a9a9a9;">In Soci—316</span></h4>
3117<div style="font-size: 82%; margin-top: 10px;">
3118<div id="qwebr-insertion-location-1"></div>
3119<noscript>Please enable JavaScript to experience the dynamic code cell content on this page.</noscript>
3120</div>
3121</section>
3122<section id="random-assignment-2" class="slide level2">
3123<h2>Random Assignment</h2>
3124<h4 id="the-class-size-example-2"><span style="color:#a9a9a9;">The Class Size Example</span></h4>
3125<div style="margin-top: 40px;">
3126
3127</div>
3128
3129<!--
3130  ═══════════════════════════════════════════════════════════════════
3131  RANDOMIZATION · Quarto RevealJS drop-in
3132  Space Grotesk · purple ramp (#856cb0)
3133  ───────────────────────────────────────────────────────────────────
3134  400 students as a dot grid, grouped by major. Randomize sends each
3135  dot to Treatment or Control (complete randomization, 200 per arm)
3136  and re-sorts each arm by major, so composition reads as stripes.
3137  Balance rows show each arm's share, the difference in percentage
3138  points on a ±10 pp bar, and the mean difference across all draws.
3139  "Run 100 draws" makes the point that differences average to ~0.
3140
3141  Edit GROUPS / N at the top of the script. Size: --rz-size.
3142  ═══════════════════════════════════════════════════════════════════
3143-->
3144
3145<style>
3146.rz {
3147  --rz-accent: #856cb0;
3148  --rz-deep:   #6f5a96;
3149  --rz-ink:    #2a2632;
3150  --rz-mute:   #857f93;
3151  --rz-tint:   #f7f4fb;
3152  --rz-size:   20px;
3153
3154  box-sizing: border-box;
3155  max-width: 1000px;
3156  margin: .4em auto 0;
3157  padding: .9em 1.15em 1em;
3158  border-radius: 14px;
3159  background: #fff;
3160  border: 1px solid rgba(133, 108, 176, .16);
3161  box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38);
3162  font-family: "Space Grotesk", -apple-system, sans-serif;
3163  font-size: var(--rz-size);
3164  line-height: 1.3;
3165  color: var(--rz-ink);
3166  text-align: left;
3167}
3168.rz *, .rz *::before, .rz *::after { box-sizing: border-box; }
3169
3170/* ── header: legend + controls ─────────────────────────────────── */
3171.rz-head { display: flex; align-items: center; gap: 1em; margin-bottom: .8em; }
3172
3173.rz-legend { display: flex; flex-wrap: wrap; gap: .3em 1em; font-size: .68em; color: var(--rz-mute); }
3174.rz-leg { display: inline-flex; align-items: center; gap: .4em; white-space: nowrap; }
3175.rz-leg b { font-weight: 500; color: var(--rz-ink); }
3176.rz-sw { width: .72em; height: .72em; border-radius: 50%; flex: none; }
3177
3178.rz-ctrls { margin-left: auto; display: flex; align-items: center; gap: .35em; }
3179.rz-btn {
3180  display: inline-flex; align-items: center; gap: .45em;
3181  margin: 0; border: 0; border-radius: 6px;
3182  padding: .42em 1em;
3183  font: inherit; font-size: .74em; font-weight: 500; white-space: nowrap;
3184  cursor: pointer;
3185  transition: background-color .2s ease, color .2s ease, transform .15s ease, box-shadow .2s ease;
3186}
3187.rz-btn:active { transform: scale(.97); }
3188.rz-btn:focus-visible, .rz-icon:focus-visible { outline: 2px solid var(--rz-accent); outline-offset: 2px; }
3189.rz-btn svg { width: .95em; height: .95em; }
3190.rz-primary { background: var(--rz-accent); color: #fff; box-shadow: 0 8px 18px -10px rgba(94, 74, 134, .9); }
3191.rz-primary:hover { background: var(--rz-deep); }
3192.rz-secondary { background: rgba(133, 108, 176, .09); color: var(--rz-deep); }
3193.rz-secondary:hover { background: rgba(133, 108, 176, .16); }
3194.rz-icon {
3195  width: 1.75em; height: 1.75em; display: grid; place-items: center;
3196  margin: 0; padding: 0; border: 0; border-radius: 50%;
3197  background: transparent; color: rgba(111, 90, 150, .7); cursor: pointer;
3198  transition: background-color .2s ease, color .2s ease;
3199}
3200.rz-icon:hover { background: var(--rz-tint); color: var(--rz-deep); }
3201.rz-icon svg { width: .8em; height: .8em; }
3202.rz-btn:disabled { opacity: .55; cursor: default; }
3203
3204/* ── stage ─────────────────────────────────────────────────────── */
3205.rz-labs {
3206  display: grid;
3207  grid-template-columns: 20fr 2fr 20fr;   /* matches the dot grid exactly */
3208  font-size: .68em;
3209  margin-bottom: .45em;
3210}
3211.rz-lab { grid-row: 1; transition: opacity .45s ease; color: var(--rz-mute); }
3212.rz-lab b { font-weight: 600; color: var(--rz-ink); margin-right: .35em; }
3213.rz-lab-all { grid-column: 1 / -1; text-align: center; }
3214.rz-lab-t   { grid-column: 1; }
3215.rz-lab-c   { grid-column: 3; }
3216.rz[data-state="pop"]   .rz-lab-t,
3217.rz[data-state="pop"]   .rz-lab-c   { opacity: 0; }
3218.rz[data-state="split"] .rz-lab-all { opacity: 0; }
3219
3220.rz-canvas { position: relative; width: 100%; aspect-ratio: 42 / 10; }
3221.rz-dot {
3222  position: absolute; left: 0; top: 0;
3223  border-radius: 50%;
3224  transition: transform .8s cubic-bezier(.2, .75, .25, 1);
3225  will-change: transform;
3226}
3227.rz.no-anim .rz-dot { transition: none !important; }
3228.rz-divide {
3229  position: absolute; left: 50%; top: -.2em; bottom: -.2em;
3230  border-left: 1px dashed rgba(133, 108, 176, .45);
3231  opacity: 0; transition: opacity .5s ease .35s;
3232}
3233.rz[data-state="split"] .rz-divide { opacity: 1; }
3234
3235/* ── balance ───────────────────────────────────────────────────── */
3236.rz-bal {
3237  display: grid;
3238  grid-template-columns: minmax(7em, 1.2fr) .8fr .8fr 2.2fr 1fr;
3239  align-items: center;
3240  column-gap: 1.1em;
3241  row-gap: .42em;
3242  margin-top: 1em;
3243  padding-top: .7em;
3244  border-top: 1px solid rgba(133, 108, 176, .14);
3245  font-size: .66em;
3246}
3247.rz-th { font-size: .88em; font-weight: 500; color: var(--rz-mute); padding-bottom: .15em; }
3248.rz-num { text-align: right; font-variant-numeric: tabular-nums; }
3249.rz-major { display: flex; align-items: center; gap: .5em; font-weight: 500; }
3250.rz-major span { font-weight: 400; color: var(--rz-mute); }
3251.rz-pct small { color: var(--rz-mute); font-size: .85em; margin-left: .25em; }
3252
3253.rz-diff { display: flex; align-items: center; gap: .7em; }
3254.rz-dval { min-width: 3.4em; text-align: right; font-variant-numeric: tabular-nums; font-weight: 500; }
3255.rz-dbar { position: relative; flex: 1; height: .5em; border-radius: 99px; background: rgba(133, 108, 176, .09); }
3256.rz-dbar::after {
3257  content: ""; position: absolute; left: 50%; top: -.25em; bottom: -.25em;
3258  width: 1px; background: rgba(42, 38, 50, .35);
3259}
3260.rz-dfill {
3261  position: absolute; top: 0; bottom: 0; left: 50%; width: 0;
3262  border-radius: 99px; background: var(--rz-accent);
3263  transition: left .6s cubic-bezier(.2, .75, .25, 1), width .6s cubic-bezier(.2, .75, .25, 1);
3264}
3265.rz-mean { color: var(--rz-deep); font-weight: 500; }
3266.rz-empty { color: rgba(133, 127, 147, .6); }
3267
3268@media (prefers-reduced-motion: reduce) {
3269  .rz-dot, .rz-dfill, .rz-lab, .rz-divide, .rz-btn { transition: none !important; }
3270}
3271</style>
3272
3273<div class="rz" data-state="pop">
3274  <div class="rz-head">
3275    <div class="rz-legend"></div>
3276    <div class="rz-ctrls">
3277      <button class="rz-icon rz-reset" type="button" aria-label="Reset">
3278        <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>
3279      </button>
3280      <button class="rz-btn rz-secondary rz-many" type="button">
3281        <svg viewbox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M4 5.5v13l8-6.5zM12 5.5v13l8-6.5z"></path></svg>
3282        Run 100 draws
3283      </button>
3284      <button class="rz-btn rz-primary rz-go" type="button">
3285        <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="16 3 21 3 21 8"></polyline><line x1="4" y1="20" x2="21" y2="3"></line><polyline points="21 16 21 21 16 21"></polyline><line x1="15" y1="15" x2="21" y2="21"></line><line x1="4" y1="4" x2="9" y2="9"></line></svg>
3286        Randomize
3287      </button>
3288    </div>
3289  </div>
3290
3291  <div class="rz-labs">
3292    <div class="rz-lab rz-lab-all"><b>All students</b><span class="rz-n-all"></span></div>
3293    <div class="rz-lab rz-lab-t"><b>Treatment</b>small classes, <span class="rz-n-t"></span></div>
3294    <div class="rz-lab rz-lab-c"><b>Control</b>regular classes, <span class="rz-n-c"></span></div>
3295  </div>
3296
3297  <div class="rz-canvas"><div class="rz-divide"></div></div>
3298
3299  <div class="rz-bal">
3300    <div class="rz-th">Major</div>
3301    <div class="rz-th rz-num">Treatment</div>
3302    <div class="rz-th rz-num">Control</div>
3303    <div class="rz-th">Difference, pp <span style="opacity:.7">(bar spans ±10)</span></div>
3304    <div class="rz-th rz-num rz-mean-h">Mean difference</div>
3305  </div>
3306</div>
3307
3308<script>
3309(function () {
3310  var GROUPS = [
3311    { name: 'Major A', color: '#4b3a78', share: .50 },
3312    { name: 'Major B', color: '#3d8fb8', share: .30 },
3313    { name: 'Major C', color: '#5a9a6e', share: .15 },
3314    { name: 'Major D', color: '#d9824b', share: .05 }
3315  ];
3316  var N = 400, ROWS = 10, COLS = 20, GAP = 2, SPAN = COLS * 2 + GAP, BAR_MAX = 10;
3317
3318  function init(root) {
3319    if (root.dataset.rzBound) return;
3320    root.dataset.rzBound = '1';
3321
3322    var q = function (s) { return root.querySelector(s); };
3323    var canvas = q('.rz-canvas'), bal = q('.rz-bal');
3324    var T = Math.floor(N / 2), C = N - T;
3325    q('.rz-n-all').textContent = N;
3326    q('.rz-n-t').textContent = 'n = ' + T;
3327    q('.rz-n-c').textContent = 'n = ' + C;
3328
3329    /* population, sorted by major; rounding drift goes to the largest group */
3330    var counts = GROUPS.map(function (g) { return Math.round(g.share * N); });
3331    counts[0] += N - counts.reduce(function (a, b) { return a + b; }, 0);
3332    var pop = [];
3333    counts.forEach(function (n, g) { for (var i = 0; i < n; i++) pop.push(g); });
3334
3335    /* legend */
3336    q('.rz-legend').innerHTML = GROUPS.map(function (g, i) {
3337      return '<span class="rz-leg"><span class="rz-sw" style="background:' + g.color + '"></span><b>' +
3338             g.name + '</b>' + Math.round(counts[i] / N * 100) + '%</span>';
3339    }).join('');
3340
3341    /* dots */
3342    var dots = pop.map(function (g) {
3343      var d = document.createElement('div');
3344      d.className = 'rz-dot';
3345      d.style.background = GROUPS[g].color;
3346      canvas.appendChild(d);
3347      return { el: d, g: g, col: 0, row: 0 };
3348    });
3349
3350    /* balance rows */
3351    var rows = GROUPS.map(function (g, i) {
3352      var cells = [
3353        '<div class="rz-major"><span class="rz-sw" style="background:' + g.color + '"></span>' + g.name + '</div>',
3354        '<div class="rz-num rz-pct rz-t"></div>',
3355        '<div class="rz-num rz-pct rz-c"></div>',
3356        '<div class="rz-diff"><span class="rz-dval"></span><span class="rz-dbar"><span class="rz-dfill"></span></span></div>',
3357        '<div class="rz-num rz-mean"></div>'
3358      ];
3359      var tmp = document.createElement('div');
3360      tmp.innerHTML = cells.join('');
3361      var els = Array.prototype.slice.call(tmp.children);
3362      els.forEach(function (e) { bal.appendChild(e); });
3363      return { t: els[1], c: els[2], dval: els[3].querySelector('.rz-dval'),
3364               fill: els[3].querySelector('.rz-dfill'), mean: els[4] };
3365    });
3366    var meanHead = q('.rz-mean-h');
3367
3368    /* ── layout ── */
3369    var cell = 0;
3370    function place(d, delay) {
3371      var size = cell * .74, off = (cell - size) / 2;
3372      d.el.style.width = d.el.style.height = size + 'px';
3373      d.el.style.transitionDelay = delay ? delay + 'ms' : '0ms';
3374      d.el.style.transform = 'translate(' + (d.col * cell + off) + 'px,' + (d.row * cell + off) + 'px)';
3375    }
3376    function relayout() {
3377      var w = canvas.offsetWidth;
3378      if (!w) return;
3379      cell = w / SPAN;
3380      root.classList.add('no-anim');
3381      dots.forEach(function (d) { place(d, 0); });
3382      void canvas.offsetWidth;
3383      root.classList.remove('no-anim');
3384    }
3385    if ('ResizeObserver' in window) new ResizeObserver(relayout).observe(canvas);
3386    else window.addEventListener('resize', relayout);
3387
3388    function slotsPop() {
3389      dots.forEach(function (d, i) {
3390        d.col = Math.floor(i / ROWS) + GAP / 2;
3391        d.row = i % ROWS;
3392      });
3393    }
3394    function slotsSplit(arm) {                  // arm[i] = 1 treated, 0 control
3395      var t = [], c = [];
3396      dots.forEach(function (d, i) { (arm[i] ? t : c).push(d); });
3397      var byMajor = function (a, b) { return a.g - b.g || Math.random() - .5; };
3398      t.sort(byMajor); c.sort(byMajor);
3399      t.forEach(function (d, j) { d.col = Math.floor(j / ROWS); d.row = j % ROWS; });
3400      c.forEach(function (d, j) { d.col = COLS + GAP + Math.floor(j / ROWS); d.row = j % ROWS; });
3401    }
3402    function animate() {
3403      dots.forEach(function (d) { place(d, Math.random() * 420); });
3404    }
3405
3406    /* ── randomization ── */
3407    function assign() {                         // complete randomization: exactly T treated
3408      var idx = dots.map(function (_, i) { return i; });
3409      for (var i = idx.length - 1; i > 0; i--) {
3410        var j = Math.floor(Math.random() * (i + 1)), k = idx[i]; idx[i] = idx[j]; idx[j] = k;
3411      }
3412      var arm = new Array(N).fill(0);
3413      for (var m = 0; m < T; m++) arm[idx[m]] = 1;
3414      return arm;
3415    }
3416    function shares(arm) {
3417      var t = GROUPS.map(function () { return 0; }), c = t.slice();
3418      dots.forEach(function (d, i) { (arm[i] ? t : c)[d.g]++; });
3419      return { t: t.map(function (x) { return x / T * 100; }), c: c.map(function (x) { return x / C * 100; }) };
3420    }
3421
3422    var draws = 0, sums = GROUPS.map(function () { return 0; });
3423    var sgn = function (x) { return (x > 0.049 ? '+' : x < -0.049 ? '\u2212' : '') + Math.abs(x).toFixed(1); };
3424
3425    function record(s) {
3426      draws++;
3427      s.t.forEach(function (_, g) { sums[g] += s.t[g] - s.c[g]; });
3428    }
3429    function paint(s) {
3430      rows.forEach(function (r, g) {
3431        if (!s) {
3432          r.t.innerHTML = r.c.innerHTML = r.dval.innerHTML = r.mean.innerHTML = '<span class="rz-empty">\u2014</span>';
3433          r.fill.style.left = '50%'; r.fill.style.width = '0';
3434          return;
3435        }
3436        var d = s.t[g] - s.c[g], w = Math.min(Math.abs(d), BAR_MAX) / BAR_MAX * 50;
3437        r.t.textContent = s.t[g].toFixed(1) + '%';
3438        r.c.textContent = s.c[g].toFixed(1) + '%';
3439        r.dval.textContent = sgn(d);
3440        r.fill.style.left = (d >= 0 ? 50 : 50 - w) + '%';
3441        r.fill.style.width = w + '%';
3442        r.mean.textContent = sgn(sums[g] / draws);
3443      });
3444      meanHead.textContent = draws
3445        ? 'Mean difference, ' + draws + (draws === 1 ? ' draw' : ' draws')
3446        : 'Mean difference';
3447    }
3448
3449    function drawOnce() {
3450      var arm = assign(), s = shares(arm);
3451      record(s);
3452      slotsSplit(arm);
3453      root.dataset.state = 'split';
3454      animate();
3455      paint(s);
3456    }
3457
3458    q('.rz-go').addEventListener('click', drawOnce);
3459
3460    q('.rz-many').addEventListener('click', function () {
3461      for (var i = 0; i < 99; i++) record(shares(assign()));   // silent draws
3462      drawOnce();                                             // the 100th is shown
3463    });
3464
3465    q('.rz-reset').addEventListener('click', function () {
3466      draws = 0; sums = sums.map(function () { return 0; });
3467      slotsPop();
3468      root.dataset.state = 'pop';
3469      animate();
3470      paint(null);
3471    });
3472
3473    /* space/enter on a button shouldn't advance the slide */
3474    root.addEventListener('keydown', function (e) {
3475      if (e.key === ' ' || e.key === 'Enter' || e.key === 'Spacebar') e.stopPropagation();
3476    });
3477
3478    slotsPop();
3479    relayout();
3480    paint(null);
3481  }
3482
3483  function scan() { document.querySelectorAll('.rz').forEach(init); }
3484  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', scan);
3485  else scan();
3486  if (window.Reveal && Reveal.on) Reveal.on('ready', scan);
3487})();
3488</script>
3488
3489
3490</section>
3491<section id="random-assignment-3" class="slide level2">
3492<h2>Random Assignment</h2>
3493<div style="margin-top: 40px;">
3494
3495</div>
3496<div class="elegant-quote">
3497<div class="shrinker-super-plus">
3498<p>When treatment assignment is randomized, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">the only thing that distinguishes the treatment group from the control group, besides the reception of the treatment, is chance</span>. This means that although the treatment and control groups consist of different individuals, the two groups are comparable to each other, <em>on average</em>, in all respects other than whether or not they received the treatment.</p>
3499<p>Random treatment assignment makes the treatment and control groups <span style="color: white; line-height: 1.4; background-color: #856cb0; padding: 1px 5px;"><em>on average</em> identical to each other in all observed and unobserved pre-treatment characteristics</span>. <span style="color: #856cb0;"><strong>Pre-treatment characteristics</strong></span> are the characteristics of the individuals in a study before the treatment is administered.</p>
3500<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:36</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p>
3501</div>
3502</div>
3503</section>
3504<section id="random-assignment-4" class="slide level2">
3505<h2>Random Assignment</h2>
3506<div style="text-align: center; margin-top: 150px;">
3507<div class="fpci-card">
3508  <div class="fpci-title">Randomization of treatment assignment</div>
3509
3510  <div class="fpci-body">
3511    <p>By randomly assigning treatment, we ensure that treatment and control groups are, on average,</p>
3512    <p class="fpci-key"><span>identical to each other in all observed and unobserved <span class="fpci-nb">pre-treatment</span> characteristics.</span></p>
3513  </div>
3514</div>
3515
3516<style>
3517.fpci-card {
3518  --fp-accent: #856cb0;
3519  --fp-deep:   #5e4a86;
3520  --fp-ink:    #2a2632;
3521  --fp-tint:   #f5f1fb;
3522
3523  display: grid;
3524  grid-template-columns: 30% 1fr;
3525  max-width: 960px;
3526  margin: 1.3em auto 0;
3527  border-radius: 14px;
3528  overflow: hidden;
3529  background: #fff;
3530  border: 1px solid rgba(133, 108, 176, .16);
3531  box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38);
3532  color: var(--fp-ink);
3533  text-align: left;
3534}
3535
3536/* title panel */
3537.fpci-title {
3538  display: flex;
3539  align-items: center;
3540  padding: 1em 1.1em 1em 1.2em;
3541  background: var(--fp-tint);
3542  border-right: 1px solid rgba(133, 108, 176, .14);
3543  font-size: .86em;
3544  font-weight: 600;
3545  line-height: 1.15;
3546  letter-spacing: -.025em;
3547  color: var(--fp-deep);
3548  text-wrap: balance;
3549}
3550
3551/* statement */
3552.fpci-body {
3553  align-self: center;
3554  padding: 1em 1.3em 1em 1.35em;
3555  font-size: .7em;
3556  line-height: 1.6;
3557}
3558.fpci-body p { margin: 0 !important; }
3559.fpci-body p:first-child {
3560  font-weight: 400;
3561  color: rgba(42, 38, 50, .78);
3562  text-wrap: pretty;
3563}
3564
3565/* the punchline: its own line, one emphasis.
3566   inline + clone, so if it ever wraps each line gets its own pill */
3567.fpci-key { margin-top: .55em !important; line-height: 1.95; text-wrap: balance; }
3568.fpci-key .fpci-nb { all: unset; white-space: nowrap; }
3569.fpci-key span {
3570  padding: .14em .55em .18em;
3571  -webkit-box-decoration-break: clone;
3572  box-decoration-break: clone;
3573  border-radius: 7px;
3574  background: var(--fp-accent);
3575  color: #fff;
3576  font-weight: 500;
3577  box-shadow: 0 6px 16px -8px rgba(94, 74, 134, .7);
3578}
3579</style>
3580<p><span class="fpci-source">Adaptation of summary box in Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:36</a>)</span>.</span></p>
3581<style>
3582.fpci-source { display: block; margin-top: 1em; text-align: center; font-size: .5em; color: #a9a9a9; }
3583</style>
3584</div>
3585</section>
3586<section id="random-assignment-5" class="slide level2">
3587<h2>Random Assignment</h2>
3588<div style="text-align: center; margin-top: 30px; font-size: 60%; color: grey; margin-bottom: -10px;">
3589<p><em>With randomization, this holds for two <strong>populations</strong></em>, <span class="math inline">C</span> <span style="font-size: 70%;">(<span class="math inline">X_{i}=0</span>)</span> <em>and</em>&nbsp; <span class="math inline">T</span> <span style="font-size: 70%;">(<span class="math inline">X_{i}=1</span>)</span></p>
3590</div>
3591<div class="quarto-figure quarto-figure-center">
3592<figure>
3593<p><a href="./media/spiderman.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><img data-src="./media/spiderman.jpg" class="quarto-figure quarto-figure-center" style="width:63.0%"></a></p>
3594</figure>
3595</div>
3596<div style="font-size: 50%; text-align: center; margin-top: -30px;">
3597<p>Image can be retrieved <a href="https://i.redd.it/bekphnqftcb41.jpg" class="external" target="_blank">here</a>.</p>
3598</div>
3599</section>
3600<section id="random-assignment-and-ates" class="slide level2">
3601<h2>Random Assignment and ATEs</h2>
3602<div style="margin-top: 40px;">
3603
3604</div>
3605<div class="elegant-quote">
3606<div class="shrinker-super-plus">
3607<p>If the treatment and control groups were comparable before the treatment was administered … <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">we can use the factual outcome of one group as an approximation for the counterfactual outcome of the other</span>. In other words, we can assume that the average outcome of the treatment group is a good estimate of the average outcome of the control group, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">had the control group received the treatment</span>. Similarly, we can assume that the average outcome of the control group is a good estimate of the average outcome of the treatment group, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">had the treatment group not received the treatment</span>. As a result, we can <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">approximate the average treatment effect by computing the difference in the average outcomes between the treatment and control groups</span>. Since both of these average outcomes <em>are</em> observed, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">this is an analysis we <em>are</em> able to perform</span>.</p>
3608<p><span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">Llaudet and Imai 2023:37</a>
3608, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p>
3609</div>
3610</div>
3611</section>
3612<section id="random-assignment-and-ates-1" class="slide level2">
3613<h2>Random Assignment and ATEs</h2>
3614<div class="po-card">
3615<p><span class="po-title">If groups were comparable before the treatment was administered</span></p>
3616<div class="po-eq">
3617<p><span class="math display">
3618\widehat{\textit{ATE}} \;=\; {\color{#856cb0}{\overline{Y}_{\text{Treatment group},\,T}}} \;-\; {\color{#8a8494}{\overline{Y}_{\text{Control group},\,C}}}
3619</span></p>
3620</div>
3621<div class="po-defs">
3622<p><span class="po-where">where</span></p>
3623<p><span class="po-term"><span class="math inline">\widehat{\textit{ATE}}</span></span></p>
3624<p><span class="po-desc">The estimated average treatment effect. <span class="po-aside">The hat marks it as an estimate.</span></span></p>
3625<p><span class="po-term"><span class="math inline">{\color{#856cb0}{\overline{Y}_{\text{Treatment group},\,T}}}</span></span></p>
3626<p><span class="po-desc">The observed average outcome for the <span class="po-t">treatment group</span>.</span></p>
3627<p><span class="po-term"><span class="math inline">{\color{#8a8494}{\overline{Y}_{\text{Control group},\,C}}}</span></span></p>
3628<p><span class="po-desc">The observed average outcome for the <span class="po-c">control group</span>.</span></p>
3629</div>
3630</div>
3631<p><span class="po-source">Adaptation of summary box in Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:37</a>)</span>.</span></p>
3632<style>
3633.po-card {
3634  --po-accent: #856cb0;
3635  --po-ink:    #2a2632;
3636  --po-mute:   #8a8494;
3637  --po-tint:   #f7f4fb;
3638
3639  max-width: 900px;
3640  margin: 1.3em auto 0;
3641  padding: 1em 1.5em 1.15em;
3642  border-radius: 14px;
3643  background: #fff;
3644  border: 1px solid rgba(133, 108, 176, .16);
3645  box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38);
3646  color: var(--po-ink);
3647  text-align: left;
3648}
3649.po-card p { margin: 0; }
3650
3651.po-title {
3652  display: block;
3653  text-align: center;
3654  font-size: .78em;
3655  font-weight: 600;
3656  letter-spacing: -.01em;
3657  color: var(--po-accent);
3658}
3659
3660.po-eq {
3661  margin: .5em 0 .8em;
3662  padding: .1em 1em;
3663  border-radius: 10px;
3664  background: var(--po-tint);
3665  font-size: .7em;
3666  text-align: center;
3667  overflow: hidden;
3668}
3669.po-eq p { margin: 0; }
3670.po-eq mjx-container[display="true"] { margin: .45em 0 !important; }
3671
3672.po-defs {
3673  display: grid;
3674  grid-template-columns: max-content 1fr;
3675  align-items: baseline;
3676  column-gap: 1.2em;
3677  row-gap: .7em;
3678  font-size: .6em;
3679  line-height: 1.5;
3680}
3681.po-defs > p:first-child { grid-column: 1 / -1; }
3682.po-where { font-style: italic; color: var(--po-mute); }
3683.po-term  { display: block; text-align: right; color: var(--po-ink); line-height: 1.7; }
3684.po-desc  { display: block; font-weight: 300; color: rgba(42, 38, 50, .85); text-wrap: pretty; }
3685.po-aside { color: var(--po-mute); font-size: .88em; margin-left: .3em; }
3686.po-t { color: var(--po-accent); font-weight: 500; }
3687.po-c { color: var(--po-mute);   font-weight: 500; }
3688
3689.po-source {
3690  display: block;
3691  margin-top: 1em;
3692  text-align: center;
3693  font-size: .5em;
3694  color: #a9a9a9;
3695}
3696</style>
3697</section>
3698<section id="random-assignment-and-ates-2" class="slide level2">
3699<h2>Random Assignment and ATEs</h2>
3700<div style="margin-top: 80px;">
3701
3702</div>
3703<div class="fpci-card fpci-long">
3704  <div class="fpci-title"><span>Estimating average causal effects using randomized experiments and the <span class="fpci-nb">difference-in-means</span> estimator</span></div>
3705
3706  <div class="fpci-body">
3707    <p>By using random treatment assignment, we can assume that the treatment and control groups were comparable before the administration of the treatment. As a result,</p>
3708    <p class="fpci-key"><span>we can rely on the <span class="fpci-nb">difference-in-means</span> estimator to provide a valid estimate of the average treatment effect.</span></p>
3709  </div>
3710</div>
3711
3712<style>
3713.fpci-card {
3714  --fp-accent: #856cb0;
3715  --fp-deep:   #5e4a86;
3716  --fp-ink:    #2a2632;
3717  --fp-tint:   #f5f1fb;
3718
3719  display: grid;
3720  grid-template-columns: 30% 1fr;
3721  max-width: 960px;
3722  margin: 1.3em auto 0;
3723  border-radius: 14px;
3724  overflow: hidden;
3725  background: #fff;
3726  border: 1px solid rgba(133, 108, 176, .16);
3727  box-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38);
3728  color: var(--fp-ink);
3729  text-align: left;
3730}
3731
3732.fpci-title {
3733  display: flex;
3734  align-items: center;
3735  padding: 1em 1.1em 1em 1.2em;
3736  background: var(--fp-tint);
3737  border-right: 1px solid rgba(133, 108, 176, .14);
3738  font-size: .86em;
3739  font-weight: 600;
3740  line-height: 1.15;
3741  letter-spacing: -.025em;
3742  color: var(--fp-deep);
3743  text-wrap: balance;
3744}
3745
3746.fpci-body {
3747  align-self: center;
3748  padding: 1em 1.3em 1em 1.35em;
3749  font-size: .7em;
3750  line-height: 1.6;
3751}
3752.fpci-body p { margin: 0 !important; }
3753.fpci-body p:first-child {
3754  font-weight: 400;
3755  color: rgba(42, 38, 50, .78);
3756  text-wrap: pretty;
3757}
3758
3759.fpci-key { margin-top: .55em !important; line-height: 1.95; text-wrap: balance; }
3760.fpci-key > span {
3761  padding: .14em .55em .18em;
3762  -webkit-box-decoration-break: clone;
3763  box-decoration-break: clone;
3764  border-radius: 7px;
3765  background: var(--fp-accent);
3766  color: #fff;
3767  font-weight: 500;
3768  box-shadow: 0 6px 16px -8px rgba(94, 74, 134, .7);
3769}
3770.fpci-nb { white-space: nowrap; }
3771
3772/* long titles: a wider title panel, set a touch smaller */
3773.fpci-long { grid-template-columns: 36% 1fr; }
3774.fpci-long .fpci-title { font-size: .76em; line-height: 1.18; }
3775</style>
3776<p><span class="fpci-source">Adaptation of summary box in Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:38</a>)</span>.</span></p>
3777<style>
3778.fpci-source { display: block; margin-top: 1em; text-align: center; font-size: .5em; color: #a9a9a9; }
3779</style>
3780</section></section>
3781<section>
3782<section id="a-class-conversation" class="title-slide slide level1 center" data-background-color="#43464B">
3783<h1>A Class Conversation</h1>
3784
3785</section>
3786<section id="the-challenges-of-causal-inference" class="slide level2">
3787<h2>The Challenges of Causal Inference</h2>
3788<div style="margin-top: 40px; text-align: center; font-size: 90%;">
3789<p>As Llaudet and Imai <span class="citation" data-cites="llaudet_data_2023">(<a href="#/references" role="doc-biblioref" onclick="">2023:38</a>)</span> note, social scientists often run into <span style="color: white; background-color: #856cb0; line-height: 1.5; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;">ethical</span>, <span style="color: white; background-color: #856cb0; line-height: 1.5; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;">logistical</span>, and <span style="color: white; background-color: #856cb0; line-height: 1.5; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;">financial</span> obstacles that ensure that fielding a randomized experiment is challenging—if not <em>impossible</em>. With this in mind, think about—and refine—your own <span style="color: white; background-color: #856cb0; line-height: 1.5; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><strong>causal</strong></span> question.</p>
3790<div class="fragment">
3791<details class="reveal-detail-above">
3792<summary>
3793Orienting Questions
3794</summary>
3795<div class="detail-content" style="font-size: 75%; text-align: center;">
3796<div class="rq-block">
3797<p>
3797Can you randomly sort respondents into <span class="math inline">C</span> <span style="font-size: 70%;">(<span class="math inline">X_{i}=0</span>)</span> <em>and</em>&nbsp; <span class="math inline">T</span> <span style="font-size: 70%;">(<span class="math inline">X_{i}=1</span>)</span>? How might you go about doing so? What are some challenges you may run into?</p>
3798</div>
3799</div>
3800</details>
3801<div class="oe-pad" data-minutes="10" data-file="research_question_updated.txt">
3802
3803  <svg class="oe-ring" aria-hidden="true">
3804    <path class="oe-track"></path>
3805    <path class="oe-prog" pathlength="1"></path>
3806  </svg>
3807
3808  <div class="oe-head">
3809    <span class="oe-title">Your Updated Research Question</span>
3810    <div class="oe-clock">
3811      <span class="oe-time" role="timer">10:00</span>
3812      <button class="oe-tbtn oe-toggle" type="button" aria-label="Start or pause the timer">
3813        <svg class="oe-i-play" viewbox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M6 4l14 8-14 8z"></path></svg>
3814        <svg class="oe-i-pause" viewbox="0 0 24 24" fill="currentColor" style="display:none" aria-hidden="true"><rect x="6" y="4" width="4" height="16" rx="1"></rect><rect x="14" y="4" width="4" height="16" rx="1"></rect></svg>
3815      </button>
3816      <button class="oe-tbtn oe-reset" type="button" aria-label="Reset the timer">
3817        <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>
3818      </button>
3819    </div>
3820  </div>
3821
3822  <textarea class="oe-editor" rows="4" placeholder="How does X shape variation in Y?"></textarea>
3823
3824  <div class="oe-actions">
3825    <button class="oe-btn oe-primary oe-export" type="button">
3826      <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
3827      Export .txt
3828    </button>
3829    <button class="oe-btn oe-secondary oe-copy" type="button">
3830      <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
3831      <span class="oe-copy-label">Copy</span>
3832    </button>
3833    <span class="oe-count">0 words</span>
3834    <button class="oe-btn oe-clear" type="button">
3835      <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path></svg>
3836      <span class="oe-clear-label">Clear</span>
3837    </button>
3838  </div>
3839</div>
3840
3841
3842</div>
3843</div>
3844</section></section>
3845<section>
3846<section id="experiments-dags-september-24th" class="title-slide slide level1 center" data-background-color="#43464B">
3847<h1>Experiments, DAGs—<br>September 24<span style="font-size: 70%;"><sup>th</sup></span></h1>
3848
3849</section>
3850<section id="your-next-assignment" class="slide level2">
3851<h2>Your Next Assignment</h2>
3852<div style="text-align: center; margin-top: 140px;">
3853<p><span class="frame-expand fx-pill" data-src="https://soci-316.netlify.app/#qualtrics">Qualtrics Survey Assessment</span></p>
3854</div>
3855</section>
3856<section id="an-invitation-from-ljst" class="slide level2">
3857<h2>An Invitation from LJST</h2>
3858<div style="text-align: center; font-size: 50%; color: grey; margin-bottom: -10px; margin-top: 25px;">
3859<p><em>Click to Expand Invitation</em></p>
3860</div>
3861<div class="quarto-figure quarto-figure-center">
3862<figure>
3863<p><a href="./media/Maggie%20Blackhawk%20Poster%20UPDATED.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><img data-src="./media/Maggie%20Blackhawk%20Poster%20UPDATED.png" class="quarto-figure quarto-figure-center" style="width:33.0%"></a></p>
3864</figure>
3865</div>
3866</section></section>
3867<section>
3868<section id="high-level-overview-experiments" class="title-slide slide level1 center" data-background-color="#43464B">
3869<h1><span class="title-ill">High-Level Overview</span> Experiments</h1>
3870
3871</section>
3872<section id="experiments" class="slide level2">
3873<h2>Experiments</h2>
3874<div class="elegant-quote" style="margin-top: 40px;">
3875<p>An <span style="color: #856cb0;"><strong>experiment</strong></span> is a research method where the researcher manipulates one or more <span style="color: #856cb0;"><strong>independent variables</strong></span> to determine the effect(s) on a <span style="color: #856cb0;"><strong>dependent variable</strong></span>. Experiments share three key features: <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">(1) manipulation of the independent variable, (2) random assignment of participants to experimental and control conditions, and (3) experimental control of other factors that could influence the outcome of the experiment</span>.</p>
3876<p><span class="citation" data-cites="carr_art_2020">(<a href="#/references" role="doc-biblioref" onclick="">Carr et al. 2020:230</a>
3876, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p>
3877</div>
3878</section>
3879<section id="experiments-1" class="slide level2 scrollable">
3880<h2>Experiments</h2>
3881<div style="margin-top: 100px; text-align: center;">
3882<p>Experiments provide leverage in three important ways.</p>
3883<div class="fragment">
3884<details class="reveal-detail" name="experiments">
3885<summary>
3886Three Key Affordances
3887</summary>
3888<div class="detail-content" style="font-size: 75%; text-align: center;">
3889<ol type="1">
3890<li class="fragment"><p>Experiments are ideal for adjudicating causal claims.</p></li>
3891<li class="fragment"><p>Experiments compel researchers to test specific posited mechanisms, i.e.&nbsp;beyond reporting descriptive associations.</p></li>
3892<li class="fragment"><p>Experiments can be used to test highly abstract theories—generally through fine control of the experimental setting.</p></li>
3893</ol>
3894</div>
3895</details>
3896</div>
3897<div class="fragment">
3898<details class="reveal-detail" name="experiments">
3899<summary>
3900A Question
3901</summary>
3902<div class="detail-content" style="font-size: 75%; text-align: center;">
3903<p>What are some of the <em>weaknesses</em> of experiments?</p>
3904</div>
3905</details>
3906</div>
3907</div>
3908</section>
3909<section id="experiments-2" class="slide level2">
3910<h2>Experiments</h2>
3911<div style="margin-top: 60px;">
3912
3913</div>
3914<!--
3915  ═══════════════════════════════════════════════════════════════════
3916  EXPERIMENTAL METHODS · Carr et al. (2020), Table 8.1
3917  Quarto RevealJS drop-in (exm- namespace)
3918  Same visual system as the sv- / md- tables · Space Grotesk · purple
3919  ───────────────────────────────────────────────────────────────────
3920  Include with  {{< include _carr-table-8-1.qmd >}}  or paste as is.
3921  The source line sits OUTSIDE the fence as a ::: {.exm-source} div,
3922  because raw HTML is not passed through citeproc.
3923
3924  Cell text is verbatim; each point in the original is its own item,
3925  marked + (strength) or − (weakness).
3926
3927  Pop-up cells
3928    · Click any strengths/weaknesses cell (or Tab + Enter/Space) to
3929      open it full-screen. ← / → step through all eight cells in
3930      reading order (lab strengths, lab weaknesses, field strengths…).
3931    · ×, Esc, or a click on the backdrop closes. The cell you last
3932      viewed stays marked (shaded, purple rule); Escape again or
3933      leaving the slide clears it. The lightbox's source line is
3934      copied from the .exm-source div.
3935    · Reveal-safe: the lightbox lives on <body>; Esc and arrows are
3936      caught before Reveal while it is open; Reveal's keyboard is
3937      suspended and restored; a mouse-opened lightbox hands the keys
3938      back to the deck on close.
3939
3940  Size knob: --exm-size on .exm-table (default .35em).
3941  Row icons: delete the <svg class="exm-ico"> elements to drop them.
3942  ═══════════════════════════════════════════════════════════════════
3943-->
3944
3945<style>
3946.reveal .exm-table {
3947  --exm-accent: #856cb0;
3948  --exm-deep:   #54407a;
3949  --exm-soft:   #b7a5d3;
3950  --exm-ink:    #241c31;
3951  --exm-mute:   #6d6480;
3952  --exm-line:   #e3dbee;
3953  --exm-wash:   #f8f5fc;
3954  --exm-edge:   #efe8f8;
3955
3956  /* ▸▸ SIZE ◂◂ */
3957  --exm-size:   .35em;
3958
3959  width: 100%;
3960  margin: .3em auto 0;
3961  font-family: "Space Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
3962  font-size: var(--exm-size);
3963  line-height: 1.3;
3964  color: var(--exm-ink);
3965  text-align: left;
3966}
3967
3968.reveal .exm-table .exm-head,
3969.reveal .exm-table .exm-row {
3970  display: grid;
3971  grid-template-columns: 17fr 41.5fr 41.5fr;
3972}
3973
3974/* ── header ─────────────────────────────────────────── */
3975.reveal .exm-table .exm-head { border-bottom: 2px solid var(--exm-
3975accent); }
3976.reveal .exm-table .exm-corner {
3977  align-self: end;
3978  padding: 0 .8em .55em 0;
3979  font-size: .95em;
3980  font-weight: 500;
3981  color: var(--exm-mute);
3982}
3983.reveal .exm-table .exm-col {
3984  display: flex;
3985  align-items: center;
3986  gap: .5em;
3987  padding: 0 1em .55em;
3988}
3989.reveal .exm-table .exm-col-title {
3990  font-size: 1.4em;
3991  font-weight: 600;
3992  letter-spacing: -.01em;
3993  color: var(--exm-deep);
3994}
3995.exm-sign {
3996  flex: none;
3997  width: 1.15em;
3998  height: 1.15em;
3999  fill: none;
4000  stroke: var(--exm-accent);
4001  stroke-width: 1.8;
4002  stroke-linecap: round;
4003}
4004
4005/* ── rows ───────────────────────────────────────────── */
4006.reveal .exm-table .exm-row { border-bottom: 1px solid var(--exm-line); }
4007
4008.reveal .exm-table .exm-label {
4009  display: flex;
4010  align-items: center;
4011  gap: .55em;
4012  padding: .6em .8em .6em 0;
4013  font-size: 1.08em;
4014  font-weight: 600;
4015  line-height: 1.2;
4016  color: var(--exm-ink);
4017}
4018.exm-ico {
4019  flex: none;
4020  width: 1.3em;
4021  height: 1.3em;
4022  fill: none;
4023  stroke: var(--exm-accent);
4024  stroke-width: 1.6;
4025  stroke-linecap: round;
4026  stroke-linejoin: round;
4027}
4028
4029.reveal .exm-table .exm-name { max-width: 6.6em; }   /* "X / experiments" on two lines throughout */
4030
4031/* clickable cells: vertically centred, full-height hover */
4032.reveal .exm-table .exm-cell {
4033  position: relative;
4034  display: flex;
4035  flex-direction: column;
4036  justify-content: center;
4037  gap: .3em;
4038  padding: .55em 1.6em .55em 1em;
4039  cursor: zoom-in;
4040  outline: none;
4041  transition: background-color .15s ease;
4042}
4043.reveal .exm-table .exm-cell:hover { background: var(--exm-wash); }
4044.reveal .exm-table .exm-cell:focus-visible { box-shadow: inset 0 0 0 1.5px var(--exm-accent); }
4045.reveal .exm-table .exm-cell.is-last {
4046  background: var(--exm-edge);
4047  box-shadow: inset 3px 0 0 var(--exm-accent);
4048}
4049
4050/* left rule grows on hover, as in the sv- tables' edge marker */
4051.reveal .exm-table .exm-cell::before {
4052  content: "";
4053  position: absolute;
4054  left: 0; top: 0; bottom: 0;
4055  width: 3px;
4056  background: var(--exm-accent);
4057  transform: scaleY(0);
4058  transition: transform .22s cubic-bezier(.2, .7, .3, 1);
4059}
4060.reveal .exm-table .exm-cell:hover::before { transform: scaleY(1); }
4061
4062/* expand glyph, top-right on hover/focus */
4063.reveal .exm-table .exm-zoom {
4064  position: absolute;
4065  top: .7em; right: .7em;
4066  width: .7em; height: .7em;
4067  fill: none;
4068  stroke: var(--exm-accent);
4069  stroke-width: 2.4;
4070  stroke-linecap: round;
4071  stroke-linejoin: round;
4072  opacity: 0;
4073  transition: opacity .2s ease, transform .2s ease;
4074  pointer-events: none;
4075}
4076.reveal .exm-table .exm-cell:hover .exm-zoom,
4077.reveal .exm-table .exm-cell:focus-visible .exm-zoom { opacity: .9; transform: translate(1px, -1px); }
4078
4079/* items: hanging + / − marks (shared by table and lightbox, hence unscoped) */
4080.exm-pt {
4081  display: grid;
4082  grid-template-columns: 1.05em 1fr;
4083  align-items: baseline;
4084}
4085.exm-pt::before {
4086  font-weight: 600;
4087  line-height: 1;
4088  color: var(--exm-accent);
4089}
4090.is-pro > .exm-pt::before { content: "+"; }
4091.is-con > .exm-pt::before { content: "\2212"; color: var(--exm-mute); }
4092
4093/* ── source line (Markdown div outside the fence) ───── */
4094.reveal .exm-source {
4095  margin-top: .6em;
4096  font-family: "Space Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
4097  font-size: .5em;
4098  text-align: center;
4099  color: #6d6480;
4100}
4101.reveal .exm-source p { margin: 0; }
4102
4103/* ── one entrance per visit (backwards: releases opacity on end) ── */
4104@keyframes exm-in {
4105  from { opacity: 0; transform: translateY(5px); }
4106  to   { opacity: 1; transform: none; }
4107}
4108.reveal section.present .exm-table .exm-row {
4109  animation: exm-in .4s ease-out backwards;
4110  animation-delay: calc(var(--i, 0) * 50ms + 40ms);
4111}
4112
4113/* ══ lightbox (on <body>, outside Reveal's scaled slides: px units) ══ */
4114.exm-box {
4115  --exm-accent: #856cb0;
4116  --exm-deep:   #54407a;
4117  --exm-soft:   #b7a5d3;
4118  --exm-ink:    #241c31;
4119  --exm-mute:   #6d6480;
4120  --exm-line:   #e3dbee;
4121  --exm-tint:   #f7f4fc;
4122  --exm-mono:   "JetBrains Mono", ui-monospace, monospace;
4123
4124  position: fixed;
4125  inset: 0;
4126  z-index: 10000;
4127  display: flex;
4128  align-items: center;
4129  justify-content: center;
4130  padding: 64px 104px 52px;
4131  background: rgba(28, 22, 38, .72);
4132  -webkit-backdrop-filter: blur(8px);
4133  backdrop-filter: blur(8px);
4134  font-family: "Space Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
4135  text-align: left;
4136  cursor: zoom-out;
4137  opacity: 0;
4138  visibility: hidden;
4139  transition: opacity .22s ease, visibility .22s;
4140}
4141.exm-box.is-open { opacity: 1; visibility: visible; }
4142
4143.exm-card {
4144  position: relative;
4145  width: min(820px, 100%);
4146  max-height: 100%;
4147  overflow: auto;
4148  background: #fff;
4149  border-radius: 12px;
4150  box-shadow: 0 24px 70px rgba(28, 22, 38, .4);
4151  cursor: default;
4152  transform: translateY(10px) scale(.985);
4153  transition: transform .26s cubic-bezier(.22, .61, .36, 1);
4154}
4155.exm-box.is-open .exm-card { transform: none; }
4156
4157.exm-content { transition: opacity .14s ease; }
4158.exm-content.is-swap { opacity: 0; }
4159
4160.exm-bar {
4161  display: flex;
4162  align-items: center;
4163  gap: 14px;
4164  padding: 18px 26px 16px;
4165  background: var(--exm-tint);
4166  border-bottom: 2px solid var(--exm-accent);
4167}
4168.exm-bar .exm-ico { width: 28px; height: 28px; }
4169.exm-bar-name {
4170  font-size: 26px;
4171  font-weight: 600;
4172  letter-spacing: -.01em;
4173  line-height: 1.15;
4174  color: var(--exm-deep);
4175}
4176.exm-tag {
4177  display: inline-flex;
4178  align-items: center;
4179  gap: 6px;
4180  padding: 4px 11px 4px 8px;
4181  border-radius: 999px;
4182  font-size: 14px;
4183  font-weight: 500;
4184  color: var(--exm-deep);
4185  background: #fff;
4186  box-shadow: inset 0 0 0 1px var(--exm-line);
4187}
4188.exm-tag .exm-sign { width: 15px; height: 15px; }
4189.exm-count {
4190  margin-left: auto;
4191  font-family: var(--exm-mono);
4192  font-size: 12px;
4193  letter-spacing: .08em;
4194  color: var(--exm-accent);
4195  font-variant-numeric: tabular-nums;
4196}
4197
4198.exm-body {
4199  display: flex;
4200  flex-direction: column;
4201  gap: 16px;
4202  padding: 26px 30px 8px 26px;
4203  font-size: clamp(19px, 2vw, 24px);
4204  line-height: 1.5;
4205  color: var(--exm-ink);
4206}
4207.exm-body .exm-pt { grid-template-columns: 1.3em 1fr; }
4208
4209.exm-foot {
4210  padding: 12px 30px 20px;
4211  font-size: 13px;
4212  text-align: right;
4213  color: var(--exm-mute);
4214}
4215.exm-foot:empty { display: none; }
4216.exm-foot code { font-family: var(--exm-mono); font-size: .92em; color: var(--exm-deep); }
4217
4218/* controls */
4219.exm-btn {
4220  position: absolute;
4221  display: grid;
4222  place-items: center;
4223  margin: 0;
4224  padding: 0;
4225  border: 1px solid rgba(255, 255, 255, .22);
4226  border-radius: 50%;
4227  background: rgba(255, 255, 255, .1);
4228  color: #fff;
4229  cursor: pointer;
4230  transition: background .2s ease, border-color .2s ease, transform .2s ease, opacity .2s ease;
4231}
4232.exm-btn svg {
4233  width: 16px; height: 16px;
4234  fill: none; stroke: currentColor; stroke-width: 2.2;
4235  stroke-linecap: round; stroke-linejoin: round;
4236}
4237.exm-btn:hover:not(:disabled) { background: var(--exm-accent); border-color: var(--exm-accent); }
4238.exm-btn:focus-visible { outline: 2px solid var(--exm-soft); outline-offset: 2px; }
4239.exm-btn:disabled { opacity: .25; cursor: default; }
4240
4241.exm-close { top: 16px; right: 20px; width: 38px; height: 38px; }
4242.exm-close:hover { transform: rotate(90deg); }
4243.exm-prev, .exm-next { top: 50%; width: 46px; height: 46px; margin-top: -23px; }
4244.exm-prev { left: 28px; }
4245.exm-next { right: 28px; }
4246
4247.exm-keys {
4248  position: absolute;
4249  bottom: 16px; left: 22px;
4250  font-family: var(--exm-mono);
4251  font-size: 10px;
4252  letter-spacing: .04em;
4253  color: rgba(255, 255, 255, .45);
4254}
4255.exm-keys b {
4256  font-weight: 500;
4257  color: rgba(255, 255, 255, .75);
4258  border: 1px solid rgba(255, 255, 255, .25);
4259  border-radius: 3px;
4260  padding: 1px 5px;
4261  margin-right: 5px;
4262}
4263.exm-keys b + b { margin-left: -2px; }
4264.exm-keys span { margin-right: 14px; }
4265
4266@media (max-width: 760px) {
4267  .exm-box { padding: 60px 12px 76px; }
4268  .exm-bar { flex-wrap: wrap; padding: 16px 18px 14px; }
4269  .exm-bar-name { font-size: 21px; }
4270  .exm-body { padding: 20px 20px 6px 18px; }
4271  .exm-prev, .exm-next { top: auto; bottom: 16px; margin-top: 0; }
4272  .exm-prev { left: calc(50% - 56px); }
4273  .exm-next { right: calc(50% - 56px); }
4274  .exm-keys { display: none; }
4275}
4276
4277@media (prefers-reduced-motion: reduce) {
4278  .reveal section.present .exm-table .exm-row { animation: none; }
4279  .reveal .exm-table .exm-cell, .reveal .exm-table .exm-cell::before, .reveal .exm-table .exm-zoom,
4280  .exm-box, .exm-card, .exm-content, .exm-btn { transition: none !important; }
4281  .exm-card { transform: none; }
4282}
4283@media print { .exm-box, .reveal .exm-table .exm-zoom { display: none !important; } }
4284</style>
4285
4286<div class="exm-table" role="table" aria-label="Strengths and weaknesses of four experimental methods, adapted from Carr et al. (2020), Table 8.1">
4287
4288  <div class="exm-head" role="row">
4289    <div class="exm-corner" role="columnheader">Experimental Method</div>
4290    <div class="exm-col" role="columnheader">
4291      <svg class="exm-sign" viewbox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9.5"></circle><path d="M12 7.5v9M7.5 12h9"></path></svg>
4292      <span class="exm-col-title">Strengths</span>
4293    </div>
4294    <div class="exm-col" role="columnheader">
4295      <svg class="exm-sign" viewbox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9.5"></circle><path d="M7.5 12h9"></path></svg>
4296      <span class="exm-col-title">Weaknesses</span>
4297    </div>
4298  </div>
4299
4300  <div class="exm-body-rows" role="rowgroup">
4301
4302    <div class="exm-row" role="row" style="--i:0">
4303      <div class="exm-label" role="rowheader">
4304        <svg class="exm-ico" viewbox="0 0 24 24" aria-hidden="true"><path d="M9 3h6M10 3v6.2L4.6 18.4A1.7 1.7 0 0 0 6.1 21h11.8a1.7 1.7 0 0 0 1.5-2.6L14 9.2V3"></path><path d="M7.2 15h9.6"></path></svg>
4305        <span class="exm-name">Laboratory experiments</span>
4306      </div>
4307      <div class="exm-cell is-pro" role="cell">
4308        <div class="exm-pt">Researcher has maximum control over the experimental setting and can ensure that the manipulation of the independent variable was salient to all participants.</div>
4309        <div class="exm-pt">The artificial setting allows researchers to test more abstract theories and to disentangle complex social phenomena.</div>
4310      </div>
4311      <div class="exm-cell is-con" role="cell">
4312        <div class="exm-pt">Most laboratory experiments rely on nonrandom samples, so results are less generalizable.</div>
4313        <div class="exm-pt">Because the laboratory environment is highly artificial, lab experiments are less effective for exploring more applied, “real world” questions.</div>
4314      </div>
4315    </div>
4316
4317    <div class="exm-row" role="row" style="--i:1">
4318      <div class="exm-label" role="rowheader">
4319        <svg class="exm-ico" viewbox="0 0 24 24" aria-hidden="true"><path d="M12 21s-6.5-5.6-6.5-11a6.5 6.5 0 0 1 13 0c0 5.4-6.5 11-6.5 11z"></path><circle cx="12" cy="10" r="2.4"></circle></svg>
4320        <span class="exm-name">Field experiments</span>
4321      </div>
4322      <div class="exm-cell is-pro" role="cell">
4323        <div class="exm-pt">Because field experiments take place in a real-world setting, they are good at evaluating more applied questions and for assessing whether an outcome, such as discrimination, occurs.</div>
4324      </div>
4325      <div class="exm-cell is-con" role="cell">
4326        <div class="exm-pt">Researchers have less control over the experimental setting and frequently rely on nonrandom or regional samples.</div>
4327        <div class="exm-pt">Less suited for evaluating the processes that produce an outcome, and less useful for testing abstract theories.</div>
4328      </div>
4329    </div>
4330
4331    <div class="exm-row" role="row" style="--i:2">
4332      <div class="exm-label" role="rowheader">
4333        <svg class="exm-ico" viewbox="0 0 24 24" aria-hidden="true"><rect x="5" y="4" width="14" height="17" rx="2"></rect><path d="M9 4V3h6v1"></path><path d="M8.5 10l1.3 1.3L12 9M8.5 15.5l1.3 1.3 2.2-2.3M14 10.3h2M14 15.8h2"></path></svg>
4334        <span class="exm-name">Survey experiments</span>
4335      </div>
4336      <div class="exm-cell is-pro" role="cell">
4337        <div class="exm-pt">Survey experiments often use representative samples, so results are generalizable.</div>
4338      </div>
4339      <div class="exm-cell is-con" role="cell">
4340        <div class="exm-pt">Researchers have less control over the experimental setting.</div>
4341        <div class="exm-pt">Often rely on self-reports, which do not always correspond to actual behavior.</div>
4342      </div>
4343    </div>
4344
4345    <div class="exm-row" role="row" style="--i:3">
4346      <div class="exm-label" role="rowheader">
4347        <svg class="exm-ico" viewbox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"></circle><path d="M3 12h18M12 3c2.6 2.6 3.8 5.6 3.8 9s-1.2 6.4-3.8 9c-2.6-2.6-3.8-5.6-3.8-9S9.4 5.6 12 3z"></path></svg>
4348        <span class="exm-name">Natural experiments</span>
4349      </div>
4350      <div class="exm-cell is-pro" role="cell">
4351        <div class="exm-pt">Realistic setting allows researchers to draw strong conclusions about specific settings.</div>
4352        <div class="exm-pt">Good for assessing whether an outcome occurs.</div>
4353      </div>
4354      <div class="exm-cell is-con" role="cell">
4355        <div class="exm-pt">Researchers cannot randomly assign participants to conditions.</div>
4356        <div class="exm-pt">Settings are often so realistic that the results are tied to the setting in which the study was conducted and are not generalizable.</div>
4357        <div class="exm-pt">Less suited for evaluating the processes that produce an outcome.</div>
4358      </div>
4359    </div>
4360
4361  </div>
4362</div>
4363
4364<script>
4365(function () {
4366  if (window.__exmReady) { window.__exmScan(); return; }
4367  window.__exmReady = true;
4368
4369  var ZOOM  = '<svg class="exm-zoom" viewBox="0 0 24 24" aria-hidden="true"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>';
4370  var CLOSE = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12"/></svg>';
4371  var PREV  = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 5l-7 7 7 7"/></svg>';
4372  var NEXT  = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7"/></svg>';
4373  var PLUS  = '<svg class="exm-sign" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9.5"/><path d="M12 7.5v9M7.5 12h9"/></svg>';
4374  var MINUS = '<svg class="exm-sign" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9.5"/><path d="M7.5 12h9"/></svg>';
4375
4376  function cells(tbl) { return tbl.querySelectorAll('.exm-cell'); }
4377  function mark(tbl, idx) {
4378    cells(tbl).forEach(function (c, k) { c.classList.toggle('is-last', k === idx); });
4379  }
4380
4381  function deck() {
4382    var R = window.Reveal;
4383    return (R && typeof R.configure === 'function' && typeof R.getConfig === 'function' &&
4384            (!R.isReady || R.isReady())) ? R : null;
4385  }
4386
4387  /* ── lightbox ─────────────────────────────────────────────────── */
4388  var box, card, content, elIco, elName, elTag, elCount, elBody, elFoot, btnX, btnPrev, btnNext;
4389  var lb = { tbl: null, idx: -1, byKey: false, kbd: null, timer: 0 };
4390
4391  function build() {
4392    if (box) return;
4393    box = document.createElement('div');
4394    box.className = 'exm-box';
4395    box.setAttribute('role', 'dialog');
4396    box.setAttribute('aria-modal', 'true');
4397    box.setAttribute('aria-hidden', 'true');
4398    box.innerHTML =
4399      '<button class="exm-btn exm-close" type="button" aria-label="Close (Esc)">' + CLOSE + '</button>' +
4400      '<button class="exm-btn exm-prev" type="button" aria-label="Previous cell">' + PREV + '</button>' +
4401      '<button class="exm-btn exm-next" type="button" aria-label="Next cell">' + NEXT + '</button>' +
4402      '<article class="exm-card"><div class="exm-content">' +
4403        '<header class="exm-bar"><span class="exm-bar-ico"></span><span class="exm-bar-name"></span>' +
4404          '<span class="exm-tag"></span><span class="exm-count"></span></header>' +
4405        '<div class="exm-body"></div>' +
4406        '<div class="exm-foot"></div>' +
4407      '</div></article>' +
4408      '<div class="exm-keys"><b>←</b><b>→</b><span>Browse</span><b>Esc</b>Close</div>';
4409    /* on <body>: a fixed box inside Reveal's transformed .slides
4410       would be positioned against the slide, not the viewport */
4411    document.body.appendChild(box);
4412
4413    card    = box.querySelector('.exm-card');
4414    content = box.querySelector('.exm-content');
4415    elIco   = box.querySelector('.exm-bar-ico');
4416    elName  = box.querySelector('.exm-bar-name');
4417    elTag   = box.querySelector('.exm-tag');
4418    elCount = box.querySelector('.exm-count');
4419    elBody  = box.querySelector('.exm-body');
4420    elFoot  = box.querySelector('.exm-foot');
4421    btnX    = box.querySelector('.exm-close');
4422    btnPrev = box.querySelector('.exm-prev');
4423    btnNext = box.querySelector('.exm-next');
4424
4425    btnX.addEventListener('click', close);
4426    btnPrev.addEventListener('click', function () { step(-1); });
4427    btnNext.addEventListener('click', function () { step(1); });
4428    box.addEventListener('click', function (e) {
4429      if (!e.target.closest('.exm-card, .exm-btn')) close();
4430    });
4431  }
4432
4433  function isOpen() { return !!box && box.classList.contains('is-open'); }
4434
4435  function render(idx, swap) {
4436    var all = cells(lb.tbl), c = all[idx];
4437    if (!c) return;
4438    lb.idx = idx;
4439    mark(lb.tbl, idx);
4440    btnPrev.disabled = idx === 0;
4441    btnNext.disabled = idx === all.length - 1;
4442
4443    function fill() {
4444      var row  = c.closest('.exm-row');
4445      var name = row.querySelector('.exm-name');
4446      var ico  = row.querySelector('.exm-ico');
4447      var pro  = c.classList.contains('is-pro');
4448      elIco.innerHTML     = ico ? ico.outerHTML : '';
4449      elName.textContent  = name ? name.textContent.trim() : '';
4450      elTag.innerHTML     = (pro ? PLUS : MINUS) + (pro ? 'Strengths' : 'Weaknesses');
4451      elCount.textContent = (idx + 1) + ' / ' + all.length;
4452      elBody.className    = 'exm-body ' + (pro ? 'is-pro' : 'is-con');
4453      elBody.innerHTML    = '';
4454      c.querySelectorAll('.exm-pt').forEach(function (p) { elBody.appendChild(p.cloneNode(true)); });
4455      box.setAttribute('aria-label', elName.textContent + ': ' + (pro ? 'strengths' : 'weaknesses'));
4456      card.scrollTop = 0;
4457    }
4458    clearTimeout(lb.timer);
4459    if (swap) {
4460      content.classList.add('is-swap');
4461      lb.timer = setTimeout(function () { fill(); content.classList.remove('is-swap'); }, 140);
4462    } else { content.classList.remove('is-swap'); fill(); }
4463  }
4464
4465  function step(d) {
4466    if (!isOpen()) return;
4467    var to = lb.idx + d;
4468    if (to >= 0 && to < cells(lb.tbl).length) render(to, true);
4469  }
4470
4471  function open(tbl, idx, byKey) {
4472    build();
4473    lb.tbl = tbl;
4474    lb.byKey = !!byKey;
4475    var scope = tbl.closest('section') || tbl.parentNode || document;
4476    var src = scope.querySelector('.exm-source');
4477    elFoot.innerHTML = '';
4478    if (src) {
4479      var copy = src.cloneNode(true);
4480      elFoot.innerHTML = (copy.querySelector('p') || copy).innerHTML;
4480   /* keeps the `Table 8.1` code span */
4481      elFoot.querySelectorAll('a').forEach(function (a) { a.replaceWith(document.createTextNode(a.textContent)); });
4482    }
4483    render(idx, false);
4484
4485    var R = deck();
4486    if (R) { lb.kbd = R.getConfig().keyboard; R.configure({ keyboard: false }); }
4487
4488    box.setAttribute('aria-hidden', 'false');
4489    box.classList.add('is-open');
4490    btnX.focus({ preventScroll: true });
4491  }
4492
4493  function close() {
4494    if (!isOpen()) return;
4495    box.classList.remove('is-open');
4496    box.setAttribute('aria-hidden', 'true');
4497
4498    var R = deck();
4499    if (R && lb.kbd !== null) { R.configure({ keyboard: lb.kbd }); lb.kbd = null; }
4500
4501    var c = cells(lb.tbl)[lb.idx];
4502    if (R && !lb.byKey) {
4503      /* mouse-opened in a deck: release focus so arrows/Space drive the deck */
4504      if (document.activeElement && document.activeElement.blur) document.activeElement.blur();
4505    } else if (c) {
4506      c.focus({ preventScroll: true });
4507    }
4508  }
4509
4510  /* ── binding ──────────────────────────────────────────────────── */
4511  window.__exmScan = function () {
4512    document.querySelectorAll('.exm-table').forEach(function (tbl) {
4513      if (tbl.dataset.exmBound) return;
4514      tbl.dataset.exmBound = '1';
4515      cells(tbl).forEach(function (c, k) {
4516        c.setAttribute('tabindex', '0');
4517        c.setAttribute('aria-haspopup', 'dialog');
4518        c.insertAdjacentHTML('beforeend', ZOOM);
4519        c.addEventListener('click', function () { open(tbl, k, false); });
4520        c.addEventListener('keydown', function (e) {
4521          if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') {
4522            e.preventDefault();
4523            e.stopPropagation();   /* Space would otherwise advance */
4524            open(tbl, k, true);
4525          }
4526        });
4527      });
4528    });
4529  };
4530
4531  /* capture phase: ahead of Reveal's document-level handler */
4532  window.addEventListener('keydown', function (e) {
4533    if (isOpen()) {
4534      var k = e.key;
4535      if (k === 'Escape') close();
4536      else if (k === 'ArrowRight' || k === 'ArrowDown' || k === 'PageDown') step(1);
4537      else if (k === 'ArrowLeft'  || k === 'ArrowUp'   || k === 'PageUp')   step(-1);
4538      else return;
4539      e.preventDefault();
4540      e.stopPropagation();
4541      return;
4542    }
4543    if (e.key !== 'Escape') return;
4544    var on = document.querySelector('.exm-table .exm-cell.is-last');
4545    if (!on) return;
4546    e.stopPropagation();       /* clear the last-viewed mark instead of opening the overview */
4547    mark(on.closest('.exm-table'), -1);
4548  }, true);
4549
4550  /* keep focus inside the lightbox while it is open */
4551  document.addEventListener('focusin', function (e) {
4552    if (isOpen() && !box.contains(e.target)) btnX.focus({ preventScroll: true });
4553  });
4554
4555  function hook() {
4556    window.__exmScan();
4557    var R = window.Reveal;
4558    if (!R || typeof R.on !== 'function') return;
4559    R.on('ready', window.__exmScan);
4560    R.on('slidechanged', function (ev) {
4561      if (ev.previousSlide) ev.previousSlide.querySelectorAll('.exm-table').forEach(function (t) { mark(t, -1); });
4562    });
4563  }
4564  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', hook);
4565  else hook();
4566})();
4567</script>
4567
4568<div class="exm-source">
4569<p>Adapted from <code>Table 8.1</code> in Carr et al. <span class="citation" data-cites="carr_art_2020">(<a href="#/references" role="doc-biblioref" onclick="">2020</a>)</span></p>
4570</div>
4571</section>
4572<section id="experiments-3" class="slide level2 scrollable">
4573<h2>Experiments</h2>
4574<h4 id="an-example"><span style="color:#a9a9a9;">An Example</span></h4>
4575<div style="margin-top: 40px;">
4576
4577</div>
4578<!--
4579  ═══════════════════════════════════════════════════════════════════
4580  VIGNETTE EXPERIMENT · "John" · Quarto RevealJS drop-in (jv-)
4581  Space Grotesk / JetBrains Mono · purple ramp (#856cb0)
4582  Same system as the Gabriel vignette (vg-) and rating conjoint (ar-)
4583  ───────────────────────────────────────────────────────────────────
4584  Include with  {{< include _vignette-john.qmd >}}  or paste as is.
4585
4586  Flow
4587    01 Vignette  — the party slot in the vignette is a button; click
4588                   it to draw Democrat or Republican (p = .5 each).
4589    02 Outcomes  — two 7-point items, enabled once John is drawn.
4590                   The arm is named only after both are answered.
4591    Then         — "Show the counterfactual" swaps in the counterfactual
4592                   vignette (ratings locked while it is shown).
4593    Reset        — available once John is drawn; clears the draw and
4594                   the ratings for the next pass. Nothing is recorded.
4595
4596  The vignette text is verbatim. The treatment is appended as its own
4597  final sentence ("John is a ___.") so the fixed text is untouched.
4598
4599  Master size: --jv-size near the top of the <style> block.
4600  Arm labels: CONFIG block in the script. Item wording: markup below.
4601  ═══════════════════════════════════════════════════════════════════
4602-->
4603
4604<style>
4605.jv-scope {
4606  --jv-accent: #856cb0;
4607  --jv-deep:   #6f5a96;
4608  --jv-mid:    #a08bc4;
4609  --jv-soft:   #b7a5d3;
4610  --jv-pale:   #e9e4ff;
4611  --jv-line:   rgba(133, 108, 176, .16);
4612  --jv-ink:    #2a2632;
4613  --jv-mute:   #8d879b;
4614  --jv-sans: "Space Grotesk", -apple-system, sans-serif;
4615  --jv-mono: "JetBrains Mono", ui-monospace, monospace;
4616
4617  /* ▸▸ MASTER SIZE CONTROL ◂◂  19px compact · 22px default · 24px large */
4618  --jv-size: 22px;
4619
4620  max-width: 900px;
4621  margin: 0 auto;
4622  font-family: var(--jv-sans);
4623  font-size: var(--jv-size);
4624  line-height: 1.4;
4625  color: var(--jv-ink);
4626  text-align: left;
4627}
4628.jv-scope *, .jv-scope *::before, .jv-scope *::after { box-sizing: border-box; }
4629.jv-scope button { font-family: inherit; margin: 0; }
4630
4631/* ── top bar: stepper · arm · results ───────────────────────────── */
4632.jv-bar {
4633  display: flex;
4634  align-items: center;
4635  gap: .9em;
4636  margin-bottom: .75em;
4637}
4638.jv-scope .jv-steps {
4639  display: flex;
4640  align-items: center;
4641  gap: .55em;
4642  margin: 0;
4643  padding: 0;
4644  list-style: none;
4645  font-family: var(--jv-mono);
4646  font-size: .56em;
4647  letter-spacing: .09em;
4648  text-transform: uppercase;
4649  color: var(--jv-mute);
4650}
4651.jv-scope .jv-steps li { display: inline-flex; align-items: center; gap: .5em; margin: 0; transition: color .25s ease; }
4652.jv-scope .jv-steps li + li::before {
4653  content: "";
4654  width: 2.2em;
4655  height: 1px;
4656  margin-right: .1em;
4657  background: var(--jv-line);
4658}
4659.jv-num {
4660  display: inline-grid;
4661  place-items: center;
4662  width: 2.1em; height: 2.1em;
4663  border: 1px solid var(--jv-line);
4664  border-radius: 50%;
4665  font-weight: 600;
4666  letter-spacing: 0;
4667  transition: background-color .25s ease, border-color .25s ease, color .25s ease;
4668}
4669.jv-steps li.is-here { color: var(--jv-deep); }
4670.jv-steps li.is-here .jv-num { border-color: var(--jv-accent); background: var(--jv-accent); color: #fff; }
4671.jv-steps li.is-done { color: var(--jv-mid); }
4672.jv-steps li.is-done .jv-num { border-color: var(--jv-soft); color: var(--jv-accent); }
4673
4674.jv-arm {
4675  margin-left: auto;
4676  font-family: var(--jv-mono);
4677  font-size: .56em;
4678  letter-spacing: .1em;
4679  text-transform: uppercase;
4680  color: var(--jv-mute);
4681  white-space: nowrap;
4682}
4683.jv-arm b { font-weight: 600; color: var(--jv-deep); }
4684
4685/* ── vignette card ──────────────────────────────────────────────── */
4686.jv-card {
4687  padding: 1.1em 1.5em 1.15em;
4688  border: 1px solid var(--jv-line);
4689  border-left: 3px solid var(--jv-accent);
4690  border-radius: 0 10px 10px 0;
4691  background: rgba(133, 108, 176, .035);
4692  transition: border-left-color .3s ease, background-color .3s ease;
4693}
4694.jv-card.is-counterfactual {
4695  border-left-style: dashed;
4696  border-left-color: var(--jv-soft);
4697  background: rgba(133, 108, 176, .02);
4698}
4699.jv-scope .jv-text {
4700  margin: 0;
4701  font-size: .9em;
4702  font-weight: 300;
4703  line-height: 1.72;
4704}
4705.jv-treat { font-weight: 400; }
4706
4707/* the slot: a Randomize button until drawn, then the treatment token */
4708.jv-t {
4709  display: inline-flex;
4710  align-items: center;
4711  gap: .35em;
4712  padding: 0 .38em;
4713  border: 1px solid var(--jv-accent);
4714  border-radius: 5px;
4715  background: var(--jv-accent);
4716  color: #fff;
4717  font-size: 1em;
4718  font-weight: 500;
4719  line-height: 1.45;
4720  vertical-align: baseline;
4721  cursor: default;
4722}
4723.jv-t:focus-visible { outline: 3px solid var(--jv-pale); outline-offset: 2px; }
4724.jv-t svg { width: .85em; height: .85em; }
4725.jv-t.is-empty {
4726  padding: .1em .6em;
4727  background: #fff;
4728  color: var(--jv-deep);
4729  border-style: dashed;
4730  font-family: var(--jv-mono);
4731  font-size: .72em;
4732  letter-spacing: .08em;
4733  text-transform: uppercase;
4734  line-height: 1.5;
4735  cursor: pointer;
4736  animation: jv-breathe 2.2s ease-in-out infinite;
4737  transition: background-color .2s ease, color .2s ease;
4738}
4739.jv-t.is-empty:hover { background: var(--jv-accent); color: #fff; border-style: solid; animation: none; }
4740@keyframes jv-breathe {
4741  0%, 100% { box-shadow: 0 0 0 0 rgba(133,108,176,.0); }
4742  50%      { box-shadow: 0 0 0 .28em rgba(133,108,176,.16); }
4743}
4744.jv-t.is-swapping { animation: jv-swap .42s cubic-bezier(.22,.61,.36,1); }
4745@keyframes jv-swap {
4746  0%   { opacity: 1; transform: translateY(0); }
4747  45%  { opacity: 0; transform: translateY(-.35em); }
4748  55%  { opacity: 0; transform: translateY(.35em); }
4749  100% { opacity: 1; transform: translateY(0); }
4750}
4751
4752.jv-flag {
4753  display: none;
4754  margin-top: .7em;
4755  font-family: var(--jv-mono);
4756  font-size: .52em;
4757  letter-spacing: .1em;
4758  text-transform: uppercase;
4759  color: var(--jv-mid);
4760}
4761.jv-card.is-counterfactual .jv-flag { display: block; }
4762.jv-card.is-counterfactual .jv-t { background: var(--jv-soft); border-color: var(--jv-soft); }
4763
4764/* ── outcomes: survey-battery rows ──────────────────────────────── */
4765.jv-items {
4766  margin-top: .8em;
4767  border-top: 1px solid var(--jv-line);
4768  transition: opacity .25s ease;
4769}
4770.jv-items.is-locked { opacity: .38; pointer-events: none; }
4771
4772.jv-item {
4773  display: grid;
4774  grid-template-columns: 1fr auto;
4775  align-items: center;
4776  gap: 1em;
4777  padding: .6em 0;
4778  border-bottom: 1px solid var(--jv-line);
4779}
4780.jv-scope .jv-q {
4781  display: flex;
4782  align-items: baseline;
4783  gap: .6em;
4784  margin: 0;
4785  font-size: .8em;
4786  font-weight: 500;
4787  line-height: 1.35;
4788  letter-spacing: -.005em;
4789}
4790.jv-qn {
4791  flex: none;
4792  font-family: var(--jv-mono);
4793  font-size: .72em;
4794  font-weight: 600;
4795  letter-spacing: .06em;
4796  color: var(--jv-accent);
4797}
4798
4799.jv-scale-row { display: flex; align-items: center; gap: .55em; }
4800.jv-anchor {
4801  flex: 0 0 8.2em;
4802  font-family: var(--jv-mono);
4803  font-size: .5em;
4804  line-height: 1.35;
4805  letter-spacing: .07em;
4806  text-transform: uppercase;
4807  text-align: center;
4808  text-wrap: balance;
4809  color: var(--jv-mute);
4810}
4811.jv-scale { display: inline-flex; gap: .26em; }
4812.jv-pt {
4813  width: 1.75em;
4814  height: 1.75em;
4815  padding: 0;
4816  border: 1px solid rgba(133, 108, 176, .32);
4817  border-radius: 6px;
4818  background: #fff;
4819  color: var(--jv-deep);
4820  font-size: .78em;
4821  font-weight: 500;
4822  font-variant-numeric: tabular-nums;
4823  cursor: pointer;
4824  transition: background-color .15s ease, color .15s ease, border-color .15s ease, transform .12s ease;
4825}
4826.jv-pt:focus-visible { outline: 2px solid var(--jv-soft); outline-offset: 2px; }
4827/* ordinal fill: every point up to the value is tinted, deeper as it climbs */
4828.jv-pt.is-fill {
4829  background: rgba(133, 108, 176, calc(.05 + var(--k) * .045));
4830  border-color: rgba(133, 108, 176, .45);
4831}
4832.jv-pt.is-peek { background: rgba(133, 108, 176, .28); border-color: var(--jv-accent); }
4833.jv-pt.is-on {
4834  background: var(--jv-accent);
4835  border-color: var(--jv-accent);
4836  color: #fff;
4837  transform: translateY(-1px);
4838  box-shadow: 0 4px 10px rgba(133, 108, 176, .32);
4839}
4840
4841/* ── after-answer row ───────────────────────────────────────────── */
4842.jv-after {
4843  display: flex;
4844  align-items: center;
4845  gap: .6em;
4846  min-height: 2.1em;
4847  margin-top: .7em;
4848}
4849.jv-swap, .jv-reset { visibility: hidden; opacity: 0; transition: opacity .25s ease; }
4850.jv-scope.is-answered .jv-swap,
4851.jv-scope.is-drawn .jv-reset { visibility: visible; opacity: 1; }
4852
4853.jv-ghost {
4854  display: inline-flex;
4855  align-items: center;
4856  gap: .45em;
4857  padding: .32em .9em;
4858  border: 1px solid var(--jv-line);
4859  border-radius: 999px;
4860  background: transparent;
4861  color: var(--jv-mute);
4862  font-family: var(--jv-mono);
4863  font-size: .54em;
4864  letter-spacing: .09em;
4865  text-transform: uppercase;
4866  cursor: pointer;
4867  transition: color .2s ease, border-color .2s ease, background-color .2s ease;
4868}
4869.jv-ghost:hover { color: var(--jv-accent); border-color: var(--jv-accent); background: rgba(133,108,176,.06); }
4870.jv-ghost:focus-visible { outline: 2px solid var(--jv-pale); outline-offset: 2px; }
4871.jv-ghost svg { width: 1em; height: 1em; }
4872.jv-ghost.is-on { color: var(--jv-deep); border-color: var(--jv-accent); background: rgba(133,108,176,.1); }
4873
4874.jv-spacer { flex: 1; }
4875
4876@media (prefers-reduced-motion: reduce) {
4877  .jv-scope * { animation: none !important; transition: none !important; }
4878}
4879</style>
4880
4881<div class="jv-scope">
4882
4883  <div class="jv-bar">
4884    <ol class="jv-steps" aria-label="Progress">
4885      <li class="jv-step is-here" data-step="1"><span class="jv-num">01</span>Vignette</li>
4886      <li class="jv-step" data-step="2"><span class="jv-num">02</span>Outcomes</li>
4887    </ol>
4888    <span class="jv-arm" aria-live="polite"></span>
4889  </div>
4890
4891  <div class="jv-main">
4892
4893    <div class="jv-card">
4894      <p class="jv-text">
4895        John is a 44-year-old white man living in a suburb of Grand Rapids, Michigan. He lives with his wife and their two teenage children. He works as a facilities manager for a regi
4895onal healthcare network. He coaches youth soccer on weekends and attends church a few times a year. He owns his home and follows local and national news when he can. He voted in the last two general elections but tends to skip the primaries.
4896        <span class="jv-treat" aria-live="polite">John is a <button class="jv-t is-empty" type="button"></button>.</span>
4897      </p>
4898      <span class="jv-flag">Counterfactual arm · not the one this respondent saw</span>
4899    </div>
4900
4901    <div class="jv-items is-locked">
4902      <div class="jv-item" data-q="0">
4903        <p class="jv-q"><span class="jv-qn">Q1</span>How likely is it that John has a college degree?</p>
4904        <div class="jv-scale-row">
4905          <span class="jv-anchor">Very unlikely</span>
4906          <div class="jv-scale" role="radiogroup" aria-label="How likely is it that John has a college degree? 1 = very unlikely, 7 = very likely"></div>
4907          <span class="jv-anchor">Very likely</span>
4908        </div>
4909      </div>
4910      <div class="jv-item" data-q="1">
4911        <p class="jv-q"><span class="jv-qn">Q2</span>How similar is John to people like you?</p>
4912        <div class="jv-scale-row">
4913          <span class="jv-anchor">Not at all similar</span>
4914          <div class="jv-scale" role="radiogroup" aria-label="How similar is John to people like you? 1 = not at all similar, 7 = very similar"></div>
4915          <span class="jv-anchor">Very similar</span>
4916        </div>
4917      </div>
4918    </div>
4919
4920    <div class="jv-after">
4921      <button class="jv-ghost jv-swap" type="button" aria-pressed="false">
4922        <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 16H3l4-4"></path><path d="M3 16h13a5 5 0 0 0 0-10h-3"></path></svg>
4923        <span class="jv-swap-label">Show the counterfactual</span>
4924      </button>
4925      <span class="jv-spacer"></span>
4926      <button class="jv-ghost jv-reset" type="button">
4927        <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"></path><path d="M3 3v5h5"></path></svg>
4928        Reset
4929      </button>
4930    </div>
4931  </div>
4932
4933</div>
4934
4935<script>
4936(function () {
4937  if (window.__jvReady) { window.__jvScan(); return; }
4938  window.__jvReady = true;
4939
4940  /* ══ CONFIG ═════════════════════════════════════════════════════
4941     The two arms. The token reads "John is a <label>." */
4942  var ARMS = [
4943    { label: 'Democrat' },
4944    { label: 'Republican' }
4945  ];
4946  var POINTS = 7;
4947  /* ═══════════════════════════════════════════════════════════════ */
4948
4949  var DICE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M3 22v-6h6"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/></svg>';
4950
4951  window.__jvScan = function () {
4952    document.querySelectorAll('.jv-scope').forEach(function (scope) {
4953      if (scope.dataset.jvBound) return;
4954      scope.dataset.jvBound = '1';
4955
4956      var $ = function (s) { return scope.querySelector(s); };
4957      var steps   = scope.querySelectorAll('.jv-step'),
4958          elArm   = $('.jv-arm'),
4959          elCard  = $('.jv-card'),
4960          elT     = $('.jv-t'),
4961          elItems = $('.jv-items'),
4962          elSwap  = $('.jv-swap'),
4963          elSwapL = $('.jv-swap-label'),
4964          elReset = $('.jv-reset'),
4965          scales  = scope.querySelectorAll('.jv-scale');
4966
4967      var arm = null, ans = [null, null], other = false;
4968
4969      /* build the two 7-point scales */
4970      scales.forEach(function (sc, q) {
4971        for (var v = 1; v <= POINTS; v++) {
4972          var b = document.createElement('button');
4973          b.type = 'button';
4974          b.className = 'jv-pt';
4975          b.textContent = v;
4976          b.dataset.v = v;
4977          b.style.setProperty('--k', v);
4978          b.setAttribute('role', 'radio');
4979          b.setAttribute('aria-checked', 'false');
4980          sc.appendChild(b);
4981        }
4982        sc.addEventListener('click', function (e) {
4983          var b = e.target.closest('.jv-pt'); if (b) rate(q, +b.dataset.v);
4984        });
4985        sc.addEventListener('mouseover', function (e) {
4986          var b = e.target.closest('.jv-pt'); if (b) peek(q, +b.dataset.v);
4987        });
4988        sc.addEventListener('mouseleave', function () { peek(q, 0); });
4989      });
4990
4991      /* ── state painting ───────────────────────────────────────── */
4992      function setStep() {
4993        var done1 = !!arm, done2 = ans[0] !== null && ans[1] !== null;
4994        steps[0].className = 'jv-step ' + (done1 ? 'is-done' : 'is-here');
4995        steps[1].className = 'jv-step ' + (done2 ? 'is-done' : done1 ? 'is-here' : '');
4996      }
4997
4998      function paintScale(q) {
4999        var val = ans[q];
5000        scales[q].querySelectorAll('.jv-pt').forEach(function (b) {
5001          var v = +b.dataset.v;
5002          b.classList.toggle('is-fill', val !== null && v < val);
5003          b.classList.toggle('is-on', v === val);
5004          b.setAttribute('aria-checked', v === val ? 'true' : 'false');
5005        });
5006      }
5007      function peek(q, upto) {
5008        scales[q].querySelectorAll('.jv-pt').forEach(function (b) {
5009          b.classList.toggle('is-peek', upto > 0 && +b.dataset.v <= upto && !b.classList.contains('is-on'));
5010        });
5011      }
5012
5013      function paintToken(label) {
5014        elT.classList.remove('is-swapping');
5015        void elT.offsetWidth;
5016        elT.classList.add('is-swapping');
5017        setTimeout(function () { elT.textContent = label; }, 180);
5018      }
5019
5020      function emptyToken() {
5021        elT.classList.remove('is-swapping');
5022        elT.classList.add('is-empty');
5023        elT.disabled = false;
5024        elT.innerHTML = DICE + '<span>Randomize</span>';
5025        elT.setAttribute('aria-label', 'Randomize John\u2019s party');
5026      }
5027
5028      /* ── actions ──────────────────────────────────────────────── */
5029      function draw() {
5030        if (arm) return;
5031        arm = ARMS[Math.floor(Math.random() * ARMS.length)];
5032        elT.classList.remove('is-empty');
5033        elT.disabled = true;
5034        elT.removeAttribute('aria-label');
5035        paintToken(arm.label);
5036        elItems.classList.remove('is-locked');
5037        scope.classList.add('is-drawn');
5038        setStep();
5039      }
5040
5041      function rate(q, v) {
5042        if (!arm || other) return;
5043        ans[q] = v;
5044        paintScale(q);
5045        peek(q, 0);
5046        setStep();
5047        if (ans[0] !== null && ans[1] !== null) {
5048          scope.classList.add('is-answered');
5049          elArm.innerHTML = 'Arm · <b>' + arm.label + '</b>';
5050        }
5051      }
5052
5053      function toggleOther() {
5054        other = !other;
5055        var alt = ARMS.filter(function (a) { return a !== arm; })[0];
5056        paintToken(other ? alt.label : arm.label);
5057        elCard.classList.toggle('is-counterfactual', other);
5058        elItems.classList.toggle('is-locked', other);
5059        elSwap.classList.toggle('is-on', other);
5060        elSwap.setAttribute('aria-pressed', other ? 'true' : 'false');
5061        elSwapL.textContent = other ? 'Back to original' : 'Show the counterfactual';
5062      }
5063
5064      function reset() {
5065        arm = null;
5065 ans = [null, null]; other = false;
5066        emptyToken();
5067        elCard.classList.remove('is-counterfactual');
5068        elItems.classList.add('is-locked');
5069        elSwap.classList.remove('is-on');
5070        elSwap.setAttribute('aria-pressed', 'false');
5071        elSwapL.textContent = 'Show the counterfactual';
5072        scope.classList.remove('is-answered', 'is-drawn');
5073        elArm.innerHTML = '';
5074        paintScale(0); paintScale(1);
5075        setStep();
5076      }
5077
5078      /* ── wiring ───────────────────────────────────────────────── */
5079      elT.addEventListener('click', draw);
5080      elSwap.addEventListener('click', toggleOther);
5081      elReset.addEventListener('click', reset);
5082
5083      /* Space/Enter on any control must not advance the Reveal slide */
5084      scope.addEventListener('keydown', function (e) {
5085        if ((e.key === ' ' || e.key === 'Spacebar' || e.key === 'Enter') && e.target.closest('button')) {
5086          e.stopPropagation();
5087        }
5088      });
5089
5090      reset();
5091    });
5092  };
5093
5094  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', window.__jvScan);
5095  else window.__jvScan();
5096  if (window.Reveal && Reveal.on) Reveal.on('ready', window.__jvScan);
5097})();
5098</script>
5098
5099</section>
5100<section id="experiments-4" class="slide level2">
5101<h2>Experiments</h2>
5102<div style="text-align: center; margin-top: 60px; font-size: 95%;">
5103<p>Let’s pause for a second.</p>
5104<div class="fragment">
5105<p>Design a <span style="color: white; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;"><strong>survey experiment</strong></span> linked to your research question.</p>
5106</div>
5107<div style="margin-top: 30px;">
5108
5109</div>
5110<div class="fragment">
5111<p>Rearrange the text in the box below to arrive at your pitch.</p>
5112<div style="margin-top: 30px;">
5113
5114</div>
5115<!--
5116  ═══════════════════════════════════════════════════════════════════
5117  EXPERIMENTAL PITCH PAD · Quarto RevealJS drop-in (oe- namespace)
5118  Space Grotesk / JetBrains Mono · purple ramp (#856cb0)
5119  ───────────────────────────────────────────────────────────────────
5120  Same component as the "Your Research Question" pad. The card's
5121  border is the clock: the stroke drains clockwise from top centre,
5122  turns red in the warning window (10% of duration, capped at 60s),
5123  and the card pulses at 0:00.
5124
5125  Per-instance attributes on .oe-pad:
5126    data-minutes  duration (fractions OK)   default 10
5127    data-file     export filename           default research_question.txt
5128  Master size control: --oe-size.
5129
5130  If the research-question pad is already in this deck, only the
5131  <div class="oe-pad"> block is needed; the style and script here are
5132  guarded and harmless if repeated.
5133  ═══════════════════════════════════════════════════════════════════
5134-->
5135
5136<style>
5137.oe-pad {
5138  --oe-accent: #856cb0;
5139  --oe-deep:   #6f5a96;
5140  --oe-pale:   #e9e4ff;
5141  --oe-warn:   #c75b5b;
5142  --oe-ink:    #2a2632;
5143  --oe-mute:   #857f93;
5144  --oe-sans: "Space Grotesk", -apple-system, sans-serif;
5145  --oe-mono: "JetBrains Mono", ui-monospace, monospace;
5146  --oe-shadow: 0 1px 2px rgba(42, 38, 50, .05), 0 16px 40px -18px rgba(111, 90, 150, .38);
5147
5148  /* ▸▸ MASTER SIZE CONTROL ◂◂  17px compact · 20px default · 24px large */
5149  --oe-size: 20px;
5150
5151  position: relative;
5152  box-sizing: border-box;
5153  max-width: 1000px;
5154  margin: 0 auto;
5155  padding: 1em 1.15em 1.05em;
5156  border-radius: 14px;
5157  background: #fff;
5158  box-shadow: var(--oe-shadow);
5159  font-family: var(--oe-sans);
5160  font-size: var(--oe-size);
5161  color: var(--oe-ink);
5162  text-align: left;
5163}
5164.oe-pad *, .oe-pad *::before, .oe-pad *::after { box-sizing: border-box; }
5165
5166/* ── perimeter clock ────────────────────────────────────────────── */
5167.oe-ring { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }
5168.oe-track { fill: none; stroke: rgba(133, 108, 176, .14); stroke-width: 2.5; }
5169.oe-prog {
5170  fill: none;
5171  stroke: var(--oe-accent);
5172  stroke-width: 2.5;
5173  stroke-linecap: round;
5174  stroke-dasharray: 1 2;
5175  opacity: .55;
5176  transition: stroke .3s ease, opacity .3s ease, filter .3s ease;
5177}
5178.oe-pad[data-state="running"] .oe-prog { opacity: 1; filter: drop-shadow(0 0 4px rgba(133, 108, 176, .55)); }
5179.oe-pad[data-state="paused"]  .oe-prog { opacity: .75; }
5180.oe-pad.is-warn .oe-prog { stroke: var(--oe-warn); filter: drop-shadow(0 0 4px rgba(199, 91, 91, .45)); }
5181.oe-pad[data-state="done"]    .oe-prog { opacity: 0; }
5182
5183@keyframes oe-ring-pulse {
5184  0%   { box-shadow: var(--oe-shadow), 0 0 0 0    rgba(199, 91, 91, .4); }
5185  100% { box-shadow: var(--oe-shadow), 0 0 0 14px rgba(199, 91, 91, 0); }
5186}
5187.oe-pad.is-pulse { animation: oe-ring-pulse 1.4s ease-out 3; }
5188
5189/* ── header ─────────────────────────────────────────────────────── */
5190.oe-head {
5191  display: flex;
5192  align-items: center;
5193  justify-content: space-between;
5194  gap: 1em;
5195  margin-bottom: .6em;
5196}
5197.oe-title {
5198  font-size: 1.15em;
5199  font-weight: 600;
5200  letter-spacing: -.015em;
5201  line-height: 1.2;
5202}
5203
5204.oe-clock { display: flex; align-items: center; gap: .35em; }
5205.oe-time {
5206  font-family: var(--oe-mono);
5207  font-size: 1.05em;
5208  font-weight: 500;
5209  color: var(--oe-accent);
5210  min-width: 3.2em;
5211  text-align: right;
5212  letter-spacing: .02em;
5213  font-variant-numeric: tabular-nums;
5214  transition: color .3s ease;
5215}
5216.oe-pad.is-warn .oe-time { color: var(--oe-warn); }
5217.oe-pad[data-state="done"] .oe-time { color: var(--oe-warn); animation: oe-blink 1.4s ease-in-out infinite; }
5218@keyframes oe-blink { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
5219
5220.oe-tbtn {
5221  width: 1.5em; height: 1.5em;
5222  display: grid; place-items: center;
5223  margin: 0; padding: 0;
5224  border: 0; border-radius: 50%;
5225  background: rgba(133, 108, 176, .08);
5226  color: var(--oe-accent);
5227  cursor: pointer;
5228  transition: background-color .2s ease, color .2s ease, transform .15s ease;
5229}
5230.oe-tbtn:hover { background: rgba(133, 108, 176, .16); }
5231.oe-tbtn:active { transform: scale(.94); }
5232.oe-tbtn:focus-visible { outline: 2px solid var(--oe-accent); outline-offset: 2px; }
5233.oe-tbtn.is-on { background: var(--oe-accent); color: #fff; }
5234.oe-tbtn svg { width: .65em; height: .65em; }
5235
5236/* ── editor ─────────────────────────────────────────────────────── */
5237.oe-editor {
5238  display: block;
5239  width: 100%;
5240  min-height: 6.5em;
5241  padding: .8em 1em;
5242  font-family: var(--oe-sans);
5243  font-size: .95em;
5244  font-weight: 300;
5245  line-height: 1.7;
5246  color: var(--oe-ink);
5247  background: #fff;
5248  border: 1px solid rgba(133, 108, 176, .18);
5249  border-radius: 8px;
5250  resize: vertical;
5251  outline: none;
5252  transition: border-color .2s ease, box-shadow .2s ease;
5253}
5254.oe-editor::placeholder { color: var(--oe-mute); font-style: italic; }
5255.oe-editor:focus {
5256  border-color: rgba(133, 108, 176, .45);
5257  box-shadow: 0 0 0 3px rgba(133, 108, 176, .1);
5258}
5259
5260/* ── actions ────────────────────────────────────────────────────── */
5261.oe-actions {
5262  display: flex;
5263  align-items: center;
5264  gap: .35em;
5265  margin-top: .5em;
5266}
5267.oe-btn {
5268  display: inline-flex;
5269  align-items: center;
5270  gap: .4em;
5271  margin: 0;
5272  border: 0;
5273  border-radius: 6px;
5274  padding: .35em 1em;
5275  font-family: var(--oe-sans);
5276  font-size: .78em;
5277  font-weight: 500;
5278  cursor: pointer;
5279  transition: background-color .2s ease, color .2s ease, transform .15s ease;
5280}
5281.oe-btn:active { transform: scale(.98); }
5282.oe-btn:focus-visible { outline: 2px solid var(--oe-accent); outline-offset: 2px; }
5283.oe-btn svg { width: .85em; height: .85em; }
5284
5285.oe-primary { background: var(--oe-accent); color: #fff; }
5286.oe-primary:hover { background: var(--oe-deep); }
5287
5288.oe-secondary { background: rgba(133, 108, 176, .09); color: rgba(133, 108, 176, .75); }
5289.oe-secondary:hover { background: rgba(133, 108, 176, .16); color: var(--oe-accent); }
5290.oe-secondary.is-done { background: var(--oe-pale); color: var(--oe-deep); }
5291
5292.oe-count {
5293  margin-left: auto;
5294  font-family: var(--oe-mono);
5295  font-size: .62em;
5296  letter-spacing: .06em;
5297  text-transform: uppercase;
5298  color: var(--oe-mute);
5299  font-variant-numeric: tabular-nums;
5300}
5301
5302.oe-clear {
5303  background: rgba(199, 91, 91, .09);
5304  color: var(--oe-warn);
5305  margin-left: .35em;
5306}
5307.oe-clear:hover { background: rgba(199, 91, 91, .16); }
5308.oe-clear.is-arming { background: var(--oe-warn); color: #fff; }
5309
5310@media (prefers-reduced-motion: reduce) {
5311  .oe-pad.is-pulse, .oe-pad[data-state="done"] .oe-time { animation: none; }
5312  .oe-prog, .oe-btn, .oe-tbtn { transition: none; }
5313}
5314</style>
5315
5316<div class="oe-pad" data-minutes="10" data-file="experiment.txt">
5317
5318  <svg class="oe-ring" aria-hidden="true">
5319    <path class="oe-track"></path>
5320    <path class="oe-prog" pathlength="1"></path>
5321  </svg>
5322
5323  <div class="oe-head">
5324    <span class="oe-title">An Experimental Pitch</span>
5325    <div class="oe-clock">
5326      <span class="oe-time" role="timer">10:00</span>
5327      <button class="oe-tbtn oe-toggle" type="button" aria-label="Start or pause the timer">
5328        <svg class="oe-i-play" viewbox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M6 4l14 8-14 8z"></path></svg>
5329        <svg class="oe-i-pause" viewbox="0 0 24 24" fill="currentColor" style="display:none" aria-hidden="true"><rect x="6" y="4" width="4" height="16" rx="1"></rect><rect x="14" y="4" width="4" height="16" rx="1"></rect></svg>
5330      </button>
5331      <button class="oe-tbtn oe-reset" type="button" aria-label="Reset the timer">
5332        <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>
5333      </button>
5334    </div>
5335  </div>
5336
5337  <textarea class="oe-editor" rows="4" placeholder="My experiment will explore ..."></textarea>
5338
5339  <div class="oe-actions">
5340    <button class="oe-btn oe-primary oe-export" type="button">
5341      <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
5342      Export .txt
5343    </button>
5344    <button class="oe-btn oe-secondary oe-copy" type="button">
5345      <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
5346      <span class="oe-copy-label">Copy</span>
5347    </button>
5348    <span class="oe-count">0 words</span>
5349    <button class="oe-btn oe-clear" type="button">
5350      <svg viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path></svg>
5351      <span class="oe-clear-label">Clear</span>
5352    </button>
5353  </div>
5354</div>
5355
5356<script>
5357(function () {
5358  if (window.__oePadReady) { window.__oePadScan(); return; }
5359  window.__oePadReady = true;
5360
5361  window.__oePadScan = function () {
5362    document.querySelectorAll('.oe-pad').forEach(function (pad) {
5363      if (pad.dataset.oeBound) return;
5364      pad.dataset.oeBound = '1';
5365
5366      var TOTAL   = (parseFloat(pad.dataset.minutes) || 10) * 60000,
5367          WARN_AT = Math.min(60000, TOTAL * 0.1);
5368
5369      var q = function (s) { return pad.querySelector(s); };
5370      var elRing   = q('.oe-ring'),   elTrack  = q('.oe-track'),  elProg  = q('.oe-prog'),
5371          elTime   = q('.oe-time'),   elToggle = q('.oe-toggle'), elPlay  = q('.oe-i-play'),
5372          elPause  = q('.oe-i-pause'), elReset = q('.oe-reset'),  elText  = q('.oe-editor'),
5373          elCount  = q('.oe-count'),  elCopy   = q('.oe-copy'),   elCopyL = q('.oe-copy-label'),
5374          elClear  = q('.oe-clear'),  elClearL = q('.oe-clear-label');
5375
5376      var left = TOTAL, deadline = 0, raf = 0, shown = -1, armed = null, flash = null;
5377
5378      /* ── perimeter path, starting at top centre ── */
5379      function draw() {
5380        var w = pad.offsetWidth, h = pad.offsetHeight;
5381        if (!w || !h) return;
5382        var s = 1.25,
5383            r = Math.max(0, Math.min(parseFloat(getComputedStyle(pad).borderTopLeftRadius) || 0, w / 2, h / 2) - s),
5384            x0 = s, y0 = s, x1 = w - s, y1 = h - s;
5385        var d = 'M' + w / 2 + ' ' + y0 + 'H' + (x1 - r) + 'A' + r + ' ' + r + ' 0 0 1 ' + x1 + ' ' + (y0 + r) +
5386                'V' + (y1 - r) + 'A' + r + ' ' + r + ' 0 0 1 ' + (x1 - r) + ' ' + y1 +
5387                'H' + (x0 + r) + 'A' + r + ' ' + r + ' 0 0 1 ' + x0 + ' ' + (y1 - r) +
5388                'V' + (y0 + r) + 'A' + r + ' ' + r + ' 0 0 1 ' + (x0 + r) + ' ' + y0 + 'Z';
5389        elRing.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
5390        elTrack.setAttribute('d', d);
5391        elProg.setAttribute('d', d);
5392      }
5393      if ('ResizeObserver' in window) new ResizeObserver(draw).observe(pad);
5394      else window.addEventListener('resize', draw);
5395
5396      /* ── timer ── */
5397      function fmt(s) { return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); }
5398
5399      function setState(st) {
5400        pad.dataset.state = st;
5401        var on = st === 'running';
5402        elToggle.classList.toggle('is-on', on);
5403        elPlay.style.display  = on ? 'none' : '';
5404        elPause.style.display = on ? '' : 'none';
5405      }
5406
5407      function paint() {
5408        elProg.style.strokeDashoffset = -(1 - left / TOTAL);
5409        var secs = Math.ceil(left / 1000);
5410        if (secs !== shown) { shown = secs; elTime.textContent = fmt(secs); }
5411        pad.classList.toggle('is-warn', left <= WARN_AT);
5412      }
5413
5414      function tick(now) {
5415        left = Math.max(0, deadline - now);
5416        paint();
5417        if (left === 0) { finish(); return; }
5418        raf = requestAnimationFrame(tick);
5419      }
5420
5421      function finish() {
5422        cancelAnimationFrame(raf);
5423        setState('done');
5424        pad.classList.remove('is-pulse');
5425        void pad.offsetWidth;
5426        pad.classList.add('is-pulse');
5427      }
5428
5429      elToggle.addEventListener('click', function () {
5430        if (pad.dataset.state === 'running') {
5431          cancelAnimationFrame(raf);
5432          left = Math.max(0, deadline - performance.now());
5433          setState('paused');
5434          paint();
5435          return;
5436        }
5437        if (left <= 0) { left = TOTAL; shown = -1; pad.classList.remove('is-pulse'); }
5438        deadline = performance.now() + left;
5439        setState('running');
5440        raf = requestAnimationFrame(tick);
5441        elText.focus();
5442      });
5443
5444      elReset.addEventListener('click', function () {
5445        cancelAnimationFrame(raf);
5446        left = TOTAL; shown = -1;
5447        pad.classList.remove('is-pulse');
5448        setState('idle');
5449        paint();
5450      });
5451
5452      /* ── word count ── */
5453      function count() {
5454        var w = elText.value.trim();
5455        var n = w ? w.split(/\s+/).length : 0;
5456        elCount.textContent = n + (n === 1 ? ' word' : ' words');
5457      }
5458      elText.addEventListener('input', count);
5459
5460      /* Reveal listens on document — keep every keystroke out of its hands */
5461      ['keydown', 'keypress', 'keyup'].forEach(function (ev) {
5462        elText.addEventListener(ev, function (e) { e.stopPropagation(); });
5463      });
5464      pad.addEventListener('keydown', function (e) {
5465        if (e.key === ' ' || e.key === 'Spacebar') e.stopPropagation();
5466      });
5467
5468      /* ── export / copy / clear ── */
5469      q('.oe-export').addEventListener('click', function () {
5470        var t = elText.value;
5471        if (!t.trim()) return;
5472        var a = document.createElement('a');
5473        a.href = URL.createObjectURL(new Blob([t], { type: 'text/plain;charset=utf-8' }));
5474        a.download = pad.dataset.file || 'research_question.txt';
5475        a.click();
5476        setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000);
5477      });
5478
5479      elCopy.addEventListener('click', function () {
5480        var t = elText.value;
5481        if (!t.trim()) return;
5482        function done() {
5483          clearTimeout(flash);
5484          elCopy.classList.add('is-done');
5485          elCopyL.textContent = 'Copied';
5486          flash = setTimeout(function () {
5487            elCopy.classList.remove('is-done');
5488            elCopyL.textContent = 'Copy';
5489          }, 1600);
5490        }
5491        if (navigator.clipboard && navigator.clipboard.writeText) {
5492          navigator.clipboard.writeText(t).then(done, function () { elText.select(); });
5493        } else { elText.select(); document.execCommand('copy'); done(); }
5494      });
5495
5496      elClear.addEventListener('click', function () {
5497        if (!elText.value.trim()) return;
5498        if (armed) {
5499          clearTimeout(armed); armed = null;
5500          elClear.classList.remove('is-arming');
5501          elClearL.textContent = 'Clear';
5502          elText.value = '';
5503          count();
5504          elText.focus();
5505          return;
5506        }
5507        elClear.classList.add('is-arming');
5508        elClearL.textContent = 'Confirm';
5509        armed = setTimeout(function () {
5510          armed = null;
5511          elClear.classList.remove('is-arming');
5512          elClearL.textContent = 'Clear';
5513        }, 3000);
5514      });
5515
5516      setState('idle');
5517      paint();
5518      count();
5519      draw();
5520    });
5521  };
5522
5523  if (document.readyState === 'loading') {
5524    document.addEventListener('DOMContentLoaded', window.__oePadScan);
5525  } else { window.__oePadScan(); }
5526  if (window.Reveal && Reveal.on) Reveal.on('ready', window.__oePadScan);
5527})();
5528</script>
5528
5529</div>
5530</div>
5531</section></section>
5532<section>
5533<section id="high-level-overview-directed-acyclic-graphs-dags" class="title-slide slide level1 center" data-background-color="#43464B">
5534<h1><span class="title-ill">High-Level Overview</span> Directed Acyclic Graphs <span style="font-size: 60%;">(DAGs)</span></h1>
5535
5536</section>
5537<section id="dags" class="slide level2">
5538<h2>DAGs</h2>
5539<div class="elegant-quote" style="margin-top: 40px; font-size: 83%;">
5540<p>Directed acyclic graphs (DAGs) offer a powerful visual framework for representing and reasoning about causal relationships. <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">At their core, DAGs capture the directional flow of causality—each arrow represents an immediate cause-and-effect relationship between variables, and the absence of cycles reflects the unidirectional nature of time and causation</span>. While social science theories often allow for reverse causality or feedback loops, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">DAGs require breaking these cycles into sequential events, making causality more explicit and analyzable</span>.</p>
5541<p><span class="citation" data-cites="cunningham2026remix">(<a href="#/references" role="doc-biblioref" onclick="">Cunningham 2026</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p>
5542</div>
5543</section>
5544<section id="dags-1" class="slide level2">
5545<h2>DAGs</h2>
5546<div class="elegant-quote" style="margin-top: 40px; font-size: 83%;">
5547<p>Despite their simplicity, <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">DAGs are grounded in the same counterfactual framework that underpins potential outcomes</span>. A causal effect is still defined as a comparison between two states of the world: the observed outcome under one intervention and the counterfactual outcome under another. <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">This connection makes DAGs a natural complement to potential outcomes by offering a graphical representation of various assumptions we must make when identifying effects</span>. In fact, this is precisely one of the strengths of this approach — <span style="color: white; line-height: 1.5; background-color: #856cb0; padding: 1px 5px;">it forces the researcher to think long and hard about the treatment assignment mechanisms</span>, which are one of the main factors that dictate whether one research design is more suitable than another.</p>
5548<p><span class="citation" data-cites="cunningham2026remix">(<a href="#/references" role="doc-biblioref" onclick="">Cunningham 2026</a>, <span style="font-size: 55%; font-family: Red Hat Display; color: white; background-color: #856cb0"><strong>EMPHASIS ADDED</strong></span>)</span></p>
5549</div>
5550</section>
5551<section id="dags-2" class="slide level2">
5552<h2>DAGs</h2>
5553<h4 id="example-from-cunningham--cunningham2026remix"><span style="color:#a9a9a9;">Example from Cunningham <span class="citation" data-cites="cunningham2026remix">(<a href="#/references" role="doc-biblioref" onclick="">2026</a>)</span></span></h4>
5554<div style="margin-top: 60px;">
5555
5556</div>
5557
5558<!--
5559  ═══════════════════════════════════════════════════════════════════
5560  DAG · controlling for family income · Quarto RevealJS drop-in
5561  Space Grotesk · purple ramp (#856cb0) · same idiom as dag-confounder
5562  ───────────────────────────────────────────────────────────────────
5563  Default: Family Income is left alone (round node).
5564  Step 2:  it is controlled for — the node squares off into a box,
5565           fills, and a "controlled" tag fades in.
5566  Advance with the normal Reveal key (it's a fragment) or click the
5567  figure. Click again, or step back, to return. Size: --dag-size.
5568  Dashed edges run from the unobserved node.
5569  ═══════════════════════════════════════════════════════════════════
5570-->
5571
5572<style>
5573.dag3 {
5574  --dag-ink:    #2a2632;
5575  --dag-accent: #856cb0;
5576  --dag-deep:   #6f5a96;
5577  --dag-pale:   #f3effa;
5578  --dag-lilac:  #c9bce2;
5579  --dag-mute:   #8a8494;
5580  --dag-size:   22px;
5581
5582  position: relative;
5583  max-width: 960px;
5584  margin: 0 auto;
5585  font-family: "Space Grotesk", -apple-system, sans-serif;
5586  font-size: var(--dag-size);
5587  line-height: 1;
5588  cursor: pointer;
5589  -webkit-tap-highlight-color: transparent;
5590}
5591.dag3 svg { display: block; width: 100%; height: auto; overflow: visible; }
5592
5593/* edges */
5594.dag3 .e { fill: none; stroke: var(--dag-ink); stroke-width: 2; }
5595.dag3 .e-u { stroke: var(--dag-mute); stroke-dasharray: 7 6; }
5596
5597/* nodes */
5598.dag3 .core, .dag3 .ring { transition: rx .45s cubic-bezier(.22,.61,.36,1), ry .45s cubic-bezier(.22,.61,.36,1), fill .45s ease, stroke .45s ease; }
5599.dag3 .ring { fill: none; stroke-width: 1.5; }
5600.dag3 .n-pe .core { fill: #fff; }              .dag3 .n-pe .ring { stroke: var(--dag-ink); }
5601.dag3 .n-d  .core { fill: var(--dag-ink); }    .dag3 .n-d  .ring { stroke: var(--dag-ink); }
5602.dag3 .n-y  .core { fill: var(--dag-lilac); }  .dag3 .n-y  .ring { stroke: var(--dag-lilac); }
5603.dag3 .n-b  .core { fill: #fff; }              .dag3 .n-b  .ring { stroke: var(--dag-mute); stroke-dasharray: 4 4; }
5604
5605/* the node that changes: circle (left alone) → box (controlled) */
5606.dag3 .n-i .core { fill: var(--dag-pale);
5606 rx: 20px; ry: 20px; }
5607.dag3 .n-i .ring { stroke: var(--dag-accent); rx: 23px; ry: 23px; }
5608.dag3.is-on .n-i .core { fill: var(--dag-accent); rx: 4px; ry: 4px; }
5609.dag3.is-on .n-i .ring { stroke: var(--dag-deep); rx: 6px; ry: 6px; }
5610
5611/* labels */
5612.dag3 .lab {
5613  position: absolute;
5614  transform: translate(-50%, -50%);
5615  padding: .22em .45em .26em;
5616  border-radius: 5px;
5617  font-weight: 500;
5618  letter-spacing: -.005em;
5619  white-space: nowrap;
5620}
5621.dag3 .lab-pe { left: 12.5%;  top: 13.2%; background: var(--dag-pale);  color: var(--dag-ink); }
5622.dag3 .lab-i  { left: 45.83%; top: 13.2%; background: var(--dag-accent); color: #fff; transition: background-color .45s ease; }
5623.dag3 .lab-d  { left: 62.5%;  top: 86.8%; background: var(--dag-ink);   color: #fff; }
5624.dag3 .lab-y  { left: 89.58%; top: 86.8%; background: var(--dag-lilac); color: var(--dag-ink); }
5625.dag3 .lab-b  { left: 12.5%;  top: 86.8%; color: var(--dag-mute); font-style: italic; font-weight: 400; }
5626.dag3.is-on .lab-i { background: var(--dag-deep); }
5627
5628/* "controlled" tag, right of the node */
5629.dag3 .tag {
5630  position: absolute;
5631  left: 49.9%; top: 25.5%;
5632  transform: translate(0, -50%) translateX(-.4em);
5633  padding: .28em .55em;
5634  border: 1px solid rgba(133, 108, 176, .45);
5635  border-radius: 999px;
5636  background: #fff;
5637  font-family: "JetBrains Mono", ui-monospace, monospace;
5638  font-size: .58em;
5639  letter-spacing: .1em;
5640  text-transform: uppercase;
5641  color: var(--dag-deep);
5642  white-space: nowrap;
5643  opacity: 0;
5644  transition: opacity .35s ease, transform .35s ease;
5645}
5646.dag3.is-on .tag { opacity: 1; transform: translate(0, -50%); transition-delay: .15s; }
5647
5648.dag3 .dag3-frag { position: absolute; width: 0; height: 0; overflow: hidden; }
5649
5650@media (prefers-reduced-motion: reduce) {
5651  .dag3 *, .dag3 .tag { transition: none !important; }
5652}
5653</style>
5654
5655<div class="dag3" role="img" aria-label="DAG: parental education affects family income and college degree; family income affects college degree and income; college degree affects income; unobserved factors affect parental education and college degree. In the second state, family income is controlled for.">
5656  <svg viewbox="0 0 960 440" aria-hidden="true">
5657    <defs>
5658      <marker id="dag3-arr" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse">
5659        <path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path>
5660      </marker>
5661      <marker id="dag3-arr-u" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse">
5662        <path d="M0 0 L10 5 L0 10 z" fill="#8a8494"></path>
5663      </marker>
5664    </defs>
5665
5666    <!-- observed paths -->
5667    <path class="e" marker-end="url(#dag3-arr)" d="M146 110 H412"></path>                       <!-- PE → I -->
5668    <path class="e" marker-end="url(#dag3-arr)" d="M143 121 L574 318"></path>                   <!-- PE → D -->
5669    <path class="e" marker-end="url(#dag3-arr)" d="M455 131 L583 307"></path>                   <!-- I → D  -->
5670    <path class="e" marker-end="url(#dag3-arr)" d="M465 102 C640 10 870 110 862 302"></path>    <!-- I → Y  -->
5671    <path class="e" marker-end="url(#dag3-arr)" d="M627 330 H832"></path>                       <!-- D → Y  -->
5672
5673    <!-- unobserved paths -->
5674    <path class="e e-u" marker-end="url(#dag3-arr-u)" d="M120 305 V138"></path>                 <!-- B → PE -->
5675    <path class="e e-u" marker-end="url(#dag3-arr-u)" d="M146 330 H570"></path>                 <!-- B → D  -->
5676
5677    <g class="n-pe"><circle class="core" cx="120" cy="110" r="20"></circle><circle class="ring" cx="120" cy="110" r="23"></circle></g>
5678    <g class="n-i"><rect class="core" x="420" y="90" width="40" height="40" rx="20" ry="20"></rect><rect class="ring" x="417" y="87" width="46" height="46" rx="23" ry="23"></rect></g>
5679    <g class="n-d"><circle class="core" cx="600" cy="330" r="20"></circle><circle class="ring" cx="600" cy="330" r="23"></circle></g>
5680    <g class="n-y"><circle class="core" cx="860" cy="330" r="20"></circle><circle class="ring" cx="860" cy="330" r="23"></circle></g>
5681    <g class="n-b"><circle class="core" cx="120" cy="330" r="20"></circle><circle class="ring" cx="120" cy="330" r="23"></circle></g>
5682  </svg>
5683
5684  <span class="lab lab-pe">Parental Education</span>
5685  <span class="lab lab-i">
5685Family Income</span>
5686  <span class="lab lab-d">College Degree</span>
5687  <span class="lab lab-y">Earnings</span>
5688  <span class="lab lab-b">Unobserved</span>
5689  <span class="tag">Adjusted</span>
5690
5691  <span class="fragment dag3-frag"></span>
5692</div>
5693
5694<script>
5695(function () {
5696  /* bind after the page has loaded, so Reveal exists whether its script
5697     sits before or after this block */
5698  function bind() {
5699    var R = window.Reveal && Reveal.on ? Reveal : null;
5700    document.querySelectorAll('.dag3').forEach(function (d) {
5701      if (d.dataset.bound) return;
5702      d.dataset.bound = '1';
5703
5704      var f = d.querySelector('.dag3-frag');
5705      function set(on) { d.classList.toggle('is-on', on); }
5706      function sync() { set(f.classList.contains('visible')); }
5707
5708      if (R) {
5709        R.on('fragmentshown',  function (e) { if (e.fragment === f) set(true);  });
5710        R.on('fragmenthidden', function (e) { if (e.fragment === f) set(false); });
5711        R.on('slidechanged', sync);
5712        R.on('ready', sync);
5713        sync();
5714      }
5715
5716      /* click toggles; with Reveal, it moves the fragment state itself so
5717         arrow keys and clicks never disagree */
5718      d.addEventListener('click', function () {
5719        var on = !d.classList.contains('is-on');
5720        var i  = parseInt(f.getAttribute('data-fragment-index'), 10);
5721        if (R && R.navigateFragment && !isNaN(i)) R.navigateFragment(on ? i : i - 1);
5722        else set(on);
5723      });
5724    });
5725  }
5726  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bind);
5727  else bind();
5728})();
5729</script>
5729
5730
5731</section>
5732<section id="dags-3" class="slide level2">
5733<h2>DAGs</h2>
5734<h4 id="correlation-neq-causation"><span style="color:#a9a9a9;">Correlation <span class="math inline">\neq</span> Causation</span></h4>
5735<div style="margin-top: 60px;">
5736
5737</div>
5738
5739
5740<!--
5741  ═══════════════════════════════════════════════════════════════════
5742  DAG · three confounder triangles · Quarto RevealJS drop-in
5743  Space Grotesk · purple ramp (#856cb0) · same idiom as dag-confounder
5744  ───────────────────────────────────────────────────────────────────
5745  Each triangle opens as "Confounder?" on a hollow, dashed node; its
5746  step 2 crossfades in the confounder and fills the node.
5747  Each reveal is a Reveal fragment, in left-to-right order: advance
5748  as usual, or click a triangle. Clicking moves the deck's fragment
5749  position, so clicking the third reveals all three and clicking the
5750  first again hides all three, exactly as the arrow keys would.
5751  Size: --dag-size. Each .dag4 also works on its own slide.
5752  ═══════════════════════════════════════════════════════════════════
5753-->
5754
5755<style>
5756.dag4-row {
5757  display: grid;
5758  grid-template-columns: repeat(3, 1fr);
5759  gap: 1.4em;
5760  max-width: 1020px;
5761  margin: 1.6em auto 0;
5762  font-size: 17px;
5763}
5764.dag4 {
5765  --dag-ink:    #2a2632;
5766  --dag-accent: #856cb0;
5767  --dag-deep:   #6f5a96;
5768  --dag-pale:   #f3effa;
5769  --dag-lilac:  #c9bce2;
5770  --dag-size:   19px;
5771
5772  position: relative;
5773  font-family: "Space Grotesk", -apple-system, sans-serif;
5774  font-size: var(--dag-size);
5775  line-height: 1;
5776  cursor: pointer;
5777  -webkit-tap-highlight-color: transparent;
5778}
5779.dag4 svg { display: block; width: 100%; height: auto; overflow: visible; }
5780
5781/* edges */
5782.dag4 .e { fill: none; stroke: var(--dag-ink); stroke-width: 2; }
5783.dag4 .e-dash { stroke-dasharray: 9 6 2 6; }
5784
5785/* nodes */
5786.dag4 .ring { fill: none; stroke-width: 1.5; }
5787.dag4 .n-x .core { fill: var(--dag-ink); }   .dag4 .n-x .ring { stroke: var(--dag-ink); }
5788.dag4 .n-y .core { fill: var(--dag-lilac); } .dag4 .n-y .ring { stroke: var(--dag-lilac); }
5789
5790.dag4 .n-z .core { fill: var(--dag-pale); transition: fill .5s ease; }
5791.dag4 .n-z .ring {
5792  stroke: var(--dag-accent);
5793  stroke-dasharray: 4 4;
5794  transition: stroke-dasharray .5s ease;
5795}
5796.dag4.is-on .n-z .core { fill: var(--dag-accent); }
5797.dag4.is-on .n-z .ring { stroke-dasharray: 0 0; }
5798
5799/* labels */
5800.dag4 .lab {
5801  position: absolute;
5802  transform: translate(-50%, -50%);
5803  display: grid;
5804  padding: .22em .45em .26em;
5805  border-radius: 5px;
5806  font-weight: 500;
5807  letter-spacing: -.005em;
5808  white-space: nowrap;
5809}
5810.dag4 .lab-x { left: 21.9%; top: 92.7%; background: var(--dag-ink);   color: #fff; }
5811.dag4 .lab-y { left: 78.1%; top: 92.7%; background: var(--dag-lilac); color: var(--dag-ink); }
5812.dag4 .lab-z {
5813  left: 50%; top: 26.7%;
5814  background: var(--dag-accent); color: #fff;
5815  transition: background-color .5s ease;
5816}
5817.dag4.is-on .lab-z { background: var(--dag-deep); }
5818
5819/* crossfade: both states share one grid cell, so the box never jumps */
5820.dag4 .lab-z > span {
5821  grid-area: 1 / 1;
5822  justify-self: center;
5823  transition: opacity .35s ease, transform .35s ease, filter .35s ease;
5824}
5825.dag4 .lab-z .s2 { opacity: 0; transform: translateY(.3em); filter: blur(3px); }
5826.dag4.is-on .lab-z .s1 { opacity: 0; transform: translateY(-.3em); filter: blur(3px); }
5827.dag4.is-on .lab-z .s2 { opacity: 1; transform: none; filter: none; transition-delay: .1s; }
5828
5829.dag4 .dag4-frag { position: absolute; width: 0; height: 0; overflow: hidden; }
5830
5831@media (prefers-reduced-motion: reduce) {
5832  .dag4 *, .dag4 .lab-z > span { transition: none !important; filter: none !important; }
5833}
5834</style>
5835
5836<div class="dag4-row">
5837
5838  <!-- 1 · Ice cream → crime, confounded by season -->
5839  <div class="dag4" role="img" aria-label="DAG: a confounder, later identified as season, causes both ice cream sales and crime.">
5840    <svg viewbox="0 0 320 300" aria-hidden="true">
5841      <defs><marker id="dag4-arr-1" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse">
5841<path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path></marker></defs>
5842      <path class="e" marker-end="url(#dag4-arr-1)" d="M150 100 C128 152 88 168 76 210"></path>
5843      <path class="e" marker-end="url(#dag4-arr-1)" d="M170 100 C192 152 232 168 244 210"></path>
5844      <path class="e e-dash" marker-end="url(#dag4-arr-1)" d="M96 236 H221"></path>
5845      <g class="n-z"><circle class="core" cx="160" cy="34" r="17"></circle><circle class="ring" cx="160" cy="34" r="20"></circle></g>
5846      <g class="n-x"><circle class="core" cx="70" cy="236" r="17"></circle><circle class="ring" cx="70" cy="236" r="20"></circle></g>
5847      <g class="n-y"><circle class="core" cx="250" cy="236" r="17"></circle><circle class="ring" cx="250" cy="236" r="20"></circle></g>
5848    </svg>
5849    <span class="lab lab-z"><span class="s1">Confounder?</span><span class="s2">Season</span></span>
5850    <span class="lab lab-x">Ice Cream</span>
5851    <span class="lab lab-y">Crime</span>
5852    <span class="fragment dag4-frag"></span>
5853  </div>
5854
5855  <!-- 2 · Private school → SAT scores, confounded by parental SES -->
5856  <div class="dag4" role="img" aria-label="DAG: a confounder, later identified as parental SES, causes both private schooling and SAT scores.">
5857    <svg viewbox="0 0 320 300" aria-hidden="true">
5858      <defs><marker id="dag4-arr-2" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path></marker></defs>
5859      <path class="e" marker-end="url(#dag4-arr-2)" d="M150 100 C128 152 88 168 76 210"></path>
5860      <path class="e" marker-end="url(#dag4-arr-2)" d="M170 100 C192 152 232 168 244 210"></path>
5861      <path class="e e-dash" marker-end="url(#dag4-arr-2)" d="M96 236 H221"></path>
5862      <g class="n-z"><circle class="core" cx="160" cy="34" r="17"></circle><circle class="ring" cx="160" cy="34" r="20"></circle></g>
5863      <g class="n-x"><circle class="core" cx="70" cy="236" r="17"></circle><circle class="ring" cx="70" cy="236" r="20"></circle></g>
5864      <g class="n-y"><circle class="core" cx="250" cy="236" r="17"></circle><circle class="ring" cx="250" cy="236" r="20"></circle></g>
5865    </svg>
5866    <span class="lab lab-z"><span class="s1">Confounder?</span><span class="s2">Parental SES</span></span>
5867    <span class="lab lab-x">Private School</span>
5868    <span class="lab lab-y">SAT Scores</span>
5869    <span class="fragment dag4-frag"></span>
5870  </div>
5871
5872  <!-- 3 · Shoe size → reading ability, confounded by age -->
5873  <div class="dag4" role="img" aria-label="DAG: a confounder, later identified as age, causes both shoe size and reading ability.">
5874    <svg viewbox="0 0 320 300" aria-hidden="true">
5875      <defs><marker id="dag4-arr-3" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path></marker></defs>
5876      <path class="e" marker-end="url(#dag4-arr-3)" d="M150 100 C128 152 88 168 76 210"></path>
5877      <path class="e" marker-end="url(#dag4-arr-3)" d="M170 100 C192 152 232 168 244 210"></path>
5878      <path class="e e-dash" marker-end="url(#dag4-arr-3)" d="M96 236 H221"></path>
5879      <g class="n-z"><circle class="core" cx="160" cy="34" r="17"></circle><circle class="ring" cx="160" cy="34" r="20"></circle></g>
5880      <g class="n-x"><circle class="core" cx="70" cy="236" r="17"></circle><circle class="ring" cx="70" cy="236" r="20"></circle></g>
5881      <g class="n-y"><circle class="core" cx="250" cy="236" r="17"></circle><circle class="ring" cx="250" cy="236" r="20"></circle></g>
5882    </svg>
5883    <span class="lab lab-z"><span class="s1">Confounder?</span><span class="s2">Age</span></span>
5884    <span class="lab lab-x">Shoe Size</span>
5885    <span class="lab lab-y">Reading Ability</span>
5886    <span class="fragment dag4-frag"></span>
5887  </div>
5888
5889</div>
5890
5891<script>
5892(function () {
5893  /* bind after load, so Reveal exists whether its script sits before or
5894     after this block */
5895  function bind() {
5896    var R = window.Reveal && Reveal.on ? Reveal : null;
5897    document.querySelectorAll('.dag4').forEach(function (d) {
5898      if (d.dataset.bound) return;
5899      d.dataset.bound = '1';
5900
5901      var f = d.querySelector('.dag4-frag');
5902      function set(on) { d.classList.toggle('is-on', on); }
5903      function sync() { set(f.classList.contains('visible')); }
5904
5905      if (R) {
5906        /* a single advance can show several fragments at once (e.g. a
5907           click on the third triangle), so check every triangle */
5908        R.on('fragmentshown',  sync);
5909        R.on('fragmenthidden', sync);
5910        R.on('slidechanged', sync);
5911        R.on('ready', sync);
5912        sync();
5913      }
5914
5915      /* click toggles by moving the deck's fragment position, so arrow
5916         keys and clicks never disagree */
5917      d.addEventListener('click', function () {
5918        var on = !d.classList.contains('is-on');
5919        var i  = parseInt(f.getAttribute('data-fragment-index'), 10);
5920        if (R && R.navigateFragment && !isNaN(i)) R.navigateFragment(on ? i : i - 1);
5921        else set(on);
5922      });
5923    });
5924  }
5925  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bind);
5926  else bind();
5927})();
5928</script>
5928
5929
5930</section>
5931<section id="dags-4" class="slide level2">
5932<h2>DAGs</h2>
5933<h4 id="do-not-condition-on-a-collider"><span style="color:#a9a9a9;">Do Not Condition on a Collider</span></h4>
5934<!--
5935  ═══════════════════════════════════════════════════════════════════
5936  DAG · conditioning on a collider (two examples) · Quarto RevealJS
5937  Space Grotesk · purple ramp (#856cb0) · same idiom as the income DAG
5938  ───────────────────────────────────────────────────────────────────
5939  Base:     two unconnected causes point into a round collider.
5940  Adjusted: the collider squares off into a box and a "Conditioned"
5941            tag fades in; a dashed purple curve draws in between the
5942            two causes (an association induced by the conditioning,
5943            not a causal arrow).
5944  Each example's Adjusted state is a Reveal fragment, left to right:
5945  advance as usual, or click a DAG. Clicks move the deck's fragment
5946  position, so they follow the same order as the arrow keys.
5947  Size: --dag-size.
5948  ═══════════════════════════════════════════════════════════════════
5949-->
5950
5951<style>
5952.dag5-row {
5953  display: grid;
5954  grid-template-columns: repeat(2, 1fr);
5955  gap: 2.4em;
5956  max-width: 1000px;
5957  margin: 1.4em auto 0;
5958  font-size: 17px;
5959}
5960.dag5 {
5961  --dag-ink:    #2a2632;
5962  --dag-accent: #856cb0;
5963  --dag-deep:   #6f5a96;
5964  --dag-pale:   #f3effa;
5965  --dag-size:   19px;
5966
5967  position: relative;
5968  font-family: "Space Grotesk", -apple-system, sans-serif;
5969  font-size: var(--dag-size);
5970  line-height: 1;
5971  cursor: pointer;
5972  -webkit-tap-highlight-color: transparent;
5973}
5974.dag5 svg { display: block; width: 100%; height: auto; overflow: visible; }
5975
5976/* causal edges */
5977.dag5 .e { fill: none; stroke: var(--dag-ink); stroke-width: 2; }
5978
5979/* induced association: dashed curve, drawn in through a mask */
5980.dag5 .assoc { fill: none; stroke: var(--dag-accent); stroke-width: 2.4; stroke-dasharray: 7 6; stroke-linecap: round; }
5981.dag5 .assoc-mask {
5982  fill: none; stroke: #fff; stroke-width: 12;
5983  stroke-dasharray: 1 1; stroke-dashoffset: 1;
5984  transition: stroke-dashoffset .8s cubic-bezier(.22,.61,.36,1);
5985}
5986.dag5.is-on .assoc-mask { stroke-dashoffset: 0; transition-delay: .2s; }
5987.dag5 .assoc-end { fill: var(--dag-accent); opacity: 0; transition: opacity .3s ease; }
5988.dag5.is-on .assoc-end { opacity: 1; transition-delay: .2s; }
5989
5990/* nodes */
5991.dag5 .core, .dag5 .ring { transition: rx .45s cubic-bezier(.22,.61,.36,1), ry .45s cubic-bezier(.22,.61,.36,1), fill .45s ease, stroke .45s ease; }
5992.dag5 .ring { fill: none; stroke-width: 1.5; }
5993.dag5 .n-a .core { fill: var(--dag-ink); } .dag5 .n-a .ring { stroke: var(--dag-ink); }
5994
5995/* the collider: circle (base) → box (conditioned) */
5996.dag5 .n-c .core { fill: var(--dag-pale); rx: 18px; ry: 18px; }
5997.dag5 .n-c .ring { stroke: var(--dag-accent); rx: 21px; ry: 21px; }
5998.dag5.is-on .n-c .core { fill: var(--dag-accent); rx: 4px; ry: 4px; }
5999.dag5.is-on .n-c .ring { stroke: var(--dag-deep); rx: 6px; ry: 6px; }
6000
6001/* labels */
6002.dag5 .lab {
6003  position: absolute;
6004  transform: translate(-50%, -50%);
6005  padding: .22em .45em .26em;
6006  border-radius: 5px;
6007  font-weight: 500;
6008  letter-spacing: -.005em;
6009  white-space: nowrap;
6010}
6011.dag5 .lab em { font-style: italic; }
6012.dag5 .lab-a1 { left: 21.6%; top: 20.6%; background: var(--dag-ink); color: #fff; }
6013.dag5 .lab-a2 { left: 78.4%; top: 20.6%; background: var(--dag-ink); color: #fff; }
6014.dag5 .lab-c  { left: 50%;   top: 94.2%; background: var(--dag-accent); color: #fff; transition: background-color .45s ease; }
6015.dag5.is-on .lab-c { background: var(--dag-deep); }
6016
6017/* "Conditioned" tag, right of the collider */
6018.dag5 .tag {
6019  position: absolute;
6020  left: 55.9%; top: 79%;
6021  transform: translate(0, -50%) translateX(-.4em);
6022  padding: .28em .55em;
6023  border: 1px solid rgba(133, 108, 176, .45);
6024  border-radius: 999px;
6025  background: #fff;
6026  font-family: "JetBrains Mono", ui-monospace, monospace;
6027  font-size: .58em;
6028  letter-spacing: .1em;
6029  text-transform: uppercase;
6030  color: var(--dag-deep);
6031  white-space: nowrap;
6032  opacity: 0;
6033  transition: opacity .35s ease, transform .35s ease;
6034}
6035.dag5.is-on .tag { opacity: 1; transform: translate(0, -50%); transition-delay: .15s; }
6036
6037.dag5 .dag5-frag { position: absolute; width: 0; height: 0; overflow: hidden; }
6038
6039@media (prefers-reduced-motion: reduce) {
6040  .dag5 *, .dag5 .tag { transition: none !important; }
6041}
6042</style>
6043
6044<div class="dag5-row">
6045
6046  <!-- 1 · Talent → Hollywood Success ← Beauty -->
6047  <div class="dag5" role="img" aria-label="Collider DAG: talent and beauty both cause Hollywood success and are otherwise unrelated. Conditioning on Hollywood success induces an association between talent and beauty.">
6048    <svg viewbox="0 0 440 310" aria-hidden="true">
6049      <defs>
6050        <marker id="dag5-arr-1" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path></marker>
6051        <mask id="dag5-m-1" maskunits="userSpaceOnUse" x="0" y="0" width="440" height="310">
6052          <path class="assoc-mask" pathlength="1" d="M119 124 Q220 196 321 124"></path>
6053        </mask>
6054      </defs>
6055      <path class="e" marker-end="url(#dag5-arr-1)" d="M111.3 127.6 L203 226.7"></path>
6056      <path class="e" marker-end="url(#dag5-arr-1)" d="M328.7 127.6 L237 226.7"></path>
6057      <path class="assoc" mask="url(#dag5-m-1)" d="M119 124 Q220 196 321 124"></path>
6058      <circle class="assoc-end" cx="119" cy="124" r="3"></circle><circle class="assoc-end" cx="321" cy="124" r="3"></circle>
6059      <g class="n-a"><circle class="core" cx="95" cy="110" r="18"></circle><circle class="ring" cx="95" cy="110" r="21"></circle></g>
6060      <g class="n-a"><circle class="core" cx="345" cy="110" r="18"></circle><circle class="ring" cx="345" cy="110" r="21"></circle></g>
6061      <g class="n-c"><rect class="core" x="202" y="227" width="36" height="36" rx="18" ry="18"></rect><rect class="ring" x="199" y="224" width="42" height="42" rx="21" ry="21"></rect></g>
6062    </svg>
6063    <span class="lab lab-a1">Talent</span>
6064    <span class="lab lab-a2">Beauty</span>
6065    <span class="lab lab-c">Hollywood Success</span>
6066    <span class="tag">Conditioned</span>
6067    <span class="fragment dag5-frag"></span>
6068  </div>
6069
6070  <!-- 2 · Rolling Stone 500 → Sample Selection ← Chart Topper -->
6071  <div class="dag5" role="img" aria-label="Collider DAG: Rolling Stone 500 inclusion and chart-topping both cause sample selection and are otherwise unrelated. Conditioning on sample selection induces an association between them.">
6072    <svg viewbox="0 0 440 310" aria-hidden="true">
6073      <defs>
6074        <marker id="dag5-arr-2" viewbox="0 0 10 10" refx="8.5" refy="5" markerwidth="7" markerheight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#2a2632"></path></marker>
6075        <mask id="dag5-m-2" maskunits="userSpaceOnUse" x="0" y="0" width="440" height="310">
6076          <path class="assoc-mask" pathlength="1" d="M119 124 Q220 196 321 124"></path>
6077        </mask>
6078      </defs>
6079      <path class="e" marker-end="url(#dag5-arr-2)" d="M111.3 127.6 L203 226.7"></path>
6080      <path class="e" marker-end="url(#dag5-arr-2)" d="M328.7 127.6 L237 226.7"></path>
6081      <path class="assoc" mask="url(#dag5-m-2)" d="M119 124 Q220 196 321 124"></path>
6082      <circle class="assoc-end" cx="119" cy="124" r="3"></circle><circle class="assoc-end" cx="321" cy="124" r="3"></circle>
6083      <g class="n-a"><circle class="core" cx="95" cy="110" r="18"></circle><circle class="ring" cx="95" cy="110" r="21"></circle></g>
6084      <g class="n-a"><circle class="core" cx="345" cy="110" r="18"></circle><circle class="ring" cx="345" cy="110" r="21"></circle></g>
6085      <g class="n-c"><rect class="core" x="202" y="227" width="36" height="36" rx="18" ry="18"></rect><rect class="ring" x="199" y="224" width="42" height="42" rx="21" ry="21"></rect></g>
6086    </svg>
6087    <span class="lab lab-a1"><em>Rolling Stone</em> 500</span>
6088    <span class="lab lab-a2">Chart Topper</span>
6089    <span class="lab lab-c">Sample Selection</span>
6090    <span class="tag">Conditioned</span>
6091    <span class="fragment dag5-frag"></span>
6092  </div>
6093
6094</div>
6095
6096<script>
6097(function () {
6098  /* bind after load, so Reveal exists whether its script sits before or
6099     after this block */
6100  function bind() {
6101    var R = window.Reveal && Reveal.on ? Reveal : null;
6102    document.querySelectorAll('.dag5').forEach(function (d) {
6103      if (d.dataset.bound) return;
6104      d.dataset.bound = '1';
6105
6106      var f = d.querySelector('.dag5-frag');
6107      function set(on) { d.classList.toggle('is-on', on); }
6108      function sync() { set(f.classList.contains('visible')); }
6109
6110      if (R) {
6111        R.on('fragmentshown',  sync);
6112        R.on('fragmenthidden', sync);
6113        R.on('slidechanged', sync);
6114        R.on('ready', sync);
6115        sync();
6116      }
6117
6118      /* click toggles by moving the deck's fragment position, so arrow
6119         keys and clicks never disagree */
6120      d.addEventListener('click', function () {
6121        var on = !d.classList.contains('is-on');
6122        var i  = parseInt(f.getAttribute('data-fragment-index'), 10);
6123        if (R && R.navigateFragment && !isNaN(i)) R.navigateFragment(on ? i : i - 1);
6124        else set(on);
6125      });
6126    });
6127  }
6128  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bind);
6129  else bind();
6130})();
6131</script>
6131
6132
6133</section>
6134<section id="dags-5" class="slide level2">
6135<h2>DAGs</h2>
6136<div class="rq-block" style="margin-top: 100px; text-align: center;">
6137<p>In the next <span style="color: white; background-color: #856cb0; padding: 1px 5px; border: 0.001px solid white; border-radius: 5px;">5-10 minutes</span>, come up with a DAG related to your research project!</p>
6138</div>
6139</section></section>
6140<section>
6141<section id="enjoy-the-weekend" class="title-slide slide level1 center" data-background-color="#43464B">
6142<h1>Enjoy the Weekend</h1>
6143
6144</section>
6145<section id="references" class="slide level2 smaller scrollable">
6146<h2>References</h2>
6147<!-- <div style="text-align: center; margin-bottom: 15px"> -->
6148<!-- [**Note**: Scroll []{style="color: #856cb0;"} to access the entire bibliography]{style="color: #9c9c9c; font-size:95%;"} -->
6149<!-- </div> -->
6150
6151<div id="refs" class="references csl-bib-body hanging-indent" role="list">
6152<div id="ref-carr_art_2020" class="csl-entry" role="listitem">
6153Carr, Deborah S., Elizabeth Heger Boyle, Benjamin Cornwell, Shelley J. Correll, Robert Crosnoe, et al. 2020. <em>The Art and Science of Social Research</em>. Second Edition. New York: W. W. Norton &amp; Company, Inc.
6154</div>
6155<div id="ref-cunningham2026remix" class="csl-entry" role="listitem">
6156Cunningham, Scott. 2026. <em><a href="https://mixtape.scunning.com/">Causal Inference: The Remix</a></em>. 2nd ed. New Haven, CT: Yale University Press.
6157</div>
6158<div id="ref-llaudet_data_2023" class="csl-entry" role="listitem">
6159Llaudet, Elena, and Kōsuke Imai. 2023. <em>Data Analysis for Social Science: A Friendly and Practical Introduction</em>. Princeton (N.J.) Oxford: Princeton University press.
6160</div>
6161<div id="ref-small_causal_2013" class="csl-entry" role="listitem">
6162Small, Mario Luis. 2013. <span>“Causal <span>Thinking</span> and <span>Ethnographic</span> <span>Research</span>.”</span> <em>American Journal of Sociology</em> 119(3):597–601. doi: <a href="https://doi.org/10.1086/675893">10.1086/675893</a>.
6163</div>
6164</div>
6165</section></section>
6166    </div>
6167  <div class="quarto-auto-generated-content" style="display: none;">
6168<p><img src="media/amherst_seal_light.png" class="slide-logo"></p>
6169<div class="footer footer-default">
6170
6171</div>
6172</div></div>
6173
6174  
6174<script>window.backupDefine = window.define; window.define = undefined;</script>
6174
6175  
6175<script src="index_files/libs/revealjs/dist/reveal.js"></script>
6175
6176  <!-- reveal.js plugins -->
6177  
6177<script src="index_files/libs/revealjs/plugin/quarto-line-highlight/line-highlight.js"></script>
6177
6178  
6178<script src="index_files/libs/revealjs/plugin/pdf-export/pdfexport.js"></script>
6178
6179  
6179<script src="index_files/libs/revealjs/plugin/reveal-menu/menu.js"></script>
6179
6180  
6180<script src="index_files/libs/revealjs/plugin/reveal-menu/quarto-menu.js"></script>
6180
6181  
6181<script src="index_files/libs/revealjs/plugin/reveal-chalkboard/plugin.js"></script>
6181
6182  
6182<script src="index_files/libs/revealjs/plugin/quarto-support/support.js"></script>
6182
6183  
6184
6185  
6185<script src="index_files/libs/revealjs/plugin/notes/notes.js"></script>
6185
6186  
6186<script src="index_files/libs/revealjs/plugin/search/search.js"></script>
6186
6187  
6187<script src="index_files/libs/revealjs/plugin/zoom/zoom.js"></script>
6187
6188  
6188<script>window.define = window.backupDefine; window.backupDefine = undefined;</script>
6188
6189
6190  
6190<script>
6191
6192      // Full list of configuration options available at:
6193      // https://revealjs.com/config/
6194      Reveal.initialize({
6195'controlsAuto': true,
6196'previewLinksAuto': true,
6197'pdfSeparateFragments': false,
6198'autoAnimateEasing': "ease",
6199'autoAnimateDuration': 1,
6200'autoAnimateUnmatched': true,
6201'jumpToSlide': true,
6202'menu': {"side":"left","useTextContentForMissingTitles":true,"markers":false,"loadIcons":false,"custom":[{"title":"Tools","icon":"<i class=\"fas fa-gear\"></i>","content":"<ul class=\"slide-menu-items\">\n<li class=\"slide-tool-item active\" data-item=\"0\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.fullscreen(event)\"><kbd>f</kbd> Fullscreen</a></li>\n<li class=\"slide-tool-item\" data-item=\"1\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.speakerMode(event)\"><kbd>s</kbd> Speaker View</a></li>\n<li class=\"slide-tool-item\" data-item=\"2\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.overview(event)\"><kbd>o</kbd> Slide Overview</a></li>\n<li class=\"slide-tool-item\" data-item=\"3\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.togglePdfExport(event)\"><kbd>e</kbd> PDF Export Mode</a></li>\n<li class=\"slide-tool-item\" data-item=\"4\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.toggleScrollView(event)\"><kbd>r</kbd> Scroll View Mode</a></li>\n<li class=\"slide-tool-item\" data-item=\"5\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.toggleChalkboard(event)\"><kbd>b</kbd> Toggle Chalkboard</a></li>\n<li class=\"slide-tool-item\" data-item=\"6\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.toggleNotesCanvas(event)\"><kbd>c</kbd> Toggle Notes Canvas</a></li>\n<li class=\"slide-tool-item\" data-item=\"7\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.downloadDrawings(event)\"><kbd>d</kbd> Download Drawings</a></li>\n<li class=\"slide-tool-item\" data-item=\"8\"><a href=\"#\" onclick=\"RevealMenuToolHandlers.keyboardHelp(event)\"><kbd>?</kbd> Keyboard Help</a></li>\n</ul>"}],"openButton":true,"numbers":true,"width":"wide"},
6203'chalkboard': {"buttons":true},
6204'smaller': false,
6205 
6206        // Display controls in the bottom right corner
6207        controls: false,
6208
6209        // Help the user learn the controls by providing hints, for example by
6210        // bouncing the down arrow when they first encounter a vertical slide
6211        controlsTutorial: false,
6212
6213        // Determines where controls appear, "edges" or "bottom-right"
6214        controlsLayout: 'edges',
6215
6216        // Visibility rule for backwards navigation arrows; "faded", "hidden"
6217        // or "visible"
6218        controlsBackArrows: 'faded',
6219
6220        // Display a presentation progress bar
6221        progress: true,
6222
6223        // Display the page number of the current slide
6224        slideNumber: false,
6225
6226        // 'all', 'print', or 'speaker'
6227        showSlideNumber: 'all',
6228
6229        // Add the current slide number to the URL hash so that reloading the
6230        // page/copying the URL will return you to the same slide
6231        hash: true,
6232
6233        // Start with 1 for the hash rather than 0
6234        hashOneBasedIndex: false,
6235
6236        // Flags if we should monitor the hash and change slides accordingly
6237        respondToHashChanges: true,
6238
6239        // Push each slide change to the browser history
6240        history: true,
6241
6242        // Enable keyboard shortcuts for navigation
6243        keyboard: true,
6244
6245        // Enable the slide overview mode
6246        overview: true,
6247
6248        // Disables the default reveal.js slide layout (scaling and centering)
6249        // so that you can use custom CSS layout
6250        disableLayout: false,
6251
6252        // Vertical centering of slides
6253        center: false,
6254
6255        // Enables touch navigation on devices with touch input
6256        touch: true,
6257
6258        // Loop the presentation
6259        loop: false,
6260
6261        // Change the presentation direction to be RTL
6262        rtl: false,
6263
6264        // see https://revealjs.com/vertical-slides/#navigation-mode
6265        navigationMode: 'linear',
6266
6267        // Randomizes the order of slides each time the presentation loads
6268        shuffle: false,
6269
6270        // Turns fragments on and off globally
6271        fragments: true,
6272
6273        // Flags whether to include the current fragment in the URL,
6274        // so that reloading brings you to the same fragment position
6275        fragmentInURL: false,
6276
6277        // Flags if the presentation is running in an embedded mode,
6278        // i.e. contained within a limited portion of the screen
6279        embedded: false,
6280
6281        // Flags if we should show a help overlay when the questionmark
6282        // key is pressed
6283        help: true,
6284
6285        // Flags if it should be possible to pause the presentation (blackout)
6286        pause: true,
6287
6288        // Flags if speaker notes should be visible to all viewers
6289        showNotes: false,
6290
6291        // Global override for autoplaying embedded media (null/true/false)
6292        autoPlayMedia: null,
6293
6294        // Global override for preloading lazy-loaded iframes (null/true/false)
6295        preloadIframes: null,
6296
6297        // Number of milliseconds between automatically proceeding to the
6298        // next slide, disabled when set to 0, this value can be overwritten
6299        // by using a data-autoslide attribute on your slides
6300        autoSlide: 0,
6301
6302        // Stop auto-sliding after user input
6303        autoSlideStoppable: true,
6304
6305        // Use this method for navigation when auto-sliding
6306        autoSlideMethod: null,
6307
6308        // Specify the average time in seconds that you think you will spend
6309        // presenting each slide. This is used to show a pacing timer in the
6310        // speaker view
6311        defaultTiming: null,
6312
6313        // Enable slide navigation via mouse wheel
6314        mouseWheel: false,
6315
6316        // The display mode that will be used to show slides
6317        display: 'block',
6318
6319        // Hide cursor if inactive
6320        hideInactiveCursor: true,
6321
6322        // Time before the cursor is hidden (in ms)
6323        hideCursorTime: 5000,
6324
6325        // Opens links in an iframe preview overlay
6326        previewLinks: false,
6327
6328        // Transition style (none/fade/slide/convex/concave/zoom)
6329        transition: 'none',
6330
6331        // Transition speed (default/fast/slow)
6332        transitionSpeed: 'default',
6333
6334        // Transition style for full page slide backgrounds
6335        // (none/fade/slide/convex/concave/zoom)
6336        backgroundTransition: 'none',
6337
6338        // Number of slides away from the current that are visible
6339        viewDistance: 3,
6340
6341        // Number of slides away from the current that are visible on mobile
6342        // devices. It is advisable to set this to a lower number than
6343        // viewDistance in order to save resources.
6344        mobileViewDistance: 2,
6345
6346        // The "normal" size of the presentation, aspect ratio will be preserved
6347        // when the presentation is scaled to fit different resolutions. Can be
6348        // specified using percentage units.
6349        width: 1050,
6350
6351        height: 700,
6352
6353        // Factor of the display size that should remain empty around the content
6354        margin: 0.1,
6355
6356        // reveal.js plugins
6357        plugins: [QuartoLineHighlight, PdfExport, RevealMenu, RevealChalkboard, QuartoSupport,
6358
6359          RevealNotes,
6360          RevealSearch,
6361          RevealZoom
6362        ]
6363      });
6364    </script>
6364
6365    
6365<script>
6366    (function () {
6367      function isFootnotesSlide(section) {
6368        if (!section) return false;
6369
6370        // Common pandoc/quarto patterns:
6371        if (section.id === "footnotes") return true;
6372        if (section.classList && section.classList.contains("footnotes")) return true;
6373
6374        // Sometimes the slide *contains* a footnotes container:
6375        if (section.querySelector && section.querySelector("div.footnotes, section.footnotes")) return true;
6376
6377        // Fallback: slide heading literally “Footnotes” (case-insensitive)
6378        const h = section.querySelector && section.querySelector("h1,h2,h3");
6379        if (h && h.textContent && h.textContent.trim().toLowerCase() === "footnotes") return true;
6380
6381        return false;
6382      }
6383
6384      function removeFootnotesSlides() {
6385        // Remove both horizontal slides and vertical stacks if needed
6386        const sections = Array.from(document.querySelectorAll(".reveal .slides section"));
6387        sections.forEach(sec => {
6388          if (isFootnotesSlide(sec)) sec.remove();
6389        });
6390
6391        // If we removed something after Reveal init, resync
6392        if (window.Reveal && typeof window.Reveal.sync === "function") {
6393          window.Reveal.sync();
6394        }
6395      }
6396
6397      // Run after load (works whether Reveal has initialized yet or not)
6398      window.addEventListener("load", removeFootnotesSlides);
6399    })();
6400    </script>
6400
6401    <style>
6402      /* Detail-panel list items: hidden until stepped in by the script */
6403      details .dx-step {
6404        opacity: 0;
6405        visibility: hidden;
6406        transition: opacity .3s ease, visibility 0s linear .3s;
6407      }
6408      details .dx-step.dx-on {
6409        opacity: 1;
6410        visibility: visible;
6411        transition: opacity .3s ease;
6412      }
6413    </style>
6414
6415    
6415<script>
6416    (() => {
6417      const SEL  = 'details.reveal-detail, details.reveal-detail-above, details.reveal-detail-push';
6418      const NEXT = new Set(['ArrowRight', 'ArrowDown', 'PageDown', 'n', 'N', 'l', 'j']);
6419      const PREV = new Set(['ArrowLeft', 'ArrowUp', 'PageUp', 'p', 'P', 'h', 'k']);
6420
6421      /* Take .incremental items inside detail panels out of Reveal's
6422         fragment system permanently; the script steps them instead. */
6423      const convert = () => document.querySelectorAll(SEL).forEach(d =>
6424        d.querySelectorAll('.fragment').forEach(el => {
6425          el.classList.remove('fragment', 'visible', 'current-fragment');
6426          el.removeAttribute('data-fragment-index');
6427          el.classList.add('dx-step');
6428        })
6429      );
6430
6431      const steps = d => Array.from(d.querySelectorAll('.dx-step'));
6432      const reset = d => steps(d).forEach(s => s.classList.remove('dx-on'));
6433
6434      /* Closed panels, empty list: the state every visit starts from */
6435      const fresh = slide => {
6436        if (!slide) return;
6437        slide.querySelectorAll(SEL).forEach(d => { d.open = false; reset(d); });
6438      };
6439
6440      /* Returns an action if an open panel on the current slide should
6441         consume this step; null means Reveal navigates as normal. */
6442      const claim = dir => {
6443        if (!window.Reveal) return null;
6444        if ((Reveal.isOverview && Reveal.isOverview()) ||
6445            (Reveal.isPaused && Reveal.isPaused())) return null;
6446        const slide = Reveal.getCurrentSlide();
6447        if (!slide) return null;
6448        const open = Array.from(slide.querySelectorAll(SEL)).filter(d => d.open);
6449        if (dir > 0) {
6450          for (const d of open) {
6451            const s = steps(d).find(x => !x.classList.contains('dx-on'));
6452            if (s) return () => s.classList.add('dx-on');
6453          }
6454        } else {
6455          for (const d of open.reverse()) {
6456            const on = steps(d).filter(x => x.classList.contains('dx-on'));
6457            if (on.length) return () => on[on.length - 1].classList.remove('dx-on');
6458          }
6459        }
6460        return null;
6461      };
6462
6463      const editable = t =>
6464        t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName));
6465
6466      /* Keyboard and presentation clickers (PageDown/PageUp/arrows) */
6467      window.addEventListener('keydown', e => {
6468        if (e.altKey || e.ctrlKey || e.metaKey || editable(e.target)) return;
6469        const onSummary = e.target.closest && e.target.closest('summary');
6470        if (onSummary && (e.key === ' ' || e.key === 'Enter')) return;  // let these toggle the panel
6471        let dir = 0;
6472        if (e.key === ' ') dir = e.shiftKey ? -1 : 1;
6473        else if (NEXT.has(e.key)) dir = 1;
6474        else if (PREV.has(e.key)) dir = -1;
6475        if (!dir) return;
6476        const act = claim(dir);
6477        if (!act) return;
6478        e.preventDefault();
6479        e.stopImmediatePropagation();
6480        act();
6481      }, true);
6482
6483      /* Reveal's on-screen navigation arrows */
6484      window.addEventListener('click', e => {
6485        const b = e.target.closest && e.target.closest(
6486          '.navigate-right, .navigate-down, .navigate-next, ' +
6487          '.navigate-left, .navigate-up, .navigate-prev');
6488        if (!b) return;
6489        const dir = b.matches('.navigate-right, .navigate-down, .navigate-next') ? 1 : -1;
6490        const act = claim(dir);
6491        if (!act) return;
6492        e.preventDefault();
6493        e.stopImmediatePropagation();
6494        act();
6495      }, true);
6496
6497      /* Opening or closing a panel restarts its list; drop focus from the
6498         summary so the next Space advances instead of re-toggling. */
6499      document.addEventListener('toggle', e => {
6500        const d = e.target;
6501        if (!(d instanceof HTMLDetailsElement) || !d.matches(SEL)) return;
6502        reset(d);
6503        const s = d.querySelector('summary');
6504        if (s && document.activeElement === s) s.blur();
6505      }, true);
6506
6507      const init = () => {
6508        convert();
6509        Reveal.sync();
6510
6511        /* Reset on the way out and on the way in, so every visit —
6512           forward, backward, or by jump link — starts fresh. */
6513        Reveal.on('slidechanged', ev => {
6514          fresh(ev.previousSlide);
6515          fresh(ev.currentSlide);
6516        });
6517
6518        /* Covers loading the deck directly onto a slide with a panel */
6519        fresh(Reveal.getCurrentSlide());
6520      };
6521
6522      convert();
6523      if (window.Reveal && Reveal.isReady && Reveal.isReady()) init();
6524      else if (window.Reveal) Reveal.on('ready', init);
6525    })();
6526    </script>
6526
6527    <!--
6528      ═══════════════════════════════════════════════════════════════════
6529      FRAME-EXPAND · trigger → near-full-page embedded site
6530      Space Grotesk / JetBrains Mono · purple ramp (#856cb0)
6531      Works unchanged in a standalone Quarto HTML page or a RevealJS deck.
6532      ───────────────────────────────────────────────────────────────────
6533      Load ONCE per document, either:
6534        • YAML:  include-after-body: frame-expand-purple.html   (preferred), or
6535        • paste inside a ```{=html} fence (one slide / anywhere in the .qmd)
6536
6537      Inline, in prose:
6538
6539          [Instructions]{.frame-expand data-src="https://soci-316-qualtrics.netlify.app/"}
6540
6541      As a centred button, add .fx-pill:
6542
6543          ::: {style="text-align: center;"}
6544          [Instructions]{.frame-expand .fx-pill data-src="https://soci-316-qualtrics.netlify.app/"}
6545          :::
6546
6547      Optional:
6548          data-caption="Survey instructions"   bar title (defaults to trigger text)
6549
6550      Closes on ×, Esc, or a click on the backdrop. Each embedded page loads
6551      on first open and stays alive afterwards (across slide changes too),
6552      so progress inside it survives close/reopen until the document reloads.
6553
6554      RevealJS specifics:
6555        • overlay lives on <body>, outside Reveal's transformed .slides
6556        • Esc is caught before Reveal (no overview); Enter/Space on a trigger
6557          opens it without advancing the slide
6558        • Reveal's keyboard is suspended while open, restored on close
6559        • after a mouse-opened panel closes, focus is released so Space /
6560          arrows drive the deck again rather than reopening the panel
6561
6562      Same class names as the Spectral/red file: load one or the other per
6563      document, never both.
6564      ═══════════════════════════════════════════════════════════════════
6565    -->
6566
6567    <style>
6568    /* tokens scoped to the component, not :root, so the deck theme is untouched */
6569    .frame-expand, .fx-overlay {
6570      --fx-accent: #856cb0;
6571      --fx-deep:   #6f5a96;
6572      --fx-soft:   #b7a5d3;
6573      --fx-tint:   #f7f4fc;
6574      --fx-line:   #c8bfff;
6575      --fx-ink:    #2a2632;
6576      --fx-scrim:  rgba(28, 22, 38, .55);
6577      --fx-sans:   "Space Grotesk", system-ui, sans-serif;
6578      --fx-mono:   "JetBrains Mono", ui-monospace, monospace;
6579    }
6580
6581    /* ── inline trigger ───────────────────────────────────────────────── */
6582    .frame-expand {
6583      color: var(--fx-accent);
6584      border-bottom: 1.5px dotted var(--fx-accent);
6585      padding-bottom: 1px;
6586      cursor: pointer;
6587      white-space: nowrap;
6588      transition: color .15s ease, border-color .15s ease;
6589    }
6590    .frame-expand:hover { color: var(--fx-deep); border-bottom-style: solid; }
6591    .frame-expand:focus-visible { outline: 2px solid var(--fx-soft); outline-offset: 2px; border-radius: 2px; }
6592
6593    .frame-expand .fx-ico {
6594      display: inline-block;
6595      width: .72em; height: .72em;
6596      margin-left: .28em;
6597      vertical-align: .02em;
6598      opacity: .75;
6599    }
6600    .frame-expand .fx-ico svg { display: block; width: 100%; height: 100%; }
6601
6602    /* ── pill trigger ─────────────────────────────────────────────────── */
6603    .frame-expand.fx-pill {
6604      display: inline-flex;
6605      align-items: center;
6606      gap: .45em;
6607      padding: .45em 1.05em;
6608      border: 1px solid rgba(133, 108, 176, .4);
6609      border-radius: 7px;
6610      background: rgba(133, 108, 176, .1);
6611      color: var(--fx-deep);
6612      font-weight: 500;
6613      line-height: 1.3;
6614      transition: background-color .18s ease, color .18s ease, border-color .18s ease,
6615                  transform .18s ease, box-shadow .18s ease;
6616    }
6617    .frame-expand.fx-pill .fx-ico { margin-left: 0; }
6618    .frame-expand.fx-pill:hover,
6619    .frame-expand.fx-pill:focus-visible {
6620      background: var(--fx-accent);
6621      border-color: var(--fx-accent);
6622      color: #fff;
6623      transform: translateY(-1px);
6624      box-shadow: 0 4px 12px rgba(133, 108, 176, .35);
6625    }
6626    .frame-expand.fx-pill:focus-visible { outline: none; box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--fx-soft); }
6627
6628    /* ── overlay (px throughout: it sits outside Reveal's scaled slides) ─ */
6629    .fx-overlay {
6630      position: fixed;
6631      inset: 0;
6632      z-index: 10000;                 /* above Quarto navbar, Reveal controls/menu */
6633      display: flex;
6634      align-items: center;
6635      justify-content: center;
6636      padding: clamp(8px, 2.5vh, 28px) clamp(8px, 2.5vw, 36px);
6637      background: var(--fx-scrim);
6638      -webkit-backdrop-filter: blur(6px);
6639      backdrop-filter: blur(6px);
6640      font-family: var(--fx-sans);
6641      opacity: 0;
6642      visibility: hidden;
6643      transition: opacity .22s ease, visibility .22s ease;
6644    }
6645    .fx-overlay.is-open { opacity: 1; visibility: visible; }
6646
6647    .fx-panel {
6648      display: flex;
6649      flex-direction: column;
6650      width: 100%;
6651      height: 100%;
6652      max-width: 1400px;
6653      background: #fff;
6654      border-radius: 12px;
6655      overflow: hidden;
6656      box-shadow: 0 24px 70px rgba(28, 22, 38, .35);
6657      transform: translateY(10px) scale(.99);
6658      transition: transform .22s ease;
6659    }
6660    .fx-overlay.is-open .fx-panel { transform: none; }
6661
6662    .fx-bar {
6663      display: flex;
6664      align-items: center;
6665      gap: 10px;
6666      padding: 9px 10px 9px 18px;
6667      background: var(--fx-tint);
6668      border-bottom: 1px solid var(--fx-line);
6669    }
6670    .fx-heading { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 12px; }
6671    .fx-title {
6672      font-family: var(--fx-sans);
6673      font-size: 16px;
6674      font-weight: 500;
6675      line-height: 1.3;
6676      color: var(--fx-ink);
6677      white-space: nowrap;
6678      overflow: hidden;
6679      text-overflow: ellipsis;
6680    }
6681    .fx-host {
6682      font-family: var(--fx-mono);
6683      font-size: 11.5px;
6684      letter-spacing: .03em;
6685      color: rgba(133, 108, 176, .7);
6686      white-space: nowrap;
6687    }
6688
6689    .fx-overlay .fx-btn {
6690      flex: none;
6691      display: inline-grid;
6692      place-items: center;
6693      width: 34px; height: 34px;
6694      margin: 0;
6695      padding: 0;
6696      font: inherit;
6697      line-height: 1;
6698      border: 1px solid rgba(133, 108, 176, .35);
6699      border-radius: 7px;
6700      background: #fff;
6701      color: var(--fx-deep);
6702      text-decoration: none;
6703      cursor: pointer;
6704      transition: background-color .15s ease, color .15s ease, border-color .15s ease;
6705    }
6706    .fx-overlay .fx-btn svg { width: 15px; height: 15px; }
6707    .fx-overlay .fx-btn:hover,
6708    .fx-overlay .fx-btn:focus-visible {
6709      background: var(--fx-accent);
6710      border-color: var(--fx-accent);
6711      color: #fff;
6712    }
6713    .fx-overlay .fx-btn:focus-visible { outline: 2px solid var(--fx-soft); outline-offset: 2px; }
6714    .fx-overlay a.fx-btn::after { display: none !important; }   /* Quarto external-link icon */
6715
6716    .fx-body { position: relative; flex: 1; background: #fff; }
6717    .fx-body iframe {
6718      position: absolute;
6719      inset: 0;
6720      width: 100%;
6721      height: 100%;
6722      max-width: none;
6723      max-height: none;
6724      margin: 0;
6725      border: 0;
6726      display: none;
6727    }
6728    .fx-body iframe.is-active { display: block; }
6729
6730    .fx-loading {
6731      position: absolute;
6732      inset: 0;
6733      display: none;
6734      place-items: center;
6735      font-family: var(--fx-mono);
6736      font-size: 12.5px;
6737      letter-spacing: .04em;
6738      color: rgba(133, 108, 176, .75);
6739    }
6740    .fx-loading.is-on { display: grid; }
6741
6742    @media (max-width: 640px) {
6743      .fx-overlay { padding: 0; }
6744      .fx-panel { border-radius: 0; }
6745      .fx-host { display: none; }
6746    }
6747    @media (prefers-reduced-motion: reduce) {
6748      .frame-expand, .frame-expand.fx-pill, .fx-overlay, .fx-panel, .fx-overlay .fx-btn { transition: none !important; }
6749      .fx-panel { transform: none; }
6750    }
6751    @media print {
6752      .fx-overlay, .frame-expand .fx-ico { display: none !important; }
6753    }
6754    </style>
6755
6756    
6756<script>
6757    (function () {
6758      if (window.__fxReady) { if (window.__fxScan) window.__fxScan(); return; }
6759      window.__fxReady = true;
6760
6761      var GLYPH = '<span class="fx-ico"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9.5 2.5h4v4M13.5 2.5 9 7M6.5 13.5h-4v-4M2.5 13.5 7 9"/></svg></span>';
6762      var ICON_TAB = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9.5 2.5h4v4M13.5 2.5 7.5 8.5M12 9.5v3a1 1 0 0 1-1 1H3.5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h3"/></svg>';
6763      var ICON_CLOSE = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"/></svg>';
6764
6765      var overlay, titleEl, hostEl, tabBtn, closeBtn, body, loading;
6766      var frames = {};                  /* src → iframe, kept alive */
6767      var opener = null, openedByKey = false;
6768      var prevOverflow = '', prevKeyboard = null;
6769
6770      function deck() {
6771        var R = window.Reveal;
6772        return (R && typeof R.configure === 'function' && typeof R.getConfig === 'function' &&
6773                (!R.isReady || R.isReady())) ? R : null;
6774      }
6775      function isOpen() { return !!overlay && overlay.classList.contains('is-open'); }
6776
6777      function build() {
6778        if (overlay) return;
6779        overlay = document.createElement('div');
6780        overlay.className = 'fx-overlay';
6781        overlay.setAttribute('role', 'dialog');
6782        overlay.setAttribute('aria-modal', 'true');
6783        overlay.setAttribute('aria-labelledby', 'fx-title');
6784        overlay.setAttribute('aria-hidden', 'true');
6785        overlay.innerHTML =
6786          '<div class="fx-panel">' +
6787            '<div class="fx-bar">' +
6788              '<div class="fx-heading"><span class="fx-title" id="fx-title"></span><span class="fx-host"></span></div>' +
6789              '<a class="fx-btn fx-tab" target="_blank" rel="noopener" aria-label="Open in new tab" title="Open in new tab">' + ICON_TAB + '</a>' +
6790              '<button type="button" class="fx-btn fx-close" aria-label="Close (Esc)" title="Close (Esc)">' + ICON_CLOSE + '</button>' +
6791            '</div>' +
6792            '<div class="fx-body"><div class="fx-loading">Loading…</div></div>' +
6793          '</div>';
6794        /* on <body>, never inside .reveal .slides (transformed ancestor
6795           would break position: fixed) */
6796        document.body.appendChild(overlay);
6797
6798        titleEl  = overlay.querySelector('.fx-title');
6799        hostEl   = overlay.querySelector('.fx-host');
6800        tabBtn   = overlay.querySelector('.fx-tab');
6801        closeBtn = overlay.querySelector('.fx-close');
6802        body     = overlay.querySelector('.fx-body');
6803        loading  = overlay.querySelector('.fx-loading');
6804
6805        closeBtn.addEventListener('click', close);
6806        overlay.addEventListener('click', function (e) { if (e.target === overlay) close(); });
6807      }
6808
6809      function open(trigger, byKey) {
6810        var src = trigger.getAttribute('data-src');
6811        if (!src) return;
6812        build();
6813        var label = trigger.getAttribute('data-caption') || trigger.textContent.trim();
6814
6815        titleEl.textContent = label;
6816        try { hostEl.textContent = new URL(src, location.href).hostname; } catch (e) { hostEl.textContent = ''; }
6817        tabBtn.href = src;
6818
6819        Object.keys(frames).forEach(function (k) { frames[k].classList.remove('is-active'); });
6820        var f = frames[src];
6821        if (!f) {
6822          f = document.createElement('iframe');
6823          f.title = label;
6824          f.setAttribute('allow', 'fullscreen; clipboard-write');
6825          loading.classList.add('is-on');
6826          f.addEventListener('load', function () { loading.classList.remove('is-on'); }, { once: true });
6827          f.src = src;
6828          body.appendChild(f);
6829          frames[src] = f;
6830        } else {
6831          loading.classList.remove('is-on');
6832        }
6833        f.classList.add('is-active');
6834
6835        opener = trigger;
6836        openedByKey = !!byKey;
6837        prevOverflow = document.documentElement.style.overflow;
6838        document.documentElement.style.overflow = 'hidden';
6839
6840        var R = deck();
6841        if (R) { prevKeyboard = R.getConfig().keyboard; R.configure({ keyboard: false }); }
6842
6843        overlay.setAttribute('aria-hidden', 'false');
6844        overlay.classList.add('is-open');
6845        closeBtn.focus({ preventScroll: true });
6846      }
6847
6848      function close() {
6849        if (!isOpen()) return;
6850        overlay.classList.remove('is-open');
6851        overlay.setAttribute('aria-hidden', 'true');
6852        document.documentElement.style.overflow = prevOverflow;
6853
6854        var R = deck();
6855        if (R && prevKeyboard !== null) { R.configure({ keyboard: prevKeyboard }); prevKeyboard = null; }
6856
6857        if (opener) {
6858          if (R && !openedByKey) {
6859            /* mouse-opened in a deck: leave the trigger unfocused so the
6860               next Space/arrow advances the deck instead of reopening */
6861            if (document.activeElement && document.activeElement.blur) document.activeElement.blur();
6862          } else {
6863            opener.focus({ preventScroll: true });
6864          }
6865        }
6866        opener = null;
6867      }
6868
6869      function scan() {
6870        document.querySelectorAll('.frame-expand[data-src]').forEach(function (t) {
6871          if (t.dataset.fxBound) return;
6872          t.dataset.fxBound = '1';
6873          t.setAttribute('role', 'button');
6874          t.setAttribute('tabindex', '0');
6875          t.setAttribute('aria-haspopup', 'dialog');
6876          t.insertAdjacentHTML('beforeend', GLYPH);
6877        });
6878      }
6879      window.__fxScan = scan;
6880
6881      document.addEventListener('click', function (e) {
6882        var t = e.target.closest && e.target.closest('.frame-expand[data-src]');
6883        if (!t) return;
6884        e.preventDefault();
6885        open(t, e.detail === 0);        /* detail 0 = synthetic/keyboard click */
6886      });
6887
6888      /* capture phase: runs before Reveal's document-level handler */
6889      document.addEventListener('keydown', function (e) {
6890        if (isOpen()) {
6891          if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); close(); }
6892          return;
6893        }
6894        var t = e.target.closest && e.target.closest('.frame-expand[data-src]');
6895        if (t && (e.key === 'Enter' || e.key === ' ')) {
6896          e.preventDefault(); e.stopPropagation(); open(t, true);
6897        }
6898      }, true);
6899
6900      /* keep focus inside the dialog while open */
6901      document.addEventListener('focusin', function (e) {
6902        if (isOpen() && !overlay.contains(e.target)) closeBtn.focus({ preventScroll: true });
6903      });
6904
6905      function init() { build(); scan(); }
6906      if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
6907      else init();
6908      if (window.Reveal && typeof Reveal.on === 'function') Reveal.on('ready', scan);
6909    })();
6910    </script>
6910
6911    
6912    
6912<script>
6913      // htmlwidgets need to know to resize themselves when slides are shown/hidden.
6914      // Fire the "slideenter" event (handled by htmlwidgets.js) when the current
6915      // slide changes (different for each slide format).
6916      (function () {
6917        // dispatch for htmlwidgets
6918        function fireSlideEnter() {
6919          const event = window.document.createEvent("Event");
6920          event.initEvent("slideenter", true, true);
6921          window.document.dispatchEvent(event);
6922        }
6923
6924        function fireSlideChanged(previousSlide, currentSlide) {
6925          fireSlideEnter();
6926
6927          // dispatch for shiny
6928          if (window.jQuery) {
6929            if (previousSlide) {
6930              window.jQuery(previousSlide).trigger("hidden");
6931            }
6932            if (currentSlide) {
6933              window.jQuery(currentSlide).trigger("shown");
6934            }
6935          }
6936        }
6937
6938        // hookup for slidy
6939        if (window.w3c_slidy) {
6940          window.w3c_slidy.add_observer(function (slide_num) {
6941            // slide_num starts at position 1
6942            fireSlideChanged(null, w3c_slidy.slides[slide_num - 1]);
6943          });
6944        }
6945
6946      })();
6947    </script>
6947
6948
6949    
6949<script id="quarto-html-after-body" type="application/javascript">
6950      window.document.addEventListener("DOMContentLoaded", function (event) {
6951        const tabsets =  window.document.querySelectorAll(".panel-tabset-tabby")
6952        tabsets.forEach(function(tabset) {
6953          const tabby = new Tabby('#' + tabset.id);
6954        });
6955        const isCodeAnnotation = (el) => {
6956          for (const clz of el.classList) {
6957            if (clz.startsWith('code-annotation-')) {                     
6958              return true;
6959            }
6960          }
6961          return false;
6962        }
6963        const onCopySuccess = function(e) {
6964          // button target
6965          const button = e.trigger;
6966          // don't keep focus
6967          button.blur();
6968          // flash "checked"
6969          button.classList.add('code-copy-button-checked');
6970          var currentTitle = button.getAttribute("title");
6971          button.setAttribute("title", "Copied!");
6972          let tooltip;
6973          if (window.bootstrap) {
6974            button.setAttribute("data-bs-toggle", "tooltip");
6975            button.setAttribute("data-bs-placement", "left");
6976            button.setAttribute("data-bs-title", "Copied!");
6977            tooltip = new bootstrap.Tooltip(button, 
6978              { trigger: "manual", 
6979                customClass: "code-copy-button-tooltip",
6980                offset: [0, -8]});
6981            tooltip.show();    
6982          }
6983          setTimeout(function() {
6984            if (tooltip) {
6985              tooltip.hide();
6986              button.removeAttribute("data-bs-title");
6987              button.removeAttribute("data-bs-toggle");
6988              button.removeAttribute("data-bs-placement");
6989            }
6990            button.setAttribute("title", currentTitle);
6991            button.classList.remove('code-copy-button-checked');
6992          }, 1000);
6993          // clear code selection
6994          e.clearSelection();
6995        }
6996        const getTextToCopy = function(trigger) {
6997          const outerScaffold = trigger.parentElement.cloneNode(true);
6998          const codeEl = outerScaffold.querySelector('code');
6999          for (const childEl of codeEl.children) {
7000            if (isCodeAnnotation(childEl)) {
7001              childEl.remove();
7002            }
7003          }
7004          return codeEl.innerText;
7005        }
7006        const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', {
7007          text: getTextToCopy
7008        });
7009        clipboard.on('success', onCopySuccess);
7010        if (window.document.getElementById('quarto-embedded-source-code-modal')) {
7011          const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', {
7012            text: getTextToCopy,
7013            container: window.document.getElementById('quarto-embedded-source-code-modal')
7014          });
7015          clipboardModal.on('success', onCopySuccess);
7016        }
7017          var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//);
7018          var mailtoRegex = new RegExp(/^mailto:/);
7019            var filterRegex = new RegExp('/' + window.location.host + '/');
7020          var isInternal = (href) => {
7021              return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href);
7022          }
7023          // Inspect non-navigation links and adorn them if external
7024         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)');
7025          for (var i=0; i<links.length; i++) {
7026            const link = links[i];
7027            if (!isInternal(link.href)) {
7028              // undo the damage that might have been done by quarto-nav.js in the case of
7029              // links that we want to consider external
7030              if (link.dataset.originalHref !== undefined) {
7031                link.href = link.dataset.originalHref;
7032              }
7033                // default icon
7034                link.classList.add("external");
7035            }
7036          }
7037        function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) {
7038          const config = {
7039            allowHTML: true,
7040            maxWidth: 500,
7041            delay: 100,
7042            arrow: false,
7043            appendTo: function(el) {
7044                return el.closest('section.slide') || el.parentElement;
7045            },
7046            interactive: true,
7047            interactiveBorder: 10,
7048            theme: 'light-border',
7049            placement: 'bottom-start',
7050          };
7051          if (contentFn) {
7052            config.content = contentFn;
7053          }
7054          if (onTriggerFn) {
7055            config.onTrigger = onTriggerFn;
7056          }
7057          if (onUntriggerFn) {
7058            config.onUntrigger = onUntriggerFn;
7059          }
7060            config['offset'] = [0,0];
7061            config['maxWidth'] = 700;
7062          window.tippy(el, config); 
7063        }
7064        const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
7065        for (var i=0; i<noterefs.length; i++) {
7066          const ref = noterefs[i];
7067          tippyHover(ref, function() {
7068            // use id or data attribute instead here
7069            let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
7070            try { href = new URL(href).hash; } catch {}
7071            const id = href.replace(/^#\/?/, "");
7072            const note = window.document.getElementById(id);
7073            if (note) {
7074              return note.innerHTML;
7075            } else {
7076              return "";
7077            }
7078          });
7079        }
7080            let selectedAnnoteEl;
7081            const selectorForAnnotation = ( cell, annotation) => {
7082              let cellAttr = 'data-code-cell="' + cell + '"';
7083              let lineAttr = 'data-code-annotation="' +  annotation + '"';
7084              const selector = 'span[' + cellAttr + '][' + lineAttr + ']';
7085              return selector;
7086            }
7087            const selectCodeLines = (annoteEl) => {
7088              const doc = window.document;
7089              const targetCell = annoteEl.getAttribute("data-target-cell");
7090              const targetAnnotation = annoteEl.getAttribute("data-target-annotation");
7091              const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
7092              const lines = annoteSpan.getAttribute("data-code-lines").split(",");
7093              const lineIds = lines.map((line) => {
7094                return targetCell + "-" + line;
7095              })
7096              let top = null;
7097              let height = null;
7098              let parent = null;
7099              if (lineIds.length > 0) {
7100                  //compute the position of the single el (top and bottom and make a div)
7101                  const el = window.document.getElementById(lineIds[0]);
7102                  top = el.offsetTop;
7103                  height = el.offsetHeight;
7104                  parent = el.parentElement.parentElement;
7105                if (lineIds.length > 1) {
7106                  const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]);
7107                  const bottom = lastEl.offsetTop + lastEl.offsetHeight;
7108                  height = bottom - top;
7109                }
7110                if (top !== null && height !== null && parent !== null) {
7111                  // cook up a div (if necessary) and position it 
7112                  let div = window.document.getElementById("code-annotation-line-highlight");
7113                  if (div === null) {
7114                    div = window.document.createElement("div");
7115                    div.setAttribute("id", "code-annotation-line-highlight");
7116                    div.style.position = 'absolute';
7117                    parent.appendChild(div);
7118                  }
7119                  div.style.top = top - 2 + "px";
7120                  div.style.height = height + 4 + "px";
7121                  div.style.left = 0;
7122                  let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter");
7123                  if (gutterDiv === null) {
7124                    gutterDiv = window.document.createElement("div");
7125                    gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter");
7126                    gutterDiv.style.position = 'absolute';
7127                    const codeCell = window.document.getElementById(targetCell);
7128                    const gutter = codeCell.querySelector('.code-annotation-gutter');
7129                    gutter.appendChild(gutterDiv);
7130                  }
7131                  gutterDiv.style.top = top - 2 + "px";
7132                  gutterDiv.style.height = height + 4 + "px";
7133                }
7134                selectedAnnoteEl = annoteEl;
7135              }
7136            };
7137            const unselectCodeLines = () => {
7138              const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"];
7139              elementsIds.forEach((elId) => {
7140                const div = window.document.getElementById(elId);
7141                if (div) {
7142                  div.remove();
7143                }
7144              });
7145              selectedAnnoteEl = undefined;
7146            };
7147              // Handle positioning of the toggle
7148          window.addEventListener(
7149            "resize",
7150            throttle(() => {
7151              elRect = undefined;
7152              if (selectedAnnoteEl) {
7153                selectCodeLines(selectedAnnoteEl);
7154              }
7155            }, 10)
7156          );
7157          function throttle(fn, ms) {
7158          let throttle = false;
7159          let timer;
7160            return (...args) => {
7161              if(!throttle) { // first call gets through
7162                  fn.apply(this, args);
7163                  throttle = true;
7164              } else { // all the others get throttled
7165                  if(timer) clearTimeout(timer); // cancel #2
7166                  timer = setTimeout(() => {
7167                    fn.apply(this, args);
7168                    timer = throttle = false;
7169                  }, ms);
7170              }
7171            };
7172          }
7173            const annoteTargets = window.document.querySelectorAll('.code-annotation-anchor');
7174            for (let i=0; i<annoteTargets.length; i++) {
7175              const annoteTarget = annoteTargets[i];
7176              const targetCell = annoteTarget.getAttribute("data-target-cell");
7177              const targetAnnotation = annoteTarget.getAttribute("data-target-annotation");
7178              const contentFn = () => {
7179                const content = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
7180                if (content) {
7181                  const tipContent = content.cloneNode(true);
7182                  tipContent.classList.add("code-annotation-tip-content");
7183                  return tipContent.outerHTML;
7184                }
7185              }
7186              const config = {
7187                allowHTML: true,
7188                content: contentFn,
7189                onShow: (instance) => {
7190                  selectCodeLines(instance.reference);
7191                  instance.reference.classList.add('code-annotation-active');
7192                  window.tippy.hideAll();
7193                },
7194                onHide: (instance) => {
7195                  unselectCodeLines();
7196                  instance.reference.classList.remove('code-annotation-active');
7197                },
7198                maxWidth: 300,
7199                delay: [50, 0],
7200                duration: [200, 0],
7201                offset: [5, 10],
7202                arrow: true,
7203                trigger: 'click',
7204                appendTo: function(el) {
7205                  return el.parentElement.parentElement.parentElement;
7206                },
7207                interactive: true,
7208                interactiveBorder: 10,
7209                theme: 'light-border',
7210                placement: 'right',
7211                positionFixed: true,
7212                popperOptions: {
7213                  modifiers: [
7214                  {
7215                    name: 'flip',
7216                    options: {
7217                      flipVariations: false, // true by default
7218                      allowedAutoPlacements: ['right'],
7219                      fallbackPlacements: ['right', 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left'],
7220                    },
7221                  },
7222                  {
7223                    name: 'preventOverflow',
7224                    options: {
7225                      mainAxis: false,
7226                      altAxis: false
7227                    }
7228                  }
7229                  ]        
7230                }      
7231              };
7232              window.tippy(annoteTarget, config); 
7233            }
7234        const findCites = (el) => {
7235          const parentEl = el.parentElement;
7236          if (parentEl) {
7237            const cites = parentEl.dataset.cites;
7238            if (cites) {
7239              return {
7240                el,
7241                cites: cites.split(' ')
7242              };
7243            } else {
7244              return findCites(el.parentElement)
7245            }
7246          } else {
7247            return undefined;
7248          }
7249        };
7250        var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
7251        for (var i=0; i<bibliorefs.length; i++) {
7252          const ref = bibliorefs[i];
7253          const citeInfo = findCites(ref);
7254          if (citeInfo) {
7255            tippyHover(citeInfo.el, function() {
7256              var popup = window.document.createElement('div');
7257              citeInfo.cites.forEach(function(cite) {
7258                var citeDiv = window.document.createElement('div');
7259                citeDiv.classList.add('hanging-indent');
7260                citeDiv.classList.add('csl-entry');
7261                var biblioDiv = window.document.getElementById('ref-' + cite);
7262                if (biblioDiv) {
7263                  citeDiv.innerHTML = biblioDiv.innerHTML;
7264                }
7265                popup.appendChild(citeDiv);
7266              });
7267              return popup.innerHTML;
7268            });
7269          }
7270        }
7271      });
7272      </script>
7272
7273    
7273<script type="module">
7274    // Handle cell initialization initialization
7275    qwebrCellDetails.map(
7276      (entry) => {
7277        // Handle the creation of the element
7278        qwebrCreateHTMLElement(entry);
7279        // In the event of interactive, initialize the monaco editor
7280        if (entry.options.context == EvalTypes.Interactive) {
7281          qwebrCreateMonacoEditorInstance(entry);
7282        }
7283      }
7284    );
7285
7286    // Identify non-interactive cells (in order)
7287    const filteredEntries = qwebrCellDetails.filter(entry => {
7288      const contextOption = entry.options && entry.options.context;
7289      return ['output', 'setup'].includes(contextOption) || (contextOption == "interactive" && entry.options && entry.options.autorun === 'true');
7290    });
7291
7292    // Condition non-interactive cells to only be run after webR finishes its initialization.
7293    qwebrInstance.then(
7294      async () => {
7295        const nHiddenCells = filteredEntries.length;
7296        var currentHiddenCell = 0;
7297
7298
7299        // Modify button state
7300        qwebrSetInteractiveButtonState(`🟡 Running hidden code cells ...`, false);
7301
7302        // Begin processing non-interactive sections
7303        // Due to the iteration policy, we must use a for() loop.
7304        // Otherwise, we would need to switch to using reduce with an empty
7305        // starting promise
7306        for (const entry of filteredEntries) {
7307
7308          // Determine cell being examined
7309          currentHiddenCell = currentHiddenCell + 1;
7310          const formattedMessage = `Evaluating hidden cell ${currentHiddenCell} out of ${nHiddenCells}`;
7311
7312          // Update the document status header
7313          if (qwebrShowStartupMessage) {
7314            qwebrUpdateStatusHeader(formattedMessage);
7315          }
7316
7317          // Display the update in non-active areas
7318          qwebrUpdateStatusMessage(formattedMessage);
7319
7320          // Extract details on the active cell
7321          const evalType = entry.options.context;
7322          const cellCode = entry.code;
7323          const qwebrCounter = entry.id;
7324
7325          if (['output', 'setup'].includes(evalType)) {
7326            // Disable further global status updates
7327            const activeContainer = document.getElementById(`qwebr-non-interactive-loading-container-${qwebrCounter}`);
7328            activeContainer.classList.remove('qwebr-cell-needs-evaluation');
7329            activeContainer.classList.add('qwebr-cell-evaluated');
7330
7331            // Update status on the code cell
7332            const activeStatus = document.getElementById(`qwebr-status-text-${qwebrCounter}`);
7333            activeStatus.innerText = " Evaluating hidden code cell...";
7334            activeStatus.classList.remove('qwebr-cell-needs-evaluation');
7335            activeStatus.classList.add('qwebr-cell-evaluated');
7336          }
7337
7338          switch (evalType) {
7339            case 'interactive':
7340              // TODO: Make this more standardized.
7341              // At the moment, we're overriding the interactive status update by pretending its
7342              // output-like. 
7343              const tempOptions = entry.options;
7344              tempOptions["context"] = "output"
7345              // Run the code in a non-interactive state that is geared to displaying output
7346              await qwebrExecuteCode(`${cellCode}`, qwebrCounter, tempOptions);
7347              break;
7348            case 'output':
7349              // Run the code in a non-interactive state that is geared to displaying output
7350              await qwebrExecuteCode(`${cellCode}`, qwebrCounter, entry.options);
7351              break;
7352            case 'setup':
7353              const activeDiv = document.getElementById(`qwebr-noninteractive-setup-area-${qwebrCounter}`);
7354
7355              // Store code in history
7356              qwebrLogCodeToHistory(cellCode, entry.options);
7357
7358              // Run the code in a non-interactive state with all output thrown away
7359              await mainWebR.evalRVoid(`${cellCode}`);
7360              break;
7361            default: 
7362              break; 
7363          }
7364
7365          if (['output', 'setup'].includes(evalType)) {
7366            // Disable further global status updates
7367            const activeContainer = document.getElementById(`qwebr-non-interactive-loading-container-${qwebrCounter}`);
7368            // Disable visibility
7369            activeContainer.style.visibility = 'hidden';
7370            activeContainer.style.display = 'none';
7371          }
7372        }
7373      }
7374    ).then(
7375      () => {
7376        // Release document status as ready
7377
7378        if (qwebrShowStartupMessage) {
7379          qwebrStartupMessage.innerText = "🟢 Ready!"
7380        }
7381      
7382        qwebrSetInteractiveButtonState(
7383          `<i class="fa-solid fa-play qwebr-icon-run-code"></i> <span>
7383Run Code</span>`, 
7384          true
7385        );  
7386      }
7387    );
7388    </script>
7388
7389    
7389<script>var lightboxQuarto = GLightbox({"closeEffect":"zoom","descPosition":"bottom","loop":false,"openEffect":"zoom","selector":".lightbox"});
7390    (function() {
7391      let previousOnload = window.onload;
7392      window.onload = () => {
7393        if (previousOnload) {
7394          previousOnload();
7395        }
7396        lightboxQuarto.on('slide_before_load', (data) => {
7397          const { slideIndex, slideNode, slideConfig, player, trigger } = data;
7398          const href = trigger.getAttribute('href');
7399          if (href !== null) {
7400            const imgEl = window.document.querySelector(`a[href="${href}"] img`);
7401            if (imgEl !== null) {
7402              const srcAttr = imgEl.getAttribute("src");
7403              if (srcAttr && srcAttr.startsWith("data:")) {
7404                slideConfig.href = srcAttr;
7405              }
7406            }
7407          } 
7408        });
7409      
7410        lightboxQuarto.on('slide_after_load', (data) => {
7411          const { slideIndex, slideNode, slideConfig, player, trigger } = data;
7412          if (window.Quarto?.typesetMath) {
7413            window.Quarto.typesetMath(slideNode);
7414          }
7415        });
7416      
7417      };
7418      
7419    })();
7420              </script>
7420
7421    
7422
7423</body></html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.