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>×</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.