diff --git a/.gitignore b/.gitignore index 879add9..9b9e01d 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,11 @@ dist/hacking/ # faces under public/cards/security/ are the committed record. # node scripts/security-pros.mjs all dist/security/ + +# Reference photographs the portraits are painted from. NOT committed: several are +# ordinary all-rights-reserved bio photos used with permission as references, and +# redistributing them in the repo is a different act from painting from them. +# data/security-references.json records where each came from, and +# node scripts/security-refs.mjs fetch +# re-downloads the lot. +assets/references/ diff --git a/README.md b/README.md index f04580e..8b3254f 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,30 @@ pnpm security all 1 5 12 # ...only these card numbers pnpm security render # re-render faces from the template, no API calls ``` -Same stages and the same resumability as Series Two, with one deliberate -difference: **`all` does not run `enhance`.** On Series Two that image-to-image +Portraits are painted **from a real photograph of the person**: + +```bash +node scripts/security-refs.mjs resolve # find + verify a reference photo per card +node scripts/security-refs.mjs fetch # download them (gitignored) +node scripts/security-refs.mjs report # who still has no face +``` + +The first art pass generated faces from text prompts alone and produced an +invented stranger for every name. That art was withdrawn. Generation is now +image-to-image off `assets/references/security-pros/`, and **a card with no +verified reference photo gets no face** — the front renders a plate saying so. +40 of 50 currently have one; `data/security-references.json` records the source, +photographer and licence for each, and the card page prints the credit. + +Identity is verified, not assumed. A name search returns humans, not the right +human: "Michael Howard" resolves to the British politician, "Robert M. Lee" to a +Confederate general, "Mark Dowd" to a Liverpool councillor. The resolver requires +a surname match, rejects pre-1950 photographs, requires a Commons filename to +*open* with the person's full name, and carries an explicit reject list with +reasons. Everything it accepts was still checked by eye. + +The same stages and resumability as Series Two, with one deliberate difference: +**`all` does not run `enhance`.** On Series Two that image-to-image finish pass rewrote text on the card faces despite the prompt forbidding it — it fabricated quotes attributed to living people, invented stat panels that were not on the card, and misspelled a name. Nothing in the pipeline catches that, because diff --git a/assets/portraits/security-pros/card_001.png b/assets/portraits/security-pros/card_001.png index a984533..d904bce 100644 Binary files a/assets/portraits/security-pros/card_001.png and b/assets/portraits/security-pros/card_001.png differ diff --git a/assets/portraits/security-pros/card_002.png b/assets/portraits/security-pros/card_002.png index 33e3bf0..c80a362 100644 Binary files a/assets/portraits/security-pros/card_002.png and b/assets/portraits/security-pros/card_002.png differ diff --git a/assets/portraits/security-pros/card_003.png b/assets/portraits/security-pros/card_003.png index 99951f1..08b95b2 100644 Binary files a/assets/portraits/security-pros/card_003.png and b/assets/portraits/security-pros/card_003.png differ diff --git a/assets/portraits/security-pros/card_004.png b/assets/portraits/security-pros/card_004.png index 43e7367..08cedcd 100644 Binary files a/assets/portraits/security-pros/card_004.png and b/assets/portraits/security-pros/card_004.png differ diff --git a/assets/portraits/security-pros/card_005.png b/assets/portraits/security-pros/card_005.png index f470845..5a876b3 100644 Binary files a/assets/portraits/security-pros/card_005.png and b/assets/portraits/security-pros/card_005.png differ diff --git a/assets/portraits/security-pros/card_006.png b/assets/portraits/security-pros/card_006.png index 7ddc4c6..3684779 100644 Binary files a/assets/portraits/security-pros/card_006.png and b/assets/portraits/security-pros/card_006.png differ diff --git a/assets/portraits/security-pros/card_007.png b/assets/portraits/security-pros/card_007.png index d790e1f..8ed44fc 100644 Binary files a/assets/portraits/security-pros/card_007.png and b/assets/portraits/security-pros/card_007.png differ diff --git a/assets/portraits/security-pros/card_008.png b/assets/portraits/security-pros/card_008.png index d9ab256..b864b84 100644 Binary files a/assets/portraits/security-pros/card_008.png and b/assets/portraits/security-pros/card_008.png differ diff --git a/assets/portraits/security-pros/card_009.png b/assets/portraits/security-pros/card_009.png index debf88d..bdd32f1 100644 Binary files a/assets/portraits/security-pros/card_009.png and b/assets/portraits/security-pros/card_009.png differ diff --git a/assets/portraits/security-pros/card_010.png b/assets/portraits/security-pros/card_010.png index 795a602..cb59e4e 100644 Binary files a/assets/portraits/security-pros/card_010.png and b/assets/portraits/security-pros/card_010.png differ diff --git a/assets/portraits/security-pros/card_011.png b/assets/portraits/security-pros/card_011.png index e2787a2..17e688b 100644 Binary files a/assets/portraits/security-pros/card_011.png and b/assets/portraits/security-pros/card_011.png differ diff --git a/assets/portraits/security-pros/card_012.png b/assets/portraits/security-pros/card_012.png index 22054c3..6867f81 100644 Binary files a/assets/portraits/security-pros/card_012.png and b/assets/portraits/security-pros/card_012.png differ diff --git a/assets/portraits/security-pros/card_013.png b/assets/portraits/security-pros/card_013.png index 76ce6dc..a917e03 100644 Binary files a/assets/portraits/security-pros/card_013.png and b/assets/portraits/security-pros/card_013.png differ diff --git a/assets/portraits/security-pros/card_014.png b/assets/portraits/security-pros/card_014.png index 2770667..99796ce 100644 Binary files a/assets/portraits/security-pros/card_014.png and b/assets/portraits/security-pros/card_014.png differ diff --git a/assets/portraits/security-pros/card_015.png b/assets/portraits/security-pros/card_015.png index 584188f..d07daa8 100644 Binary files a/assets/portraits/security-pros/card_015.png and b/assets/portraits/security-pros/card_015.png differ diff --git a/assets/portraits/security-pros/card_016.png b/assets/portraits/security-pros/card_016.png index bca299f..780cd93 100644 Binary files a/assets/portraits/security-pros/card_016.png and b/assets/portraits/security-pros/card_016.png differ diff --git a/assets/portraits/security-pros/card_017.png b/assets/portraits/security-pros/card_017.png index 5d282fd..9043a34 100644 Binary files a/assets/portraits/security-pros/card_017.png and b/assets/portraits/security-pros/card_017.png differ diff --git a/assets/portraits/security-pros/card_018.png b/assets/portraits/security-pros/card_018.png index dbc2054..0bb698a 100644 Binary files a/assets/portraits/security-pros/card_018.png and b/assets/portraits/security-pros/card_018.png differ diff --git a/assets/portraits/security-pros/card_019.png b/assets/portraits/security-pros/card_019.png index 4a15018..e789c3c 100644 Binary files a/assets/portraits/security-pros/card_019.png and b/assets/portraits/security-pros/card_019.png differ diff --git a/assets/portraits/security-pros/card_020.png b/assets/portraits/security-pros/card_020.png index 8c946de..1af7f25 100644 Binary files a/assets/portraits/security-pros/card_020.png and b/assets/portraits/security-pros/card_020.png differ diff --git a/assets/portraits/security-pros/card_021.png b/assets/portraits/security-pros/card_021.png deleted file mode 100644 index 2735548..0000000 Binary files a/assets/portraits/security-pros/card_021.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_022.png b/assets/portraits/security-pros/card_022.png index 7b5ffa0..c49e22c 100644 Binary files a/assets/portraits/security-pros/card_022.png and b/assets/portraits/security-pros/card_022.png differ diff --git a/assets/portraits/security-pros/card_023.png b/assets/portraits/security-pros/card_023.png index d8867a9..920b10d 100644 Binary files a/assets/portraits/security-pros/card_023.png and b/assets/portraits/security-pros/card_023.png differ diff --git a/assets/portraits/security-pros/card_024.png b/assets/portraits/security-pros/card_024.png index c2edea6..8753f02 100644 Binary files a/assets/portraits/security-pros/card_024.png and b/assets/portraits/security-pros/card_024.png differ diff --git a/assets/portraits/security-pros/card_025.png b/assets/portraits/security-pros/card_025.png index 5d858e1..bb3a9ee 100644 Binary files a/assets/portraits/security-pros/card_025.png and b/assets/portraits/security-pros/card_025.png differ diff --git a/assets/portraits/security-pros/card_026.png b/assets/portraits/security-pros/card_026.png deleted file mode 100644 index 02d3847..0000000 Binary files a/assets/portraits/security-pros/card_026.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_027.png b/assets/portraits/security-pros/card_027.png index 1ced391..1d5a67f 100644 Binary files a/assets/portraits/security-pros/card_027.png and b/assets/portraits/security-pros/card_027.png differ diff --git a/assets/portraits/security-pros/card_028.png b/assets/portraits/security-pros/card_028.png deleted file mode 100644 index 73c6a6f..0000000 Binary files a/assets/portraits/security-pros/card_028.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_029.png b/assets/portraits/security-pros/card_029.png deleted file mode 100644 index 63011ec..0000000 Binary files a/assets/portraits/security-pros/card_029.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_030.png b/assets/portraits/security-pros/card_030.png deleted file mode 100644 index e076d03..0000000 Binary files a/assets/portraits/security-pros/card_030.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_031.png b/assets/portraits/security-pros/card_031.png index b798931..4864e7c 100644 Binary files a/assets/portraits/security-pros/card_031.png and b/assets/portraits/security-pros/card_031.png differ diff --git a/assets/portraits/security-pros/card_032.png b/assets/portraits/security-pros/card_032.png deleted file mode 100644 index ed0648a..0000000 Binary files a/assets/portraits/security-pros/card_032.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_033.png b/assets/portraits/security-pros/card_033.png deleted file mode 100644 index c28a487..0000000 Binary files a/assets/portraits/security-pros/card_033.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_034.png b/assets/portraits/security-pros/card_034.png deleted file mode 100644 index 2a92e9c..0000000 Binary files a/assets/portraits/security-pros/card_034.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_035.png b/assets/portraits/security-pros/card_035.png index eaf43ec..09c15e9 100644 Binary files a/assets/portraits/security-pros/card_035.png and b/assets/portraits/security-pros/card_035.png differ diff --git a/assets/portraits/security-pros/card_036.png b/assets/portraits/security-pros/card_036.png index d0b980e..73f4af3 100644 Binary files a/assets/portraits/security-pros/card_036.png and b/assets/portraits/security-pros/card_036.png differ diff --git a/assets/portraits/security-pros/card_037.png b/assets/portraits/security-pros/card_037.png index d93595f..258ef48 100644 Binary files a/assets/portraits/security-pros/card_037.png and b/assets/portraits/security-pros/card_037.png differ diff --git a/assets/portraits/security-pros/card_038.png b/assets/portraits/security-pros/card_038.png index 32d741c..8d966be 100644 Binary files a/assets/portraits/security-pros/card_038.png and b/assets/portraits/security-pros/card_038.png differ diff --git a/assets/portraits/security-pros/card_039.png b/assets/portraits/security-pros/card_039.png index 99709be..4dd76ef 100644 Binary files a/assets/portraits/security-pros/card_039.png and b/assets/portraits/security-pros/card_039.png differ diff --git a/assets/portraits/security-pros/card_040.png b/assets/portraits/security-pros/card_040.png index 8395c29..b886319 100644 Binary files a/assets/portraits/security-pros/card_040.png and b/assets/portraits/security-pros/card_040.png differ diff --git a/assets/portraits/security-pros/card_041.png b/assets/portraits/security-pros/card_041.png index f19528a..60ae166 100644 Binary files a/assets/portraits/security-pros/card_041.png and b/assets/portraits/security-pros/card_041.png differ diff --git a/assets/portraits/security-pros/card_042.png b/assets/portraits/security-pros/card_042.png deleted file mode 100644 index 47a8fa2..0000000 Binary files a/assets/portraits/security-pros/card_042.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_043.png b/assets/portraits/security-pros/card_043.png index b4ddc87..667f780 100644 Binary files a/assets/portraits/security-pros/card_043.png and b/assets/portraits/security-pros/card_043.png differ diff --git a/assets/portraits/security-pros/card_044.png b/assets/portraits/security-pros/card_044.png index 4586780..d0f7ecc 100644 Binary files a/assets/portraits/security-pros/card_044.png and b/assets/portraits/security-pros/card_044.png differ diff --git a/assets/portraits/security-pros/card_045.png b/assets/portraits/security-pros/card_045.png index 54611ab..5bc5ddb 100644 Binary files a/assets/portraits/security-pros/card_045.png and b/assets/portraits/security-pros/card_045.png differ diff --git a/assets/portraits/security-pros/card_046.png b/assets/portraits/security-pros/card_046.png index b778eb1..e81e6d1 100644 Binary files a/assets/portraits/security-pros/card_046.png and b/assets/portraits/security-pros/card_046.png differ diff --git a/assets/portraits/security-pros/card_047.png b/assets/portraits/security-pros/card_047.png index 093ccf8..a2b7df9 100644 Binary files a/assets/portraits/security-pros/card_047.png and b/assets/portraits/security-pros/card_047.png differ diff --git a/assets/portraits/security-pros/card_048.png b/assets/portraits/security-pros/card_048.png index d804524..63cfaef 100644 Binary files a/assets/portraits/security-pros/card_048.png and b/assets/portraits/security-pros/card_048.png differ diff --git a/assets/portraits/security-pros/card_049.png b/assets/portraits/security-pros/card_049.png index bb4eb64..cfe561a 100644 Binary files a/assets/portraits/security-pros/card_049.png and b/assets/portraits/security-pros/card_049.png differ diff --git a/assets/portraits/security-pros/card_050.png b/assets/portraits/security-pros/card_050.png deleted file mode 100644 index d872803..0000000 Binary files a/assets/portraits/security-pros/card_050.png and /dev/null differ diff --git a/data/security-references.json b/data/security-references.json new file mode 100644 index 0000000..9100796 --- /dev/null +++ b/data/security-references.json @@ -0,0 +1,747 @@ +[ + { + "number": 1, + "slug": "whitfield-diffie", + "name": "Whitfield Diffie", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/0/0c/Whitfield_Diffie_Royal_Society.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Duncan.Hull", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Whitfield_Diffie_Royal_Society.jpg", + "file": "Whitfield Diffie Royal Society.jpg", + "qid": "Q462089", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American cryptographer (born 1944)" + }, + { + "number": 2, + "slug": "martin-hellman", + "name": "Martin Hellman", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/d/d4/Martin-Hellman.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "User .:Ajvol:. on en.wikipedia", + "license": "CC BY-SA 3.0", + "licenseUrl": "http://creativecommons.org/licenses/by-sa/3.0/", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Martin-Hellman.jpg", + "file": "Martin-Hellman.jpg", + "qid": "Q476466", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American cryptologist (born 1945)" + }, + { + "number": 3, + "slug": "ron-rivest", + "name": "Ron Rivest", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/0/0b/Rivest_cropped.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Sherman,_Rivest,_and_Chaum.jpg: carback1\nderivative work: Bonnifac (talk)", + "license": "CC BY-SA 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Rivest_cropped.jpg", + "file": "Rivest cropped.jpg", + "commonsFile": "Rivest cropped.jpg", + "via": "override" + }, + "candidates": [] + }, + { + "number": 4, + "slug": "adi-shamir", + "name": "Adi Shamir", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/1/1e/Adi_Shamir_at_TU_Darmstadt_%282013%29.jpg/1280px-Adi_Shamir_at_TU_Darmstadt_%282013%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Erik Tews", + "license": "CC BY-SA 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Adi_Shamir_at_TU_Darmstadt_(2013).jpg", + "file": "Adi Shamir at TU Darmstadt (2013).jpg", + "qid": "Q320624", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "Israeli cryptographer (born 1952)" + }, + { + "number": 5, + "slug": "leonard-adleman", + "name": "Leonard Adleman", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/a/af/Len-mankin-pic.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "len adlmen", + "license": "CC BY-SA 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Len-mankin-pic.jpg", + "file": "Len-mankin-pic.jpg", + "qid": "Q918650", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer scientist" + }, + { + "number": 6, + "slug": "phil-zimmermann", + "name": "Phil Zimmermann", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/8/85/Phil_zimmermann.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Phil Zimmermann, uploaded by Matt Crypto", + "license": "CC BY-SA 3.0", + "licenseUrl": "http://creativecommons.org/licenses/by-sa/3.0/", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Phil_zimmermann.jpg", + "file": "Phil zimmermann.jpg", + "qid": null, + "via": "commons-search" + }, + "candidates": [ + "Phil zimmermann.jpg" + ] + }, + { + "number": 7, + "slug": "bruce-schneier", + "name": "Bruce Schneier", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/4/45/Bruce_Schneier_1.jpg/1280px-Bruce_Schneier_1.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "sfllaw", + "license": "CC BY-SA 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Bruce_Schneier_1.jpg", + "file": "Bruce Schneier 1.jpg", + "qid": "Q368328", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer scientist (born 1963)" + }, + { + "number": 8, + "slug": "paul-kocher", + "name": "Paul Kocher", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/a/ac/Paul_kocher_deepcrack.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "https://www.oocities.org", + "license": "CC0", + "licenseUrl": "http://creativecommons.org/publicdomain/zero/1.0/deed.en", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Paul_kocher_deepcrack.jpg", + "file": "Paul kocher deepcrack.jpg", + "qid": "Q3371582", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American cryptographer" + }, + { + "number": 9, + "slug": "dorothy-denning", + "name": "Dorothy E. Denning", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/a/a9/Dorothy-Denning-Feb2013-head.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Unknown photographer, US Navy", + "license": "Public domain", + "licenseUrl": "", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Dorothy-Denning-Feb2013-head.jpg", + "file": "Dorothy-Denning-Feb2013-head.jpg", + "qid": "Q11542", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American information security researcher" + }, + { + "number": 10, + "slug": "gene-spafford", + "name": "Gene Spafford", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/9/9d/Gene-spafford.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Chlor at English Wikipedia", + "license": "CC BY-SA 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Gene-spafford.jpg", + "file": "Gene-spafford.jpg", + "qid": "Q5531458", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer scientist" + }, + { + "number": 11, + "slug": "ross-anderson", + "name": "Ross Anderson", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/b/b3/Ross_Anderson_%28security_researcher%29.jpg/1280px-Ross_Anderson_%28security_researcher%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Markus Kuhn", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Ross_Anderson_(security_researcher).jpg", + "file": "Ross Anderson (security researcher).jpg", + "qid": "Q110901283", + "via": "commons-search" + }, + "candidates": [ + "Ross Anderson (security researcher).jpg", + "Ross Anderson (7862797770).jpg", + "Ross Anderson (7862798682).jpg", + "Ross Anderson 15 June 2018 IMG 4317.jpg", + "Ross Anderson (Men of affairs, 1914).png" + ] + }, + { + "number": 12, + "slug": "matt-blaze", + "name": "Matt Blaze", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/c/c6/Matt_Blaze_DEF_CON_20.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "DEFCONConference", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Matt_Blaze_DEF_CON_20.jpg", + "file": "Matt Blaze DEF CON 20.jpg", + "qid": null, + "via": "commons-search" + }, + "candidates": [ + "Matt Blaze DEF CON 20.jpg" + ] + }, + { + "number": 13, + "slug": "radia-perlman", + "name": "Radia Perlman", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/a/af/Radia_Perlman_2009.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Scientist-100 at English Wikipedia", + "license": "Public domain", + "licenseUrl": "", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Radia_Perlman_2009.jpg", + "file": "Radia Perlman 2009.jpg", + "qid": "Q7558", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American software designer and network engineer (born 1951)" + }, + { + "number": 14, + "slug": "steven-bellovin", + "name": "Steven M. Bellovin", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/9/95/Steven_M_Bellovin_2016.jpg/1280px-Steven_M_Bellovin_2016.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "New America", + "license": "CC BY 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Steven_M_Bellovin_2016.jpg", + "file": "Steven M Bellovin 2016.jpg", + "qid": "Q1485905", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer scientist" + }, + { + "number": 15, + "slug": "william-cheswick", + "name": "William Cheswick", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/f/fc/William_Cheswick_2024.jpg/1280px-William_Cheswick_2024.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Kevin Payravi", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:William_Cheswick_2024.jpg", + "file": "William Cheswick 2024.jpg", + "qid": "Q8006762", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American engineer" + }, + { + "number": 16, + "slug": "marcus-ranum", + "name": "Marcus J. Ranum", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/1/12/Mjr-portrait-picture-mid.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Marcus Ranum (per discussion on ticket)", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Mjr-portrait-picture-mid.jpg", + "file": "Mjr-portrait-picture-mid.jpg", + "qid": "Q3846688", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "researcher" + }, + { + "number": 17, + "slug": "wietse-venema", + "name": "Wietse Venema", + "ref": { + "url": "https://live.staticflickr.com/104/276434076_581ed1215d_b.jpg", + "credit": "Alexandre Dulaunoy", + "license": "CC BY-SA 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/2.0/", + "sourceUrl": "https://www.flickr.com/photos/31797858@N00/276434076", + "note": "hack.lu 2006. Supersedes the Wikidata P18 file, which shows a different man.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 18, + "slug": "dan-farmer", + "name": "Dan Farmer", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/9/99/Dan_Farmer_in_2007.JPG?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Ian Manka", + "license": "CC BY-SA 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Dan_Farmer_in_2007.JPG", + "file": "Dan Farmer in 2007.JPG", + "qid": "Q5213467", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer programmer, computer security researcher and businessman" + }, + { + "number": 19, + "slug": "gordon-lyon", + "name": "Gordon Lyon", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/f/fc/Fyodor_Hope6_Appelbaum_1100x1100.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Jacob Appelbaum", + "license": "CC BY-SA 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Fyodor_Hope6_Appelbaum_1100x1100.jpg", + "file": "Fyodor Hope6 Appelbaum 1100x1100.jpg", + "qid": "Q3091346", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American network security expert, open source programmer, writer, and a hacker" + }, + { + "number": 20, + "slug": "martin-roesch", + "name": "Martin Roesch", + "ref": { + "url": "https://media.kasperskycontenthub.com/wp-content/uploads/sites/103/2013/07/07043722/sourcefire.jpg", + "credit": "Sourcefire press photo", + "license": "supplied", + "sourceUrl": "https://threatpost.com/", + "note": "Sourcefire-era press photograph. Supersedes the Commons files, which are a different Martin.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 21, + "slug": "gerald-combs", + "name": "Gerald Combs", + "ref": null, + "candidates": [] + }, + { + "number": 22, + "slug": "renaud-deraison", + "name": "Renaud Deraison", + "ref": { + "url": "https://live.staticflickr.com/122/276307114_f29f0a38ec_b.jpg", + "credit": "Alexandre Dulaunoy", + "license": "CC BY-SA 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/2.0/", + "sourceUrl": "https://www.flickr.com/photos/31797858@N00/276307114", + "note": "hack.lu 2006 opening speech.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 23, + "slug": "paul-vixie", + "name": "Paul Vixie", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/d/d6/Paul_Vixie_-_2014.jpg/1280px-Paul_Vixie_-_2014.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "ImaginingtheInternet", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Paul_Vixie_-_2014.jpg", + "file": "Paul Vixie - 2014.jpg", + "qid": "Q92777", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American internet pioneer" + }, + { + "number": 24, + "slug": "niels-provos", + "name": "Niels Provos", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/d/dc/Niels_provos.jpg/1280px-Niels_provos.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "NonAlphabetic", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Niels_provos.jpg", + "file": "Niels provos.jpg", + "qid": "Q7031568", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "cryptographer" + }, + { + "number": 25, + "slug": "jeremiah-grossman", + "name": "Jeremiah Grossman", + "ref": { + "url": "https://cdn.prod.website-files.com/62deb8a5505ca121b1665bbe/63cf05d85cad9419bfa83096_Photo%20Oct%2003%202022%2C%206%2054%2000%20AM-p-800.jpg", + "credit": "Jeremiah Grossman", + "license": "supplied", + "sourceUrl": "https://www.jeremiahgrossman.com/", + "note": "Publicity photo from his own site, supplied by the site owner as reference.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 26, + "slug": "robert-hansen", + "name": "Robert Hansen", + "ref": null, + "candidates": [ + "Robert Hansen ex libris.jpg", + "Robert Hansen Jr 180425.jpg" + ] + }, + { + "number": 27, + "slug": "mark-curphey", + "name": "Mark Curphey", + "ref": { + "url": "https://user-images.githubusercontent.com/143776/193322138-009524de-bae1-4190-989d-2d729ad0beb8.png", + "credit": "Mark Curphey", + "license": "supplied", + "sourceUrl": "https://www.crash-override.com/", + "note": "Portrait published by his own company.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 28, + "slug": "jeff-williams", + "name": "Jeff Williams", + "ref": null, + "candidates": [], + "rejected": "\"Jeff Williams photo.jpg\" is one of several unrelated Jeff Williamses. Cannot confirm it is the Contrast Security founder." + }, + { + "number": 29, + "slug": "gary-mcgraw", + "name": "Gary McGraw", + "ref": null, + "candidates": [], + "rejected": "Only candidate is a group briefing shot with audience heads across the frame; the other Commons hits are unrelated US Navy photographs." + }, + { + "number": 30, + "slug": "michael-howard", + "name": "Michael Howard", + "ref": null, + "candidates": [], + "rejected": "Every Commons hit is a different Michael Howard — the British politician, or a young man with no connection to the Microsoft SDL author." + }, + { + "number": 31, + "slug": "adam-shostack", + "name": "Adam Shostack", + "ref": { + "url": "https://shostack.org/img/site/Adam-Shostack-headshot.png", + "credit": "Adam Shostack", + "license": "supplied", + "sourceUrl": "https://shostack.org/about/adam", + "note": "Headshot published on his own site.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 32, + "slug": "ivan-ristic", + "name": "Ivan Ristić", + "ref": null, + "candidates": [] + }, + { + "number": 33, + "slug": "mark-dowd", + "name": "Mark Dowd", + "ref": null, + "candidates": [] + }, + { + "number": 34, + "slug": "halvar-flake", + "name": "Thomas Dullien", + "ref": null, + "candidates": [] + }, + { + "number": 35, + "slug": "alex-sotirov", + "name": "Alex Sotirov", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/c/c5/Alex_Sotirov.jpg/1280px-Alex_Sotirov.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Alexander Klink", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Alex_Sotirov.jpg", + "file": "Alex Sotirov.jpg", + "qid": null, + "via": "commons-search" + }, + "candidates": [ + "Alex Sotirov.jpg" + ] + }, + { + "number": 36, + "slug": "thomas-ptacek", + "name": "Thomas Ptacek", + "ref": { + "url": "https://live.staticflickr.com/2733/4302617844_52625091af_b.jpg", + "credit": "cfournie", + "license": "CC BY 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by/2.0/", + "sourceUrl": "https://www.flickr.com/photos/37932109@N05/4302617844", + "via": "override" + }, + "candidates": [] + }, + { + "number": 37, + "slug": "matthew-green", + "name": "Matthew D. Green", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/d/da/Matthew_Green.jpeg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Calhbca", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Matthew_Green.jpeg", + "file": "Matthew Green.jpeg", + "qid": "Q16729426", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American cryptographer and researcher" + }, + { + "number": 38, + "slug": "j-alex-halderman", + "name": "J. Alex Halderman", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/b/b2/J._Alex_Halderman_-_2018.jpg/1280px-J._Alex_Halderman_-_2018.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "media.ccc.de", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:J._Alex_Halderman_-_2018.jpg", + "file": "J. Alex Halderman - 2018.jpg", + "qid": "Q130974774", + "via": "commons-search" + }, + "candidates": [ + "J. Alex Halderman - 2018.jpg" + ] + }, + { + "number": 39, + "slug": "nadia-heninger", + "name": "Nadia Heninger", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/5/5a/Nadia_Heninger_2013_at_Chaos_Communication_Congress_30C3_DJB_03.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Ordercrazy", + "license": "CC0", + "licenseUrl": "http://creativecommons.org/publicdomain/zero/1.0/deed.en", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Nadia_Heninger_2013_at_Chaos_Communication_Congress_30C3_DJB_03.jpg", + "file": "Nadia Heninger 2013 at Chaos Communication Congress 30C3 DJB 03.jpg", + "qid": "Q56678255", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American cryptographer, computer security expert" + }, + { + "number": 40, + "slug": "troy-hunt", + "name": "Troy Hunt", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/1/11/Troy_Hunt.jpg/1280px-Troy_Hunt.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Troy Hunt", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Troy_Hunt.jpg", + "file": "Troy Hunt.jpg", + "qid": "Q23308064", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "Australian web security expert" + }, + { + "number": 41, + "slug": "window-snyder", + "name": "Window Snyder", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/4/42/Window_Snyder_Web_Summit_2017_cropped.jpg/1280px-Window_Snyder_Web_Summit_2017_cropped.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Web Summit", + "license": "CC BY 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Window_Snyder_Web_Summit_2017_cropped.jpg", + "file": "Window Snyder Web Summit 2017 cropped.jpg", + "qid": "Q8024355", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "Computer security expert" + }, + { + "number": 42, + "slug": "heather-adkins", + "name": "Heather Adkins", + "ref": null, + "candidates": [] + }, + { + "number": 43, + "slug": "parisa-tabriz", + "name": "Parisa Tabriz", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/6/67/Parisa_Tabriz_Blackhat%2717_profile.jpg/1280px-Parisa_Tabriz_Blackhat%2717_profile.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "mrisher", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Parisa_Tabriz_Blackhat%2717_profile.jpg", + "file": "Parisa Tabriz Blackhat'17 profile.jpg", + "qid": "Q17496835", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American/Iranian computer security expert" + }, + { + "number": 44, + "slug": "alex-stamos", + "name": "Alex Stamos", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/6/6a/Alex_Stamos_at_Web_Summit_2015_-_Dublin%2C_Ireland.jpg/1280px-Alex_Stamos_at_Web_Summit_2015_-_Dublin%2C_Ireland.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Web Summit", + "license": "CC BY 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Alex_Stamos_at_Web_Summit_2015_-_Dublin,_Ireland.jpg", + "file": "Alex Stamos at Web Summit 2015 - Dublin, Ireland.jpg", + "qid": "Q50822408", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer scientist and academic, former chief security officer at Facebook" + }, + { + "number": 45, + "slug": "dan-geer", + "name": "Dan Geer", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/4/4d/Dan_Geer_-_Freedom_to_Connect_2015_%2816109600123%29.jpg/1280px-Dan_Geer_-_Freedom_to_Connect_2015_%2816109600123%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Doc Searls from Santa Barbara, USA", + "license": "CC BY 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Dan_Geer_-_Freedom_to_Connect_2015_(16109600123).jpg", + "file": "Dan Geer - Freedom to Connect 2015 (16109600123).jpg", + "qid": "Q5213551", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer scientist" + }, + { + "number": 46, + "slug": "kevin-mandia", + "name": "Kevin Mandia", + "ref": { + "url": "https://ballisticventures.com/wp-content/uploads/2024/06/Kevin-Mandia.png", + "credit": "Ballistic Ventures", + "license": "supplied", + "sourceUrl": "https://www.ballisticventures.com/team/kevin-mandia", + "note": "Partner bio photo.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 47, + "slug": "mikko-hypponen", + "name": "Mikko Hyppönen", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/c/ce/Mikko_Hypp%C3%B6nen_-_2021_%28cropped%29.jpg/1280px-Mikko_Hypp%C3%B6nen_-_2021_%28cropped%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Ville Erkkilä", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Mikko_Hypp%C3%B6nen_-_2021_(cropped).jpg", + "file": "Mikko Hyppönen - 2021 (cropped).jpg", + "qid": "Q2489610", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "Finnish computer security expert" + }, + { + "number": 48, + "slug": "robert-m-lee", + "name": "Robert M. Lee", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/9/92/Robert_M._Lee_at_World_Economic_Forum_Davos_2022.png?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "World Economic Forum", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Robert_M._Lee_at_World_Economic_Forum_Davos_2022.png", + "file": "Robert M. Lee at World Economic Forum Davos 2022.png", + "qid": null, + "via": "commons-search" + }, + "candidates": [ + "Robert M. Lee at World Economic Forum Davos 2022.png" + ] + }, + { + "number": 49, + "slug": "lesley-carhart", + "name": "Lesley Carhart", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/7/70/Lesley_Carhart_-_What_Can_We_Learn_About_Cybersecurity_Trashfires.jpg/1280px-Lesley_Carhart_-_What_Can_We_Learn_About_Cybersecurity_Trashfires.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "TechCrunch", + "license": "CC BY 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Lesley_Carhart_-_What_Can_We_Learn_About_Cybersecurity_Trashfires.jpg", + "file": "Lesley Carhart - What Can We Learn About Cybersecurity Trashfires.jpg", + "qid": null, + "via": "commons-search" + }, + "candidates": [ + "Lesley Carhart - What Can We Learn About Cybersecurity Trashfires.jpg", + "Lesley Carhart - What Can We Learn About Cybersecurity Trashfires (cropped).jpg" + ] + }, + { + "number": 50, + "slug": "katie-nickels", + "name": "Katie Nickels", + "ref": null, + "candidates": [] + } +] diff --git a/public/cards/security/001-whitfield-diffie-back.png b/public/cards/security/001-whitfield-diffie-back.png index 915e9f9..22db10a 100644 Binary files a/public/cards/security/001-whitfield-diffie-back.png and b/public/cards/security/001-whitfield-diffie-back.png differ diff --git a/public/cards/security/001-whitfield-diffie-front.png b/public/cards/security/001-whitfield-diffie-front.png index a94cd2a..456a390 100644 Binary files a/public/cards/security/001-whitfield-diffie-front.png and b/public/cards/security/001-whitfield-diffie-front.png differ diff --git a/public/cards/security/002-martin-hellman-back.png b/public/cards/security/002-martin-hellman-back.png index 13e8958..35c28d3 100644 Binary files a/public/cards/security/002-martin-hellman-back.png and b/public/cards/security/002-martin-hellman-back.png differ diff --git a/public/cards/security/002-martin-hellman-front.png b/public/cards/security/002-martin-hellman-front.png index 4c44c51..94fecf8 100644 Binary files a/public/cards/security/002-martin-hellman-front.png and b/public/cards/security/002-martin-hellman-front.png differ diff --git a/public/cards/security/003-ron-rivest-back.png b/public/cards/security/003-ron-rivest-back.png index 065bb24..8d8dc62 100644 Binary files a/public/cards/security/003-ron-rivest-back.png and b/public/cards/security/003-ron-rivest-back.png differ diff --git a/public/cards/security/003-ron-rivest-front.png b/public/cards/security/003-ron-rivest-front.png index 9e84ffd..d366c2c 100644 Binary files a/public/cards/security/003-ron-rivest-front.png and b/public/cards/security/003-ron-rivest-front.png differ diff --git a/public/cards/security/004-adi-shamir-back.png b/public/cards/security/004-adi-shamir-back.png index 5a55fd0..c316c6d 100644 Binary files a/public/cards/security/004-adi-shamir-back.png and b/public/cards/security/004-adi-shamir-back.png differ diff --git a/public/cards/security/004-adi-shamir-front.png b/public/cards/security/004-adi-shamir-front.png index 19d8ce3..6fa0c76 100644 Binary files a/public/cards/security/004-adi-shamir-front.png and b/public/cards/security/004-adi-shamir-front.png differ diff --git a/public/cards/security/005-leonard-adleman-back.png b/public/cards/security/005-leonard-adleman-back.png index ee6ecde..2aa1e27 100644 Binary files a/public/cards/security/005-leonard-adleman-back.png and b/public/cards/security/005-leonard-adleman-back.png differ diff --git a/public/cards/security/005-leonard-adleman-front.png b/public/cards/security/005-leonard-adleman-front.png index 1e72d2f..5667914 100644 Binary files a/public/cards/security/005-leonard-adleman-front.png and b/public/cards/security/005-leonard-adleman-front.png differ diff --git a/public/cards/security/006-phil-zimmermann-back.png b/public/cards/security/006-phil-zimmermann-back.png index 013dcb2..32cad92 100644 Binary files a/public/cards/security/006-phil-zimmermann-back.png and b/public/cards/security/006-phil-zimmermann-back.png differ diff --git a/public/cards/security/006-phil-zimmermann-front.png b/public/cards/security/006-phil-zimmermann-front.png index 88e40f7..b30f44d 100644 Binary files a/public/cards/security/006-phil-zimmermann-front.png and b/public/cards/security/006-phil-zimmermann-front.png differ diff --git a/public/cards/security/007-bruce-schneier-back.png b/public/cards/security/007-bruce-schneier-back.png index 0aa4626..665c466 100644 Binary files a/public/cards/security/007-bruce-schneier-back.png and b/public/cards/security/007-bruce-schneier-back.png differ diff --git a/public/cards/security/007-bruce-schneier-front.png b/public/cards/security/007-bruce-schneier-front.png index 09ee26d..8192b1d 100644 Binary files a/public/cards/security/007-bruce-schneier-front.png and b/public/cards/security/007-bruce-schneier-front.png differ diff --git a/public/cards/security/008-paul-kocher-back.png b/public/cards/security/008-paul-kocher-back.png index f59ff81..beee244 100644 Binary files a/public/cards/security/008-paul-kocher-back.png and b/public/cards/security/008-paul-kocher-back.png differ diff --git a/public/cards/security/008-paul-kocher-front.png b/public/cards/security/008-paul-kocher-front.png index 1ad6909..c2148af 100644 Binary files a/public/cards/security/008-paul-kocher-front.png and b/public/cards/security/008-paul-kocher-front.png differ diff --git a/public/cards/security/009-dorothy-denning-back.png b/public/cards/security/009-dorothy-denning-back.png index 316ee33..a6aed79 100644 Binary files a/public/cards/security/009-dorothy-denning-back.png and b/public/cards/security/009-dorothy-denning-back.png differ diff --git a/public/cards/security/009-dorothy-denning-front.png b/public/cards/security/009-dorothy-denning-front.png index 7cc90d9..4528550 100644 Binary files a/public/cards/security/009-dorothy-denning-front.png and b/public/cards/security/009-dorothy-denning-front.png differ diff --git a/public/cards/security/010-gene-spafford-back.png b/public/cards/security/010-gene-spafford-back.png index 0e7499e..2ccf828 100644 Binary files a/public/cards/security/010-gene-spafford-back.png and b/public/cards/security/010-gene-spafford-back.png differ diff --git a/public/cards/security/010-gene-spafford-front.png b/public/cards/security/010-gene-spafford-front.png index 53deabb..6aa9e79 100644 Binary files a/public/cards/security/010-gene-spafford-front.png and b/public/cards/security/010-gene-spafford-front.png differ diff --git a/public/cards/security/011-ross-anderson-back.png b/public/cards/security/011-ross-anderson-back.png index 5b828bd..90a8bbe 100644 Binary files a/public/cards/security/011-ross-anderson-back.png and b/public/cards/security/011-ross-anderson-back.png differ diff --git a/public/cards/security/011-ross-anderson-front.png b/public/cards/security/011-ross-anderson-front.png index 3e2d83e..69d034a 100644 Binary files a/public/cards/security/011-ross-anderson-front.png and b/public/cards/security/011-ross-anderson-front.png differ diff --git a/public/cards/security/012-matt-blaze-back.png b/public/cards/security/012-matt-blaze-back.png index f62ff8a..8d2e733 100644 Binary files a/public/cards/security/012-matt-blaze-back.png and b/public/cards/security/012-matt-blaze-back.png differ diff --git a/public/cards/security/012-matt-blaze-front.png b/public/cards/security/012-matt-blaze-front.png index 7a04bb5..513dbf4 100644 Binary files a/public/cards/security/012-matt-blaze-front.png and b/public/cards/security/012-matt-blaze-front.png differ diff --git a/public/cards/security/013-radia-perlman-back.png b/public/cards/security/013-radia-perlman-back.png index 1ec4b05..e382a9a 100644 Binary files a/public/cards/security/013-radia-perlman-back.png and b/public/cards/security/013-radia-perlman-back.png differ diff --git a/public/cards/security/013-radia-perlman-front.png b/public/cards/security/013-radia-perlman-front.png index 52e33b4..7b7052c 100644 Binary files a/public/cards/security/013-radia-perlman-front.png and b/public/cards/security/013-radia-perlman-front.png differ diff --git a/public/cards/security/014-steven-bellovin-back.png b/public/cards/security/014-steven-bellovin-back.png index 1b0a1bf..76121f3 100644 Binary files a/public/cards/security/014-steven-bellovin-back.png and b/public/cards/security/014-steven-bellovin-back.png differ diff --git a/public/cards/security/014-steven-bellovin-front.png b/public/cards/security/014-steven-bellovin-front.png index a6c48d4..ce3f9d8 100644 Binary files a/public/cards/security/014-steven-bellovin-front.png and b/public/cards/security/014-steven-bellovin-front.png differ diff --git a/public/cards/security/015-william-cheswick-back.png b/public/cards/security/015-william-cheswick-back.png index 1b2d5b6..c785144 100644 Binary files a/public/cards/security/015-william-cheswick-back.png and b/public/cards/security/015-william-cheswick-back.png differ diff --git a/public/cards/security/015-william-cheswick-front.png b/public/cards/security/015-william-cheswick-front.png index f96a23e..2880d76 100644 Binary files a/public/cards/security/015-william-cheswick-front.png and b/public/cards/security/015-william-cheswick-front.png differ diff --git a/public/cards/security/016-marcus-ranum-back.png b/public/cards/security/016-marcus-ranum-back.png index ce73574..3fc6923 100644 Binary files a/public/cards/security/016-marcus-ranum-back.png and b/public/cards/security/016-marcus-ranum-back.png differ diff --git a/public/cards/security/016-marcus-ranum-front.png b/public/cards/security/016-marcus-ranum-front.png index c877732..6bb4d11 100644 Binary files a/public/cards/security/016-marcus-ranum-front.png and b/public/cards/security/016-marcus-ranum-front.png differ diff --git a/public/cards/security/017-wietse-venema-back.png b/public/cards/security/017-wietse-venema-back.png index e667238..4eaabb4 100644 Binary files a/public/cards/security/017-wietse-venema-back.png and b/public/cards/security/017-wietse-venema-back.png differ diff --git a/public/cards/security/017-wietse-venema-front.png b/public/cards/security/017-wietse-venema-front.png index b40d480..52efeaf 100644 Binary files a/public/cards/security/017-wietse-venema-front.png and b/public/cards/security/017-wietse-venema-front.png differ diff --git a/public/cards/security/018-dan-farmer-back.png b/public/cards/security/018-dan-farmer-back.png index 5cb3d65..a5bbdae 100644 Binary files a/public/cards/security/018-dan-farmer-back.png and b/public/cards/security/018-dan-farmer-back.png differ diff --git a/public/cards/security/018-dan-farmer-front.png b/public/cards/security/018-dan-farmer-front.png index 473abf7..bb4bbbb 100644 Binary files a/public/cards/security/018-dan-farmer-front.png and b/public/cards/security/018-dan-farmer-front.png differ diff --git a/public/cards/security/019-gordon-lyon-back.png b/public/cards/security/019-gordon-lyon-back.png index cd2b3f9..6624303 100644 Binary files a/public/cards/security/019-gordon-lyon-back.png and b/public/cards/security/019-gordon-lyon-back.png differ diff --git a/public/cards/security/019-gordon-lyon-front.png b/public/cards/security/019-gordon-lyon-front.png index 9ec0efb..84437d4 100644 Binary files a/public/cards/security/019-gordon-lyon-front.png and b/public/cards/security/019-gordon-lyon-front.png differ diff --git a/public/cards/security/020-martin-roesch-back.png b/public/cards/security/020-martin-roesch-back.png index 02d4a65..3b38d35 100644 Binary files a/public/cards/security/020-martin-roesch-back.png and b/public/cards/security/020-martin-roesch-back.png differ diff --git a/public/cards/security/020-martin-roesch-front.png b/public/cards/security/020-martin-roesch-front.png index 8c832f0..d1ec09b 100644 Binary files a/public/cards/security/020-martin-roesch-front.png and b/public/cards/security/020-martin-roesch-front.png differ diff --git a/public/cards/security/021-gerald-combs-front.png b/public/cards/security/021-gerald-combs-front.png index e1a455e..65f8bd8 100644 Binary files a/public/cards/security/021-gerald-combs-front.png and b/public/cards/security/021-gerald-combs-front.png differ diff --git a/public/cards/security/022-renaud-deraison-back.png b/public/cards/security/022-renaud-deraison-back.png index 0217894..bb38908 100644 Binary files a/public/cards/security/022-renaud-deraison-back.png and b/public/cards/security/022-renaud-deraison-back.png differ diff --git a/public/cards/security/022-renaud-deraison-front.png b/public/cards/security/022-renaud-deraison-front.png index ce87913..15424ec 100644 Binary files a/public/cards/security/022-renaud-deraison-front.png and b/public/cards/security/022-renaud-deraison-front.png differ diff --git a/public/cards/security/023-paul-vixie-back.png b/public/cards/security/023-paul-vixie-back.png index e6c6456..f850fa4 100644 Binary files a/public/cards/security/023-paul-vixie-back.png and b/public/cards/security/023-paul-vixie-back.png differ diff --git a/public/cards/security/023-paul-vixie-front.png b/public/cards/security/023-paul-vixie-front.png index 821a620..1785230 100644 Binary files a/public/cards/security/023-paul-vixie-front.png and b/public/cards/security/023-paul-vixie-front.png differ diff --git a/public/cards/security/024-niels-provos-back.png b/public/cards/security/024-niels-provos-back.png index f89601b..593d746 100644 Binary files a/public/cards/security/024-niels-provos-back.png and b/public/cards/security/024-niels-provos-back.png differ diff --git a/public/cards/security/024-niels-provos-front.png b/public/cards/security/024-niels-provos-front.png index cf9a1aa..d713bc2 100644 Binary files a/public/cards/security/024-niels-provos-front.png and b/public/cards/security/024-niels-provos-front.png differ diff --git a/public/cards/security/025-jeremiah-grossman-back.png b/public/cards/security/025-jeremiah-grossman-back.png index d047bb6..667dd89 100644 Binary files a/public/cards/security/025-jeremiah-grossman-back.png and b/public/cards/security/025-jeremiah-grossman-back.png differ diff --git a/public/cards/security/025-jeremiah-grossman-front.png b/public/cards/security/025-jeremiah-grossman-front.png index ad6925a..a80bca1 100644 Binary files a/public/cards/security/025-jeremiah-grossman-front.png and b/public/cards/security/025-jeremiah-grossman-front.png differ diff --git a/public/cards/security/026-robert-hansen-front.png b/public/cards/security/026-robert-hansen-front.png index cb5823c..247ea7f 100644 Binary files a/public/cards/security/026-robert-hansen-front.png and b/public/cards/security/026-robert-hansen-front.png differ diff --git a/public/cards/security/027-mark-curphey-back.png b/public/cards/security/027-mark-curphey-back.png index bb236cb..2148450 100644 Binary files a/public/cards/security/027-mark-curphey-back.png and b/public/cards/security/027-mark-curphey-back.png differ diff --git a/public/cards/security/027-mark-curphey-front.png b/public/cards/security/027-mark-curphey-front.png index 11ec5d1..16ca9dc 100644 Binary files a/public/cards/security/027-mark-curphey-front.png and b/public/cards/security/027-mark-curphey-front.png differ diff --git a/public/cards/security/028-jeff-williams-front.png b/public/cards/security/028-jeff-williams-front.png index d5c7f86..9cf3ff4 100644 Binary files a/public/cards/security/028-jeff-williams-front.png and b/public/cards/security/028-jeff-williams-front.png differ diff --git a/public/cards/security/029-gary-mcgraw-front.png b/public/cards/security/029-gary-mcgraw-front.png index 0605914..e272c3a 100644 Binary files a/public/cards/security/029-gary-mcgraw-front.png and b/public/cards/security/029-gary-mcgraw-front.png differ diff --git a/public/cards/security/030-michael-howard-front.png b/public/cards/security/030-michael-howard-front.png index 8ecbb8d..b29227e 100644 Binary files a/public/cards/security/030-michael-howard-front.png and b/public/cards/security/030-michael-howard-front.png differ diff --git a/public/cards/security/031-adam-shostack-back.png b/public/cards/security/031-adam-shostack-back.png index 2fba100..a68f21a 100644 Binary files a/public/cards/security/031-adam-shostack-back.png and b/public/cards/security/031-adam-shostack-back.png differ diff --git a/public/cards/security/031-adam-shostack-front.png b/public/cards/security/031-adam-shostack-front.png index 2120988..c30a674 100644 Binary files a/public/cards/security/031-adam-shostack-front.png and b/public/cards/security/031-adam-shostack-front.png differ diff --git a/public/cards/security/032-ivan-ristic-front.png b/public/cards/security/032-ivan-ristic-front.png index 8b9f45d..42ebd24 100644 Binary files a/public/cards/security/032-ivan-ristic-front.png and b/public/cards/security/032-ivan-ristic-front.png differ diff --git a/public/cards/security/033-mark-dowd-front.png b/public/cards/security/033-mark-dowd-front.png index 8700220..d57f13c 100644 Binary files a/public/cards/security/033-mark-dowd-front.png and b/public/cards/security/033-mark-dowd-front.png differ diff --git a/public/cards/security/034-halvar-flake-front.png b/public/cards/security/034-halvar-flake-front.png index 86064ce..8caa1fe 100644 Binary files a/public/cards/security/034-halvar-flake-front.png and b/public/cards/security/034-halvar-flake-front.png differ diff --git a/public/cards/security/035-alex-sotirov-back.png b/public/cards/security/035-alex-sotirov-back.png index fd1cb3f..e6ca68a 100644 Binary files a/public/cards/security/035-alex-sotirov-back.png and b/public/cards/security/035-alex-sotirov-back.png differ diff --git a/public/cards/security/035-alex-sotirov-front.png b/public/cards/security/035-alex-sotirov-front.png index 94dba9a..b330006 100644 Binary files a/public/cards/security/035-alex-sotirov-front.png and b/public/cards/security/035-alex-sotirov-front.png differ diff --git a/public/cards/security/036-thomas-ptacek-back.png b/public/cards/security/036-thomas-ptacek-back.png index 8112421..887c6a6 100644 Binary files a/public/cards/security/036-thomas-ptacek-back.png and b/public/cards/security/036-thomas-ptacek-back.png differ diff --git a/public/cards/security/036-thomas-ptacek-front.png b/public/cards/security/036-thomas-ptacek-front.png index d1bed8f..80d67b8 100644 Binary files a/public/cards/security/036-thomas-ptacek-front.png and b/public/cards/security/036-thomas-ptacek-front.png differ diff --git a/public/cards/security/037-matthew-green-back.png b/public/cards/security/037-matthew-green-back.png index 605ad9a..6dd1e29 100644 Binary files a/public/cards/security/037-matthew-green-back.png and b/public/cards/security/037-matthew-green-back.png differ diff --git a/public/cards/security/037-matthew-green-front.png b/public/cards/security/037-matthew-green-front.png index a7a100c..270c2f0 100644 Binary files a/public/cards/security/037-matthew-green-front.png and b/public/cards/security/037-matthew-green-front.png differ diff --git a/public/cards/security/038-j-alex-halderman-back.png b/public/cards/security/038-j-alex-halderman-back.png index 68721d7..806661f 100644 Binary files a/public/cards/security/038-j-alex-halderman-back.png and b/public/cards/security/038-j-alex-halderman-back.png differ diff --git a/public/cards/security/038-j-alex-halderman-front.png b/public/cards/security/038-j-alex-halderman-front.png index 9074d70..47f6f07 100644 Binary files a/public/cards/security/038-j-alex-halderman-front.png and b/public/cards/security/038-j-alex-halderman-front.png differ diff --git a/public/cards/security/039-nadia-heninger-back.png b/public/cards/security/039-nadia-heninger-back.png index fb9eaa0..4cfd6c5 100644 Binary files a/public/cards/security/039-nadia-heninger-back.png and b/public/cards/security/039-nadia-heninger-back.png differ diff --git a/public/cards/security/039-nadia-heninger-front.png b/public/cards/security/039-nadia-heninger-front.png index 8a47094..7a9a407 100644 Binary files a/public/cards/security/039-nadia-heninger-front.png and b/public/cards/security/039-nadia-heninger-front.png differ diff --git a/public/cards/security/040-troy-hunt-back.png b/public/cards/security/040-troy-hunt-back.png index d24a191..d4b6cb5 100644 Binary files a/public/cards/security/040-troy-hunt-back.png and b/public/cards/security/040-troy-hunt-back.png differ diff --git a/public/cards/security/040-troy-hunt-front.png b/public/cards/security/040-troy-hunt-front.png index 5572fad..fc1558c 100644 Binary files a/public/cards/security/040-troy-hunt-front.png and b/public/cards/security/040-troy-hunt-front.png differ diff --git a/public/cards/security/041-window-snyder-back.png b/public/cards/security/041-window-snyder-back.png index 984b62d..93cf0f4 100644 Binary files a/public/cards/security/041-window-snyder-back.png and b/public/cards/security/041-window-snyder-back.png differ diff --git a/public/cards/security/041-window-snyder-front.png b/public/cards/security/041-window-snyder-front.png index 9d4d275..d0678bb 100644 Binary files a/public/cards/security/041-window-snyder-front.png and b/public/cards/security/041-window-snyder-front.png differ diff --git a/public/cards/security/042-heather-adkins-front.png b/public/cards/security/042-heather-adkins-front.png index 1a6b554..c5c04b2 100644 Binary files a/public/cards/security/042-heather-adkins-front.png and b/public/cards/security/042-heather-adkins-front.png differ diff --git a/public/cards/security/043-parisa-tabriz-back.png b/public/cards/security/043-parisa-tabriz-back.png index 1f8a070..fb941b3 100644 Binary files a/public/cards/security/043-parisa-tabriz-back.png and b/public/cards/security/043-parisa-tabriz-back.png differ diff --git a/public/cards/security/043-parisa-tabriz-front.png b/public/cards/security/043-parisa-tabriz-front.png index 950dff9..a139857 100644 Binary files a/public/cards/security/043-parisa-tabriz-front.png and b/public/cards/security/043-parisa-tabriz-front.png differ diff --git a/public/cards/security/044-alex-stamos-back.png b/public/cards/security/044-alex-stamos-back.png index 7357be2..a53d389 100644 Binary files a/public/cards/security/044-alex-stamos-back.png and b/public/cards/security/044-alex-stamos-back.png differ diff --git a/public/cards/security/044-alex-stamos-front.png b/public/cards/security/044-alex-stamos-front.png index 2b3c68e..e2a00a4 100644 Binary files a/public/cards/security/044-alex-stamos-front.png and b/public/cards/security/044-alex-stamos-front.png differ diff --git a/public/cards/security/045-dan-geer-back.png b/public/cards/security/045-dan-geer-back.png index 9afe5c4..f691a64 100644 Binary files a/public/cards/security/045-dan-geer-back.png and b/public/cards/security/045-dan-geer-back.png differ diff --git a/public/cards/security/045-dan-geer-front.png b/public/cards/security/045-dan-geer-front.png index 98f7dd9..21499e6 100644 Binary files a/public/cards/security/045-dan-geer-front.png and b/public/cards/security/045-dan-geer-front.png differ diff --git a/public/cards/security/046-kevin-mandia-back.png b/public/cards/security/046-kevin-mandia-back.png index 0d7eadf..5c95854 100644 Binary files a/public/cards/security/046-kevin-mandia-back.png and b/public/cards/security/046-kevin-mandia-back.png differ diff --git a/public/cards/security/046-kevin-mandia-front.png b/public/cards/security/046-kevin-mandia-front.png index e4e68ae..1fabb79 100644 Binary files a/public/cards/security/046-kevin-mandia-front.png and b/public/cards/security/046-kevin-mandia-front.png differ diff --git a/public/cards/security/047-mikko-hypponen-back.png b/public/cards/security/047-mikko-hypponen-back.png index 49aefc2..c3a6b91 100644 Binary files a/public/cards/security/047-mikko-hypponen-back.png and b/public/cards/security/047-mikko-hypponen-back.png differ diff --git a/public/cards/security/047-mikko-hypponen-front.png b/public/cards/security/047-mikko-hypponen-front.png index d2aa740..4ef3354 100644 Binary files a/public/cards/security/047-mikko-hypponen-front.png and b/public/cards/security/047-mikko-hypponen-front.png differ diff --git a/public/cards/security/048-robert-m-lee-back.png b/public/cards/security/048-robert-m-lee-back.png index cb39e91..5dc332b 100644 Binary files a/public/cards/security/048-robert-m-lee-back.png and b/public/cards/security/048-robert-m-lee-back.png differ diff --git a/public/cards/security/048-robert-m-lee-front.png b/public/cards/security/048-robert-m-lee-front.png index 71b9c74..a02063c 100644 Binary files a/public/cards/security/048-robert-m-lee-front.png and b/public/cards/security/048-robert-m-lee-front.png differ diff --git a/public/cards/security/049-lesley-carhart-back.png b/public/cards/security/049-lesley-carhart-back.png index 39ae333..ec156eb 100644 Binary files a/public/cards/security/049-lesley-carhart-back.png and b/public/cards/security/049-lesley-carhart-back.png differ diff --git a/public/cards/security/049-lesley-carhart-front.png b/public/cards/security/049-lesley-carhart-front.png index 62a29c6..6d91a98 100644 Binary files a/public/cards/security/049-lesley-carhart-front.png and b/public/cards/security/049-lesley-carhart-front.png differ diff --git a/public/cards/security/050-katie-nickels-front.png b/public/cards/security/050-katie-nickels-front.png index 118e8db..7e9b953 100644 Binary files a/public/cards/security/050-katie-nickels-front.png and b/public/cards/security/050-katie-nickels-front.png differ diff --git a/scripts/security-pros.mjs b/scripts/security-pros.mjs index 50bdfde..59d2972 100644 --- a/scripts/security-pros.mjs +++ b/scripts/security-pros.mjs @@ -25,6 +25,17 @@ therefore opt-in only and never part of `all`. Running it means accepting that every affected face must be checked by eye before publish. + PORTRAITS ARE CONDITIONED ON A REAL PHOTOGRAPH + ---------------------------------------------- + The first pass generated faces from text prompts alone, so every portrait was an + invented stranger. Generation now takes a verified reference photo as input (see + scripts/security-refs.mjs), and a card with no verified photo gets NO FACE — the + front renders a plate saying so. We do not invent a likeness of a real person. + + The reference photo's photographer is credited: most are CC BY / CC BY-SA, and a + conditioned portrait is a derivative work. data/security-references.json carries + the provenance and the card page prints it. + The roster is src/data/security.ts, imported directly (Node strips the types), so there is no second copy of the data to keep in sync. @@ -72,6 +83,9 @@ process.emitWarning = (warning, ...rest) => { return emitWarning(warning, ...rest); }; +const REFS_DIR = path.join(ROOT, 'assets', 'references', 'security-pros'); +const REF_MANIFEST = path.join(ROOT, 'data', 'security-references.json'); + const DIRS = { approvedArt: path.join(ROOT, 'assets', 'portraits', 'security-pros'), workingArt: path.join(ROOT, 'assets', 'portraits-art', 'security-pros'), @@ -207,6 +221,40 @@ async function editImage(pngBuffer, prompt, { size = '1024x1536' } = {}) { // -------------------------------------------------------------- prompts ---- +function referenceFor(h) { + const p = path.join(REFS_DIR, `${cardId(h.number)}.jpg`); + return fssync.existsSync(p) ? p : null; +} + +let refManifest = null; +function refInfo(slug) { + if (!refManifest) { + refManifest = fssync.existsSync(REF_MANIFEST) + ? JSON.parse(fssync.readFileSync(REF_MANIFEST, 'utf8')) + : []; + } + return refManifest.find((r) => r.slug === slug)?.ref || null; +} + +/** + * Image-to-image instruction. The reference photograph is the subject; the model's + * job is to restyle it, not to reimagine who it is. Says so several ways, because + * the failure we are guarding against is precisely a plausible different face. + */ +function likenessPrompt(h) { + const motif = (h.domains || []).slice(0, 2).join(' and ') || 'computer security'; + return [ + 'Repaint this photograph as a realistic semi-painterly portrait for a premium collectible trading card.', + 'CRITICAL: keep the SAME PERSON. Preserve the exact facial structure, likeness, age, hairstyle, facial hair,', + 'glasses, skin tone and expression of the person in the photograph. Do not beautify, do not de-age,', + 'do not substitute a different face. The result must be recognisable as the same individual.', + 'Head and shoulders, facing the viewer, warm cinematic studio lighting with a cool rim light,', + 'detailed painted realism, dignified and serious.', + `Replace the background with a dark background carrying subtle faint motifs of ${motif}.`, + 'No text, no letters, no logos, no border, no card frame, no watermark.', + ].join(' '); +} + function portraitPrompt(h) { const motif = (h.domains || []).slice(0, 2).join(' and ') || 'computer security'; const era = h.era ? ` Period-accurate to ${h.era}.` : ''; @@ -331,9 +379,20 @@ async function pool(items, label, task) { async function portraits(roster) { await ensureDirs(); console.log(`portraits: ${provider()} / ${provider() === 'openai' ? OPENAI_MODEL() : GEMINI_MODEL()}`); - return pool(roster, 'portraits', async (h) => { + const withRef = roster.filter((h) => referenceFor(h)); + const without = roster.filter((h) => !referenceFor(h)); + if (without.length) { + console.log( + `portraits: ${without.length} have no verified reference photo and will get no face ` + + `(${without.map((h) => h.number).join(', ')}). Run scripts/security-refs.mjs to look again.` + ); + } + return pool(withRef, 'portraits', async (h) => { if (portraitFor(h)) return 'skip'; - const buf = await withRetry(cardId(h.number), () => generateImage(portraitPrompt(h))); + // Image-to-image off the real photograph. Never generateImage() — a text-only + // prompt is what produced the invented faces this pipeline exists to prevent. + const ref = await fs.readFile(referenceFor(h)); + const buf = await withRetry(cardId(h.number), () => editImage(ref, likenessPrompt(h), { size: '1024x1024' })); await fs.writeFile(path.join(DIRS.workingArt, `${cardId(h.number)}.png`), buf); }); } @@ -367,10 +426,11 @@ async function render(roster) { const id = cardId(h.number); try { const art = portraitFor(h); - if (!art) throw new Error('no portrait art — run `portraits` first'); - const uri = `data:image/png;base64,${(await fs.readFile(art)).toString('base64')}`; - await shoot(buildFront(h, uri), id, 'front'); - await shoot(buildBack(h), id, 'back'); + // No art is a legitimate outcome: nobody with no verified reference photo + // gets an invented face. buildFront renders a "no likeness" plate instead. + const uri = art ? `data:image/png;base64,${(await fs.readFile(art)).toString('base64')}` : null; + await shoot(buildFront(h, uri, refInfo(h.slug)), id, 'front'); + await shoot(buildBack(h, refInfo(h.slug)), id, 'back'); ok++; console.log(`✓ ${String(h.number).padStart(2, '0')} ${h.name} (${h.rarity})`); } catch (err) { diff --git a/scripts/security-refs.mjs b/scripts/security-refs.mjs new file mode 100644 index 0000000..63aa105 --- /dev/null +++ b/scripts/security-refs.mjs @@ -0,0 +1,388 @@ +#!/usr/bin/env node +/* + Security Professionals (Series Three) — reference photo resolution. + + WHY THIS EXISTS + --------------- + The first art pass generated portraits from text prompts alone, so every face + was an invented stranger rather than the real person. Portraits are now + conditioned on an actual photograph, and this stage is what finds one. + + IDENTITY IS NOT ASSUMED. A Wikidata search for a name returns humans, not the + right human: "Michael Howard" resolves to the British Conservative leader, + "Jeff Williams" to a Canadian rugby player, "Ivan Ristic" to an art historian. + Shipping those would put a stranger's face on the card, which is worse than the + generic one it replaces. So a candidate is only accepted when it clears + `looksRight()` below, and everything else is reported for a human to judge. + + Resolution order, highest authority first: + 1. OVERRIDES hand-verified, including photos supplied directly + 2. Wikidata P18 free file already wired to a verified item + 3. Commons search free file that exists but is not wired to the item + 4. nothing the card gets no face; it does NOT get an invented one + + Provenance for every accepted photo is written to data/security-references.json + and is what the card page credits. A CC BY / CC BY-SA photo obliges us to name + the photographer, and a derivative portrait inherits that obligation. + + Run: + node scripts/security-refs.mjs resolve # find candidates, write the manifest + node scripts/security-refs.mjs fetch # download accepted references + node scripts/security-refs.mjs report # who is still missing a face +*/ +import fs from 'node:fs/promises'; +import fssync from 'node:fs'; +import path from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const UA = 'opensourcelegends.com card pipeline (anthony@profullstack.com)'; + +const emitWarning = process.emitWarning.bind(process); +process.emitWarning = (warning, ...rest) => { + const code = + rest.find((r) => typeof r === 'string' && r.startsWith('MODULE_')) ?? + rest.find((r) => r && typeof r === 'object')?.code; + if (code === 'MODULE_TYPELESS_PACKAGE_JSON') return; + return emitWarning(warning, ...rest); +}; + +const REFS_DIR = path.join(ROOT, 'assets', 'references', 'security-pros'); +const MANIFEST = path.join(ROOT, 'data', 'security-references.json'); +const cardId = (n) => `card_${String(n).padStart(3, '0')}`; + +/** + * Hand-verified references. An entry here overrides every lookup. + * + * `license` must describe the actual terms. "supplied" means the site owner + * provided the file directly and is asserting the right to use it: that is not + * a free licence, so the card credits the source rather than claiming CC terms + * the deck cannot back. + */ +const OVERRIDES = { + // Wikidata's P18 for Rivest is a two-person conference photo; a portrait pass + // conditioned on it could easily lock onto the wrong face. This is the solo crop. + 'ron-rivest': { commonsFile: 'Rivest cropped.jpg' }, + + // CC-licensed conference photography, found via Openverse where Commons had + // nothing. Alexandre Dulaunoy shot hack.lu 2006 and released the set CC BY-SA. + 'wietse-venema': { + url: 'https://live.staticflickr.com/104/276434076_581ed1215d_b.jpg', + credit: 'Alexandre Dulaunoy', + license: 'CC BY-SA 2.0', + licenseUrl: 'https://creativecommons.org/licenses/by-sa/2.0/', + sourceUrl: 'https://www.flickr.com/photos/31797858@N00/276434076', + note: 'hack.lu 2006. Supersedes the Wikidata P18 file, which shows a different man.', + }, + 'renaud-deraison': { + url: 'https://live.staticflickr.com/122/276307114_f29f0a38ec_b.jpg', + credit: 'Alexandre Dulaunoy', + license: 'CC BY-SA 2.0', + licenseUrl: 'https://creativecommons.org/licenses/by-sa/2.0/', + sourceUrl: 'https://www.flickr.com/photos/31797858@N00/276307114', + note: 'hack.lu 2006 opening speech.', + }, + 'thomas-ptacek': { + url: 'https://live.staticflickr.com/2733/4302617844_52625091af_b.jpg', + credit: 'cfournie', + license: 'CC BY 2.0', + licenseUrl: 'https://creativecommons.org/licenses/by/2.0/', + sourceUrl: 'https://www.flickr.com/photos/37932109@N05/4302617844', + }, + + // Official bio / press photographs, used as portrait references on the site + // owner's authorisation. These are not freely licensed, so the manifest records + // where each came from rather than asserting CC terms the deck cannot back. + 'adam-shostack': { + url: 'https://shostack.org/img/site/Adam-Shostack-headshot.png', + credit: 'Adam Shostack', + license: 'supplied', + sourceUrl: 'https://shostack.org/about/adam', + note: 'Headshot published on his own site.', + }, + 'kevin-mandia': { + url: 'https://ballisticventures.com/wp-content/uploads/2024/06/Kevin-Mandia.png', + credit: 'Ballistic Ventures', + license: 'supplied', + sourceUrl: 'https://www.ballisticventures.com/team/kevin-mandia', + note: 'Partner bio photo.', + }, + 'mark-curphey': { + url: 'https://user-images.githubusercontent.com/143776/193322138-009524de-bae1-4190-989d-2d729ad0beb8.png', + credit: 'Mark Curphey', + license: 'supplied', + sourceUrl: 'https://www.crash-override.com/', + note: 'Portrait published by his own company.', + }, + 'martin-roesch': { + url: 'https://media.kasperskycontenthub.com/wp-content/uploads/sites/103/2013/07/07043722/sourcefire.jpg', + credit: 'Sourcefire press photo', + license: 'supplied', + sourceUrl: 'https://threatpost.com/', + note: 'Sourcefire-era press photograph. Supersedes the Commons files, which are a different Martin.', + }, + + 'jeremiah-grossman': { + url: 'https://cdn.prod.website-files.com/62deb8a5505ca121b1665bbe/63cf05d85cad9419bfa83096_Photo%20Oct%2003%202022%2C%206%2054%2000%20AM-p-800.jpg', + credit: 'Jeremiah Grossman', + license: 'supplied', + sourceUrl: 'https://www.jeremiahgrossman.com/', + note: 'Publicity photo from his own site, supplied by the site owner as reference.', + }, +}; + +/** Wikidata items confirmed to be the wrong human — never accept these. */ +const REJECT_QIDS = new Set([ + 'Q325381', // Michael Howard, British politician + 'Q38051181', // Robert Hansen, Danish crime novelist 1883-1957 +]); + +/** + * Auto-resolution found something, a human looked at it, and it did not hold up. + * These get no face. Recorded with the reason so nobody re-adds them by rerunning + * the resolver and trusting the output. + */ +const REJECTED = { + 'jeff-williams': + '"Jeff Williams photo.jpg" is one of several unrelated Jeff Williamses. Cannot confirm it is the Contrast Security founder.', + 'gary-mcgraw': + 'Only candidate is a group briefing shot with audience heads across the frame; the other Commons hits are unrelated US Navy photographs.', + 'michael-howard': + 'Every Commons hit is a different Michael Howard — the British politician, or a young man with no connection to the Microsoft SDL author.', +}; + +const j = async (url) => { + const r = await fetch(url, { headers: { 'User-Agent': UA } }); + if (!r.ok) throw new Error(`${r.status} for ${url}`); + return r.json(); +}; + +async function loadRoster() { + // src/data/security.ts statically imports the manifest, so it has to exist + // before the roster can be loaded — including on the very first resolve, and + // after someone deletes it to force a clean run. + if (!fssync.existsSync(MANIFEST)) { + await fs.mkdir(path.dirname(MANIFEST), { recursive: true }); + await fs.writeFile(MANIFEST, '[]\n'); + } + const mod = await import(path.join(ROOT, 'src', 'data', 'security.ts')); + return mod.pros; +} + +/** + * Does this Wikidata item plausibly describe the person on the card? + * Deliberately conservative: a miss costs a manual lookup, a false accept ships + * a stranger's face. + */ +const FIELD = + /secur|crypt|comput|software|engineer|scientist|hacker|program|internet|research|professor|technolog|inform|network|business|writer/i; + +function looksRight(entity, pro) { + if (!entity) return false; + const claims = entity.claims || {}; + if (!(claims.P31 || []).some((c) => c.mainsnak?.datavalue?.value?.id === 'Q5')) return false; + + const label = entity.labels?.en?.value || ''; + const desc = entity.descriptions?.en?.value || ''; + const aliases = (entity.aliases?.en || []).map((a) => a.value); + + // Surname must actually appear — "Mark Dowd" matching "Mark Dowdall" is a miss. + const surname = pro.name.split(/\s+/).pop().toLowerCase(); + const names = [label, ...aliases].map((s) => s.toLowerCase()); + if (!names.some((n) => n.split(/\s+/).includes(surname))) return false; + + // A dead-before-the-internet birth date rules the person out. + const born = claims.P569?.[0]?.mainsnak?.datavalue?.value?.time; + if (born) { + const year = Number(String(born).slice(1, 5)); + if (year && year < 1920) return false; + } + + return FIELD.test(desc) || FIELD.test(label); +} + + +const norm = (s) => + String(s) + .toLowerCase() + .replace(/[._-]+/g, ' ') + .replace(/[^a-z0-9 ]+/g, '') + .replace(/\s+/g, ' ') + .trim(); + +/** + * True only when the filename *begins* with the person's full name, so + * "Alex Sotirov.jpg" and "J. Alex Halderman - 2018.jpg" pass while + * "Martin Wagenleiter (Roesch) 861x1200.jpg" and "HT-Jeffrey-Williams.jpg" do not. + */ +function filenameOpensWithName(filename, name) { + const f = norm(filename.replace(/\.[a-z0-9]+$/i, '')); + return f.startsWith(norm(name)); +} + +/** Commons file -> direct URL plus the licence and author we are obliged to credit. */ +async function commonsMeta(filename) { + const d = await j( + `https://commons.wikimedia.org/w/api.php?action=query&format=json&prop=imageinfo&iiprop=url|extmetadata&iiurlwidth=1024&titles=${encodeURIComponent('File:' + filename)}` + ); + const page = Object.values(d.query?.pages || {})[0]; + const info = page?.imageinfo?.[0]; + if (!info) return null; + const meta = info.extmetadata || {}; + const strip = (v) => String(v?.value ?? '').replace(/<[^>]*>/g, '').trim(); + // A 19th-century photographer credit means the search found a historical + // namesake, not the person on the card. + const dateRaw = strip(meta.DateTimeOriginal) || strip(meta.DateTime); + const year = Number((dateRaw.match(/\b(1[6-9]\d{2}|20\d{2})\b/) || [])[1]); + if (year && year < 1950) return null; + + return { + url: info.thumburl || info.url, + credit: strip(meta.Artist) || 'Unknown', + license: strip(meta.LicenseShortName) || 'unknown', + licenseUrl: strip(meta.LicenseUrl) || '', + sourceUrl: info.descriptionurl, + file: filename, + }; +} + +async function resolve() { + const roster = await loadRoster(); + const manifest = []; + + for (const pro of roster) { + const row = { number: pro.number, slug: pro.slug, name: pro.name, ref: null, candidates: [] }; + + if (REJECTED[pro.slug]) { + row.rejected = REJECTED[pro.slug]; + manifest.push(row); + console.log(`${String(pro.number).padStart(2)} ${pro.name.padEnd(22)} REJECTED ${row.rejected.slice(0, 58)}`); + continue; + } + + if (OVERRIDES[pro.slug]) { + const o = OVERRIDES[pro.slug]; + row.ref = o.commonsFile + ? { ...(await commonsMeta(o.commonsFile)), ...o, via: 'override' } + : { ...o, via: 'override' }; + manifest.push(row); + console.log(`${String(pro.number).padStart(2)} ${pro.name.padEnd(22)} OVERRIDE ${row.ref.license}`); + continue; + } + + let entity = null; + let qid = null; + try { + const s = await j( + `https://www.wikidata.org/w/api.php?action=wbsearchentities&search=${encodeURIComponent(pro.name)}&language=en&format=json&limit=6&type=item` + ); + for (const hit of s.search || []) { + if (REJECT_QIDS.has(hit.id)) continue; + const e = await j(`https://www.wikidata.org/wiki/Special:EntityData/${hit.id}.json`); + const ent = e.entities?.[hit.id]; + if (looksRight(ent, pro)) { + entity = ent; + qid = hit.id; + break; + } + } + } catch (err) { + row.error = String(err.message).slice(0, 80); + } + + const p18 = entity?.claims?.P18?.[0]?.mainsnak?.datavalue?.value; + if (p18) { + const meta = await commonsMeta(p18); + if (meta) { + row.ref = { ...meta, qid, via: 'wikidata-p18' }; + row.desc = entity.descriptions?.en?.value || ''; + } + } + + // No P18: a free file may still exist on Commons, unlinked. Containing the + // name somewhere is far too weak a test — "Martin Wagenleiter (Roesch)" and + // "Merseyside PTE 40th anniversary" both matched that way, and a search for + // "Robert M. Lee" happily returns a daguerreotype of the Confederate general. + // So the filename must OPEN with the person's full name. + if (!row.ref) { + try { + const s = await j( + `https://commons.wikimedia.org/w/api.php?action=query&format=json&generator=search&gsrnamespace=6&gsrlimit=10&gsrsearch=${encodeURIComponent(pro.name)}` + ); + for (const p of Object.values(s.query?.pages || {})) { + const f = p.title.replace(/^File:/, ''); + if (!/\.(jpe?g|png)$/i.test(f)) continue; + if (!filenameOpensWithName(f, pro.name)) continue; + row.candidates.push(f); + } + if (row.candidates.length) { + const meta = await commonsMeta(row.candidates[0]); + if (meta) row.ref = { ...meta, qid, via: 'commons-search' }; + } + } catch {} + } + + manifest.push(row); + const tag = row.ref ? row.ref.via.toUpperCase().padEnd(10) : 'NONE '; + console.log( + `${String(pro.number).padStart(2)} ${pro.name.padEnd(22)} ${tag} ${row.ref ? `${row.ref.license} | ${row.ref.credit.slice(0, 34)}` : (row.desc || '')}` + ); + } + + await fs.mkdir(path.dirname(MANIFEST), { recursive: true }); + await fs.writeFile(MANIFEST, JSON.stringify(manifest, null, 2) + '\n'); + const got = manifest.filter((m) => m.ref).length; + console.log(`\nresolve: ${got}/${manifest.length} have a reference photo -> ${path.relative(ROOT, MANIFEST)}`); + const none = manifest.filter((m) => !m.ref); + console.log(`no photo (${none.length}) — these get no face, never an invented one:`); + for (const m of none) console.log(` ${String(m.number).padStart(2)} ${m.name}${m.rejected ? ' — ' + m.rejected.slice(0, 70) : ''}`); +} + +async function fetchRefs() { + const manifest = JSON.parse(await fs.readFile(MANIFEST, 'utf8')); + await fs.mkdir(REFS_DIR, { recursive: true }); + let ok = 0; + const failed = []; + for (const row of manifest.filter((m) => m.ref)) { + const dest = path.join(REFS_DIR, `${cardId(row.number)}.jpg`); + if (fssync.existsSync(dest)) { + ok++; + continue; + } + try { + const r = await fetch(row.ref.url, { headers: { 'User-Agent': UA } }); + if (!r.ok) throw new Error(String(r.status)); + await fs.writeFile(dest, Buffer.from(await r.arrayBuffer())); + ok++; + console.log(`✓ ${String(row.number).padStart(2)} ${row.name}`); + } catch (err) { + failed.push(`${row.number} ${row.name}: ${err.message}`); + } + } + console.log(`fetch: ${ok} references in ${path.relative(ROOT, REFS_DIR)}, ${failed.length} failed`); + for (const f of failed) console.log(' ✗ ' + f); +} + +async function report() { + const manifest = JSON.parse(await fs.readFile(MANIFEST, 'utf8')); + const byLicense = {}; + for (const m of manifest.filter((x) => x.ref)) { + byLicense[m.ref.license] = (byLicense[m.ref.license] || 0) + 1; + } + console.log('references by licence:', byLicense); + const missing = manifest.filter((m) => !m.ref); + console.log(`\n${missing.length} with no reference photo — these get no face, not an invented one:`); + for (const m of missing) console.log(` ${String(m.number).padStart(2)} ${m.name}`); +} + +const cmd = process.argv[2] || 'resolve'; +const fns = { resolve, fetch: fetchRefs, report }; +if (!fns[cmd]) { + console.error(`unknown command: ${cmd}\n\n resolve | fetch | report`); + process.exit(1); +} +fns[cmd]().catch((err) => { + console.error('ERROR:', err.message); + process.exit(1); +}); diff --git a/scripts/security-template.mjs b/scripts/security-template.mjs index 1ebc489..df8ef75 100644 --- a/scripts/security-template.mjs +++ b/scripts/security-template.mjs @@ -85,7 +85,7 @@ const shell = (accent) => `${FONTS} .crest { display:flex; align-items:center; gap:7px; } .crest .t { font-family:'Oswald'; color:#fff; font-weight:700; font-size:11px; letter-spacing:.6px; line-height:1.05; }`; -export function buildFront(pro, portraitDataUri) { +export function buildFront(pro, portraitDataUri, ref) { const accent = accentOf(pro); const kw = traits(pro).join('  ·  '); const alias = pro.handle @@ -98,6 +98,13 @@ export function buildFront(pro, portraitDataUri) { invented text. The caption band is now sized to what it actually holds. */ .portrait { position:absolute; left:14px; right:14px; top:14px; height:72%; border-radius:16px; overflow:hidden; box-shadow:inset 0 0 0 2px ${accent}55; } .portrait img { width:100%; height:100%; object-fit:cover; object-position:center 18%; } + /* A card with no verified reference photo says so, rather than carrying an + invented face. This is a deliberate, visible gap. */ + .nolikeness { width:100%; height:100%; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:16px; + background:radial-gradient(90% 70% at 50% 35%, #171d26 0%, #0c1016 70%); } + .nolikeness .wm { opacity:.12; position:absolute; } + .nolikeness .initials { font-family:'Oswald'; font-weight:700; font-size:96px; letter-spacing:6px; color:${accent}; opacity:.85; z-index:1; } + .nolikeness .nlnote { font-family:'JetBrains Mono',monospace; font-size:15px; line-height:1.5; color:#8b93a5; text-align:center; letter-spacing:.6px; z-index:1; } .portrait::after { content:''; position:absolute; left:0; right:0; bottom:0; height:34%; background:linear-gradient(180deg,transparent,rgba(11,15,17,.94)); } .num { position:absolute; top:18px; left:20px; z-index:4; font-family:'Oswald'; font-weight:700; font-size:40px; line-height:1; padding:2px 14px 4px; background:${accent}; color:#0a0810; border-radius:0 0 12px 0; clip-path:polygon(0 0,100% 0,84% 100%,0 100%); } .crestbox { position:absolute; top:20px; right:20px; z-index:4; } @@ -110,7 +117,13 @@ export function buildFront(pro, portraitDataUri) { .kw { font-family:'Oswald'; flex:1; text-align:center; color:#cfd2dc; font-weight:600; font-size:15px; letter-spacing:1.2px; }
-
+
${ + portraitDataUri + ? `` + : `${emblem(accent, 150)}${esc( + pro.name.split(/\s+/).map((w) => w[0]).join('') + )}no freely-licensed photograph
of this person was found
` + }
${pad2(pro.number)}
${emblem(accent, 24)}SECURITY
PROFESSIONALS
${esc(pro.name)}
${alias}
${esc(pro.title)}
@@ -118,7 +131,7 @@ export function buildFront(pro, portraitDataUri) {
`; } -export function buildBack(pro) { +export function buildBack(pro, ref) { const accent = accentOf(pro); const rarity = String(pro.rarity).toUpperCase(); const domains = (pro.domains || []).slice(0, 5).map((d) => `
  • ${esc(d)}
  • `).join(''); @@ -167,6 +180,9 @@ export function buildBack(pro) { .foot { border-top:1px solid ${accent}55; padding-top:11px; color:#9a9ead; font-size:11px; letter-spacing:.3px; } .foot .r { display:flex; justify-content:space-between; align-items:center; } .foot b { color:#d7d9e2; } + /* CC BY and CC BY-SA reference photos oblige us to name the photographer, and + a conditioned portrait is a derivative work. */ + .credit { color:#7f8698; font-size:10px; letter-spacing:.2px; }
    @@ -189,6 +205,13 @@ export function buildBack(pro) {
    ERA ${esc(pro.era)}NATIONALITY ${esc(pro.nationality)}
    KNOWN FOR ${esc(pro.knownFor)}${lockGlyph(accent, 18)}
    + ${ + ref + ? `
    PORTRAIT AFTER A PHOTOGRAPH BY ${esc( + ref.credit + )}${ref.license && ref.license !== 'supplied' ? ` · ${esc(ref.license)}` : ''}
    ` + : '' + }
    `; } diff --git a/src/app/security-professionals/[slug]/page.tsx b/src/app/security-professionals/[slug]/page.tsx index f6ef4dd..5457ae0 100644 --- a/src/app/security-professionals/[slug]/page.tsx +++ b/src/app/security-professionals/[slug]/page.tsx @@ -1,13 +1,15 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; import CardDetail from '@/components/CardDetail'; -import { pros, getPro, rarityLabel, statusLabel } from '@/data/security'; +import { pros, getPro, rarityLabel, statusLabel, getPortraitCredit } from '@/data/security'; import { site } from '@/data/site'; type Params = { params: Promise<{ slug: string }> }; +// Only illustrated cards get a page — an entry with no face has nothing to show, +// and listing it here would prerender 404s. export function generateStaticParams() { - return pros.map((p) => ({ slug: p.slug })); + return pros.filter((p) => p.front).map((p) => ({ slug: p.slug })); } export async function generateMetadata({ params }: Params): Promise { @@ -37,8 +39,8 @@ export default async function ProPage({ params }: Params) { const prev = i > 0 ? pros[i - 1] : undefined; const next = i < pros.length - 1 ? pros[i + 1] : undefined; - // Art is published for the whole set, but keep the page honest if a face is - // ever missing rather than rendering a broken image. + // A card page exists only once its art does. While the set is unillustrated this + // is every slug, which is intended — better a 404 than a page with no card on it. if (!p.front || !p.back) notFound(); const jsonLd = { @@ -92,6 +94,7 @@ export default async function ProPage({ params }: Params) { { label: 'Influence', value: p.influence }, ]} sources={p.sources} + portraitCredit={getPortraitCredit(p.slug)} front={p.front} back={p.back} prev={prev?.front ? { slug: prev.slug, name: prev.name, front: prev.front } : undefined} diff --git a/src/app/security-professionals/page.tsx b/src/app/security-professionals/page.tsx index 8874498..24db57b 100644 --- a/src/app/security-professionals/page.tsx +++ b/src/app/security-professionals/page.tsx @@ -42,6 +42,11 @@ const CRITERIA = [ head: 'Nobody appears twice', body: 'Series Two documents the people who broke things. This one documents the people whose job was to hold. A name in Hacking Legends cannot also be here.', }, + { + tag: 'Real faces', + head: 'No invented likenesses', + body: 'A portrait is painted from an identified photograph of that person, credited to the photographer. Where no properly licensed photo exists, the card carries no face. An earlier version of this set generated faces from text alone; those were fabrications and they were withdrawn.', + }, ]; export default function SecurityProfessionalsPage() { @@ -65,15 +70,11 @@ export default function SecurityProfessionalsPage() { line rather than crossing it.

    - {illustratedCount >= totalPlanned - ? `All ${totalPlanned} cards are illustrated. The set was built in public and the - roster stays open to argument — every stat line and scouting report is a pull - request away from changing.` - : `This set is being built in public. Copy is drafted, stats are in flux, and ${ - illustratedCount === 0 - ? 'not a single portrait has been illustrated yet' - : `${illustratedCount} of ${totalPlanned} cards are illustrated so far` - }. What you see below is the working roster.`} + Every portrait here is painted from a real, identified photograph of the person, + and the photographer is credited on the card. {totalPlanned - illustratedCount} of + the {totalPlanned} have no freely-licensed photograph we could find, so those cards + carry no face rather than an invented one. If you can point us at a properly + licensed photo of someone, that is the single most useful thing you can contribute.

    @@ -106,7 +107,7 @@ export default function SecurityProfessionalsPage() { {lockedCount}/{totalPlanned}
    - Artwork rendered + Portraits from a verified photo

    What ships next

      - {illustratedCount >= totalPlanned ? ( - <> -
    1. Take nominations and re-cut any slot the argument goes against.
    2. -
    3. Re-render any card whose copy or stat line changes in review.
    4. -
    5. Print run and packs, alongside the open-licensed downloads.
    6. - - ) : ( - <> -
    7. Take nominations on the {totalPlanned} names and settle the disputed slots.
    8. -
    9. Lock the stat lines and freeze the list.
    10. -
    11. Illustrate fronts and backs, same treatment as Series One and Two.
    12. -
    13. Card pages, print run and packs, alongside the open-licensed downloads.
    14. - - )} +
    15. + Find licensed photographs for the {totalPlanned - illustratedCount} cards with + no face. Pull requests welcome, and so are pointers. +
    16. +
    17. Take nominations and re-cut any slot the argument goes against.
    18. +
    19. Re-render any card whose copy or stat line changes in review.
    20. +
    21. Print run and packs, alongside the open-licensed downloads.
    diff --git a/src/components/CardDetail.tsx b/src/components/CardDetail.tsx index e6c13f8..872ed73 100644 --- a/src/components/CardDetail.tsx +++ b/src/components/CardDetail.tsx @@ -9,6 +9,18 @@ const CARD_VERSION = 'g1'; export type DetailStat = { label: string; value: number }; export type DetailSource = { label: string; url: string }; +/** + * The photograph a portrait was painted from. Most are CC BY or CC BY-SA, which + * oblige us to name the photographer — and a conditioned portrait is a derivative + * work, so the obligation travels with the card. + */ +export type DetailPortraitCredit = { + credit: string; + license: string; + licenseUrl?: string; + sourceUrl?: string; +}; + export type DetailNeighbour = { slug: string; name: string; front: string }; export type CardDetailProps = { @@ -38,6 +50,7 @@ export type CardDetailProps = { /** Secondary stat bars. Series Two prints four; Series One has none. */ stats?: DetailStat[]; sources?: DetailSource[]; + portraitCredit?: DetailPortraitCredit; statusLabel?: string; front: string; back: string; @@ -76,6 +89,7 @@ export default function CardDetail(props: CardDetailProps) { noteLabel = 'Collector’s note', stats, sources, + portraitCredit, statusLabel, front, back, @@ -229,6 +243,37 @@ export default function CardDetail(props: CardDetailProps) {
    )} + {portraitCredit && ( +
    +

    Portrait

    +

    + Painted from a photograph of {name} by{' '} + {portraitCredit.sourceUrl ? ( + + {portraitCredit.credit} + + ) : ( + portraitCredit.credit + )} + {portraitCredit.license === 'supplied' ? ( + ', supplied by the subject.' + ) : ( + <> + , licensed{' '} + {portraitCredit.licenseUrl ? ( + + {portraitCredit.license} + + ) : ( + portraitCredit.license + )} + . + + )} +

    +
    + )} + {sources && sources.length > 0 && (

    Sources

    diff --git a/src/data/security.ts b/src/data/security.ts index 0911938..371f256 100644 --- a/src/data/security.ts +++ b/src/data/security.ts @@ -1,6 +1,13 @@ -// Security Professionals — Series Three. IN PROGRESS: copy is written, art is not started. -// Hand-curated (not auto-generated). `front`/`back` will be written by the publish step -// once portraits exist — do not hand-edit those two fields. +// Security Professionals — Series Three. Copy is final; ART IS WITHDRAWN. +// Hand-curated (not auto-generated). `front`/`back` are written by the publish step — +// do not hand-edit those two fields. +// +// 2026-09-08: the first art pass was generated from text prompts alone, so every portrait +// was an invented face rather than a likeness of the real person. For a set that presents +// itself as documented history that is a fabrication, so all 50 faces were withdrawn and +// the source portraits deleted. The replacement pass conditions generation on a real, +// freely-licensed reference photo and credits the photographer; anyone with no such photo +// gets no face at all rather than an invented one. // // The brief for this set is deliberately narrower than Series Two. Hacking Legends // documents the people who broke things; this one documents the people whose job was to @@ -1357,3 +1364,27 @@ export const featuredPros = pros.filter((p) => p.status === 'locked').slice(0, 6 export function getPro(slug: string) { return pros.find((p) => p.slug === slug); } + +export type PortraitCredit = { + credit: string; + license: string; + licenseUrl?: string; + sourceUrl?: string; +}; + +type RefRow = { slug: string; ref?: (PortraitCredit & { url?: string }) | null }; + +/** + * Photographer credits for the portraits, written by scripts/security-refs.mjs. + * Every portrait in this set is painted from a real, identified photograph — most + * under CC BY or CC BY-SA, which require attribution, and a conditioned portrait + * is a derivative work. A card with no entry here has no face at all. + */ +import refs from '../../data/security-references.json' with { type: 'json' }; + +export function getPortraitCredit(slug: string): PortraitCredit | undefined { + const row = (refs as RefRow[]).find((r) => r.slug === slug); + if (!row?.ref) return undefined; + const { credit, license, licenseUrl, sourceUrl } = row.ref; + return { credit, license, licenseUrl, sourceUrl }; +}