diff --git a/_data/service.yml b/_data/service.yml index 51a4a963..86123e83 100644 --- a/_data/service.yml +++ b/_data/service.yml @@ -1,4 +1,6 @@ - organization: Maryland Defense Force + image: "/assets/img/mddf-dui.webp" + image_alt: "Maryland Defense Force distinctive unit insignia" role: Captain dates: 2006–Present · Captain since 2013 current: true @@ -7,6 +9,8 @@ detail_page: /service/maryland-defense-force/ - organization: Society of the War of 1812 in Maryland + image: "/assets/img/service/organizations/maryland-war-of-1812-seal.jpg" + image_alt: "Historic seal of the Maryland Society of the War of 1812" id: maryland-war-of-1812 role: Treasurer · Member, Board of Managers dates: 2025–Present @@ -23,6 +27,13 @@ since January 2025. I also serve as Treasurer. - organization: Maryland Society, Sons of the American Revolution + image: "/assets/img/service/organizations/sar-logo.jpg" + image_alt: "Sons of the American Revolution insignia" + image_credit: "SAR insignia: Steeznuts" + image_source: "https://commons.wikimedia.org/wiki/File:SAR_Logo_color.jpg" + image_license: "CC BY-SA 4.0" + image_license_url: "https://creativecommons.org/licenses/by-sa/4.0/" + image_changes: "Unmodified" id: maryland-sar role: Vice President, Charles Carroll of Carrollton Chapter dates: 2026–Present @@ -48,6 +59,8 @@ url: https://www.augustansociety.org/ - organization: Chapman and Hall / CRC Press + image: "/assets/img/service/organizations/crc-press.svg" + image_alt: "CRC Press logo" id: crc-analytics-or-series role: Series Editor, Focus Series in Analytics and Operations Research dates: 2020–Present @@ -66,6 +79,14 @@ url: https://www.mors.org/publications/mor-journal - organization: Howard County, Maryland + image: "/assets/img/news/thomas-viaduct.webp" + image_alt: "Thomas Viaduct in Howard County" + image_kind: "photo" + image_credit: "Photo: Jim Kuhn" + image_source: "https://commons.wikimedia.org/wiki/File:Thomas_Viaduct_Inside_Curve_(Relay,_MD).jpg" + image_license: "CC BY 2.0" + image_license_url: "https://creativecommons.org/licenses/by/2.0/" + image_changes: "Cropped" id: howard-county-board-of-appeals role: Chair and Member, Board of Appeals · Member, Charter Review Commission dates: 2011–2021 @@ -83,6 +104,14 @@ detail_page: /service/howard-county/ - organization: Columbia Association + image: "/assets/img/news/columbia-lakeK.webp" + image_alt: "Columbia lakefront at Lake Kittamaqundi" + image_kind: "photo" + image_credit: "Photo: Scott Saghirian" + image_source: "https://commons.wikimedia.org/wiki/File:Columbia_Lake_Front.jpg" + image_license: "CC BY 2.0" + image_license_url: "https://creativecommons.org/licenses/by/2.0/" + image_changes: "Cropped" id: columbia-association-finance role: Budget Committee Member and Chair · Financial Advisory Committee Member dates: 2004–2010 diff --git a/_data/settings.yml b/_data/settings.yml index 8559e8ee..ff703cb3 100644 --- a/_data/settings.yml +++ b/_data/settings.yml @@ -70,25 +70,27 @@ sites: footer: false order: 30 theme: cmna - image: https://jameshoward.us/assets/img/CMNA-cover-300.webp + # Image-only crop (38, 512, 1024, 1119) of the 1024px VitalSource cover, vbid 9781351646505. + image: /assets/img/projects/cmna-abacus.webp image_position: center 30% preview_title: CMNA preview_subtitle: Numerical analysis, built where you can see it - id: defenseor - title: Modern Defense Operations Research + title: Handbook of Military & Defense Operations Research kind: Book companion - description: The companion site for Modern Defense Operations Research, bringing the book, its authors, and supporting material together. + description: The companion site for Handbook of Military & Defense Operations Research, bringing the book, its authors, and supporting material together. url: https://defenseor.com/ target: _blank showcase: true footer: false order: 40 theme: defenseor - image: https://defenseor.com/assets/covers/second-edition.jpg - image_position: center 28% + # Image-only crop (180, 574, 854, 1028) of the 1024px VitalSource cover, vbid 9781040047033. + image: /assets/img/projects/mdor-operations.webp + image_position: center top preview_title: Defense OR - preview_subtitle: Modern Defense Operations Research + preview_subtitle: Handbook of Military & Defense Operations Research - id: dungeon title: The Manor of the Dungeon in Kent diff --git a/about-me.md b/about-me.md index b552f873..07f0a4c2 100644 --- a/about-me.md +++ b/about-me.md @@ -9,6 +9,8 @@ guid: http://localhost/wp-faculty/?page_id=2 +
+ :::figure{src="/assets/img/jhoward-greenglow.webp" alt="Me at the 2017 Howard County Library Evening in the Stacks" align="right" width="4" role="portrait"} Me at the 2017 Howard County Library Evening in the Stacks ::: @@ -33,6 +35,8 @@ assumptions, systems have constraints, and decisions have consequences. Much of the job is making those things visible—and explaining them clearly enough that a real person can act on them. +
+ ## Professional Work James is a data scientist at the [Johns Hopkins Applied Physics @@ -81,6 +85,8 @@ boards, professional-society work, the Maryland Defense Force, and long-running community and university service. These are places where the method is tested in the presence of actual rules, budgets, and constraints. +
+ ## Credentials and Recognition ### Education @@ -97,7 +103,10 @@ the presence of actual rules, budgets, and constraints. * College Park Scholars Citation, [International Studies](/assets/docs/JHoward-UMD-CPS.pdf) * Post Secondary Educational Options Program, Miami University -:::credential-grid +
+ +
+ ### Professional Titles * [European Engineer](/assets/docs/JHoward-EURING.pdf) @@ -106,6 +115,10 @@ the presence of actual rules, budgets, and constraints. * Chartered Mathematician * [Chartered IT Professional](/assets/docs/JHoward-FBCS.pdf) +
+ +
+ ### Certifications * [Project Management Professional](/assets/docs/certifications/JHoward-PMI-PMP.pdf) @@ -113,12 +126,16 @@ the presence of actual rules, budgets, and constraints. * [PMI Professional in Business Analysis](/assets/docs/certifications/JHoward-PMI-PBA.pdf) * [Certified Associate in Project Management](/assets/docs/certifications/JHoward-PMI-CAPM.pdf) * [Certified Government Financial Manager](/assets/docs/certifications/JHoward-CGFM.pdf) -::: +
+ +
### Professional Recognition ::professional-recognition +
+ ## Other Accomplishments * [Correspondence Chess Expert](/2024/01/22/i-earned-iccfs-correspondence-chess-expert-title) diff --git a/consulting.md b/consulting.md index b0b6d03b..0e8d24df 100644 --- a/consulting.md +++ b/consulting.md @@ -24,6 +24,8 @@ problem can become a data-engineering problem. A software system can become a governance problem. A policy question can collapse into an argument about assumptions. This is normal. +
+ ## What I Can Help With ### Analysis and Decision Support @@ -51,6 +53,8 @@ Sometimes the useful answer is not another model. I also review analyses, assumptions, methods, code, and technical claims, especially where the result will be used to make an operational, policy, or technical decision. +
+ ## What the Work Looks Like Most consulting problems start less neatly than the headings above. The data are @@ -73,6 +77,8 @@ through problems; [Books](/books/) contains the things that got entirely out of hand; and [Teaching](/teaching/) shows how I explain the machinery when somebody has to learn it. +
+ ## Work With Me Have a problem? Tell me what you are trying to do. @@ -81,3 +87,5 @@ If I can help, we can talk about what that would look like. If I cannot, I would rather tell you that than invent a reason I should. [Contact me →](/contact-me/) + +
diff --git a/games.md b/games.md index c91e88dc..cb733e95 100644 --- a/games.md +++ b/games.md @@ -5,7 +5,7 @@ date: 2018-02-19T16:33:40-05:00 author: James Howard layout: page guid: https://jameshoward.us/?page_id=6181 -image: /assets/images/games-header.webp +featured_image: /assets/img/news/games-header.webp menu-order: 91 menu-label: Games --- diff --git a/public/assets/css/books.css b/public/assets/css/books.css index eb712f04..6f60890c 100644 --- a/public/assets/css/books.css +++ b/public/assets/css/books.css @@ -1,281 +1,28 @@ -.article-body--flush-bottom { - min-height: 0; - padding-bottom: 0; -} - -.books-introduction { - margin: 0 0 2.5rem; - font-family: "Spectral", Georgia, serif; - line-height: 1.65; -} - -.books-section { - padding: 56px 0 64px; -} - -.books-featured-band { - width: 100vw; - margin-right: calc(50% - 50vw); - margin-left: calc(50% - 50vw); -} - -.books-other-band { - width: 100vw; - margin-right: calc(50% - 50vw); - margin-left: calc(50% - 50vw); - margin-bottom: -32px; - padding-bottom: 96px; - background: #2b3c52; - color: #fff; -} - -.books-other-band .other-book { - color: #fff; -} - -.books-other-band .book-card .book-card-kicker, -.other-book .book-card-kicker { - color: #feefa8; -} - -.books-other-band .other-book h3 { - color: #fff; -} - -.books-other-band .book-card-role { - color: rgba(255, 255, 255, 0.78); -} - -.books-other-band .book-links a { - color: #feefa8; -} - -.books-other-band .book-links a:hover { - color: #fff; -} - -.books-section .section-heading { - margin: 0 0 2.5rem; -} - -.books-section .section-heading h2 { - margin-bottom: 10px; -} - -.books-section .description { - font-family: "Spectral", Georgia, serif; - line-height: 1.55; -} - -.book-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 34px; -} - -.book-card { - display: grid; - grid-template-columns: minmax(130px, 36%) 1fr; - gap: 25px; - align-items: start; - padding: 28px; - background: #fff; - box-shadow: 0 8px 26px rgba(45, 42, 34, 0.08); -} - -.book-cover, -.book-detail-cover { - display: block; - background: #f5f2ea; -} - -.book-cover img, -.book-detail-cover img { - display: block; - width: 100%; - height: auto; - max-height: 390px; - margin: 0 auto; - object-fit: contain; - box-shadow: 0 5px 15px rgba(25, 24, 20, 0.18); -} - -.book-card-kicker { - margin: 0 0 7px; - color: #897b55; - font-family: "Cinzel", Georgia, serif; - font-size: 0.76em; - font-weight: 700; - letter-spacing: 0.05em; - text-transform: uppercase; -} - -.book-card h3 { - margin: 0 0 7px; - font-size: 1.4em; - line-height: 1.23; -} - -.book-card h3 a { - color: #2e2c28; -} - -.book-card h3 a:hover, -.book-links a:hover { - color: #1c5aac; -} - -.book-card .book-card-role, -.book-card .book-card-publisher, -.other-book .book-card-role, -.other-book .book-card-publisher { - margin: 0.2rem 0; - color: #615d53; - font-size: 0.94em; -} - -.book-card-role { - font-weight: 700; -} - -.book-card-copy > p:not(.book-card-kicker):not(.book-card-role):not(.book-card-publisher):not(.book-links) { - font-size: 0.9em; - margin-top: 1rem; - line-height: 1.58; -} - -.book-card .book-links, -.other-book .book-links { - display: flex; - flex-wrap: wrap; - gap: 0.85rem 1.1rem; - margin: 1.05rem 0 0; - font-weight: 700; -} - -.book-links a { - color: #285aa6; - text-decoration: none; - border-bottom: 1px solid currentColor; -} - -.other-book { +/* Book detail pages use the same palette and type as the catalog. */ +.book-detail { margin: 0 0 3rem; } +.book-detail-hero { display: grid; - grid-template-columns: 120px 1fr; - gap: 25px; + grid-template-columns: minmax(8rem, 14rem) minmax(0, 1fr); align-items: start; - padding: 25px 0 0; + gap: 2.5rem; + margin-bottom: 2rem; } - -.other-book img { +.book-detail-hero > img { display: block; width: 100%; height: auto; - box-shadow: 0 4px 12px rgba(25, 24, 20, 0.18); -} - -.other-book h3 { - margin-top: 0; -} - -.book-detail { - margin: 0 auto 3rem; -} - -.book-detail-hero { - display: grid; - grid-template-columns: minmax(180px, 280px) 1fr; - gap: 42px; - align-items: start; - margin-bottom: 3rem; -} - -.book-detail-cover img { - max-height: none; -} - -.book-detail-intro { - font-family: "Spectral", Georgia, serif; - font-size: 1.12em; - line-height: 1.62; -} - -.book-detail-section { - border-top: 1px solid #d8d0bf; - padding-top: 1.5rem; -} - -.book-detail-section h2 { - margin-top: 0; -} - -.book-facts { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 0; - margin: 0; -} - -.book-facts div { - padding: 0.7rem 1rem 0.7rem 0; - border-bottom: 1px solid #ebe6db; -} - -.book-facts dt { - color: #786f5f; - font-family: "Cinzel", Georgia, serif; - font-size: 0.72em; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; -} - -.book-facts dd { - margin: 0.25rem 0 0; -} - -.book-prior-editions { - margin: 1.25rem 0 0; - color: #615d53; -} - -@media (max-width: 991px) { - .book-grid { - grid-template-columns: 1fr; - } -} - -@media (max-width: 600px) { - .books-section { - padding: 40px 0; - } - - .book-card, - .book-detail-hero, - .other-book { - grid-template-columns: 1fr; - } - - .book-card { - gap: 20px; - padding: 22px; - } - - .book-cover { - width: min(220px, 70%); - margin: 0 auto; - } - - .book-detail-cover { - width: min(260px, 75%); - margin: 0 auto; - } - - .other-book img { - width: 150px; - } - - .book-facts { - grid-template-columns: 1fr; - } + padding: 1rem; + border-radius: 0; + background: var(--color-cream); + box-shadow: none; +} +.book-detail-hero p:first-child { margin-top: 0; } +.book-detail .facts { gap: 0; border: 0; border-top: 1px solid var(--color-slate-300); background: transparent; } +.book-detail .facts > div { padding: 1rem 1rem 1rem 0; border-bottom: 1px solid var(--color-slate-150); } +.book-detail .facts dt { color: var(--color-slate-700); font: 700 .8rem/1.5 var(--font-sans); } +.book-detail .facts dd { overflow-wrap: anywhere; } +@media (max-width: 48rem), (max-aspect-ratio: 4/5) { + .book-detail-hero { grid-template-columns: 1fr; gap: 1.5rem; } + .book-detail-hero > img { max-width: 14rem; } + .book-detail .facts { grid-template-columns: 1fr; } } diff --git a/public/assets/css/golden-honors.css b/public/assets/css/golden-honors.css index 31086712..9eba662e 100644 --- a/public/assets/css/golden-honors.css +++ b/public/assets/css/golden-honors.css @@ -1,63 +1,31 @@ -/* Detail pages for private heraldic and fraternal recognitions. */ -.golden-honor-introduction { - margin: 0 0 1.75rem; - color: #635e53; - font-family: "Spectral", Georgia, serif; - line-height: 1.65; -} - -.golden-honor > .section-heading { - margin: 4.25rem auto 2rem; - padding: 0; -} - -.golden-honor > .section-heading h2 { - color: #2e2c28; - text-align: center; -} +/* Documentary detail pages for private heraldic and fraternal recognitions. */ +.golden-honor-introduction { margin: 0 0 2rem; max-width: var(--reading-width); color: var(--color-slate-700); font-family: var(--font-serif); line-height: 1.65; } +.golden-honor > .section-heading { margin: 3.5rem 0 2rem; padding: 0; } +.golden-honor > .section-heading h2 { color: var(--color-navy-900); text-align: left; } +.golden-honor > :is(p, ul) { max-width: var(--reading-width); } .golden-honor > p { line-height: 1.72; } - .golden-honor-record { - margin: 4rem calc(50% - 50vw); + margin: 4rem 0; padding: 3.5rem 0; - background: #283f5c; - color: #fff; -} - -.golden-honor-record .section-heading { - margin: 0 auto; - padding: 0; -} - -.golden-honor-record h2 { - margin-top: 0; - color: #f0d660; - text-align: center; -} - -.golden-honor-record .row { margin: 2.25rem 0; } -.golden-honor-record .golden-honor-document { display: block; color: #feefa8; font-family: Lato, sans-serif; font-size: .9em; font-weight: 700; text-align: center; } -.golden-honor-document img { display: block; width: 100%; margin: 0 0 1rem; border: 1px solid rgba(255,255,255,.3); } -.golden-honor-document:hover img { border-color: #feefa8; } - -.golden-honor-facts { - margin: 0; - padding: 1.5rem 1.75rem; - border: 1px solid rgba(255,255,255,.28); - background: rgba(13,30,49,.32); -} -.golden-honor-facts dt { color: #f0d660; font-family: Lato, sans-serif; font-size: .77em; letter-spacing: .04em; text-transform: uppercase; } -.golden-honor-facts dd { margin: 0 0 1rem; line-height: 1.45; } + background: var(--color-navy-950); + color: var(--color-cream); + box-shadow: 0 0 0 100vmax var(--color-navy-950); + clip-path: inset(0 -100vmax); +} +.golden-honor-record .section-heading { margin: 0 0 2rem; padding: 0; } +.golden-honor-record h2 { margin-top: 0; color: var(--color-gold-300); text-align: left; } +.golden-honor-record .content-grid { gap: 2rem; } +.golden-honor-document { display: block; color: var(--color-gold-300); font: 700 .9rem/1.5 var(--font-sans); } +.golden-honor-document img { display: block; width: 100%; margin: 0 0 1rem; border: 1px solid rgb(255 255 255 / 30%); border-radius: 0; } +.golden-honor-document:hover img { border-color: var(--color-gold-300); } +.golden-honor-facts { margin: 0; padding: 1.5rem; border: 1px solid rgb(255 255 255 / 28%); background: var(--color-navy-800); } +.golden-honor-facts dt { color: var(--color-gold-300); font: 700 .8rem/1.5 var(--font-sans); } +.golden-honor-facts dd { margin: .3rem 0 1rem; line-height: 1.5; } .golden-honor-facts dd:last-child { margin-bottom: 0; } -.golden-honor-record .container > p { font-family: "Spectral", Georgia, serif; line-height: 1.65; } - +.golden-honor-record :is(p, a) { color: var(--color-cream); } .golden-honor > ul { padding-left: 1.35rem; } .golden-honor > ul li { margin-bottom: .85rem; line-height: 1.55; } - -@media (max-width: 767px) { - .golden-honor > .section-heading { margin-top: 3.25rem; } - .golden-honor-record { margin-top: 3rem; margin-bottom: 3rem; padding-top: 2.5rem; padding-bottom: 2.5rem; } - .golden-honor-record .row { margin: 1.75rem auto; } - .golden-honor-record .col-md-6 + .col-md-6 { margin-top: 1.75rem; } - .golden-honor-facts { padding: 1.25rem; } +@media (max-width: 48rem), (max-aspect-ratio: 4/5) { + .golden-honor-record { margin-block: 3rem; padding-block: 2.5rem; } + .golden-honor-record .content-grid__item { flex-basis: 100%; } } diff --git a/public/assets/css/mddf.css b/public/assets/css/mddf.css index 0c5dd477..42047ddb 100644 --- a/public/assets/css/mddf.css +++ b/public/assets/css/mddf.css @@ -4,28 +4,28 @@ gap: 0.45rem 1.1rem; margin: 2rem 0 3rem; padding: 1rem 0; - border-top: 1px solid #d8d0bf; - border-bottom: 1px solid #d8d0bf; + border-top: 1px solid var(--color-slate-300); + border-bottom: 1px solid var(--color-slate-300); font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 0.92em; font-weight: 700; } .mddf-toc a { - color: #154c9e; + color: var(--color-blue-600); text-decoration: none; } .mddf-toc a:hover, .mddf-toc a:focus { - color: #6e5b25; + color: var(--color-gold-700); text-decoration: underline; } .content h2[id], .content h3[id], .content h4[id] { - scroll-margin-top: 5rem; + scroll-margin-top: 6rem; } .content > h2 { @@ -86,7 +86,7 @@ .content .ribbon-grid .ribbon:focus-visible, .content .ribbonbar .ribbon:focus-visible { - outline: 2px solid #154c9e; + outline: 2px solid var(--color-blue-600); outline-offset: 2px; } @@ -119,13 +119,13 @@ .content blockquote { margin: 1rem 0 1.5rem; padding: 0.9rem 1.2rem; - border-left: 3px solid #c5a47e; - background: #faf8f3; - color: #393631; + border-left: 3px solid var(--color-gold-500); + background: var(--color-cream); + color: var(--color-ink); } .content strong { - color: #4b432f; + color: var(--color-navy-900); } @media (max-width: 600px) { diff --git a/public/assets/img/projects/cmna-abacus.webp b/public/assets/img/projects/cmna-abacus.webp new file mode 100644 index 00000000..7dc8543c Binary files /dev/null and b/public/assets/img/projects/cmna-abacus.webp differ diff --git a/public/assets/img/projects/mdor-operations.webp b/public/assets/img/projects/mdor-operations.webp new file mode 100644 index 00000000..130f47e1 Binary files /dev/null and b/public/assets/img/projects/mdor-operations.webp differ diff --git a/public/assets/img/service/organizations/README.md b/public/assets/img/service/organizations/README.md new file mode 100644 index 00000000..977c7e99 --- /dev/null +++ b/public/assets/img/service/organizations/README.md @@ -0,0 +1,22 @@ +# Service card image provenance + +Reviewed 2026-10-06. Images are used to identify organizations in James Howard's +service record. Marks remain in their original colors and proportions; their use +does not suggest organizational endorsement of the website. + +| Card | Local asset | Source and reuse basis | Display changes | +| --- | --- | --- | --- | +| Maryland Defense Force | `/assets/img/mddf-dui.webp` (existing) | [Commons description](https://commons.wikimedia.org/wiki/File:Maryland_Defense_Force_distinctive_unit_insignia.svg), uploaded by Howardjp from this website; Commons lists public-domain status. Reuses the existing site insignia. | Contained, not cropped. | +| Maryland Society of the War of 1812 | `maryland-war-of-1812-seal.jpg` | [Commons description](https://commons.wikimedia.org/wiki/File:Seal_of_the_Maryland_Society_of_the_War_of_1812.jpg); unknown artist, published in the Society's 1914 proceedings; public domain in the United States due to expired copyright. | Original downloaded file; contained, not cropped. | +| Sons of the American Revolution | `sar-logo.jpg` | [Commons description](https://commons.wikimedia.org/wiki/File:SAR_Logo_color.jpg); Steeznuts; [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). | Original downloaded file; contained, not cropped. Original license retained. | +| CRC Press | `crc-press.svg` | [Commons description](https://commons.wikimedia.org/wiki/File:CRC_Press_logo.svg); CRC Press; public-domain text logo (PD-textlogo). Trademark ownership remains with CRC Press. | Original downloaded file; contained, not cropped. | +| Howard County Board of Appeals | `/assets/img/news/thomas-viaduct.webp` (existing) | [Thomas Viaduct Inside Curve (Relay, MD)](https://commons.wikimedia.org/wiki/File:Thomas_Viaduct_Inside_Curve_(Relay,_MD).jpg); Jim Kuhn; [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/). | Existing WebP conversion and crop; cropped further by the thumbnail frame. | +| Columbia Association | `/assets/img/news/columbia-lakeK.webp` (existing) | [Columbia Lake Front](https://commons.wikimedia.org/wiki/File:Columbia_Lake_Front.jpg); Scott Saghirian; [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/). | Existing WebP conversion and crop; cropped further by the thumbnail frame. | + +Creative Commons credits, source links, license links, and alteration notes appear +on the corresponding service cards. The new SAR file is distributed unchanged +under CC BY-SA 4.0; the website's code and other images retain their own licenses. + +Official-site Augustan, MORS, UMGC, and other marks were considered but were not +included without sufficiently clear reuse terms. No placeholder image is required +for a card without an approved asset. Unselected downloads are not committed. diff --git a/public/assets/img/service/organizations/crc-press.svg b/public/assets/img/service/organizations/crc-press.svg new file mode 100644 index 00000000..9a5d8fe9 --- /dev/null +++ b/public/assets/img/service/organizations/crc-press.svg @@ -0,0 +1,2 @@ + + diff --git a/public/assets/img/service/organizations/maryland-war-of-1812-seal.jpg b/public/assets/img/service/organizations/maryland-war-of-1812-seal.jpg new file mode 100644 index 00000000..4ad64604 Binary files /dev/null and b/public/assets/img/service/organizations/maryland-war-of-1812-seal.jpg differ diff --git a/public/assets/img/service/organizations/sar-logo.jpg b/public/assets/img/service/organizations/sar-logo.jpg new file mode 100644 index 00000000..fc6afa89 Binary files /dev/null and b/public/assets/img/service/organizations/sar-logo.jpg differ diff --git a/public/assets/seasonal/css/america250.css b/public/assets/seasonal/css/america250.css index f1c6660d..fd33c3b0 100644 --- a/public/assets/seasonal/css/america250.css +++ b/public/assets/seasonal/css/america250.css @@ -36,7 +36,8 @@ body.america250-mode .home-hero::before { filter: drop-shadow(0 3px 5px rgb(0 0 0 / 45%)); } -body.america250-mode .home-hero .home-grid { +body.america250-mode .home-hero .home-hero__top, +body.america250-mode .home-hero .home-hero__blog { position: relative; z-index: 3; } diff --git a/public/assets/seasonal/css/halloween.css b/public/assets/seasonal/css/halloween.css index 9b6b519b..464c7272 100644 --- a/public/assets/seasonal/css/halloween.css +++ b/public/assets/seasonal/css/halloween.css @@ -22,7 +22,8 @@ body.halloween-mode .home-hero::before { z-index: 1; } -body.halloween-mode .home-hero .home-grid { +body.halloween-mode .home-hero .home-hero__top, +body.halloween-mode .home-hero .home-hero__blog { z-index: 3; } diff --git a/public/assets/seasonal/js/halloween.js b/public/assets/seasonal/js/halloween.js index dde50dab..a4507005 100644 --- a/public/assets/seasonal/js/halloween.js +++ b/public/assets/seasonal/js/halloween.js @@ -33,7 +33,7 @@ const homeHero = document.querySelector(".home-hero"); if (!homeHero) return; - const arms = homeHero.querySelector(".home-grid > img"); + const arms = homeHero.querySelector(".home-hero__arms"); if (arms) { arms.dataset.defaultSrc = arms.getAttribute("src") || ""; arms.setAttribute("src", ASSETS.arms); diff --git a/scholarship.md b/scholarship.md index 19d094c4..c170bc3e 100644 --- a/scholarship.md +++ b/scholarship.md @@ -27,8 +27,12 @@ the person using it cannot tell what it means. The selected work below is a way into those recurring questions; the complete record remains below it for anyone who needs the citations. +
+ ## Selected Work +
+ ### Forecasting, Artificial Intelligence, and Complex Systems **[Crystal Cube: Forecasting Disruptive Events](https://doi.org/10.1080/08839514.2021.2001179)** (2021, with Anna L. Buczak and colleagues), _Applied Artificial Intelligence_. Crystal Cube treated disruption forecasting as a practical systems problem: combine disparate signals, make the model useful, and confront the difference between a prediction and an operational decision. @@ -37,28 +41,54 @@ who needs the citations. **[Forecasting Artificial Earth Satellite Populations](/2018/07/30/forecasting-artificial-earth-satellite-populations/)** (2018), Joint Statistical Meetings. Satellite populations are a small example of a larger habit: find the mechanism behind a number that looks simple, then model the mechanism rather than merely extrapolating the number. +
+ +
+ ### Risk, Policy, and Operational Decisions +
+ Cover of Socioeconomic Effects of the National Flood Insurance Program +
+ **[Socioeconomic Effects of the National Flood Insurance Program](/books/socioeconomic-effects-national-flood-insurance-program/)** (2016). This book grew from research on the policy, economic, and social consequences of a federal insurance program that exists precisely because risk refuses to stay inside a spreadsheet. It remains the clearest single record of the public-policy side of this work. **[Handbook of Military and Defense Operations Research](/books/handbook-military-defense-operations-research/)** (2nd ed., 2024, co-editor with Natalie M. Scala). Operations research is what happens when good intentions encounter limited time, limited resources, and a decision that cannot be postponed. This volume brings together work on those problems in defense and security settings. **[Mixed Reality for Post-Disaster Situational Awareness](https://www.jhuapl.edu/Content/techdigest/pdf/V35-N03/35-03-Howard.pdf)** (2020, with Arthur O. Tucker IV and colleagues), _Johns Hopkins APL Technical Digest_. A post-disaster system has to make information usable under conditions that are actively hostile to usability. The work joined sensing, visualization, and response rather than treating any one of them as sufficient. +
+ +
+ ### Mathematics, Statistics, and Scientific Computing +
+ Cover of Computational Methods for Numerical Analysis with R +
+ **[Computational Methods for Numerical Analysis with R](/books/computational-methods-numerical-analysis-r/)** (2017). Numerical analysis becomes much more interesting when the computation is visible. This book uses R not as decorative software around mathematics, but as a way to make approximation, error, and algorithmic choices concrete. **[Phonetic Spelling Algorithm Implementations for R](https://www.jstatsoft.org/article/view/v095i08)** (2020), _Journal of Statistical Software_. A scholarly software paper on the `phonics` R package, which implements phonetic algorithms for record linkage and related work. It is a good example of software treated as a method that should be inspectable, reusable, and citable. **[Blockchain Compliance with Federal Cryptographic Information Processing Standards](https://ieeexplore.ieee.org/document/8965252)** (2020, with Maria E. Vachino), _IEEE Security & Privacy_. This paper asks the unglamorous but necessary question behind a technical proposal: whether a blockchain design can satisfy the standards and governance requirements of the system it is supposed to serve. +
+ +
+ ### Quantitative Learning and Technical Communication **[Teaching and Learning Mathematics Online](/books/teaching-learning-mathematics-online/)** (2nd ed., 2025, co-editor with John F. Beyers). Online mathematics education is not a matter of putting a textbook behind a login screen. The work in this volume concerns what students can actually do, see, test, and explain when the classroom is distributed. **[Teaching STEM Online at the Tertiary Level During the COVID-19 Pandemic](https://doi.org/10.1080/0020739X.2021.1954251)** (2023, with Mina Sedaghatjou and colleagues), _International Journal of Mathematical Education in Science and Technology_. The pandemic forced the question in real time: which parts of technical teaching are genuinely portable, and which have to be redesigned when the room disappears? +
+ +
+ +
+ ## Complete Scholarly Record @@ -72,3 +102,5 @@ who needs the citations. ::fragment{name="grants"} + +
diff --git a/scripts/validate-build.mjs b/scripts/validate-build.mjs index f283c4f3..b37c2941 100644 --- a/scripts/validate-build.mjs +++ b/scripts/validate-build.mjs @@ -183,15 +183,25 @@ const ancestrySubtitleSearchRecord = searchRecords.find((item) => item.url === a if (!ancestrySubtitleSearchRecord || !ancestrySubtitleSearchRecord.excerpt.startsWith('Member jure Jeremiah Cloud')) failures.push(`${ancestrySubtitleRoute}: search excerpt contains Markdown instead of plain text`); const homepage = await readFile(path.join(dist, 'index.html'), 'utf8'); -const homepageSections = ['subjects', 'work', 'elsewhere', 'blog'].map((section) => homepage.indexOf(`data-home-section="${section}"`)); +const homepageSections = ['blog', 'projects', 'subjects', 'elsewhere'].map((section) => homepage.indexOf(`data-home-section="${section}"`)); if (homepageSections.some((position) => position < 0) || homepageSections.some((position, index) => index > 0 && position <= homepageSections[index - 1])) failures.push('homepage: discovery sections are missing or out of order'); if (homepage.includes('Explore My World') || homepage.includes('The working archive') || homepage.includes('jh-card--featured')) failures.push('homepage: legacy six-card destination architecture remains'); +if (homepage.includes('data-home-section="work"') || homepage.includes('Browse the Work')) failures.push('homepage: redundant main-menu destination section remains'); +const homepageSubjects = [...homepage.matchAll(/]*data-home-subject[^>]*href="([^"]+)"/g)].map((match) => match[1]); +if (homepageSubjects.length !== generatedSubjects.subjects.length || new Set(homepageSubjects).size !== generatedSubjects.subjects.length) failures.push('homepage: Subject index does not cover every canonical Subject exactly once'); +for (const subject of generatedSubjects.subjects) if (!homepageSubjects.includes(subject.route)) failures.push(`homepage: missing Subject index entry for ${subject.route}`); +const homepageArchives = [...homepage.matchAll(/]*data-home-archive="[^"]+"[^>]*href="([^"]+)"/g)].map((match) => match[1]); +if (homepageArchives.length !== 4 || new Set(homepageArchives).size !== 4) failures.push('homepage: expected four distinct archive destinations'); +const homepageProjects = [...homepage.matchAll(/]*data-home-project="([^"]+)"[^>]*href="([^"]+)"/g)]; +const projectRegistry = JSON.parse(await readFile(path.join(root, '.generated/data/site.json'), 'utf8')).settings.sites.filter((project) => project.showcase); +if (homepageProjects.length !== projectRegistry.length || new Set(homepageProjects.map((match) => match[1])).size !== projectRegistry.length) failures.push('homepage: showcased projects are missing or duplicated'); +for (const project of projectRegistry) if (!homepageProjects.some((match) => match[1] === project.id && match[2] === project.url)) failures.push(`homepage: missing canonical project ${project.id}`); for (const subject of generatedSubjects.subjects) if (!homepage.includes(`href="${subject.route}"`)) failures.push(`homepage: missing canonical Subject ${subject.route}`); for (const route of ['/writing/', '/scholarship/', '/books/', '/software/', '/teaching/', '/service/', '/consulting/', '/ancestry/', '/honors/', '/coat-of-arms/', '/media/', '/blog/']) { if (!homepage.includes(`href="${route}"`)) failures.push(`homepage: missing destination ${route}`); } -const homepagePostRoutes = [...homepage.matchAll(/class="card-media" href="([^"]+)"/g)].map((match) => match[1]); -if (homepagePostRoutes.length !== 3) failures.push(`homepage: expected 3 recent Blog posts, found ${homepagePostRoutes.length}`); +const homepagePostRoutes = [...homepage.matchAll(/data-home-post href="([^"]+)"/g)].map((match) => match[1]); +if (homepagePostRoutes.length !== 4) failures.push(`homepage: expected 4 recent Blog posts, found ${homepagePostRoutes.length}`); for (const route of homepagePostRoutes) if (!searchRecords.some((record) => record.type === 'Blog' && record.url === route)) failures.push(`homepage: recent post is not in the Blog collection: ${route}`); const homepageJsonLd = [...homepage.matchAll(/ diff --git a/src/components/SubjectDirectory.astro b/src/components/SubjectDirectory.astro index fe6872a6..b8585959 100644 --- a/src/components/SubjectDirectory.astro +++ b/src/components/SubjectDirectory.astro @@ -21,8 +21,6 @@ const Heading = heading; diff --git a/src/components/SubjectLinks.astro b/src/components/SubjectLinks.astro index 5961db2c..0bdca450 100644 --- a/src/components/SubjectLinks.astro +++ b/src/components/SubjectLinks.astro @@ -25,12 +25,12 @@ const links = subjects.filter((subject) => subject.slug !== current); diff --git a/src/components/TableOfContents.astro b/src/components/TableOfContents.astro index 8326f049..3e2d03fa 100644 --- a/src/components/TableOfContents.astro +++ b/src/components/TableOfContents.astro @@ -3,4 +3,4 @@ const { headings=[] } = Astro.props; const shown = headings.filter((h:any)=>h.depth>=2&&h.depth<=3); --- {shown.length>1 && } - + diff --git a/src/components/TeachingInstitution.astro b/src/components/TeachingInstitution.astro new file mode 100644 index 00000000..d3e093a3 --- /dev/null +++ b/src/components/TeachingInstitution.astro @@ -0,0 +1,58 @@ +--- +import katex from 'katex'; +import { slugifyTag } from '../lib/site'; +const { institution, dark = false } = Astro.props; +const escapeHtml = (text: string) => text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"'); +const descriptionHtml = (text: string) => text.split(/\[latex\]([\s\S]*?)\[\/latex\]/g).map((part, index) => + index % 2 ? katex.renderToString(part, { throwOnError: true }) : escapeHtml(part) +).join(''); +const courseId = (code: string) => `teaching-${slugifyTag(institution.id)}-${slugifyTag(code)}`; +--- +
+
+

{institution.name}

+ +
+
+ {institution.courses.map((course: any) => { + const documents = course.syllabi?.filter((syllabus: any) => syllabus.file).length ?? 0; + return
+ + {course.code}{course.title}{documents ? `${documents} syllabus ${documents === 1 ? 'file' : 'files'}` : 'Course description'} + + +
+

+ {course.syllabi?.length > 0 &&

    {course.syllabi.map((syllabus: any) =>
  • {syllabus.file ? {syllabus.label} : }
  • )}
} +
+
; + })} +
+
+ diff --git a/src/components/WritingDirectory.astro b/src/components/WritingDirectory.astro index dc01bad0..022ca032 100644 --- a/src/components/WritingDirectory.astro +++ b/src/components/WritingDirectory.astro @@ -22,8 +22,6 @@ const Heading = heading; diff --git a/src/components/WritingLinks.astro b/src/components/WritingLinks.astro index a946bfc3..b552e449 100644 --- a/src/components/WritingLinks.astro +++ b/src/components/WritingLinks.astro @@ -18,7 +18,7 @@ const links = [ + diff --git a/src/pages/[...route].astro b/src/pages/[...route].astro index b7d8a73d..285add28 100644 --- a/src/pages/[...route].astro +++ b/src/pages/[...route].astro @@ -4,6 +4,8 @@ import BaseLayout from '../layouts/BaseLayout.astro'; import PageIntro from '../ import siteData from '../../.generated/data/site.json'; import writingData from '../../.generated/data/writing.json'; import subjectsData from '../../.generated/data/subjects.json'; import {formatDate,isPublished,normalizeAssetPath,routeParam,slugifyTag} from '../lib/site'; import { subjectsForRoute } from '../lib/subjects'; import { plainInlineMarkdown } from '../lib/markdown'; +import ScholarshipIndex from '../components/ScholarshipIndex.astro'; +import PageIndex from '../components/PageIndex.astro'; export async function getStaticPaths(){ const blog=(await getCollection('blog')).filter(p=>isPublished(p.data));const ancestry=await getCollection('ancestry');const pages=await getCollection('pages');const entries=[...blog.map(e=>({entry:e,kind:'blog'})),...ancestry.map(e=>({entry:e,kind:'ancestry'})),...pages.map(e=>({entry:e,kind:'page'}))]; const native=['/','/blog/','/ancestry/','/books/','/honors/','/media/','/service/','/software/','/teaching/','/search/','/404.html','/contact-me/','/contact-me.html','/feed.xml','/atom.xml','/sitemap.xml',...writingData.routes,...subjectsData.routes];const occupied=new Set([...native,...entries.map(x=>x.entry.data.route)]); @@ -16,19 +18,24 @@ if(kind==='redirect') return Astro.redirect((Astro.props as any).to,301); if(entry?.data?.redirect_to) return Astro.redirect(entry.data.redirect_to,301); const {Content,headings}=await render(entry);const data:any=entry.data;const image=normalizeAssetPath(data.featured_image);const ancestryServices=(data.service_ids??[]).map((id:string)=>siteData.service.find((r:any)=>r.id===id)).filter((r:any)=>r?.ancestry);const hasManualToc=entry.body?.includes('class="page-toc"');const hasInlineToc=hasManualToc||entry.body?.includes('aria-label="On this page"');const hasContentFlow=entry.body?.includes('class="content-flow"');const isArmory=Boolean(data.armory_section);const wideContent=kind==='ancestry'||hasManualToc||isArmory;const relatedTags=[...new Set([...String(entry.body??'').matchAll(/::related-posts\{[^}]*tag=(["'])(.*?)\1[^}]*\}/g)].map((match:any)=>match[2]))]; const description=plainInlineMarkdown(data.description??data.excerpt??data.subtitle??'');const seriesMemberships=kind==='blog'?writingData.series.filter((series:any)=>series.posts.some((post:any)=>post.route===data.route)):[];const isScholarship=kind==='page'&&data.route==='/scholarship/';const resourceSubjects=subjectsForRoute(subjectsData.subjects,data.route);const nestedBook=data.route.match(/^\/books\/([^/]+)\/.+\/$/);const owningBook=nestedBook?siteData.books.published.find((book:any)=>book.slug===nestedBook[1]):undefined;const parentNavigation=kind==='ancestry'?{href:'/ancestry/',label:'Ancestry Research Index'}:owningBook?{href:owningBook.detail_url,label:owningBook.title}:kind==='page'&&/^\/books\/[^/]+\/$/.test(data.route)?{href:'/books/',label:'All Books'}:kind==='page'&&data.route.startsWith('/honors/')?{href:'/honors/',label:'Honors'}:kind==='page'&&data.route.startsWith('/service/')?{href:'/service/',label:'Service'}:kind==='page'&&data.route.startsWith('/games/')?{href:'/games/',label:'Games'}:undefined;const hasConnections=kind==='blog'||resourceSubjects.length>0||Boolean(parentNavigation);const escapeHtml=(value:string)=>value.replaceAll('&','&').replaceAll('<','<').replaceAll('>','>').replaceAll('"','"');const creditsHtml=data.credits?escapeHtml(String(data.credits)).replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g,'$1').replace(/^_(.*)_$/s,'$1'):undefined; +const usePageIndex=kind!=='blog'&&!wideContent&&!hasInlineToc&&!isArmory&&!isScholarship&&headings.filter((h:any)=>h.depth>=2&&h.depth<=3).length>1; --- {isArmory&&} {kind==='blog'&&data.tags?.length>0&&}
-
+
+ {isScholarship&&} + {usePageIndex&&} +
{kind==='blog'&&data.historical_status&&} {seriesMemberships.map((series:any)=>)} - {!hasInlineToc&&!isArmory&&} -
+ {!hasInlineToc&&!isArmory&&!isScholarship&&!usePageIndex&&} +
{ancestryServices.map((role:any,i:number)=>

Society service and leadership

{role.ancestry.heading}

{role.ancestry.summary}

View the broader service record →
)} {creditsHtml&&

} +
{isScholarship&&
} @@ -40,4 +47,4 @@ const description=plainInlineMarkdown(data.description??data.excerpt??data.subti } {wideContent&&}
- + diff --git a/src/pages/ancestry/index.astro b/src/pages/ancestry/index.astro index 2ed41c4b..c001ef14 100644 --- a/src/pages/ancestry/index.astro +++ b/src/pages/ancestry/index.astro @@ -4,4 +4,4 @@ const records=(await getCollection('ancestry')).sort((a,b)=>a.data.title.localeC const searchable=(r:any)=>[r.data.title,r.data.subtitle,r.data.member_number,...(r.data.qualifying_ancestors??[]),...(r.data.places??[]),...(r.data.topics??[])].filter(Boolean).join(' ').toLowerCase(); ---

Genealogy is mostly an exercise in refusing to believe family stories until the paperwork shows up. This section collects historical genealogy as a series of case studies in documented descent.

Ambiguity stays visible. A tidy story is not a substitute for a defensible one.

Showing all {records.length} records.

{groups.map(([kind,label])=>
{records.filter(r=>r.data.class===kind).map(r=>{const d:any=r.data;return

{d.title}

{d.qualifying_ancestors?.length>0&&<>

Jure

{d.qualifying_ancestors.join(', ')}

}{d.member_number&&}View record →
})}
)}
- + diff --git a/src/pages/books/index.astro b/src/pages/books/index.astro index b07f93cb..47fb897d 100644 --- a/src/pages/books/index.astro +++ b/src/pages/books/index.astro @@ -13,9 +13,17 @@ const relatedSubjects = subjectsData.subjects.filter((subject) => ['mathematics-
-
{siteData.books.published.map((book:any)=>{book.cover_alt}

{book.edition} · {book.year}

{book.title}

)}

+
{siteData.books.published.map((book:any)=>{book.cover_alt}

{book.edition} · {book.year}

{book.title}

)}

- + diff --git a/src/pages/contact-me/index.astro b/src/pages/contact-me/index.astro index 3763c664..c904bcf7 100644 --- a/src/pages/contact-me/index.astro +++ b/src/pages/contact-me/index.astro @@ -21,4 +21,4 @@ import PageIntro from '../../components/PageIntro.astro'; - + diff --git a/src/pages/honors/index.astro b/src/pages/honors/index.astro index f4b3973d..80506875 100644 --- a/src/pages/honors/index.astro +++ b/src/pages/honors/index.astro @@ -1,5 +1,5 @@ --- -import BaseLayout from '../../layouts/BaseLayout.astro';import PageIntro from '../../components/PageIntro.astro';import DataCard from '../../components/DataCard.astro';import SectionHeading from '../../components/SectionHeading.astro';import siteData from '../../../.generated/data/site.json';const groups=[['professional','Professional Recognition'],['civic','Civic and Service Recognition'],['heraldic','Heraldic Recognition'],['ceremonial','Orders and Ceremonial Honors'],['peerage','Peerages and Titles'],['other','Other Distinctions']]; +import BaseLayout from '../../layouts/BaseLayout.astro';import PageIntro from '../../components/PageIntro.astro';import DataCard from '../../components/DataCard.astro';import SectionHeading from '../../components/SectionHeading.astro';import SectionNav from '../../components/SectionNav.astro';import siteData from '../../../.generated/data/site.json';const groups=[['professional','Professional Recognition'],['civic','Civic and Service Recognition'],['heraldic','Heraldic Recognition'],['ceremonial','Orders and Ceremonial Honors'],['peerage','Peerages and Titles'],['other','Other Distinctions']]; const usefulQualifiers=new Set(['Kentucky Colonel','President’s Volunteer Service Award — Bronze','Maryland Defense Force Meritorious Service Ribbon','Maryland Defense Force Commendation Ribbon','Grant of Arms','Knight Companion']); --- -{groups.map(([kind,label],index)=>
{siteData.honors.filter((x:any)=>x.category===kind).map((x:any)=>)}
)}
+({href:`#honors-${kind}`,label}))}/>{groups.map(([kind,label],index)=>
{siteData.honors.filter((x:any)=>x.category===kind).map((x:any)=>)}
)}
diff --git a/src/pages/index.astro b/src/pages/index.astro index 77c51098..faa9f08c 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,162 +1,349 @@ --- import { getCollection } from 'astro:content'; import BaseLayout from '../layouts/BaseLayout.astro'; -import DestinationDirectory from '../components/DestinationDirectory.astro'; -import HeadingDivider from '../components/HeadingDivider.astro'; -import PostCard from '../components/PostCard.astro'; -import ProjectShowcase from '../components/ProjectShowcase.astro'; -import SectionHeading from '../components/SectionHeading.astro'; -import SubjectDirectory from '../components/SubjectDirectory.astro'; -import { isPublished } from '../lib/site'; +import { formatDate, isPublished, normalizeAssetPath, sitePath } from '../lib/site'; import siteData from '../../.generated/data/site.json'; import subjects from '../../.generated/data/subjects.json'; +const books = siteData.books; -const posts = (await getCollection('blog')) - .filter((post) => isPublished(post.data)) - .sort((a, b) => +b.data.date - +a.data.date) - .slice(0, 3); - -const featuredProjects = (siteData.settings.sites ?? []) - .filter((site: any) => site.showcase) +const posts = (await getCollection('blog')).filter((post) => isPublished(post.data)) + .sort((a, b) => +b.data.date - +a.data.date).slice(0, 4); +const [leadPost, ...recentPosts] = posts; +const projects = (siteData.settings.sites ?? []).filter((site: any) => site.showcase) .sort((a: any, b: any) => (a.order ?? 999) - (b.order ?? 999)); - -const workDestinations = [ - { - title: 'Writing', - href: '/writing/', - description: 'Selected essays, subjects, and series drawn from the complete Blog archive.', - }, - { - title: 'Scholarship', - href: '/scholarship/', - description: 'Scholarly work and the complete research record.', - }, - { - title: 'Books', - href: '/books/', - description: 'Long-form work in mathematics, policy, operations research, and teaching.', - }, - { - title: 'Software', - href: '/software/', - description: 'Open-source projects and working tools built around difficult problems.', - }, - { - title: 'Teaching', - href: '/teaching/', - description: 'Courses, instructional work, and a documentary syllabus archive.', - }, - { - title: 'Service', - href: '/service/', - description: 'Public work, professional stewardship, and institutional responsibility.', - }, +const historyProject = projects.find((project: any) => project.id === 'war1812'); +const exhibitionProject = projects.find((project: any) => project.id === 'judith'); +const bookProjects = projects.filter((project: any) => ['cmna', 'defenseor'].includes(project.id)) + .map((project: any) => ({ ...project, + book: books.published.find((book: any) => book.short_route === (project.id === 'cmna' ? '/cmna/' : '/mdor/')), + })); +const otherProjects = projects.filter((project: any) => !['war1812', 'judith', 'cmna', 'defenseor'].includes(project.id)); +const additionalDestinations = [ + { title: 'Books', href: '/books/', description: 'Long-form work in mathematics, policy, operations research, and teaching.' }, + { title: 'Software', href: '/software/', description: 'Open-source projects and working tools built around difficult problems.' }, + { title: 'Consulting', href: '/consulting/', description: 'Have a complicated problem of your own?' }, ]; - const personalDestinations = [ - { - title: 'Ancestry', - href: '/ancestry/', - description: 'Documented lineages, people, places, and the evidence connecting them.', - }, - { - title: 'Honors', - href: '/honors/', - description: 'Awards, appointments, decorations, and other recognition.', - }, - { - title: 'Coat of Arms', - href: '/coat-of-arms/', - description: 'The grant, insignia, emblazonments, tartan, and documentary record.', - }, - { - title: 'Media Archive', - href: '/media/', - description: 'Interviews, quotations, profiles, and appearances collected over the years.', - }, + { id: 'ancestry', title: 'Ancestry', href: '/ancestry/', description: 'Documented lineages, people, places, and the evidence connecting them.', image: '/assets/img/ancestry/ofgd/granville-grant-teague-1756-11-05.webp', position: 'center 35%' }, + { id: 'arms', title: 'Coat of Arms', href: '/coat-of-arms/', description: 'The grant, insignia, emblazonments, tartan, and documentary record.', image: '/assets/img/armory/jhoward-coa-achernushkin-960px.webp', position: 'center' }, + { id: 'honors', title: 'Honors', href: '/honors/', description: 'Awards, appointments, decorations, and other recognition.', image: '/assets/img/kingdom-of-hawaii-medal-hero.webp', position: 'center 38%' }, + { id: 'media', title: 'Media Archive', href: '/media/', description: 'Interviews, quotations, profiles, and appearances collected over the years.', image: '/assets/img/news/radio-studio.webp', position: 'center' }, ]; --- -
-
- James Howard coat of arms -
-

James Howard

- -

A Mathematician, a Different Kind of Mathematician, and a Statistician

+
+
+
+
+ James Howard coat of arms +
+
+

+ A mathematician. + A different kind
of mathematician.
+ And a statistician. +

+

Mathematics, software, public policy—and the occasional strange project.

+ +
+
-
-
+ -
-
- -

I use mathematics, statistics, software, and artificial intelligence to understand complicated systems—and explain what they mean when people and institutions have to make decisions.

-
-
- -
-
- - -
-
+
+ +
-
-
- - -
-
+
+

Independent sites & ongoing work

Selected Projects

+ {historyProject &&
+ +
+
+

{historyProject.kind}

+

1812 The Whole Story

+

{historyProject.description}

+ Begin the story +
+
+
} + {exhibitionProject &&
+
+
+

{exhibitionProject.kind}

+

Judith &
Holofernes

+

The Fatal Image

+

{exhibitionProject.description}

+ Enter the exhibition +
+
} +
+
+

Books with a life beyond the page

Methods you can put to work.

+
+ {bookProjects.map((project: any) =>
+ {project.book && {project.book.cover_alt}} +
+

{project.kind}

+

{project.title}

+ {project.book &&

{project.book.role} · {project.book.edition}

} +

{project.description}

+ Explore the companion +
+
)} +
+ {otherProjects.length > 0 &&
+ {otherProjects.map((project: any) => )} +
} +
+
+
-
-
- - -

Have a complicated problem of your own? Consulting →

-
-
+ -
-
- - -
-
+
+
+
+ +

Find your way in

Explore
by Subject

+
+ +
+
-
-
- -
{posts.map((post) => )}
- -
-
+
+
+

People, symbols & the documentary record

The personal archive.

+
+ {personalDestinations.map((destination) => +
+

{destination.title}

{destination.description}

+
)} +
+
+
+
diff --git a/src/pages/search/index.astro b/src/pages/search/index.astro index 2a4e5634..7668fe64 100644 --- a/src/pages/search/index.astro +++ b/src/pages/search/index.astro @@ -8,4 +8,4 @@ fetch('/data/search.json').then(r=>r.json()).then(items=>{fuse=new Fuse(items,{k function run(){if(!input||!results||!status||!fuse)return;const q=input.value.trim();results.replaceChildren();if(!q){status.textContent='Type to search the archive.';return;}const matches=fuse.search(q,{limit:50}).map(x=>x.item);status.textContent=`${matches.length} result${matches.length===1?'':'s'} for “${q}”.`;for(const item of matches){const li=document.createElement('li');const h=document.createElement('h2');const a=document.createElement('a');a.href=item.url;a.textContent=item.title;h.append(a);const p=document.createElement('p');p.textContent=item.excerpt;const meta=document.createElement('p');meta.textContent=item.type;li.append(h,meta,p);results.append(li);}} input?.addEventListener('input',run);form?.addEventListener('reset',()=>setTimeout(run)); - + diff --git a/src/pages/service/index.astro b/src/pages/service/index.astro index 0188f499..1854d36a 100644 --- a/src/pages/service/index.astro +++ b/src/pages/service/index.astro @@ -1,4 +1,4 @@ --- -import BaseLayout from '../../layouts/BaseLayout.astro';import PageIntro from '../../components/PageIntro.astro';import DataCard from '../../components/DataCard.astro';import SectionHeading from '../../components/SectionHeading.astro';import siteData from '../../../.generated/data/site.json';import {slugifyTag} from '../../lib/site';const current=siteData.service.filter((x:any)=>x.current);const past=siteData.service.filter((x:any)=>!x.current); +import BaseLayout from '../../layouts/BaseLayout.astro';import PageIntro from '../../components/PageIntro.astro';import DataCard from '../../components/DataCard.astro';import SectionHeading from '../../components/SectionHeading.astro';import SectionNav from '../../components/SectionNav.astro';import siteData from '../../../.generated/data/site.json';import {slugifyTag} from '../../lib/site';const current=siteData.service.filter((x:any)=>x.current);const past=siteData.service.filter((x:any)=>!x.current); --- -
{current.map((x:any)=>)}
{past.map((x:any)=>)}
+
{current.map((x:any)=>)}
{past.map((x:any)=>)}
diff --git a/src/pages/software/index.astro b/src/pages/software/index.astro index dca9e5de..e88e3c2c 100644 --- a/src/pages/software/index.astro +++ b/src/pages/software/index.astro @@ -3,16 +3,19 @@ import BaseLayout from '../../layouts/BaseLayout.astro'; import PageIntro from '../../components/PageIntro.astro'; import DataCard from '../../components/DataCard.astro'; import SectionHeading from '../../components/SectionHeading.astro'; +import SectionNav from '../../components/SectionNav.astro'; import SubjectLinks from '../../components/SubjectLinks.astro'; import siteData from '../../../.generated/data/site.json'; import subjectsData from '../../../.generated/data/subjects.json'; import { slugifyTag } from '../../lib/site'; const groups = Object.entries(siteData.software).filter(([, value]) => Array.isArray(value)); +const groupTitle = (name: string) => name.replaceAll('_',' ').replace(/\b\w/g,(character: string)=>character.toUpperCase()); const relatedSubjects = subjectsData.subjects.filter((subject) => ['artificial-intelligence', 'mathematics-statistics', 'operations-research-decision-systems', 'risk-resilience', 'software-open-systems'].includes(subject.slug)); --- - {groups.map(([name,items]:any,index)=>
character.toUpperCase())}/>
{items.map((item:any)=>)}
)} + ({href:`#software-group-${slugifyTag(name)}`,label:groupTitle(name)}))}/> + {groups.map(([name,items]:any,index)=>
{items.map((item:any)=>)}
)}
diff --git a/src/pages/subjects/[subject].astro b/src/pages/subjects/[subject].astro index 3c40cc60..37757fc1 100644 --- a/src/pages/subjects/[subject].astro +++ b/src/pages/subjects/[subject].astro @@ -46,6 +46,6 @@ const sectionClass = (index: number) => index % 3 === 0 ? 'section--soft' : inde diff --git a/src/pages/subjects/index.astro b/src/pages/subjects/index.astro index 040bf6d9..e7eeb5f2 100644 --- a/src/pages/subjects/index.astro +++ b/src/pages/subjects/index.astro @@ -18,5 +18,5 @@ const description = 'Ways into the site by idea rather than by document type.'; diff --git a/src/pages/teaching/index.astro b/src/pages/teaching/index.astro index d482c87a..00ce70a0 100644 --- a/src/pages/teaching/index.astro +++ b/src/pages/teaching/index.astro @@ -2,20 +2,28 @@ import BaseLayout from '../../layouts/BaseLayout.astro'; import PageIntro from '../../components/PageIntro.astro'; import SectionHeading from '../../components/SectionHeading.astro'; +import SectionNav from '../../components/SectionNav.astro'; +import TeachingInstitution from '../../components/TeachingInstitution.astro'; import SubjectLinks from '../../components/SubjectLinks.astro'; import siteData from '../../../.generated/data/site.json'; import subjectsData from '../../../.generated/data/subjects.json'; -import { slugifyTag } from '../../lib/site'; -const current = siteData.teaching.institutions.filter((institution:any) => institution.current); -const historical = siteData.teaching.institutions.filter((institution:any) => !institution.current); +const current = siteData.teaching.institutions.filter((institution: any) => institution.current); +const historical = siteData.teaching.institutions.filter((institution: any) => !institution.current); const relatedSubjects = subjectsData.subjects.filter((subject) => ['mathematics-statistics', 'operations-research-decision-systems', 'risk-resilience', 'public-policy-institutions', 'teaching-learning'].includes(subject.slug)); -const courseId = (institution:string, course:string) => `teaching-${slugifyTag(institution)}-${slugifyTag(course)}`; --- -
{current.map((institution:any)=>

{institution.name}

{institution.courses.map((course:any)=>
{course.code} — {course.title} ({course.syllabi?.length??0} syllabus files)

{course.description}

{course.syllabi?.length>0&&
    {course.syllabi.map((syllabus:any)=>
  • {syllabus.file?{syllabus.label}:syllabus.label}
  • )}
}
)}
)}
-
{historical.map((institution:any)=>

{institution.name}

{institution.courses.map((course:any)=>
{course.code} — {course.title} ({course.syllabi?.length??0} syllabus files)

{course.description}

{course.syllabi?.length>0&&
    {course.syllabi.map((syllabus:any)=>
  • {syllabus.file?{syllabus.label}:syllabus.label}
  • )}
}
)}
)}
-
+ +
+
+ {current.map((institution: any) => )} +
+
+
+
+ {historical.map((institution: any) => )} +
+
+
- diff --git a/src/pages/writing/index.astro b/src/pages/writing/index.astro index 74375b06..3a1f3149 100644 --- a/src/pages/writing/index.astro +++ b/src/pages/writing/index.astro @@ -34,7 +34,7 @@ const description = 'Essays, explanations, arguments, and occasional excursions @@ -42,12 +42,12 @@ const description = 'Essays, explanations, arguments, and occasional excursions
-
+
Browse the complete Blog Archive → diff --git a/src/styles/content.css b/src/styles/content.css index f9729493..6f9afd2a 100644 --- a/src/styles/content.css +++ b/src/styles/content.css @@ -12,7 +12,7 @@ .content hr { width: min(12rem, 40%); margin: var(--space-6) auto; border: 0; border-top: 1px solid var(--color-gold-500); } .content :not(pre) > code { padding: .08em .3em; border: 1px solid var(--color-slate-150); border-radius: .2rem; background: var(--color-slate-075); font-size: .88em; overflow-wrap: anywhere; white-space: break-spaces; } -.toc, .page-toc { margin: 0 auto var(--space-6); padding: var(--space-4); border-top: 3px solid var(--color-gold-500); background: var(--color-slate-075); font-family: var(--font-sans); } +.toc, .page-toc { margin: 0 auto var(--space-6); padding: var(--space-4); border-top: 3px solid var(--color-gold-500); background: var(--color-cream); font-family: var(--font-sans); } .toc h2, .page-toc h2 { margin: 0 0 var(--space-2); font-size: 1rem; letter-spacing: .06em; text-transform: uppercase; } .toc ol, .page-toc ol, .page-toc ul { columns: 2 16rem; margin: 0; padding-left: 1.2rem; } .toc li, .page-toc li { margin-block: .25rem; break-inside: avoid; } @@ -22,6 +22,8 @@ .content .section-heading h2, .content .section-heading h3 { margin: 0; padding: 0; } .content .section-heading h2::after { display: none; } .content .jh-card h3 { margin-top: 0; } +.content .section-heading--left { max-width: none; margin-inline: 0; text-align: left; } +.content :is(h2, h3), .content .section-heading { scroll-margin-top: 6rem; } .content .content-figure { width: min(var(--figure-width, 100%), 100%); margin: var(--space-5) auto; } .content .content-figure--left { float: left; margin: .4rem var(--space-4) var(--space-3) 0; } @@ -53,7 +55,7 @@ .related-posts h2 { margin: 0; padding: 0; font-size: 1.5rem; } .related-posts h2::after { display: none; } .related-posts p { margin: .75rem 0 0; } -.document-action, .embed-link { margin-block: var(--space-5); padding: var(--space-3) var(--space-4); border-left: 3px solid var(--color-gold-500); background: var(--color-slate-075); font-family: var(--font-sans); } +.document-action, .embed-link { margin-block: var(--space-5); padding: var(--space-3) var(--space-4); border-left: 3px solid var(--color-gold-500); background: var(--color-cream); font-family: var(--font-sans); } .document-embed { margin-block: var(--space-5); } .document-embed iframe { width: 100%; min-height: min(70vh, 45rem); border: 1px solid var(--color-slate-300); } .content-callout { margin-block: var(--space-5); padding: var(--space-4); border: 1px solid var(--color-slate-150); border-top: 3px solid var(--color-gold-500); background: var(--color-cream); } @@ -105,9 +107,9 @@ .content-preface { text-align: center; } .content-lead { max-width: 54rem; margin: 0 auto var(--space-5); color: var(--color-slate-700); font-size: clamp(1.08rem, 1.6vw, 1.28rem); } .content-band { clear: both; clip-path: inset(0 -100vmax); } -.content-band--navy { color: #fff; background: var(--color-navy-700); box-shadow: 0 0 0 100vmax var(--color-navy-700); } +.content-band--navy { color: #fff; background: var(--color-navy-950); box-shadow: 0 0 0 100vmax var(--color-navy-950); } .content-band--cream { background: var(--color-cream); box-shadow: 0 0 0 100vmax var(--color-cream); } -.content-band--soft { background: var(--color-slate-075); box-shadow: 0 0 0 100vmax var(--color-slate-075); } +.content-band--soft { background: var(--color-cream); box-shadow: 0 0 0 100vmax var(--color-cream); } .content-band--navy :is(h2, h3, h4, a) { color: #fff; } .content-band--navy .section-heading--gold :is(h2, h3) { color: var(--color-gold-300); } .content-band--navy .section-heading__description, .content-band--navy .metadata { color: rgb(255 255 255 / 76%); } @@ -178,7 +180,7 @@ .katex-display { overflow-x: auto; overflow-y: hidden; padding-block: var(--space-2); } @media (max-width: 48rem) { - .content .content-figure--left, .content .content-figure--right, .inset, .content-float-right, .content-float-left { float: none; width: 100%; margin: var(--space-5) 0; } + .content .content-figure--left, .content .content-figure--right, .inset, .content .content-float-right, .content .content-float-left { float: none; width: 100%; margin: var(--space-5) 0; } .content .content-figure--gallery { display: block; width: 100%; margin: var(--space-4) 0; } .toc ol, .page-toc ol, .page-toc ul { columns: 1; } .book-detail-hero, .facts, .credential-grid, .media-fact-grid, .details-list--grid, .document-grid, .content-columns { grid-template-columns: 1fr; } @@ -187,3 +189,10 @@ .timeline > div { grid-template-columns: 1fr; gap: var(--space-1); } .ribbon-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + +@media (min-width: 48.001rem) and (max-width: 80rem) and (max-aspect-ratio: 4/5) { + .book-detail-hero, .credential-grid, .media-fact-grid, .document-grid, .content-columns, .figure-group--two, .figure-group--three, .figure-group--third-wide { grid-template-columns: 1fr; } + .content-grid__item { flex-basis: 100%; } + .content .content-figure--left, .content .content-figure--right, .inset, .content .content-float-right, .content .content-float-left { float: none; width: 100%; margin: var(--space-5) 0; } + .book-detail-hero > img { max-width: 16rem; } +} diff --git a/src/styles/document-pages.css b/src/styles/document-pages.css new file mode 100644 index 00000000..48f61ada --- /dev/null +++ b/src/styles/document-pages.css @@ -0,0 +1,92 @@ +.document-layout { + display: grid; + grid-template-columns: 16rem minmax(0, 48rem); + justify-content: center; + align-items: start; + gap: clamp(2rem, 4vw, 4rem); +} +.document-reading { min-width: 0; } +.page-index { + position: sticky; + top: 6rem; + max-height: calc(100dvh - 8rem); + padding: 1.2rem .5rem .5rem 0; + overflow-y: auto; + border-top: 2px solid var(--color-gold-500); + font: .88rem/1.5 var(--font-sans); + scrollbar-width: thin; +} +.page-index ul { margin: 0; padding: 0; list-style: none; } +.page-index li { margin: .25rem 0; padding-left: .85rem; } +.page-index a { display: block; padding-block: .35rem; text-decoration: none; } +.page-index a:hover { text-decoration: underline; } +.page-index .page-index__section { margin-top: 1.25rem; padding-left: 0; font-weight: 700; } +.page-index .page-index__section:first-child { margin-top: .5rem; } + +/* Documentary layouts retain wide media, with a readable measure for prose. */ +.content--document > :is(p, ul, ol) { max-width: var(--reading-width); } +.content--document > :is(h2, h3) { margin-top: 3rem; } +.content--document .section-heading { text-align: left; } +.content--document .section-heading__description { margin-inline: 0; } +.content--document .section-heading .heading-divider { width: 3rem; margin-inline: 0; } +.content--document .section-heading .heading-divider img, +.content--document .section-heading .heading-divider__rule:last-child { display: none; } +.content--document .section-heading .heading-divider__rule { height: 2px; opacity: 1; } +.content--document .content-figure--documentary, +.content--document .documentary-figure { border-radius: 0; background: var(--color-cream); box-shadow: none; } +.content--document .content-preface { text-align: left; } +.content--document .content-lead { margin-inline: 0; } +.content--document :is(h4, h5) { font-family: var(--font-sans); letter-spacing: 0; } +.content--document .page-toc { display: flex; flex-wrap: wrap; gap: .65rem 1.5rem; } +.content--document .page-toc > a { display: inline-block; padding-block: .25rem; } +.content--document .page-toc > :is(ul, ol) { flex-basis: 100%; } +.content--document .page-toc--inline, .content--document .page-toc--inline ul { justify-content: flex-start; margin-inline: 0; } + +.content--about .about-introduction { + display: grid; + grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); + gap: 0 2rem; + align-items: start; +} +.content--about .about-introduction > .content-figure { + grid-column: 2; + grid-row: 1 / span 3; + float: none; + width: 100%; + margin: 0; +} +.content--about .about-introduction > p { grid-column: 1; margin: 0 0 1.25rem; } +.content--about .about-introduction > p:first-of-type { color: var(--color-navy-900); font-size: 1.2rem; } +.content--about .about-introduction img { border-radius: 0; } +.content--about .about-credentials { margin-top: 3.5rem; padding-top: 2rem; border-top: 1px solid var(--color-gold-500); } +.content--about .about-credentials > h2 { margin-top: 0; } +.content--about .about-credentials li { margin-block: .65rem; } +.content--about .about-credentials li li { color: var(--color-slate-700); font-size: .94em; } +.content--about .credential-grid { margin-block: 2.5rem; } +.content--about .credential-grid > section { padding: 1.5rem; border-top: 2px solid var(--color-gold-500); background: var(--color-cream); } +.content--about .credential-grid h3 { margin-top: 0; } +.content--about .credential-grid ul { padding-left: 1.2rem; } +.content--about .figure-group { margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--color-slate-150); } +.content--consulting .consulting-capabilities { margin-block: 3.5rem; padding: 2rem; border-top: 2px solid var(--color-gold-500); background: var(--color-cream); } +.content--consulting .consulting-capabilities > h2 { margin-top: 0; } +.content--consulting .consulting-capabilities h3 { margin-top: 2rem; } +.content--consulting .consulting-contact { margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--color-gold-500); } +.content--consulting .consulting-contact h2 { margin-top: 0; } +.content--consulting .consulting-contact a { display: inline-block; padding: .65rem 1.1rem; color: var(--color-cream); background: var(--color-navy-900); font: 700 1rem var(--font-sans); text-decoration: none; } +.content--consulting .consulting-contact a:hover { background: var(--color-blue-600); } + +@media (max-width: 74rem), (max-aspect-ratio: 4/5) { + .document-layout { display: block; max-width: var(--reading-width); } + .page-index { position: static; max-height: none; margin-bottom: 2.5rem; padding: 1.2rem 1.4rem; overflow: visible; background: var(--color-cream); } + .page-index ul { columns: 2 16rem; column-gap: 2rem; } + .page-index li { break-inside: avoid; } +} +@media (max-width: 48rem), (max-aspect-ratio: 4/5) { + .content--about .about-introduction { display: block; } + .content--about .about-introduction > .content-figure { width: min(100%, 18rem); margin: 0 0 2rem; } + .content--about .figure-group img { max-height: 26rem; object-fit: contain; } +} +@media (max-width: 40rem) { + .page-index ul { columns: 1; } + .content--consulting .consulting-capabilities { padding: 1.5rem; } +} diff --git a/src/styles/global.css b/src/styles/global.css index 2c7a7505..41ce21b7 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -2,7 +2,7 @@ @import './tokens.css'; *, *::before, *::after { box-sizing: border-box; } -html { max-width: 100%; overflow-x: clip; color-scheme: light; scroll-behavior: smooth; } +html { scroll-padding-top: 6rem; max-width: 100%; overflow-x: clip; color-scheme: light; scroll-behavior: smooth; } body { max-width: 100%; margin: 0; overflow-x: clip; color: var(--color-ink); background: var(--color-white); font: 1.075rem/1.72 var(--font-serif); } img, svg, video, iframe { max-width: 100%; } img { height: auto; } @@ -10,6 +10,7 @@ a { color: var(--color-blue-600); text-decoration-thickness: .08em; text-underli a:hover { color: var(--color-gold-700); } :focus-visible { outline: 3px solid #fff; outline-offset: 3px; box-shadow: 0 0 0 6px var(--color-navy-900); } h1, h2, h3, h4, h5, h6 { color: var(--color-navy-900); font-family: var(--font-display); font-weight: 600; line-height: 1.18; text-wrap: balance; } +h1, h2, h3 { font-family: var(--font-serif); font-weight: 400; } h1 { font-size: clamp(2.25rem, 5vw, 4.65rem); } h2 { margin-top: 2.2em; font-size: clamp(1.75rem, 3vw, 2.6rem); } h3 { font-size: clamp(1.25rem, 2vw, 1.65rem); } @@ -31,14 +32,20 @@ button { font: inherit; } .skip-link:focus { transform: none; } .eyebrow { margin: 0 0 var(--space-2); color: var(--color-gold-600); font: 700 .78rem var(--font-sans); letter-spacing: .1em; text-transform: uppercase; } .metadata { color: var(--color-slate-700); font-family: var(--font-sans); font-size: .94rem; } -.section { padding-block: var(--space-7); } -.section--navy { color: #fff; background: var(--color-navy-700); } +.section { padding-block: clamp(3rem, 6vw, 6rem); } +.section--navy { color: #fff; background: var(--color-navy-950); } .section--navy h2, .section--navy h3, .section--navy a { color: #fff; } -.section--soft { background: var(--color-slate-075); } +.section--soft { background: var(--color-cream); } .section--cream { background: var(--color-cream); } .section-heading { max-width: 48rem; margin: 0 auto var(--space-5); text-align: center; } .section-heading h2, .section-heading h3 { margin: 0; } +.section-heading--left { max-width: none; margin-inline: 0; text-align: left; } +.section-heading--left .section-heading__description { max-width: 48rem; margin-inline: 0; } +.section-heading--left .heading-divider { width: 3rem; margin-inline: 0; margin-top: 1.1rem; color: var(--color-gold-500); } +.section-heading--left .heading-divider img, .section-heading--left .heading-divider__rule:last-child { display: none; } +.section-heading--left .heading-divider__rule { height: 2px; opacity: 1; } +.section-heading--light .eyebrow { color: var(--color-gold-300); } .section-heading__description { margin: var(--space-3) auto 0; color: var(--color-slate-700); font-size: 1.1rem; } .section-heading--light h2, .section-heading--light h3 { color: #fff; } .section-heading--light .section-heading__description { color: rgb(255 255 255 / 78%); } @@ -52,31 +59,43 @@ button { font: inherit; } .card-grid, .record-grid, .book-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-4); } .record-grid, .book-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } -.jh-card { overflow: hidden; border: 1px solid var(--color-slate-150); border-radius: var(--radius-md); color: var(--color-ink); background: #fff; box-shadow: var(--shadow-sm); } +.jh-card { min-width: 0; overflow: hidden; border: 1px solid var(--color-slate-150); border-radius: 0; color: var(--color-ink); background: #fff; box-shadow: none; } .jh-card__media { display: block; aspect-ratio: 16 / 9; overflow: hidden; background: var(--color-navy-800); } .jh-card__media img { width: 100%; height: 100%; object-fit: cover; transition: transform .35s ease; } .jh-card:hover .jh-card__media img { transform: scale(1.025); } .jh-card__body { padding: var(--space-4); } -.jh-card :is(h2, h3) { margin: 0 0 var(--space-2); font-size: 1.25rem; } +.jh-card :is(h2, h3) { margin: 0 0 var(--space-2); font-size: clamp(1.35rem, 2vw, 1.8rem); line-height: 1.2; } +.jh-card__body > :last-child { margin-bottom: 0; } .jh-card :is(h2, h3) a { color: var(--color-navy-900); text-decoration-line: underline; text-decoration-color: var(--color-gold-500); text-decoration-thickness: .08em; text-underline-offset: .16em; } .jh-card :is(h2, h3) a:hover, .jh-card :is(h2, h3) a:focus-visible { color: var(--color-gold-700); text-decoration-thickness: .12em; } +.data-card { border-top: 2px solid var(--color-gold-500); } .jh-card--compact .jh-card__body { padding: var(--space-3) var(--space-4); } -.jh-card--featured { border-top: 3px solid var(--color-gold-500); box-shadow: var(--shadow-md); } +.jh-card--featured { border-top: 2px solid var(--color-gold-500); } .jh-card--dark { border-color: rgb(255 255 255 / 18%); color: #fff; background: var(--color-navy-800); } .jh-card--dark :is(h2, h3), .jh-card--dark :is(h2, h3) a, .jh-card--dark a { color: #fff; } .jh-card--dark :is(h2, h3) a { text-decoration-color: var(--color-gold-300); } +.data-card.jh-card--dark { border-top-color: var(--color-gold-300); } .jh-card--dark .metadata { color: rgb(255 255 255 / 72%); } .book-card { display: grid; grid-template-columns: minmax(7rem, 10rem) 1fr; } -.book-card .jh-card__media { aspect-ratio: 2 / 3; } +.book-card .jh-card__media { display: flex; height: 100%; aspect-ratio: auto; padding: 1rem; align-items: center; background: var(--color-cream); } +.book-card .jh-card__media img { height: auto; max-height: 24rem; object-fit: contain; } .book-card .jh-card__body { align-self: center; } +.blog-archive { align-items: start; gap: var(--space-6) var(--space-5); } +.blog-archive .post-card { border: 0; background: transparent; } +.blog-archive .jh-card__body { padding: 1.4rem 0 0; } +.blog-archive .post-card--lead { grid-column: span 2; } +.blog-archive .post-card--lead .jh-card__media { aspect-ratio: 2 / 1; } +.blog-archive .post-card--lead :is(h2, h3) { font-size: clamp(1.8rem, 3vw, 3rem); } +.blog-archive .post-card--lead .jh-card__body { max-width: 42rem; } + .link-row { display: flex; flex-wrap: wrap; gap: .45rem 1rem; font: 700 .88rem var(--font-sans); } .pill-list { display: flex; flex-wrap: wrap; gap: .45rem; margin: var(--space-3) 0 0; padding: 0; list-style: none; } .pill-list a { display: inline-block; padding: .3rem .7rem; border: 1px solid rgb(255 255 255 / 40%); border-radius: 100vw; color: inherit; font: .78rem var(--font-sans); text-decoration: none; } -.button { display: inline-flex; min-height: 2.75rem; align-items: center; justify-content: center; padding: .65rem 1.1rem; border: 1px solid var(--color-blue-600); border-radius: var(--radius-sm); color: #fff; background: var(--color-blue-600); font: 700 1rem var(--font-sans); text-decoration: none; transition: background-color var(--transition-ui), border-color var(--transition-ui), color var(--transition-ui); } +.button { display: inline-flex; min-height: 2.75rem; align-items: center; justify-content: center; padding: .65rem 1.1rem; border: 1px solid var(--color-blue-600); border-radius: 0; color: #fff; background: var(--color-blue-600); font: 700 1rem var(--font-sans); text-decoration: none; transition: background-color var(--transition-ui), border-color var(--transition-ui), color var(--transition-ui); } .button:hover { border-color: var(--color-navy-900); color: #fff; background: var(--color-navy-900); } .control-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); margin-bottom: var(--space-5); } -.control-grid > div { display: grid; gap: var(--space-2); } +.control-grid > div { min-width: 0; display: grid; gap: var(--space-2); } .details-list { display: grid; gap: 1px; margin: 0; border: 1px solid var(--color-slate-150); background: var(--color-slate-150); } .details-list > div { display: grid; grid-template-columns: minmax(8rem, .35fr) 1fr; gap: var(--space-3); padding: var(--space-3); background: #fff; } .details-list dt { font: 700 .8rem var(--font-sans); letter-spacing: .06em; text-transform: uppercase; } @@ -91,13 +110,19 @@ button { font: inherit; } body { font-size: 1rem; } .section { padding-block: var(--space-6); } .card-grid, .record-grid, .book-grid, .control-grid { grid-template-columns: 1fr; } - .book-card { grid-template-columns: 7rem 1fr; } + .book-card { grid-template-columns: minmax(6rem, 8rem) minmax(0, 1fr); } + .blog-archive .post-card--lead { grid-column: auto; } .details-list > div { grid-template-columns: 1fr; gap: var(--space-1); } .pagination { align-items: stretch; flex-direction: column; text-align: center; } } @media (max-width: 30rem) { .book-card { grid-template-columns: 1fr; } - .book-card .jh-card__media { aspect-ratio: 16 / 10; } + .book-card .jh-card__media { height: auto; justify-content: center; } + .book-card .jh-card__media img { width: auto; max-width: 100%; max-height: 18rem; } +} +@media (min-width: 48.001rem) and (max-width: 80rem) and (max-aspect-ratio: 4/5) { + .card-grid, .record-grid, .book-grid, .control-grid { grid-template-columns: 1fr; } + .blog-archive .post-card--lead { grid-column: auto; } } @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } diff --git a/src/styles/scholarship.css b/src/styles/scholarship.css new file mode 100644 index 00000000..7d96c426 --- /dev/null +++ b/src/styles/scholarship.css @@ -0,0 +1,47 @@ +.content .scholarship-selected { margin-top: 4rem; } +.content .scholarship-selected > h2 { margin-top: 0; } +.content .scholarship-topic { + display: flow-root; + margin-top: 2.75rem; + padding-top: 1.75rem; + border-top: 1px solid var(--color-slate-150); +} +.content .scholarship-topic h3 { + margin: 0 0 1.5rem; + font-size: clamp(1.5rem, 2.5vw, 2rem); + line-height: 1.25; +} +.content .scholarship-topic p { margin-block: 1.5rem; } +.content .scholarship-topic > :last-child { margin-bottom: 0; } +.content .scholarship-topic strong > a { color: var(--color-navy-900); } +.content .scholarship-topic strong > a:hover { color: var(--color-gold-700); } +.content .scholarship-cover { + float: right; + width: clamp(5rem, 15vw, 7.5rem); + margin: .25rem 0 1rem 1.75rem; +} +.content .scholarship-cover a { display: block; } +.content .scholarship-cover img { display: block; width: 100%; height: auto; border-radius: 0; } +.content .scholarship-topic:has(.scholarship-cover) > p ~ p { clear: both; } + +.content .scholarship-record { + margin-top: 4.5rem; + padding-top: 2.5rem; + border-top: 1px solid var(--color-gold-500); +} +.content .scholarship-record > h2 { margin-top: 0; } +.content .scholarship-record h3 { margin-top: 2.5rem; } +.content .scholarship-record h4 { + margin: 2rem 0 1rem; + font: 700 1rem/1.5 var(--font-sans); +} +.content .scholarship-record ul { padding-left: 1.25rem; } +.content .scholarship-record li { margin-bottom: 1.15rem; padding-left: .3rem; } +.content .scholarship-record li::marker { color: var(--color-slate-700); } + +@media (max-width: 40rem) { + .content .scholarship-selected { margin-top: 3rem; } + .content .scholarship-topic { margin-top: 2rem; padding-top: 1.5rem; } + .content .scholarship-cover { margin-left: 1rem; } + .content .scholarship-record { margin-top: 3rem; padding-top: 2rem; } +} diff --git a/src/styles/tokens.css b/src/styles/tokens.css index 482fff3b..3ad96230 100644 --- a/src/styles/tokens.css +++ b/src/styles/tokens.css @@ -20,7 +20,7 @@ --color-white: #fff; --color-ink: #222a33; --color-danger: #a3392e; - --hero-overlay: rgb(48 48 48 / 90%); + --hero-overlay: linear-gradient(115deg, rgb(7 17 31 / 90%), rgb(12 35 60 / 80%)); --font-display: 'Cinzel', Georgia, serif; --font-serif: 'Cambo', Georgia, serif; --font-sans: 'Lato', 'Helvetica Neue', Arial, sans-serif; @@ -32,7 +32,7 @@ --space-6: 4rem; --space-7: 6rem; --space-8: 8rem; - --reading-width: 56rem; + --reading-width: 48rem; --content-width: 78rem; --archive-width: 88rem; --radius-sm: .25rem;