PageSourceSearch

https://1520s-project.org/documentation/

html 1520s-project.org collected 2026-09-25 21:44:38 UTC 56,412 bytes, 1,956 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en"><head>
3  <meta charset="utf-8">
4  <meta http-equiv="X-UA-Compatible" content="IE=edge">
5  <meta name="viewport" content="width=device-width, initial-scale=1"><!-- Begin Jekyll SEO tag v2.8.0 -->
6<title>documentation | The 1520s Project</title>
7<meta name="generator" content="Jekyll v3.10.0" />
8<meta property="og:title" content="documentation" />
9<meta property="og:locale" content="en_US" />
10<link rel="canonical" href="/documentation/" />
11<meta property="og:url" content="/documentation/" />
12<meta property="og:site_name" content="The 1520s Project" />
13<meta property="og:type" content="website" />
14<meta name="twitter:card" content="summary" />
15<meta property="twitter:title" content="documentation" />
16<script type="application/ld+json">
17{"@context":"https://schema.org","@type":"WebPage","headline":"documentation","url":"/documentation/"}</script>
17
18<!-- End Jekyll SEO tag -->
19<link rel="stylesheet" href="/assets/main.css">
20  <link rel="shortcut icon" type="image/png" href="/images/favicon.png"><link type="application/atom+xml" rel="alternate" href="/feed.xml" title="The 1520s Project" />
21<!-- Google tag (gtag.js) -->
vendor: 64 bytes, lines 21-22
21
22<script async src="https://www.googletagmanager.com/gtag/js?id=
22G-38882FHV3H
vendor: 12 bytes, line 22
22"></script>
23<script>
24  
vendor: 134 bytes, lines 24-28
24window.dataLayer = window.dataLayer || [];
25  function gtag(){dataLayer.push(arguments);}
26  gtag('js', new Date());
27
28  gtag('config', '
28G-38882FHV3H
vendor: 4 bytes, line 28
28');
29</script>
29
30
31</head>
32<body><style>
33
34.site-header {
35  background: rgba(254, 247, 231, 0.92); /* #fef7e7 */
36  backdrop-filter: blur(14px);
37  -webkit-backdrop-filter: blur(14px);
38  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
39  position: sticky;
40  top: 0;
41  z-index: 1000;
42  isolation: isolate;
43  min-height: 72px;
44
45}
46
47@media (min-width: 769px) {
48  .nav-trigger {
49    display: none !important;
50  }
51}
52
53.site-header .wrapper {
54  max-width: 800px;
55  width: 100%;
56  margin: 0 auto;
57  padding: 8px 24px 4px;
58  box-sizing: border-box;
59}
60
61.site-header .header-main {
62  display: flex;
63  align-items: center; /* vertical centering */
64  justify-content: space-between;
65  flex-wrap: wrap;
66}
67
68@media (min-width: 901px) {
69  .site-header--work .wrapper {
70    max-width: none;
71    padding-left: 30px;
72    padding-right: 30px;
73  }
74}
75
76.site-title img {
77  display: block;
78  height: 64px;
79  margin: 0;
80}
81
82/* Ensure nav sits inline and vertically centered */
83.site-nav {
84  display: flex;
85  align-items: center;
86  gap: 12px;
87  position: relative;
88}
89
90.site-nav label {
91  display: none;
92}
93
94.site-nav .trigger {
95  display: flex;
96  align-items: center;
97  gap: 26px;
98}
99
100.site-nav .page-link {
101  position: relative;
102  font-size: 16px;
103  font-weight: 500;
104  padding: 6px 2px;
105  color: #6A6156;
106  letter-spacing: 0.015em;
107  border-radius: 0;
108  transition: background-color 0.15s ease, color 0.15s ease;
109}
110
111/* Dropdown container */
112.site-nav .dropdown {
113  position: relative;
114  display: inline-block;
115}
116
117.site-nav .dropdown-trigger {
118  display: inline-flex;
119  align-items: center;
120  gap: 7px;
121  border: 0;
122  background: transparent;
123  line-height: inherit;
124  cursor: pointer;
125}
126
127.site-nav .dropdown-trigger::after {
128  content: "";
129  width: 5px;
130  height: 5px;
131  border-right: 1px solid currentColor;
132  border-bottom: 1px solid currentColor;
133  transform: rotate(45deg);
134}
135
136.site-nav .page-link:focus-visible {
137  outline: 2px solid #6A6156;
138  outline-offset: 4px;
139}
140
141.site-nav .dropdown::after {
142  content: "";
143  position: absolute;
144  left: 0;
145  right: 0;
146  height: 8px;
147  top: 100%;
148}
149
150/* Dropdown content */
151.site-nav .dropdown-content {
152  display: none;
153  position: absolute;
154  left: 2px;
155  top: 100%;
156  margin-top: 8px;
157  z-index: 200;
158
159  background: rgba(254, 247, 231, 0.98); /* #fef7e7 */
160  backdrop-filter: blur(16px);
161  -webkit-backdrop-filter: blur(16px);
162
163  will-change: transform;
164  transform: translateZ(0);
165
166  border: 1px solid rgba(0,0,0,0.08);
167  border-radius: 12px;
168  padding: 4px;
169  min-width: 140px;
170
171  box-shadow: 0 12px 30px rgba(0,0,0,0.12);
172}
173
174/* Dropdown links */
175.site-nav .dropdown-content a {
176  color: black;
177  padding: 7px 11px;
178  border-radius: 8px;
179  text-decoration: none;
180  display: block;
181  font-size: 15px;
182  font-weight: 500;
183  letter-spacing: 0.015em;
184  white-space: nowrap;
185}
186
187/* Change nav link colors */
188.site-nav .page-link,
189.site-nav .dropdown-content a {
190  font-family: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
191  color: #6A6156;
192}
193
194.site-nav .dropdown-content a:hover {
195  background-color: rgba(106, 97, 86, 0.08);
196  color: #27231F;
197  text-decoration: none;
198}
199
200.site-nav .page-link:hover {
201  background-color: transparent;
202  color: #27231F;
203  text-decoration: underline;
204  text-decoration-thickness: 1px;
205  text-underline-offset: 4px;
206}
207
208.site-nav .dropdown-trigger:hover {
209  background-color: transparent;
210  color: #6A6156;
211  text-decoration: none;
212}
213
214
215/* Keep visual and accessible disclosure state in sync. */
216.site-nav .dropdown-trigger[aria-expanded="true"] + .dropdown-content {
217  display: block;
218}
219
220@media (min-width: 769px) {
221  .site-nav .dropdown-content a:hover,
222  .site-nav .dropdown-content a:focus-visible {
223    background-color: rgba(106, 97, 86, 0.08);
224    color: #27231F;
225    text-decoration: none;
226  }
227}
228
229/* Mobile view adjustments */
230@media (max-width: 768px) {
231  .site-header {
232    min-height: 0;
233  }
234
235  .site-header .wrapper {
236    max-width: none;
237    padding: 6px 16px;
238  }
239
240  .site-header .header-main {
241    flex-wrap: nowrap;
242    gap: 14px;
243  }
244
245  .site-title img {
246    height: 52px;
247  }
248
249  .site-nav {
250    margin-left: auto;
251  }
252
253  .site-nav label[for="nav-trigger"] {
254    display: inline-flex;
255    align-items: center;
256    justify-content: center;
257    width: 38px;
258    height: 34px;
259    float: none;
260    color: #284b63;
261    background: rgba(255, 253, 248, 0.72);
262    border: 1px solid rgba(40, 75, 99, 0.26);
263    border-radius: 4px;
264    cursor: pointer;
265  }
266
267  .site-nav label[for="nav-trigger"]:hover {
268    color: #132f43;
269    border-color: rgba(40, 75, 99, 0.44);
270    background: rgba(255, 253, 248, 0.94);
271  }
272
273  .site-nav .menu-icon,
274  .site-nav .menu-icon svg {
275    display: block;
276  }
277
278  .site-nav .menu-icon {
279    width: auto;
280    height: auto;
281    margin: 0;
282    padding: 0;
283    float: none;
284    line-height: 0;
285    text-align: center;
286  }
287
288  .site-nav .menu-icon svg {
289    width: 18px;
290    height: 15px;
291  }
292
293  .site-nav .menu-icon svg path {
294    fill: currentColor;
295  }
296
297  .site-nav .nav-trigger {
298    position: absolute;
299    opacity: 0;
300    pointer-events: none;
301  }
302
303  .site-nav .trigger {
304    display: none;
305    position: absolute;
306    top: calc(100% + 10px);
307    right: 0;
308    width: min(260px, calc(100vw - 32px));
309    padding: 7px;
310    flex-direction: column;
311    align-items: stretch;
312    gap: 0;
313    background: rgba(254, 247, 231, 0.98);
314    border: 1px solid rgba(40, 75, 99, 0.18);
315    border-radius: 4px;
316    box-shadow: 0 14px 28px rgba(0,0,0,0.14);
317  }
318
319  .site-nav .nav-trigger:checked ~ .trigger {
320    display: flex;
321  }
322
323  .site-nav .page-link,
324  .site-nav .dropdown-content a {
325    display: block;
326    padding: 10px 11px;
327    font-size: 16px;
328    line-height: 1.25;
329  }
330
331  .site-nav .trigger > .page-link::after,
332  .site-nav .dropdown > .dropdown-trigger::after,
333  .site-nav .dropdown::after {
334    display: none;
335  }
336
337  .site-nav .dropdown {
338    display: block;
339  }
340
341  .site-nav .dropdown > .dropdown-trigger.page-link {
342    display: none !important;
343  }
344
345  .site-nav .dropdown-content {
346    display: block;
347    position: static;
348    min-width: 0;
349    margin: 0 0 3px;
350    padding: 0;
351    background: transparent;
352    border: 0;
353    border-radius: 0;
354    box-shadow: none;
355  }
356
357  .site-nav .dropdown-content a {
358    color: #6A6156;
359  }
360
361}
362
363</style>
364
365<header class="site-header" role="banner">
366  <link type="image/x-icon" href="/images/favicon.png" rel="shortcut icon">
367  <div class="wrapper">
368    <div class="header-main"><a class="site-title" rel="author" href="/">
369      <img src="/images/1520s_logo.png"
370     alt="The 1520s Project Logo">
371    </a><nav class="site-nav">
372        <input type="checkbox" id="nav-trigger" class="nav-trigger" />
373        <label for="nav-trigger" aria-label="Open navigation menu">
374          <span class="menu-icon">
375            <svg viewBox="0 0 18 15" width="18px" height="15px">
376              <path d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.032C17.335,0,18,0.665,18,1.484L18,1.484z M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.032C17.335,6.031,18,6.696,18,7.516L18,7.516z M18,13.516C18,14.335,17.335,15,16.516,15H1.484 C0.665,15,0,14.335,0,13.516l0,0c0-0.82,0.665-1.483,1.484-1.483h15.032C17.335,12.031,18,12.695,18,13.516L18,13.516z"/>
377            </svg>
378          </span>
379        </label>
380
381        <div class="trigger"><!-- Initialize a counter --><a class="page-link" href="/about/">about</a><a class="page-link" href="/repertoire/">repertoire</a><!-- Insert the dropdown after the second link -->
382                <div class="dropdown">
383                  <button class="page-link dropdown-trigger" type="button" aria-expanded="false" aria-controls="resources-navigation">resources</button>
384                  <div class="dropdown-content" id="resources-navigation">
385                    <a class="page-link" href="/Josquin/">Josquin attributions</a>
386                  </div>
387                </div><a class="page-link" href="/documentation/">documentation</a></div>
388      </nav></div>
389  </div>
390</header>
391
392<script>
393(() => {
394  const dropdown = document.querySelector('.site-header .dropdown');
395  if (!dropdown) return;
396  const button = dropdown.querySelector('.dropdown-trigger');
397  let pinned = false;
398  const setOpen = (open) => {
399    button.setAttribute('aria-expanded', String(open));
400    if (!open) pinned = false;
401  };
402
403  button.addEventListener('click', () => {
404    pinned = !pinned;
405    setOpen(pinned);
406  });
407  dropdown.addEventListener('pointerenter', (event) => {
408    if (event.pointerType === 'mouse') setOpen(true);
409  });
410  dropdown.addEventListener('pointerleave', (event) => {
411    if (event.pointerType === 'mouse' && !pinned && !dropdown.contains(document.activeElement)) setOpen(false);
412  });
413  dropdown.addEventListener('focusin', () => setOpen(true));
414  dropdown.addEventListener('focusout', (event) => {
415    if (!dropdown.contains(event.relatedTarget)) setOpen(false);
416  });
417  dropdown.addEventListener('keydown', (event) => {
418    if (event.key === 'Escape') {
419      button.focus();
420      setOpen(false);
421    } else if (event.key === 'ArrowDown' && event.target === button) {
422      event.preventDefault();
423      setOpen(true);
424      dropdown.querySelector('.dropdown-content a').focus();
425    }
426  });
427  document.addEventListener('click', (event) => {
428    if (!dropdown.contains(event.target)) setOpen(false);
429  });
430})();
431</script>
431
432<main class="page-content" aria-label="Content">
433      <div class="wrapper">
434        <article class="post">
435
436  <header class="post-header">
437    <h1 class="post-title">documentation</h1>
438  </header>
439
440  <div class="post-content">
441    
441<script src="https://plugin.humdrum.org/scripts/humdrum-notation-plugin-worker.js"></script>
441
442
443<style>
444
445article.post {
446  width: 100%;
447}
448
449body {
450  background-color: #FFFAF1;
451}
452
453html {
454  scroll-padding-top: 110px;
455}
456
457.documentation-layout {
458  display: flex;
459  max-width: 1200px;
460  margin: 0 auto;
461  padding-top: 22px;
462  gap: 30px;
463}
464
465.main-content {
466  flex: 1;
467}
468
469.main-content p,
470.main-content div > p {
471  max-width: 760px;
472  margin: 0 auto 14px;
473}
474
475.main-content section {
476  position: relative;
477  margin-bottom: 18px;
478}
479
480.main-content section::after {
481  content: "";
482  display: block;
483  width: 100%;
484  height: 1px;
485  margin: 5px auto 0;
486
487  background: linear-gradient(
488    to right,
489    rgba(0,0,0,0.15),
490    rgba(0,0,0,0)
491  );
492}
493
494.main-content section:last-of-type::after {
495  display: none;
496}
497
498.main-content h2:first-of-type {
499  margin-top: 0;
500}
501
502.main-content h2 {
503  padding-top: 0;
504  max-width: 760px;
505}
506
507.main-content h2:not(:first-of-type) {
508  margin-top: 24px;
509  padding-top: 0;
510}
511
512.main-content h3 {
513  max-width: 760px;
514  margin: 26px auto 8px;
515}
516
517.main-content [id] {
518  scroll-margin-top: 110px;
519}
520
521.main-content h4 {
522  font-size: 15px;
523  font-weight: 500;
524  margin: 32px auto 12px;
525  max-width: 760px;
526}
527
528.wrapper {
529  max-width: 1000px;
530}
531
532.post-title {
533  transform: translateX(50px);
534}
535
536.toc-container {
537  position: sticky;
538  align-self: flex-start;
539  top: 96px;
540  max-width: 240px;
541  min-width: 240px;
542  padding: 6px 18px 8px 0;
543  border-right: 1px solid rgba(40, 75, 99, 0.16);
544  font-size: 13px;
545  line-height: 1.35;
546  flex-shrink: 0;
547}
548
549.toc-container h4 {
550  margin: 0 0 10px;
551  color: #5f5951;
552  font-size: 12px;
553  font-weight: 600;
554  letter-spacing: 0.04em;
555  text-transform: uppercase;
556}
557
558.toc-container ul {
559  list-style: none;
560  padding-left: 0;
561  margin: 0;
562}
563
564.toc-container li {
565  margin: 3px 0;
566}
567
568.toc-container li ul {
569  margin: 3px 0 6px 14px;
570  color: #6A6156;
571  font-size: 12.5px;
572  line-height: 1.3;
573}
574
575.toc-container li ul li {
576  margin: 2px 0;
577}
578
579.toc-container li a {
580  display: inline-block;
581  padding-left: 1em;
582  color: #284b63;
583  text-indent: -1em;
584  text-decoration: none;
585}
586
587.toc-container li a:hover {
588  color: #132f43;
589  text-decoration: underline;
590}
591
592.main-content ul,
593.main-content ol {
594  max-width: 760px;
595  margin: 0 auto 16px;
596  padding-left: 20px;
597}
598
599.main-content li {
600  margin-bottom: 6px;
601  line-height: 1.6;
602}
603
604.main-content img {
605  max-width: 100%;
606  height: auto;
607  display: block;
608  margin: 14px auto 20px;
609}
610
611.documentation-figure {
612  max-width: 760px;
613  margin: 16px auto 18px;
614}
615
616.documentation-figure object,
617.documentation-figure img {
618  margin: 0 auto 8px;
619}
620
621.documentation-figure object {
622  display: block;
623  width: 100%;
624  height: auto;
625  aspect-ratio: 550 / 102;
626  pointer-events: auto;
627}
628
629.main-content svg {
630  width: 100% !important;
631  height: auto !important;
632}
633
634/* More generous indentation for documentation lists */
635.main-content ul.spaced-list {
636  padding-left: 32px;
637  font-size: 14px;
638}
639
640.main-content ul.narrow-list {
641  padding-left: 32px;
642  font-size: 14px;
643}
644
645.main-content ul.spaced-list li {
646  padding-left: 0;
647}
648
649code {
650  display: block;
651  max-width: 760px;
652  margin: 12px auto 20px;
653  padding: 10px 12px;
654  overflow-x: auto;
655  color: #1c1c1e;
656  background: rgba(255, 253, 248, 0.74);
657  border: 1px solid rgba(40, 75, 99, 0.22);
658  border-radius: 4px;
659  font-size: 13px;
660  line-height: 1.45;
661  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
662}
663
664.url-block {
665  max-width: 760px;
666  margin: 0 auto 16px;
667  padding: 9px 12px;
668  border: 1px solid rgba(40, 75, 99, 0.22);
669  border-radius: 4px;
670  background: rgba(255, 253, 248, 0.74);
671
672  font-size: 14px;
673  line-height: 1.6;
674}
675
676.url-block a {
677  color: #284b63;
678  text-decoration: none;
679  word-break: break-all;
680}
681
682.url-block a:hover {
683  color: #132f43;
684  text-decoration: underline;
685}
686
687.workflow-list {
688  list-style: none;
689  counter-reset: step;
690  padding-left: 0;
691}
692
693.workflow-list > li {
694  counter-increment: step;
695  position: relative;
696  padding-left: 2.6rem;
697  margin-bottom: 1.6rem;
698}
699
700.workflow-list > li::before {
701  content: counter(step);
702  position: absolute;
703  left: 0;
704  top: 0.1rem;
705
706  width: 22px;
707  height: 22px;
708  border-radius: 50%;
709
710  display: flex;
711  align-items: center;
712  justify-content: center;
713
714  background: rgba(0,0,0,0.06);
715  color: #1c1c1e;
716
717  font-size: 13px;
718  font-weight: 500;
719}
720
721.workflow-list strong {
722  display: block;
723  font-weight: 500;
724  margin-bottom: 0.25rem;
725}
726
727.workflow-list p {
728  margin: 0.25rem 0 0.75rem;
729}
730
731.workflow-list img {
732  margin-top: 0.5rem;
733}
734
735/* Reset nested lists inside workflow steps to normal body style */
736.workflow-list ul {
737  font-size: 15px;
738  line-height: 1.65;
739  margin-top: 0.5rem;
740}
741
742.workflow-list ul li {
743  padding-left: 0;
744  margin-bottom: 0.4rem;
745}
746
747/* Mobile Styles */
748@media (max-width: 768px) {
749  .documentation-layout {
750    flex-direction: column;
751    width: 100%;
752    padding: 0 16px;
753    margin: 0;
754    box-sizing: border-box;
755  }
756
757  .main-content {
758    width: 100%;
759  }
760
761  .toc-container {
762    display: none;
763  }
764
765  h1 {
766    font-size: 28px;
767    line-height: 1.3;
768  }
769
770  h2 {
771    font-size: 24px;
772    line-height: 1.4;
773    margin-top: 1em;
774  }
775
776  h3 {
777    font-size: 20px;
778    line-height: 1.3;
779    margin-top: 0.8em;
780  }
781
782  h4 {
783    font-size: 18px;
784    line-height: 1.3;
785    margin-top: 0.6em;
786  }
787
788  /* List adjustments */
789  ul,
790  ol {
791    padding-left: 4px;
792  }
793
794  ul.spaced-list li,
795  ol.spaced-list li {
796    margin-bottom: 0.4em;
797  }
798
799
800  /* Image adjustments */
801  img {
802    max-width: 100%;
803    height: auto;
804    padding: 10px 0;
805    display: block;
806    margin: 0 auto;
807  }
808
809  /* Table adjustments for mobile */
810  table {
811    width: 100%;
812    overflow-x: auto;
813    display: block;
814  }
815
816  table th, 
817  table td {
818    text-align: left;
819    padding: 8px;
820  }
821
822  /* Reduced padding between lines for readability */
823  .main-content div {
824    margin-bottom: 1em;
825  }
826}
827</style>
828
829<style>
830
831body {
832  font-family: -apple-system, BlinkMacSystemFont,
833               "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
834}
835
836.site-header,
837.site-nav,
838.site-title {
839  float: none !important;
840  line-height: normal !important;
841}
842
843main.page-content {
844  background-color: #FFFAF1;
845  color: #1c1c1e;
846  padding: 0;
847}
848
849h1,
850h1.post-title {
851  display: none;
852}
853
854main.page-content p,
855main.page-content ul {
856  font-size: 15px;
857  line-height: 1.6;
858}
859
860main.page-content h2 {
861  font-size: 22px;
862  font-weight: 400;
863  margin: 30px auto 8px;
864  max-width: 800px;
865}
866
867main.page-content h3 {
868  font-size: 17px;
869  font-weight: 400;
870  margin: 16px auto 4px;
871  max-width: 800px;
872}
873
874main.page-content a {
875  color: #007aff;
876  text-decoration: none;
877}
878
879main.page-content a:hover {
880  text-decoration: underline;
881}
882
883.section-header {
884  display: flex;
885  align-items: center;
886  justify-content: space-between;
887
888  margin: 6px 0 6px 0;
889  padding: 0 4px;
890}
891
892.section-header h4 {
893  margin: 0;
894
895  font-size: 13px;
896  font-weight: 600;
897  letter-spacing: 0.04em;
898  text-transform: uppercase;
899
900  color: #6A6156;
901}
902
903.section-header.page-title {
904  margin-top: 14px;
905  margin-bottom: 16px;
906}
907
908.section-header.page-title h4 {
909  font-family: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
910  font-size: 24px;
911  font-weight: 500;
912  letter-spacing: 0.01em;
913  line-height: 1.15;
914  text-transform: none;
915}
916
917.section-header::after {
918  content: "";
919  flex: 1;
920  height: 1px;
921  margin-left: 12px;
922
923  background: linear-gradient(
924    to right,
925    rgba(0,0,0,0.15),
926    rgba(0,0,0,0)
927  );
928}
929
930table.general {
931  width: 100%;
932  border-collapse: separate;
933  border-spacing: 0;
934
935  background: rgba(255,255,255,0.55);
936  backdrop-filter: blur(14px);
937  -webkit-backdrop-filter: blur(14px);
938
939  border-radius: 16px;
940  overflow: hidden;
941
942  box-shadow: 0 8px 24px rgba(0,0,0,0.06);
943
944  font: 400 14px/1.4 -apple-system, BlinkMacSystemFont,
945        "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
946}
947
948table.general th {
949  text-align: left;
950  font-size: 12px;
951  font-weight: 600;
952  letter-spacing: 0.04em;
953  text-transform: uppercase;
954
955  color: #6A6156;
956  background: transparent;
957
958  padding: 7px 8px;
959  border-bottom: 1px solid rgba(0,0,0,0.08);
960}
961
962table.general td {
963  padding: 4px 5px;
964  vertical-align: top;
965  border-bottom: 1px solid rgba(0,0,0,0.06);
966}
967
968table.general tbody tr:hover {
969  background-color: rgba(0,0,0,0.035);
970}
971
972table.general a {
973  text-decoration: none;
974  font-weight: 500;
975}
976
977table.general a:hover {
978  text-decoration: underline;
979}
980
981table.general .work-subtitle {
982  color: #6A6156;
983  font-weight: 400;
984}
985
986table.general .last-name {
987  font-weight: 600;
988}
989
990table.general .first-name {
991  color: #6A6156;
992}
993
994@media (max-width: 768px) {
995  body {
996    overflow-x: hidden;
997    -webkit-text-size-adjust: 100%;
998  }
999
1000  main.page-content {
1001    padding: 0 16px;
1002    overflow-x: hidden;
1003    box-sizing: border-box;
1004  }
1005
1006  main.page-content .wrapper {
1007    max-width: 100%;
1008    padding-left: 0;
1009    padding-right: 0;
1010    box-sizing: border-box;
1011  }
1012
1013  main.page-content p,
1014  main.page-content ul {
1015    font-size: 15px;
1016    line-height: 1.58;
1017  }
1018
1019  main.page-content h2 {
1020    font-size: 20px;
1021    line-height: 1.28;
1022    margin-top: 24px;
1023  }
1024
1025  main.page-content h3 {
1026    font-size: 16px;
1027    line-height: 1.35;
1028  }
1029
1030  .section-header {
1031    padding: 0;
1032  }
1033
1034  .section-header.page-title h4 {
1035    font-size: 22px;
1036  }
1037
1038  table.general {
1039    display: block;
1040    width: 100%;
1041    max-width: 100%;
1042    overflow-x: auto;
1043    border-radius: 4px;
1044    box-shadow: none;
1045    -webkit-overflow-scrolling: touch;
1046  }
1047
1048  table.general th,
1049  table.general td {
1050    white-space: nowrap;
1051  }
1052}
1053
1054</style>
1055
1056<div class="section-header page-title"><h4>Documentation</h4></div>
1057
1058<div class="documentation-layout">
1059	<div class="toc-container">
1060	  <h4>Table of Contents</h4>
1061	  <ul>
1062	    <li><strong><a href="#musical-documentation">Musical documentation</a></strong>
1063	      <ul>
1064	        <li><a href="#attributions">Attributions</a></li>
1065	        <li><a href="#voice-names">Voice names</a></li>
1066	        <li><a href="#sections">Sections</a></li>
1067	        <li><a href="#notes">Note values and Mensurstriche</a></li>
1068	        <li><a href="#mensuration-signs">Mensuration signs</a></li>
1069	        <li><a href="#terminal-notes">Terminal notes and fermatas</a></li>
1070	        <li><a href="#editorial-accidentals">Editorial accidentals</a></li>
1071	      </ul>
1072	    </li>
1073	    <li><strong><a href="#technical-documentation">Technical documentation</a></strong>
1074	      <ul>
1075	        <li><a href="#filenames">Filenames</a></li>
1076	        <li><a href="#score-preparation">Score preparation</a></li>
1077	        <li><a href="#encoded-information">Encoded information</a></li>
1078	        <li><a href="#source-metadata">Source metadata</a></li>
1079	        <li><a href="#pitch-range-plots">Pitch range plots</a></li>
1080	        <li><a href="#website">Website</a>
1081	          <ul>
1082	            <li><a href="#cloudflare">Cloudflare</a></li>
1083	            <li><a href="#javascript-tools">JavaScript tools</a></li>
1084	          </ul>
1085	        </li>
1086	        <li><a href="#data-server">Data server</a></li>
1087	      </ul>
1088	    </li>
1089	    <li><strong><a href="#download-data">Download project data</a></strong></li>
1090	  </ul>
1091	</div>
1092
1093<div class="main-content">
1094
1095<h2 id="musical-documentation">Musical Documentation</h2>
1096
1097<section>
1098	<p>The 1520s Project is a digital scholarly edition of Renaissance music. It provides high quality data with consistent editorial standards between scores. Scores are transcribed from reliable sources and avoid unhelpful features of some modern editions (e.g., impractical barring). The project is above all a powerful finding aid and analytical tool. It is not intended to replace published critical editions.</p>
1099	<p>Shared editorial standards with the <a href="https://josquin.stanford.edu/" target="_blank">Josquin Research Project</a> enable rigorous comparisons and analysis of the musical data.</p>
1100</section>
1101
1102<h3 id="attributions">Attributions</h3>
1103
1104<section>
1105	<p>Most musical works in the early sixteenth century can be securely attributed to a single composer. For a small group of pieces, sorting out attributions presents additional complications:</p>
1106	<ul class="spaced-list">
1107		<li><strong>Secure attributions</strong> are listed under the composer's name.</li>
1108		<li><strong>Plausible attributions</strong> need to be carefully considered but given the information available, lack sufficient strength to assign authorship. 
1109			<p>A test case might be the attributions to Lupus Hellinck for the motet <a href="https://1520s-project.org/work/?id=Ric2016">Jerusalem surge</a>, believed to be by Jean Richafort. On the whole, the sources favor Richafort, but an early Pierre Attaingnant print, constituting a separate branch of transmission, attributes the motet to Lupus. The attributions to Lupus are therefore plausible.</p>
1110		</li>
1111		<li><strong>Improbable attributions</strong> cannot be ruled out but are unlikely to be correct.
1112			<p>A five- or six-voice imitative chanson in a mid-sixteenth-century style and attributed in a late sixteenth-century source to Jean Mouton for the first time——some forty years after Mouton's death——is unlikely to be accurate.</p>
1113		</li>
1114		<li><strong>Implausible attributions</strong> are all but impossible.
1115			<p>One example: Jachet of Mantua's <a href="https://1520s-project.org/work/?id=Jom2014">Omnes sancti tui quesumus</a> is attributed to Adrian Willaert in a single source geographically distant from Willaert with no clear connection to Willaert. Attributions to Jachet, by contrast, appear in an overwhelming number of sources closer to the composer, including in a number of single-author prints of his motets. The attribution to Willaert is therefore implausible.</p>
1116		</li>
1117	</ul>
1118	<p>This project only considers attributions in sixteenth-century sources. Implied attributions on the basis of so-called <i>Nestertheorie</i> are not adopted. Where modern scholarship has attributed works to a given composer without a sixteenth-century attribution, these pieces are "linked" to the composer. This means: results of a search for the composer Philippe Verdelot will include anonymous madrigals in the Newberry Partbooks long assumed to be by Verdelot, but for which no sixteenth-century concordance attributes the work to the Florentine composer.</p>
1119</section>
1120
1121<h3 id="voice-names">Voice names</h3>
1122
1123<section><p>Early sixteenth-century musical sources often use interchangeable names for voices, such as Superius, Cantus, and Discantus for the top voice in a given texture; or Tenor II, Quintus, and Sextus for a middle voice. And different sources for a given piece might use different names for the same musical line. To maintain consistent nomenclature, this project uses four voice designations only: superius, altus, tenor, and bassus. When a piece uses more than four voices, the project assigns the most logical voice on the basis of range, followed by a roman numeral (for instance, Tenor II).</p></section>
1124
1125<h3 id="sections">Sections</h3>
1126
1127<section><p>Each section in a piece is separated by a double bar, or at the end of the piece, a final barline. The start of each section of a piece has a section label (for instance, "Secunda pars: Ave solem genuisti").</p></section>
1128
1129<h3 id="notes">Note values and Mensurstriche</h3>
1130
1131<section><p>All scores appear in unreduced note values. This means that a breve is equal to a double whole note; a minim as a half note, etc.<br /><br /><i>Mensurstriche</i>, an editorial practice that allows note values to cross over a modern bar line without using a tie, is avoided. Programs such as Finale and Sibelius cannot properly render <i>Mensurstriche</i>, and so any workarounds used in these programs by scholars are lost when exp
1131orting to MusicXML.</p></section>
1132
1133<h3 id="mensuration-signs">Mensuration signs</h3>
1134
1135<section><p>Where possible, scores use the mensuration signs found in the earliest source for a given piece. On the browse page, users can search for the mensuration sign or signs used in the earliest source.</p></section>
1136
1137<h3 id="terminal-notes">Terminal notes and fermatas</h3>
1138
1139<section><p>Terminal notes are defined as the last note in a given voice prior to a double barline or the end of a given work. They are displayed as longs regardless of their actual durations. Medial cadences are followed by double barlines; terminal cadences are followed by a final barline.<br /><br /> Fermatas on terminal notes are removed, since terminal longs represent an indefinite value. The placement of fermatas elsewhere follows the musical sources.</p></section>
1140
1141<h3 id="editorial-accidentals">Editorial accidentals</h3>
1142
1143<section>
1144	<p>Editorial accidentals are not strictly part of the musical text, but instead must be realized by performers. <strong>All analyses by The 1520s Project depend on the version of the score without editorial accidentals.</strong> Editorial accidentals are displayed on scores in order to:</p>
1145	<ul class="workflow-list">
1146		<li>
1147			<strong>avoid melodic tritones</strong>
1148			<p>In the following example, taken from the bassus of Noel Bauldeweyn's <a href="https://1520s-project.org/work/?id=Bau3001">En douleur en tristesse</a>, a tritone in m. 14 is avoided by flatting the E following the B-flat. A second tritone produced by stepwise motion in m. 15 can be sidestepped through the addition of a second flat.<img src="/images/Bau3001_example.svg" alt="Bauldeweyn editorial accidentals example" /></p>
1149		</li>
1150		<li>
1151			<strong>inflect cadences</strong>
1152			<p>Editorial accidentals are added when moving from an imperfect consonance to a perfect consonance. In this repertoire, cadences often (but not always) feature sixth-to-octave motion preceded by a seven-to-six suspension in the top voice. See, for instance, mm. 59–61 of Nicolas Champion's <a href="https://1520s-project.org/work/?id=Cha2001">De profundis clamavi</a>, in which a seven-to-six suspension is followed by movement to an octave:<img src="/images/Cha2001_example.svg" alt="Champion editorial accidentals example" /></p>
1153		</li>
1154	</ul>
1155	<p>This project views cadences as having both a contrapuntal and a rhetorical component. In order to be classified as a cadence, the required contrapuntal elements must coincide with one or both of the voices ending a line of text. Usually——although by ca. 1530 not always——one or both of the voices will rest following the cadence.</p>
1156	<p>Users can see scores without editorial accidentals by toggling on/off the suggested editorial accidentals using the "Hide/Show Editorial Accidentals" button. They can download the scores to adapt the accidentals to their own preferences.</p>
1157</section>
1158
1159<h2 id="technical-documentation">Technical documentation</h2> 
1160
1161<h3 id="filenames">Filenames</h3>
1162
1163<section>
1164	<p><strong>Composer codes and numbering</strong></p>
1165	<p>Each piece is assigned a three-letter composer code in close coordination with the Josquin Research Project. Each work is then assigned a four-digit number. The first digit indicates the genre (1 for a mass; 2 for a motet; 3 for a secular work). The last two or three digits are an enumeration of the works in the given genre, typically moving from the first circulating work by a composer to the last. For example, Wil2001 is the first circulating motet (or one of the first) by the composer Adrian Willaert.</p>
1166	<p>If a piece, such as a mass, consists of multiple parts, the filename for each section additionally assumes a letter. For instance, Wil1001a is the Kyrie from Willaert's Missa Mente tota; Wil1001b is the Gloria from the same mass.</p>
1167	<p>Where this project collaborates with existing editions, the first two digits reflect the volume in the series. For example, The 1520s Project incorporates scores from the <a href="https://senflonline-eng.com/new-senfl-edition/" target="_blank"><i>New Senfl Edition</i></a>. In this case, Snf0101 is the first work in the first volume of the <i>New Senfl Edition</i>. Following the NSE's numbering, Snf0238 is no. 38 in the overall series and is in the second volume.</p>
1168	<p><strong>Work name</strong></p>
1169	<p>After the composer code and number, the filename continues with a dash, followed by the name of the work, using only the characters A-Z, a-z, and 0-9, with underscores separating individual words, then (if applicable) followed by a dash and then the section name. For example, the Kyrie of Willaert's Missa Mente tota would be Wil1001a-Missa_Mente_tota-Kyrie. All special characters (e.g., apostrophes and accents) are removed.</p>
1170	<p><strong>Unique sources</strong></p>
1171	<p>If a piece survives uniquely in one source (<i>unicum</i>
1171), these naming conventions are followed by two dashes (--) and then the name of the source, written in an easy-to-read shorthand. For instance, Maistre Jan's Vox de celis is <i>unicum</i> in the manuscript I-Bc Q.19, better known as Bologna Q19. The filename is therefore Jan2002-Vox_de_celis--Bologna_Q19. The shorthand consists of the source location followed by a short version of the source signature, separated by an underscore.</p>
1172</section>
1173
1174<h3 id="score-preparation">Score preparation</h3>
1175
1176<section>
1177	<p> Each score is prepared in Sibelius by adjusting the following elements:</p>
1178	<ul class="narrow-list"> 
1179		<li>Any <i>Mensurstriche</i> is removed.</li>
1180		<li>Instrument names are corrected.</li>
1181		<li>Editorial accidentals are adjusted and placed in parentheses.</li>
1182		<li>Metadata (title, subtitles for sections, composer, encoder) is added to the score.</li>
1183		<li>The staff size is changed to 5.5 mm.</li>
1184		<li>System brackets are removed.</li>
1185		<li>Final notes and fermatas are formatted</li>
1186		<li>Information about canonic material is given</li>
1187	</ul>
1188	<p>Scores are then exported to MusicXML files and converted using the <a href="https://extras.humdrum.org/man/xml2hum/" target="_blank">musicxml2hum command</a> in the Humdrum Extras toolkit.</p>
1189</section>
1190
1191<h3 id="encoded-information">Encoded information</h3>
1192
1193<section>
1194	<p> A range of information is encoded in the humdrum files:</p>
1195		<ul class="narrow-list"> 
1196			<li>id: the ID for the work</li>
1197			<li>AGN: the genre</li>
1198			<li>voices: the number of voices</li>
1199			<li>COM: the composer </li>
1200			<li>OTL: the title of the work</li>
1201			<li>OPR: the title of the parent work</li>
1202			<li>ENC: the encoder</li>
1203			<li>END: the date of the encoding</li>
1204		</ul>
1205	<p>Scores coming from published editions have additional categories of information:</p>
1206	<ul class="narrow-list"> 
1207		<li>PTL: the title of the edition</li>
1208		<li>PPR: publisher of the edition</li>
1209		<li>PC#: the edition's series</li>
1210		<li>PDT: the year of the edition</li>
1211		<li>PED: the editors</li>
1212		<li>YEC: copyright information</li>
1213		<li>EED: the electornic encoder of the edition</li>
1214		<li>EEV: the date of the encoding</li>
1215	</ul>
1216</section>
1217
1218<h3 id="source-metadata">Source metadata</h3>
1219
1220<section><p>Metadata for each piece includes the (probable) earliest source in the piece's transmission and its dating. External links are provided to <a href="https://www.diamm.ac.uk/" target="_blank">DIAMM</a> or <a href="https://rism.online/" target="_blank">RISM</a> as appropriate. Names of manuscript sources on the browse page and on individual work pages follow RISM nomenclature.</p></section>
1221
1222<h3 id="pitch-range-plots">Pitch range plots</h3>
1223
1224<section>
1225	<p>On work pages, pitch range plots summarize the pitch space of each voice. Each voice has its own staff; the final <i>all</i> row combines them.</p>
1226	<figure class="documentation-figure pitch-range-figure">
1227		<object class="interactive-svg" data="/images/pitch_range_plot_example.svg" type="image/svg+xml" aria-label="Example pitch range plot showing individual voice ranges and the combined all-voices row">
1228			<img src="/images/pitch_range_plot_example.svg" alt="Example pitch range plot showing individual voice ranges and the combined all-voices row" />
1229		</object>
1230	</figure>
1231	<p>Large black noteheads mark lowest and highest pitches, the small white notehead the median, and gray noteheads final notes. Hover over noteheads for marker explanations.</p>
1232	<p>Histogram bars show pitch prevalence. In <strong>Note counts</strong>, pitches are weighted by attacks; in <strong>Durations</strong>, by total sounding duration. Hover over a bar for its pitch and count or duration.</p>
1233	<p>Accidental colors come from the encoded score: flats in dark red/brown, sharps in blue. Because notated and sounding accidentals are not always fully distinguished, the colors are a practical performance guide, not a definitive record of original notation.</p>
1234</section>
1235
1236<h3 id="website">Website</h3>
1237
1238<section>
1239  <p>Files for the website for The 1520s Project are hosted on GitHub at:</p>
1240
1241  <p class="url-block">
1242    <a href="https://github.com/benory/1520s-project-website" target="_blank">
1243      https://github.com/benory/1520s-project-website
1244    </a>
1245  </p>
1246
1247  <p>The website is served through <a href="https://pages.github.com" target="_blank">GitHub Pages</a>
1248     and uses the markdown language <a href="https://jekyllrb.com" target="_blank">Jekyll</a>.
1249  </p>
1250
1251<h4 id="cloudflare">Cloudflare</h4>
1252	<p>Work pages include an error-report popup. Submissions are protected by <a href="https://www.cloudflare.com/application-services/products/turnstile/" target="_blank">Cloudflare Turnstile</a>, routed through a <a href="https://developers.cloudflare.com/workers/" target="_blank">Cloudflare Worker</a>, and saved to a Google Form. An Apps Script trigger sends plain-text notification emails to the project account.</p>
1253
1254<h4 id="javascript-tools">JavaScript tools</h4>
1255	<ul class="spaced-list">
1256		<li>
1257			<strong>Humdrum Notation Plugin</strong>
1258			<p>The <a href="https://plugin.humdrum.org/" target="_blank">Humdrum Notation Plugin</a> is an interface that simplifies working with the JavaScript toolkit version of Verovio.</p>
1259		</li>
1260		<li>
1261			<strong>Vega-Lite</strong>
1262			<p><a href="https://vega.github.io/vega-lite/" target="_blank">Vega-Lite</a> is a visualization grammar for statistical graphics, which functions similar to ggplot. The 1520s Project uses Vega-Lite for histograms and bar charts.</p>
1263		</li>
1264		<li>
1265			<strong>Verovio</strong>
1266			<p><a href="https://www.verovio.org/index.xhtml" target="_blank">Verovio</a> is an SVG music notation renderer developed primarily by Laurent Pugin. The humlib library is embedded within Verovio and is used to process and analyze the Humdrum scores for the website as well as convert the scores into MEI for rendering in Verovio.</p>
1267		</li>
1268		<li>
1269			<strong>Verovio Humdrum Viewer</strong>
1270			<p><a href="https://verovio.humdrum.org/" target="_blank">Verovio Humdrum Viewer</a> is an online Humdrum file notation editor and renderer created by Craig Sapp. It is used for the final preparation of the scores and for various analyses not yet implemented on the project website.</p>
1271		</li>
1272	</ul>
1273</section>
1274
1275<h3 id="data-server">Data server</h3>
1276
1277<section>
1278	<p> Scores uploaded to GitHub are processed using a <a href="https://data.1520s-project.org" target="_blank">data server</a> created by Sapp and hosted at Stanford University. The data server supports The 1520s Project, the Josquin Research Project, and the <a href="http://tassomusic.org" target="_blank">Tasso in Music Project</a>. Below is a screenshot of The 1520s Project's data server API webpage:<br /><br /><a href="https://data.1520s-project.org" target="_blank"><img src="/images/data_server.png" alt="data server" /></a></p>
1279	<p> The data server creates alternate digital score formats and analyses that are inconvenient to process within the web browser. The Humdrum files in The 1520s Project's score repository are first copied onto the server. The server then generates derivative digital scores that exactly match the Humdrum files (MusicXML, MEI, MIDI, MP3, etc), renders data visualizations such as activity plots and keyscape plots, and builds the timemap required for MP3 playback highlighting. All files created by the data server (including more than are shown on this website) are freely available for download and use. Users also have the ability to incorporate this project's data on their own websites using Verovio, and to embed generated analyses within their own projects.</p>
1280</section>
1281
1282<h2 id="download-data">Download project data</h2>
1283
1284<section>
1285	<p><a href="https://github.com/benory/1520s-project-scores" target="_blank">The project's GitHub repository</a>, which hosts Sibelius files, MusicXML files, Humdrum files, and PDFs, can be cloned in terminal by using the following command:</p>
1286	<code>git clone https://github.com/benory/1520s-project-scores.git</code>
1287	<p><strong>Note:</strong> the Sibelius and MusicXML files on the project's GitHub repository are pre-corrected version of the Humdrum files (the data server provides post-corrected MusicXML files that are derived from the corrected Humdrum files).</p>
1288	<p>Metadata used to search on the <a href="https://1520s-project.org/repertoire/">browse page</a>, as well as display additional information about scores on the work pages, is available as a set of JSON files used by the website:</p>
1289	<ul class="spaced-list">
1290		<li><a href="https://raw.githubusercontent.com/benory/1520s-project-website/main/_includes/metadata/works.json" target="_blank">Works</a> (metadata about the scores used for searches on the browse page)</li>
1291		<li><a href="https://raw.githubusercontent.com/benory/1520s-project-website/main/_includes/metadata/composers.json" target="_blank">Composers</a>
1291 (metadata and links for composers used on the browse and work pages)</li>
1292		<li><a href="https://raw.githubusercontent.com/benory/1520s-project-website/main/_includes/metadata/modern_editions.json" target="_blank">Modern Editions</a> (information about modern editions for works used on the workpages)</li>
1293		<li><a href="https://raw.githubusercontent.com/benory/1520s-project-website/main/_includes/metadata/statistics.json" target="_blank">Statistics</a> (number of works by year, compiled for use on the browse page)</li>
1294	</ul>
1295</section>
1296
1297</div>
1298</div>
1299
1300  </div>
1301
1302</article>
1303
1304      </div>
1305    </main>
1306
1307
1308<style>
1309.site-footer {
1310  margin-top: 0;
1311  padding: 14px 20px;
1312
1313  background: rgba(255, 250, 241, 0.95);
1314  backdrop-filter: blur(14px);
1315  -webkit-backdrop-filter: blur(14px);
1316
1317  border-top: 1px solid rgba(0, 0, 0, 0.08);
1318
1319  font-size: 12px;
1320  color: #FFFAF1;
1321}
1322
1323.footer-inner {
1324  max-width: 1000px;
1325  margin: 0 auto;
1326
1327  display: flex;
1328  align-items: center;
1329  justify-content: space-between;
1330  gap: 12px;
1331}
1332
1333.footer-title {
1334  font-weight: 600;
1335  color: #1c1c1e;
1336}
1337
1338.footer-left,
1339.footer-right {
1340  display: flex;
1341  align-items: center;
1342  gap: 8px;
1343  white-space: nowrap;
1344}
1345
1346.footer-left {
1347  align-items: flex-start;
1348  flex-direction: column;
1349  gap: 3px;
1350  line-height: 1.1;
1351}
1352
1353.site-footer .footer-email {
1354  color: #8e8e93;
1355  font-weight: 400;
1356}
1357
1358.site-footer a {
1359  color: #007aff;
1360  text-decoration: none;
1361  font-weight: 500;
1362}
1363
1364.site-footer a:hover {
1365  text-decoration: underline;
1366}
1367
1368.footer-contact-button {
1369  appearance: none;
1370  margin: 0;
1371  padding: 0;
1372  color: #007aff;
1373  background: transparent;
1374  border: 0;
1375  font: inherit;
1376  font-weight: 500;
1377  cursor: pointer;
1378}
1379
1380.footer-contact-button:hover,
1381.footer-contact-button:focus-visible {
1382  text-decoration: underline;
1383}
1384
1385.footer-sep {
1386  opacity: 0.5;
1387}
1388</style>
1389
1390<footer class="site-footer">
1391  <div class="footer-inner">
1392    <div class="footer-left">
1393      <span class="footer-title">The 1520s Project</span>
1394      <a class="footer-email" href="mailto:[email protected]">[email protected]</a>
1395    </div>
1396
1397    <div class="footer-right">
1398      <button type="button" id="contact-form-open" class="footer-contact-button" aria-haspopup="dialog" aria-controls="contact-form-modal">Contact</button>
1399      <span class="footer-sep">·</span>
1400      <a href="https://github.com/benory/1520s-project-scores" target="_blank">
1401        GitHub
1402      </a>
1403    </div>
1404  </div>
1405</footer>
1406
1407
1408
1409<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
1409
1410
1411
1412<style>
1413body.contact-form-modal-open {
1414  overflow: hidden;
1415}
1416
1417.contact-form-modal[hidden],
1418.contact-form-form[hidden],
1419.contact-form-success[hidden] {
1420  display: none !important;
1421}
1422
1423.contact-form-modal {
1424  position: fixed;
1425  inset: 0;
1426  z-index: 1100;
1427  display: grid;
1428  place-items: center;
1429  box-sizing: border-box;
1430  padding: 24px;
1431}
1432
1433.contact-form-backdrop {
1434  position: absolute;
1435  inset: 0;
1436  background: rgba(15, 28, 38, 0.48);
1437}
1438
1439.contact-form-dialog {
1440  position: relative;
1441  z-index: 1;
1442  box-sizing: border-box;
1443  width: min(560px, 100%);
1444  max-height: min(720px, calc(100vh - 48px));
1445  overflow: auto;
1446  padding: 16px 18px 14px;
1447  color: #1e2b31;
1448  background: #ffffff;
1449  border: 1px solid #b7c2c8;
1450  border-radius: 6px;
1451  box-shadow: 0 18px 45px rgba(12, 24, 32, 0.26);
1452  font-size: 16px;
1453  text-align: left;
1454}
1455
1456.contact-form-modal--success .contact-form-dialog {
1457  max-width: 520px;
1458  padding: 26px 32px 28px;
1459  overflow: visible;
1460}
1461
1462.contact-form-header {
1463  display: flex;
1464  align-items: center;
1465  justify-content: space-between;
1466  gap: 16px;
1467  margin-bottom: 6px;
1468}
1469
1470.contact-form-header h2 {
1471  margin: 0;
1472  color: #123654;
1473  font-size: 20px;
1474  font-weight: 650;
1475  line-height: 1.2;
1476}
1477
1478.contact-form-close {
1479  appearance: none;
1480  display: inline-flex;
1481  align-items: center;
1482  justify-content: center;
1483  width: 32px;
1484  height: 32px;
1485  flex: 0 0 32px;
1486  margin: 0;
1487  padding: 0;
1488  color: #41515a;
1489  background: transparent;
1490  border: 1px solid transparent;
1491  border-radius: 4px;
1492  font: inherit;
1493  font-size: 25px;
1494  line-height: 1;
1495  cursor: pointer;
1496}
1497
1498.contact-form-close:hover,
1499.contact-form-close:focus-visible {
1500  color: #071f36;
1501  background: #eef3f5;
1502  border-color: #b9c4c8;
1503  outline: none;
1504}
1505
1506.contact-form-modal--success .contact-form-header {
1507  display: block;
1508  margin: 0;
1509}
1510
1511.contact-form-modal--success .contact-form-close {
1512  position: absolute;
1513  top: 13px;
1514  right: 13px;
1515  width: 28px;
1516  height: 28px;
1517  flex-basis: 28px;
1518  font-size: 24px;
1519}
1520
1521.contact-form-modal--success .contact-form-header h2 {
1522  position: absolute;
1523  width: 1px;
1524  height: 1px;
1525  margin: -1px;
1526  padding: 0;
1527  overflow: hidden;
1528  border: 0;
1529  clip: rect(0 0 0 0);
1530  white-space: nowrap;
1531}
1532
1533.contact-form-intro,
1534.contact-form-config-note,
1535.contact-form-status {
1536  font-size: 13.5px;
1537  line-height: 1.45;
1538}
1539
1540.contact-form-intro {
1541  margin: 0 0 11px;
1542  color: #40505a;
1543}
1544
1545.contact-form-form {
1546  margin: 0;
1547}
1548
1549.contact-form-success {
1550  margin: 0;
1551  padding-right: 28px;
1552}
1553
1554.contact-form-success-message {
1555  margin: 0 0 7px;
1556  color: #2f6f45;
1557  font-size: 20px;
1558  font-weight: 650;
1559  line-height: 1.2;
1560  white-space: nowrap;
1561}
1562
1563.contact-form-success-instruction {
1564  margin: 0;
1565  color: #40505a;
1566  font-size: 13.5px;
1567  line-height: 1.45;
1568}
1569
1570.contact-form-field {
1571  margin-bottom: 10px;
1572}
1573
1574.contact-form-field label {
1575  display: block;
1576  margin-bottom: 5px;
1577  color: #242b2f;
1578  font-size: 13.5px;
1579  font-weight: 650;
1580}
1581
1582.contact-form-field textarea,
1583.contact-form-field input[type="email"] {
1584  box-sizing: border-box;
1585  width: 100%;
1586  padding: 8px 9px;
1587  color: #1e2b31;
1588  background: #fbfcfc;
1589  border: 1px solid #b9c4c8;
1590  border-radius: 4px;
1591  font: inherit;
1592  font-size: 14px;
1593  line-height: 1.35;
1594}
1595
1596.contact-form-field textarea:focus,
1597.contact-form-field input[type="email"]:focus {
1598  background: #ffffff;
1599  border-color: #527895;
1600  box-shadow: 0 0 0 3px rgba(82, 120, 149, 0.18);
1601  outline: none;
1602}
1603
1604.contact-form-field textarea {
1605  min-height: 112px;
1606  resize: vertical;
1607}
1608
1609.contact-form-field--turnstile {
1610  min-height: 65px;
1611}
1612
1613.contact-form-config-note {
1614  margin: 2px 0 14px;
1615  padding: 8px 10px;
1616  color: #40505a;
1617  background: #f5f7f7;
1618  border: 1px solid #d4dde1;
1619  border-radius: 4px;
1620}
1621
1622.contact-form-actions {
1623  display: flex;
1624  flex-wrap: wrap;
1625  gap: 8px;
1626  align-items: center;
1627  justify-content: flex-end;
1628  margin-top: 2px;
1629}
1630
1631.contact-form-button {
1632  appearance: none;
1633  margin: 0;
1634  padding: 8px 12px;
1635  border: 1px solid #b9c4c8;
1636  border-radius: 4px;
1637  font: inherit;
1638  font-size: 14px;
1639  line-height: 1.2;
1640  cursor: pointer;
1641}
1642
1643.contact-form-cancel {
1644  color: #40505a;
1645  background: #ffffff;
1646}
1647
1648.contact-form-submit {
1649  color: #ffffff;
1650  background: #123654;
1651  border-color: #123654;
1652}
1653
1654.contact-form-submit:hover,
1655.contact-form-submit:focus-visible {
1656  color: #ffffff;
1657  background: #0a2a45;
1658  border-color: #0a2a45;
1659}
1660
1661.contact-form-form button[disabled] {
1662  opacity: 0.72;
1663  cursor: wait;
1664}
1665
1666.contact-form-status {
1667  margin-top: 7px;
1668  color: #40505a;
1669}
1670
1671.contact-form-status:empty {
1672  display: none;
1673}
1674
1675.contact-form-status--error {
1676  color: #a12a2a;
1677}
1678
1679@media (max-width: 600px) {
1680  .contact-form-modal {
1681    padding: 12px;
1682  }
1683
1684  .contact-form-dialog {
1685    max-height: calc(100vh - 24px);
1686    padding: 17px 16px 18px;
1687  }
1688
1689  .contact-form-success-message {
1690    white-space: normal;
1691  }
1692
1693  .contact-form-actions {
1694    justify-content: stretch;
1695  }
1696
1697  .contact-form-actions .contact-form-button {
1698    flex: 1 1 140px;
1699  }
1700}
1701</style>
1702
1703<div id="contact-form-modal" class="contact-form-modal" role="dialog" aria-modal="true" aria-labelledby="contact-form-title" aria-describedby="contact-form-intro" hidden>
1704  <div class="contact-form-backdrop" data-contact-form-close></div>
1705  <div class="contact-form-dialog" tabindex="-1">
1706    <div class="contact-form-header">
1707      <h2 id="contact-form-title">Contact The 1520s Project</h2>
1708      <button type="button" class="contact-form-close" aria-label="Close contact form" data-contact-form-close>&times;</button>
1709    </div>
1710
1711    <p id="contact-form-intro" class="contact-form-intro">Fields marked * are required.</p>
1712
1713    <form id="contact-form" class="contact-form-form" action="https://1520s-project-submissions.benjaminory.workers.dev/" method="POST" novalidate>
1714      <input type="hidden" name="category" value="Contact">
1715      <input id="contact-form-target" type="hidden" name="target" value="">
1716      <input id="contact-form-user-agent" type="hidden" name="user_agent" value="">
1717
1718      <div class="contact-form-field">
1719        <label for="contact-form-email">Email address *</label>
1720        <input id="contact-form-email" name="email" type="email" required autocomplete="email" placeholder="[email protected]">
1721      </div>
1722
1723      <div class="contact-form-field">
1724        <label for="contact-form-message">Message *</label>
1725        <textarea id="contact-form-message" name="description" rows="6" required placeholder="How can we help?"></textarea>
1726      </div>
1727
1728      
1729      <div class="contact-form-field contact-form-field--turnstile">
1730        <div id="contact-form-turnstile" class="cf-turnstile" data-sitekey="0x4AAAAAAD3NIL73ga8wkLDT" data-action="contact"></div>
1731      </div>
1732      
1733
1734      <div class="contact-form-actions">
1735        <button type="button" class="contact-form-button contact-form-cancel" data-contact-form-close>Cancel</button>
1736        <button type="submit" class="contact-form-button contact-form-submit">Send message</button>
1737      </div>
1738
1739      <div id="contact-form-status" class="contact-form-status" aria-l
1739ive="polite"></div>
1740    </form>
1741
1742    <div id="contact-form-success" class="contact-form-success" tabindex="-1" hidden>
1743      <p class="contact-form-success-message">Thank you for contacting The 1520s Project.</p>
1744      <p class="contact-form-success-instruction">Please close this popup to return to the site.</p>
1745    </div>
1746  </div>
1747</div>
1748
1749<script>
1750(function () {
1751  var modal = document.querySelector("#contact-form-modal");
1752  var openButton = document.querySelector("#contact-form-open");
1753  var form = document.querySelector("#contact-form");
1754
1755  if (!modal || !openButton || !form) {
1756    return;
1757  }
1758
1759  var dialog = modal.querySelector(".contact-form-dialog");
1760  var title = modal.querySelector("#contact-form-title");
1761  var intro = modal.querySelector("#contact-form-intro");
1762  var success = modal.querySelector("#contact-form-success");
1763  var email = form.querySelector("#contact-form-email");
1764  var message = form.querySelector("#contact-form-message");
1765  var target = form.querySelector("#contact-form-target");
1766  var userAgent = form.querySelector("#contact-form-user-agent");
1767  var status = form.querySelector("#contact-form-status");
1768  var submitButton = form.querySelector('[type="submit"]');
1769  var closeControls = modal.querySelectorAll("[data-contact-form-close]");
1770  var returnFocusElement = null;
1771  var focusableSelector = [
1772    "a[href]",
1773    "button:not([disabled])",
1774    "textarea:not([disabled])",
1775    "input:not([disabled])",
1776    '[tabindex]:not([tabindex="-1"])'
1777  ].join(",");
1778
1779  function isValidEmail(value) {
1780    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
1781  }
1782
1783  function setContext() {
1784    target.value = document.title + " — " + window.location.href;
1785    userAgent.value = navigator.userAgent;
1786  }
1787
1788  function setStatus(text, modifier) {
1789    status.className = modifier
1790      ? "contact-form-status contact-form-status--" + modifier
1791      : "contact-form-status";
1792    status.textContent = text;
1793  }
1794
1795  function resetTurnstile() {
1796    if (window.turnstile && typeof window.turnstile.reset === "function") {
1797      window.turnstile.reset("#contact-form-turnstile");
1798    }
1799  }
1800
1801  function showForm() {
1802    modal.classList.remove("contact-form-modal--success");
1803    title.textContent = "Contact The 1520s Project";
1804    intro.hidden = false;
1805    form.hidden = false;
1806    success.hidden = true;
1807    modal.setAttribute("aria-describedby", "contact-form-intro");
1808  }
1809
1810  function showSuccess() {
1811    modal.classList.add("contact-form-modal--success");
1812    title.textContent = "Thank you for contacting The 1520s Project.";
1813    intro.hidden = true;
1814    form.hidden = true;
1815    success.hidden = false;
1816    modal.setAttribute("aria-describedby", "contact-form-success");
1817    window.requestAnimationFrame(function () {
1818      success.focus();
1819    });
1820  }
1821
1822  function openModal() {
1823    returnFocusElement = document.activeElement;
1824    showForm();
1825    setContext();
1826    setStatus("");
1827    modal.hidden = false;
1828    document.body.classList.add("contact-form-modal-open");
1829    window.requestAnimationFrame(function () {
1830      message.focus();
1831    });
1832  }
1833
1834  function closeModal() {
1835    modal.hidden = true;
1836    document.body.classList.remove("contact-form-modal-open");
1837    if (returnFocusElement && typeof returnFocusElement.focus === "function") {
1838      returnFocusElement.focus();
1839    }
1840  }
1841
1842  function visibleFocusableElements() {
1843    return Array.prototype.slice.call(dialog.querySelectorAll(focusableSelector)).filter(function (element) {
1844      return element.offsetParent !== null;
1845    });
1846  }
1847
1848  function handleKeydown(event) {
1849    if (modal.hidden) {
1850      return;
1851    }
1852
1853    if (event.key === "Escape") {
1854      event.preventDefault();
1855      closeModal();
1856      return;
1857    }
1858
1859    if (event.key !== "Tab") {
1860      return;
1861    }
1862
1863    var focusable = visibleFocusableElements();
1864    if (!focusable.length) {
1865      event.preventDefault();
1866      dialog.focus();
1867      return;
1868    }
1869
1870    var first = focusable[0];
1871    var last = focusable[focusable.length - 1];
1872
1873    if (!focusable.includes(document.activeElement)) {
1874      event.preventDefault();
1875      (event.shiftKey ? last : first).focus();
1876    } else if (event.shiftKey && document.activeElement === first) {
1877      event.preventDefault();
1878      last.focus();
1879    } else if (!event.shiftKey && document.activeElement === last) {
1880      event.preventDefault();
1881      first.focus();
1882    }
1883  }
1884
1885  openButton.addEventListener("click", openModal);
1886  closeControls.forEach(function (control) {
1887    control.addEventListener("click", closeModal);
1888  });
1889  document.addEventListener("keydown", handleKeydown);
1890
1891  form.addEventListener("submit", async function (event) {
1892    event.preventDefault();
1893
1894    var endpoint = form.getAttribute("action").trim();
1895    var emailValue = email.value.trim();
1896    var messageValue = message.value.trim();
1897    var turnstileWidget = form.querySelector(".cf-turnstile");
1898    var tokenField = form.querySelector('[name="cf-turnstile-response"]');
1899    var turnstileToken = tokenField ? tokenField.value.trim() : "";
1900
1901    if (!endpoint) {
1902      setStatus("Contact form submissions are not configured yet.", "error");
1903      return;
1904    }
1905
1906    if (!emailValue || !messageValue) {
1907      setStatus("Please complete all required fields.", "error");
1908      return;
1909    }
1910
1911    if (!isValidEmail(emailValue)) {
1912      setStatus("Please enter a valid email address.", "error");
1913      return;
1914    }
1915
1916    if (turnstileWidget && !turnstileToken) {
1917      setStatus("Please complete the verification.", "error");
1918      return;
1919    }
1920
1921    setContext();
1922    setStatus("Sending message...");
1923    submitButton.disabled = true;
1924
1925    try {
1926      var response = await fetch(endpoint, {
1927        method: "POST",
1928        body: new FormData(form)
1929      });
1930      var result = await response.json().catch(function () {
1931        return {};
1932      });
1933
1934      if (!response.ok || !result.ok) {
1935        throw new Error(result.error || "Submission failed");
1936      }
1937
1938      form.reset();
1939      setContext();
1940      resetTurnstile();
1941      setStatus("");
1942      showSuccess();
1943    } catch (error) {
1944      console.error("Contact form submission failed:", error);
1945      resetTurnstile();
1946      setStatus("Sorry, your message could not be sent. Please try again.", "error");
1947    } finally {
1948      submitButton.disabled = false;
1949    }
1950  });
1951}());
1952</script>
1952
1953
1954</body>
1955
1956</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.