From 7102a31a499601460c81e19ac84822ff899ecc14 Mon Sep 17 00:00:00 2001 From: Christian Johannesen Date: Sat, 24 Oct 2026 09:01:49 +0200 Subject: [PATCH] Add automated program screenshots --- README.md | 19 ++++ cipher-wheel/screenshot.png | Bin 0 -> 4004 bytes code-breaker/screenshot.png | Bin 0 -> 4006 bytes compose.yaml | 13 +++ dice-poker/screenshot.png | Bin 0 -> 4491 bytes fifteen-puzzle/screenshot.png | Bin 0 -> 4851 bytes fireworks/screenshot.png | Bin 0 -> 4732 bytes happy-birthday-beca/screenshot.png | Bin 0 -> 5954 bytes haunted-night/screenshot.png | Bin 0 -> 4900 bytes infinite-road/screenshot.png | Bin 0 -> 8158 bytes jiffy-clock/screenshot.png | Bin 0 -> 4334 bytes kaleidoscope/screenshot.png | Bin 0 -> 6685 bytes life-lab/screenshot.png | Bin 0 -> 4804 bytes lunar-lander/screenshot.png | Bin 0 -> 4557 bytes memory-matrix/screenshot.png | Bin 0 -> 3975 bytes meteor-dodge/screenshot.png | Bin 0 -> 4062 bytes morse-trainer/screenshot.png | Bin 0 -> 4097 bytes music-box/screenshot.png | Bin 0 -> 4755 bytes neon-rift/screenshot.png | Bin 0 -> 7126 bytes nim-challenge/screenshot.png | Bin 0 -> 4370 bytes orbit-lab/screenshot.png | Bin 0 -> 4580 bytes paddle-ball/screenshot.png | Bin 0 -> 4250 bytes petscii-aquarium/screenshot.png | Bin 0 -> 4710 bytes pi-drop/screenshot.png | Bin 0 -> 4769 bytes pixel-painter/screenshot.png | Bin 0 -> 3655 bytes plasma-grid/screenshot.png | Bin 0 -> 8661 bytes prime-workshop/screenshot.png | Bin 0 -> 5740 bytes projectile-lab/screenshot.png | Bin 0 -> 5061 bytes public/app.js | 16 ++- public/styles.css | 6 +- raster-bars/screenshot.png | Bin 0 -> 4987 bytes reaction-timer/screenshot.png | Bin 0 -> 3770 bytes robot-maze/screenshot.png | Bin 0 -> 4929 bytes screenshots.json | 88 +++++++++++++++ server.mjs | 25 ++++- sid-arpeggiator/screenshot.png | Bin 0 -> 5452 bytes smooth-scroller/screenshot.png | Bin 0 -> 4394 bytes snake-trail/screenshot.png | Bin 0 -> 4083 bytes sprite-playground/screenshot.png | Bin 0 -> 5148 bytes starfield/screenshot.png | Bin 0 -> 3892 bytes stock-trader/screenshot.png | Bin 0 -> 4952 bytes submarine-hunt/screenshot.png | Bin 0 -> 5123 bytes tools/screenshots/Dockerfile | 8 ++ tools/screenshots/capture.mjs | 166 ++++++++++++++++++++++++++++ tools/screenshots/package-lock.json | 66 +++++++++++ tools/screenshots/package.json | 8 ++ typing-sprint/screenshot.png | Bin 0 -> 4045 bytes wave-composer/screenshot.png | Bin 0 -> 5171 bytes word-scramble/screenshot.png | Bin 0 -> 4503 bytes world-quiz/screenshot.png | Bin 0 -> 4670 bytes 50 files changed, 412 insertions(+), 3 deletions(-) create mode 100644 cipher-wheel/screenshot.png create mode 100644 code-breaker/screenshot.png create mode 100644 dice-poker/screenshot.png create mode 100644 fifteen-puzzle/screenshot.png create mode 100644 fireworks/screenshot.png create mode 100644 happy-birthday-beca/screenshot.png create mode 100644 haunted-night/screenshot.png create mode 100644 infinite-road/screenshot.png create mode 100644 jiffy-clock/screenshot.png create mode 100644 kaleidoscope/screenshot.png create mode 100644 life-lab/screenshot.png create mode 100644 lunar-lander/screenshot.png create mode 100644 memory-matrix/screenshot.png create mode 100644 meteor-dodge/screenshot.png create mode 100644 morse-trainer/screenshot.png create mode 100644 music-box/screenshot.png create mode 100644 neon-rift/screenshot.png create mode 100644 nim-challenge/screenshot.png create mode 100644 orbit-lab/screenshot.png create mode 100644 paddle-ball/screenshot.png create mode 100644 petscii-aquarium/screenshot.png create mode 100644 pi-drop/screenshot.png create mode 100644 pixel-painter/screenshot.png create mode 100644 plasma-grid/screenshot.png create mode 100644 prime-workshop/screenshot.png create mode 100644 projectile-lab/screenshot.png create mode 100644 raster-bars/screenshot.png create mode 100644 reaction-timer/screenshot.png create mode 100644 robot-maze/screenshot.png create mode 100644 screenshots.json create mode 100644 sid-arpeggiator/screenshot.png create mode 100644 smooth-scroller/screenshot.png create mode 100644 snake-trail/screenshot.png create mode 100644 sprite-playground/screenshot.png create mode 100644 starfield/screenshot.png create mode 100644 stock-trader/screenshot.png create mode 100644 submarine-hunt/screenshot.png create mode 100644 tools/screenshots/Dockerfile create mode 100644 tools/screenshots/capture.mjs create mode 100644 tools/screenshots/package-lock.json create mode 100644 tools/screenshots/package.json create mode 100644 typing-sprint/screenshot.png create mode 100644 wave-composer/screenshot.png create mode 100644 word-scramble/screenshot.png create mode 100644 world-quiz/screenshot.png diff --git a/README.md b/README.md index 39e11ed..862781b 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,25 @@ docker compose --profile tools run --rm assembler The tools profile is opt-in and is not started by the normal app command. +## Capturing screenshots + +Every program has a recipe in `screenshots.json`. The recipe waits for the emulator, focuses it, dismisses the splash screen, performs any program-specific actions, and captures a clean 960×720 PNG from the browser compositor. Screenshots are stored as `screenshot.png` beside each program and appear automatically in the grid view; no web-app rebuild is required. + +Capture screenshots that do not exist yet: + +```text +docker compose --profile tools run --rm screenshots +``` + +Capture one program again, or rebuild the whole gallery: + +```text +docker compose --profile tools run --rm screenshots haunted-night --force +docker compose --profile tools run --rm screenshots --force +``` + +When adding a program, add its slug to `screenshots.json`. In most cases an empty object uses the collection's standard splash-screen recipe. Add explicit timed key actions when a program needs a particular scene. Always run and inspect the new screenshot before committing the program. + ## Source format Each program directory contains a loadable `.prg` and its readable source: diff --git a/cipher-wheel/screenshot.png b/cipher-wheel/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..132d97ddac8fb56ea08058d5e71d5b3f8a5034b3 GIT binary patch literal 4004 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk1AnBai(^Q|oHut)=ROKx za0u)-aa|&HDdbvY%G_m6rkcrO69d>-KODWG_G6ByagAJE#Ko(hpTGZb_V(%LKtuk# z|N8gkhac~O%pSD`j0_3_7Z?}>I5HR*I9N;=7+9F3fc7!+GB7kYurV|^I50CfD2yr_ zd;zje`oizO#~Bz7Y%SaUJaO_G+DFuv4YzIk>p#~0H@tIbievx1+4F3_Zu+e3SO56x zVP5UO4#7S*Z)`m_*Y4!!-hCemUM?2<)?K#tW&N^eKZ1=vIWy=9ffL$`SgP54ls*A5}hS`hU&k zb9Q{15EH^~z>XA)^0SMMem82)yOVtW>fD@j%gQ{eMHr?+lDxb(x7qex4(X?~F0S0Y z)NYmU+O~P-_d{~BZeEfrP1q@wpn_zOe^I!*P5--w>eklv%WmCTIyrgU_h*wgyi?J) z|KOB>ka2OU-O08*Dy8tx>WfumGkDm4jo?FA9cN7defguqx56373+8WP&{YWIQ#Yq z*#rCb?6a4Zi*5ww*aKX^Tzq{46EFwIjOOprl5DhSAFaejYxU7)&S-;fw1qp`mM6P& zv*B*Ty|?q&85la~(9$05hYduJ^8-Hv!~g$BwsbE6_Kn#&)z?qy?y$5UIvB_ zmtX(B{O{v^Ak$!00~M_sf3Ue7Ju1UGkYQc7N#~uRpxgyZ!sb+GY9H zcR%dV-MgFF{kz?17T@xN4zcBF{<6p3ygxQkc*}*?b3ZPg-dC{or0@Ixt$mCSrg4JP z*q0TdPyQB*Ma9qD#8sMg^Y`n6KU_aZCNI~qId9>3dhg`r{Ga~W zA3uM>xi*LKT@zArJilZ2=Y_$1KX%1GOU^&m7?pb_{9n=w{ST%t$$AG2W-x4bLrUGp z=URQN<^HZ`@m2Rb7r+b{XP)wzf5vrC-TXthJ-L3zCW=1&M|2$=n5{aK#7bzyDuR{qv4ZPxIvkhs$qE*rit;-1hRB-12YxJ{kHO zo_-yh@jh8CW9#H+m)WzP&a_qKeegzj%cn0(cL(^}ZxfBXd$e4>sFKrW*8RWA%hI7Ku6j4{8%67nDG$W6eY@`f#LuEApPuqU~iiZ)XipQsH*j1onqQN Q6)3{s>FVdQ&MBb@047b&YXATM literal 0 HcmV?d00001 diff --git a/compose.yaml b/compose.yaml index 6998697..77a9a47 100644 --- a/compose.yaml +++ b/compose.yaml @@ -18,3 +18,16 @@ services: volumes: - .:/programs + screenshots: + profiles: ["tools"] + build: + context: . + dockerfile: tools/screenshots/Dockerfile + working_dir: /programs + environment: + SCREENSHOT_BASE_URL: http://c64-basic:8080 + volumes: + - .:/programs + depends_on: + - c64-basic + diff --git a/dice-poker/screenshot.png b/dice-poker/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..40ab41b679850fa2116658e8e675971213744653 GIT binary patch literal 4491 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 QZU!7V6zJRVPSm-71AkwtA^-pY literal 0 HcmV?d00001 diff --git a/fireworks/screenshot.png b/fireworks/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..f143f8211abbf9c81fbe52b02259e5e97f37abab GIT binary patch literal 4732 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 literal 0 HcmV?d00001 diff --git a/jiffy-clock/screenshot.png b/jiffy-clock/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..7390499133bfbb2e7f9de5efcc08a832f0b2d272 GIT binary patch literal 4334 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zkg8+l4i(^Q|oHutpi*7|R zI0Obwbxkonx6J6A!sDD%alO+&vaeh8M|1IuSDdEbER}_H1I?>0JB$arz7l z2mb&4_xo|x@87Hp3^RNkm>C=t7BDg>2wVUf&5^;tz`p&BryMr;rRNrwf8fg ziGSQKecJ45y~W#2&xnyNrj(c|Y$(Jc%HKxl7{Wt1A+ADg!iPiY$y|lG2A6|>v zQLWp@YFv47K%sFp9J>2?u$9wq?F7qRIBPYh+)_40Bit*(C; z^K%})+dciiT(&@b3sU;zPfXGOe}dKd-Q0b-@^_TyWUQvWg8#)+&dQMUG{b6ACBy`j(qu#uRzlMFM)0Q`L?XK$$MWoJ+*dA?7Yv5 z{rP6^oA5z-9dm8m<>~Tqf1U1LzMQvt?pKBDjdC-;T{OV)=|HIeqh+onRfSkHoXY{xHRo?*K?WqNiola;Tdn=-v7tv$FHBR zpZtvT^yBdRbL<*xAEl@9nQszKe{5UGop!~3`K-?hr;o+jyq&Xuz4c#?+qa)u_Vetz zzB;MqPBnk&lf4_;tGZ`@KJnPU;#L>;>k{Lvlv{h#KD}~lO59v%9=)S~-@Vfd|A4ap zZE%$4G_o-ya7F|3e?vXQ9WTxoM=>)nNJC=aMR80j4?{yUa(qx&IOOl$V-wH7@PKc$ za2YLaN9&c*iWa5iQIO>CZ?FEYwz4v=_K>6U<9A=@Fz!%j8nX8OQ^TtVCQzr6e=Y<|J72VJcF9~9kF8Dx5q>WRk7yLVQ& zTFE_r_DD4I*2HFQw_|?`gxsqba@-&doyN=3H-GH9-gf(=F<)*G+l_7{&5UBVp3UHL z&qFry!`r|1|Nd0%t&uEiKBR6?`}0ZX)tZkj&0;;fZ!TTQxQg}q2K)8*>+56kZ!VMc782d2oP=vwL)z4*} HQ$iB}-4T;8 literal 0 HcmV?d00001 diff --git a/kaleidoscope/screenshot.png b/kaleidoscope/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..65f64df963bd9709fc09fdedcd123134b7c544f9 GIT binary patch literal 6685 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 HqOSf8zMQp! literal 0 HcmV?d00001 diff --git a/life-lab/screenshot.png b/life-lab/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..f3ce48e393f1e38f94a462c9f7de1a3d6b68c5a7 GIT binary patch literal 4804 zcmeI0YfzF|7=VxBU}Z(FgJvySZ6lj`YfUAUbREO2O)JY%6uXFKYl)F0XrZ;4S!%XZ ztc20lrqfL{D-GN}?ILC>mQ87dm>OgxctsF`#V+Rl*uU-0EPv0ObH4e``@GM2-*a?r zNYIRFbEW|RW(2PcTn_-O1_0BRDW-;z7Gz#G0JA`F;Ex+pS?@%v^MrM#{_AY(7gVp! z$M&~bjQn8N@4aJo&V31SU~!TAszhC2aNUXmr}tzS0C5^A9DqiLb0%QktQNyQQ+5C_ z#cU}6W~Ort-!Pd0KxPIUWcd5Tfd4$>wEypcWC}U< zD^?__RYC#rcZy-I>9oauX$1ur|h( zp7Bh{9^UmrjxQ4xF1v@OzL@HV?Dv{PD5tZXcq!kHo{HMp({Z%W#tf~z11;lgZ#NDv zVe7daNbxxY+q`0=$+tBPI+4FUMA5qrt(LFV>+an+jMv_6j*%?1)1{pyBu1*5>x1o* z>(T9N=0q_+9yTH(c}t4(b5vcL6+D_CSa%^SD8en0dH^4VStV!`E$dkb3DLH=Nk$nf zCo)QLE^Vl;J?7~?Z`8MX+KDak#|B<$6;8*Z3MPM9#y$f$;?+r{8np7 zg|2HL8>_hy)!U*+S^GR1+cgy%CB6z(|C#GuOW#VX19_bzb}1+A-)lomL1Flq8<(w(&RZ#Na0k2Pz>t7EE* zur_B2ueC$|kT1g0RQe&EKVD&PHlgmqYj~C)HSLOeYk%xk;ZD6plXhec4$^ zr6B;}_=J%A`;^KM3}P=ce_K0_a@L&opf|VGaI$5>V<^aj$aV%36f3S_0=?*Ak0{>Q z7v9*I!Fq0Y9sf7}rgpj$r8Xj(bb@^)HPsS$F8ET1yzvl;9LofVZ5)+*r_JCy++i!O z&(ozM4?qf-bdwOo2UjZ-z#|Fa)2wo8qSQ@b9;9o9Ls2>0T literal 0 HcmV?d00001 diff --git a/lunar-lander/screenshot.png b/lunar-lander/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..a263c671747ce3ba3c94ce84c406a34677c4f458 GIT binary patch literal 4557 zcmeHL>r>Kc6n+s0v_#sh(JU|3m^*7hiFu{Ovh^~8ZCUDi!7^`asnxEAiC}J#t7}uP zk%6{NCA(8z(&?sv)Ru0O1gR;kSHe(}G=ww=WifV={R3+Hz_;JLbLRb>bDrlpm$zsq zAqcU^eh~lw5fU7@3jin&05hS5nf^q$yi^Ln5)cv?5OItt?+?ged=GHnMQVyif9Sv` zF#}q~5sequOUK>H%fmNG%vuTFDZ?JE31QcJ;aJ^K&5&C0+8hAwVCNHoQc58Jz&mJW zufHCG0HA;h15_Li3UJQ9LxA%Vq5e-6sQ_4*djMc=GW+rpGISR*tI@nS2fSTKWD9|I z!2Hu=Npp0}CuRP!J{ENu(=OQ%bGKVnI2x zZ4EJmsTD~I`z>FN4eaz$*{+Y)L7o+2|`Q?DIo-rW%s@>B6@p~vGrkt)qC;_>L@hB62hW4JrpW`H6$2h<89uQ)HU!eYF@4a=EE(0_W_ zDkeo*O|0RlJiw6mN3#=7PLyN$d43M3$|eCw4>+fmyp}1t|NcJT$;e$VW zDSF3ie#75TQf= z1NftRV3_x%1cl#-T&L>eVOT5T>(|qnGrjL8@Whv#0t>-AL;`^QJ^8He-u@;ZVu{+X(uQT#{dV0HJ zYI1HStz^rBx$tI(CX8+xf3Vfo_nen{WdCd~YlCPit(lA)I~1RZq0SmYR0&5d_@oPx z&_o#h)X4#-_Ep{eBy1vIq}=L9l8Jhmm23tgrwd;?jNZo&VI(>}o~*0L?$TfqM@Fu4 za?6kw-p)p-Qk>E=Qli=CH;>?E#0e9Z))rV6E)K4)i#WUoR*y6oI@&u}^^)B0_IDq5 zE8KK-4D$CQ<-WKk%Q5F^pL4bpUyAPD;ndm#cUCMdkQ&QXxTSsN#cp@yw$sfGazsXR z)0Mc5^@3%xtjJh4B(}(?R7^f-K-V~s&!!VA>K|86NOO1Zr4-rRPn(XN5VxxjaA&S= zN%12Bm_bc%q*A2dOeX8E;F$qDGBK>>0n<Vle%-dL5~ACqeZS?otlIbp z+rV={NdP@d-{LPB>SgMM7and|MHHu@`i}}Tb!pQ)Vj9p)qo`>%H!Ulswd_mUQ`-=u zG#b$rfYT*LbLpqupO3^Y4*&p?J^vP9THrtHQ^gy!eo)itUaYyz)PKO{5rBd*?pS%h SRmC6r6A-e45Lmk{;l#gnND$Nj literal 0 HcmV?d00001 diff --git a/memory-matrix/screenshot.png b/memory-matrix/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..85891400cc7cafe0dce986a0b9165568e213138c GIT binary patch literal 3975 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk1HX-@i(^Q|oHutJi)IBd zG$dMIc79^=EGRO4VyT{}|K#M7i9M68l70w^eEl7^&C9XtjQx4_KX?Cp`1WZhBLl;~ z@4x!W}pS|(R zt2C3u)8U`euJ1X&ytZlGrhAun7oPFk_u=KEGhgLmzqQ$IV9-{9B%{S`i@&Y6tSCEl z<7(jZ9yZ?G!`GP~`&5@3r|i>Gb}vr1ms-E|Uh~zZ%~_vbCQUQ{{q_4feum9Rsq~9} z&C$w)vzIyB<``E#-u3>w*mpDWX?@R|PpUglzRY0Zi0O;4U#|5!{wkI4P4?e?Zx_~un?J5!dRg{>4qR=Mz5Rbs z)~l3w$+cT~{|lM_UtbhW=ZZULd3%pma@hl``)|0G7xcvZKDsUOPu2OKC*psU7;f>1 zT7OddfRNlPHrDm;{eK^K1BIOd6L0xZ2W1BFWKd-Pmy=H<~+YqS_2 zt*}OG@X;pCXya|PH9OjFC$T|%@G;w$vfq0c85&M-jCO>EXP56kuowORe`Z{RJCMN! c>PIs(n5t-awYGOZ1&T0uy85}Sb4q9e03#l_4*&oF literal 0 HcmV?d00001 diff --git a/meteor-dodge/screenshot.png b/meteor-dodge/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..77a257abd768f7eaf57c7e644c52662351d2e319 GIT binary patch literal 4062 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk1OH@C7srr_IdATs^?e<| z;P6mkuhKI1UyGQoZJuE&dO_UQM>4;$B_M9c`Qv^6AN&q;*(Ur*qNJqu?VnvhGbaB9 zlbjh03>+*b3=AwxQb6+=c^McQ8`u~c92}S#926EXGAIaKU|w;ew z_oXzv_kwswe5TQ}?;FoOUn+$ye?4>WWcrr(v;S5cx|w{q`tASK z_iOfaf4g_{F?hKD54oDv4oraR#~%od;QdPvA@k^B%Nq- zWcK+DZ1VDFz8PviuHAO*>YQHt@aC;Z$*yYR?C0J2>xzBieOqP}O5g3xKlSnIU!`;J z`rNPRRi~|AtYmp&{@a)TVh`x;o3++3HPzTEq90=G{ewSdKlCd}zTO;@U3%vF-Al23 zD?YwG9A31@KmEIX^!LU#&12hZP+m+4swrp`q+;cW3r% zpUS(c?nkR`pW89@ zSKpKh_qmr~>;@{l448NsWUf@4s65kP^4bu{fs}3sMEDpU$i%puaFl3-6?O?+>gTe~DWM4f4XN~7 literal 0 HcmV?d00001 diff --git a/morse-trainer/screenshot.png b/morse-trainer/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..4741abae984968e17580d053d7d1625eddf9257e GIT binary patch literal 4097 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk1OGNp7srr_IdASd20e;m zaJX0!s(Dr%AenVc;fcy=NTCo ze(b;gefiRm4)4Fq17mUet+(^qeT|rDA5r(qc0ZrE{@r)^*OJvnPoB*9xPDpiS<4?) zc{P%s?=L94zWX0{-jzA;Qy6aWL6XfplZDk5&)%rqdC0%~{7>IGuK%hG3X?2+1?J7o z-t#7eKl7mZ3%$H&f9hEoHpn6+w}0*NKQq6k>RUbCoXx$?XUE1bC->+^e{kFRoS|SE zQexaMt~F;(oJI8+-a9|8zJHPa#Xh%mQ=R#}9aR&hOE+{<#rx%U*W$0V$NcP{kzp}zm#NfLrV3(uFe0rYaaKsdIp97PDEy?YOGyQA7yIz ziF>2}wsSvT@?^*P8|@2AIjAjPzfgPaft|OZ_u)GGTJO&>p0SjT;aw9_x}088-Yp=uC!yP%p`jPKR2Baf zRbCj;^YLrKqa>beGlquWst_BNpUcayF|aGON`JF{M*YF|~3nXkkBE ztsy1o(R!X%#SdBKFIxFI>bB7U8EuavRjR|bgVOYt?MvQw8wLi46t>YcKP=OH(|=(9 m{r~@S!EMih3^q^?o|(bTI6(5lgF}~qA`G6celF{r5}E)Ik^-jy literal 0 HcmV?d00001 diff --git a/music-box/screenshot.png b/music-box/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..f6f06b81ae7fbc3a265068ed3e2da0ac3f271f20 GIT binary patch literal 4755 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 b#RY+P$45xVCsp=>XMp3!>@3TVc-;I?)iPro literal 0 HcmV?d00001 diff --git a/nim-challenge/screenshot.png b/nim-challenge/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..bba75b21da881dee149715c5e6648570b17b322e GIT binary patch literal 4370 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zkg8+l4i(^Q|oHuv9{T_QU zI9yctsubaSLnN84=cY&`@97HWfFqlQ9%ozsFLt|7C>ZRyc=ads|7QF5?61pZU}*SX z{k!5`{oj0M1_mFs1&j;|0vCX0a%3jQQil66|f4gyT!a=jg{c?BG zPEFP`NnO8;N5!Ij*y@H7UBIn9}9se|c zj^13BhS^A|_r=3n13tOVeZM~or`<7l@=Pu37W`=T4NK&rv zslWE%?tRAcU%rd;%NNBc{ZyZ?%)tOGcx={+ zvngsnXC1e@<-%BCh0Qho`}bGY|Gj0?bMUFfx+Am9&biEdVlDZ4mQ%x1w)*MsZr?us z`Cqm+BGORHk^WEr9;UIb<~O;f5`Q&f`(1_vT^yEt47_(-PWHK%PV#lx>_ujiXQni8 ze+sKh8c%lTd+vcqS(scc6<6u>Gx&xFV|l=aqMyQGWX-X?>`7# zuD=;;%eSib+9Norxq{HO{#?Jk zX43cnGt!<(-RotDnSiapS%q*-X^PGJtzxH0A=E?rY{MQzS zu`x)4GGJ#z+~rTI$0T1zr^oG;i>Wfb`0|~oMe2!EUOvZ&_PC=Py&3v*%Yw{HSU4L( zk#a(nueY7ihwl?4r#r;%6fOVs?A@N%4gWlQ>yJHrWq#Z4n~SnF^MSvDh?xHpI^+Aw zL;R06xu3l3+ob>c!8*A+&-muN`*-JiIg=>EYGJU_7q5+y_ZI&NIw~4|;@JEbC9jWM z->_j8pJ2>4v7?W}H_6@p{B&|n!HJiBOLgWy3B9*bPl=_%2VOB9y!<}@Ui^MD{=)~u z9-Pws`JlZ1QG;>Br}S6P_SD+E-@lCssrHJ|T%O1fqdxe`Z?puB6s@CmGE)4FwmwFi zW25cU(Uv%BlVY^A9Cp>m!N+W0%6@NRWN0|SH5%Cjy98GpBIW>x1a^bQ0)}KK{XZiE m!~g&3-l{%8Z?J*9!OZaHyTRW!E{~T$5e83JKbLh*2~7ZE@}H*w literal 0 HcmV?d00001 diff --git a/orbit-lab/screenshot.png b/orbit-lab/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..e7b6a99fd44800963bbe5aea5aeac8399038ad6f GIT binary patch literal 4580 zcmeHLdsLEV7=O`1OT|_*Q)k$ztY*uml=n-fsadF*wKOl))Jjvlp?D|G=4@dyyL-WE zmDNJ4O;AfIo0m#pV@f8e1&WzSf}n`V&|0uRc24_iJ1748&ilRJ^Zm|q`8~hqDF{G% z8_YMI4*)Rm_3_*RfHoBXErza^@`Pbj$N^vh@b%oZGre^9dHXHqF5te;ws>o0o|mJK z!_P%gAr+-5MRSb;{1#h_z6~t@rf_;?Zttu+6dp0xT9V zlm>MV0-!qw0l*wBQ{~Sf1^{65pdioPT^qPtXs&&{4pE5YEi8hYTqx~69)ovId$b5n zCp-0fosLW68V#7lrL|f4=UxkjN*6f6@r_y6Ug4b6xer-gQlIdC7kc#+(@sF8)bS;) zC)*9;$YK_|78Uk3$6O9M4#~gdFu@(7EDkozH>Q_QNMwgHqRDMF9WEv1Ch&{Ib~A37 zc_v~VoQnw`^2xE2Z%3BexOEV+qTz)+7%uA%H1-R%+&zCMG%>iTgOAo?S{VxCUH7gp zZr)gSD%~M;DrI?#G!VXyXh~VE7R;(clC<0OhM&gxJ&N|}{o{2wXF-|uo@FS(FA&@f z_}YlAgc{kCu~Zve3p+LD-U+G@j%xD?LFbTi_4+=4TQC`$nLPcyvgEo^JwQ)?Db>N8 zx<(n(rY+^_VK!dpDJW9CeRciGRNF0?ZB`RxK=M#4s@Tal+!YaZiQi=xHsANqz3Psq zutNy19l!2x#&*w@tWuTUpxAo8Y}k*##u%L~*})`kh)T{kYrNjHMKHDSh8_)tpov~= zC^)khJ-N_3$tIK6@XYbtXe0az8$I1cB#D>qVpndQ$GVgZ`Php;UlW8K5LUGRVyyX}%HYCTgD;6eo{tB9N>#G$d_+3#yvU-nH^z=N5ENIO? zA-!x{8f4!f<|Cc_O3o%C!qas4&DZtqi_{3u*x`jQ!;dtCVi}opj2D(C5%O2cMk+Uj zCk1wNRb^eknl`Q$%R0yROy2gan`kSWVtN8VQtAMUblpUC;5IyE4FcqI}6ta31$ERKq zbEl^BLKmqQIOXX5!+6N|c28R-Ug9CK4iqajhDT6+@l{<*a;uHZLH9de$SXE&EX5e6R!XO{k!!9?OiPS8UMb5%d>j7$F)t%$WxNATA z?zIv@5T5R0c=p%;>0eVI$|?a=m^5A>JEbRQM!(OOuO28BGZ<<{BC2k1;-v_Ypm6_X8J)uB8AzFtUA Jl1JpxKLKlTDX;(l literal 0 HcmV?d00001 diff --git a/paddle-ball/screenshot.png b/paddle-ball/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..1180404dff9aba3dca7d086e976134be91d9ed98 GIT binary patch literal 4250 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zkg8+l4i(^Q|oHuv9i*7|Q zI0Wv}>OPV6VVTi6g~vIk)~KC+(HN^|-YXjWx3So*uUBOP%T5pek8i)6TrXem$G~u) z{_mf^e;+^3S7%^IFk$3nU}$V$V`y-2U}kVoSis1jAaDU_2S)}20|$!<0|N__6wv-r zWrHt3KI~zv-MMGq-vYb+J^MdySRKjhW4h$Jj`8})ZA z5{nU?ee;>(nVXZvW8R%*<-MPF%hvz%pFh|4&a3*YyJ^v-Er&PH`ru@kw)1!1g0Eqp z!i{I{X;iak^*jGv=SJ$z%Vq($O!|)qf>Xtpt+VrY+TBZ7dYC_dzx?NapI*(q^Wl-$ zoU*>X_a5nQZ8VDxEKm72&!*^{dHUAt^J>1nXzO3rW9hW{o9+H?+p>eAThC^HI+2{8 zJdb;JF3_@~N0A?<#>VN1-I*+@hDd=gBE{?-KbOtURF8b-^LxUalT!IhgL{+R^8Vb{ ztNwJkjxSBTy4I=W)4ZDg_5A0~9bSc$Tx;Jxn|UYmx=d}(<#Xrs_ucvEUEWu>=jZFJ z^>>q-RtqYJ|F>DNJ7T-}@rQTzoQ=PGH~UkXbjIGPHvhGm%;btw_i)`72Iqno+1>Gc zp9Ak+EM35^_GH<~*RSgMxp%J4miQ;NXRlu1o^8G67v5=C?u~rAPecMK_q<4s{4iPO zT*1brtmXUN?@ku~bK?3(+Z%2+&kkFj*eg~4@LPX;9IyV*S83n#rZ4}qdF>NfPDGCT zCGbsZr{SNOUmixDomtkW98+wfTf1I#Yj3*rPQ(9m-dyFpHZi!iOuYKa-QMuX4@bTO z9sjmFclpektxW94ir##kfBjgSKCAI3BTa4H@2||->Qz9wxT)Um|KscZ_RE())D>Yk zAmjgaDVKeH{r?{~A@K^%?Z57B@mE=5EY8-;z+l4w$@g{UZ*K)1p59q_LXzRZM~ZX8 ze(U-1Y<2ZJ_snBp02ez-3-0R{_1tXCVrXbrM+ug~Mor zZM11RlH2Mx&i(vj^x^vHr(ao6TMY@Nhn3CO<)taGvy<4GIPl7DUrfOdZO&SD#d1gF z40fQ5=h6d4RWRIhXkBKV*wO(;r{@Q`Tu{*mR@b3acm&tm>ELf`)eBqt$Ya-VeoYI Kb6Mw<&;$UtU`~?& literal 0 HcmV?d00001 diff --git a/petscii-aquarium/screenshot.png b/petscii-aquarium/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ab4180ebf9826179abb3d73f65236d9c5dbaf0d5 GIT binary patch literal 4710 zcmeHLdr(qY96o4iX^5jGrlx6GThl|+3>PuiGIMin%f?p`l%;N=QWhvCU=OS;W!Ba( zA2iLDwPk85X<3o&SUw7Sfrg-H8yYG;%RqFmrR{ZQcc+~mQ#<`}XSj!R?m6f8eZTK~ zzjNqEA5Wy7nH~TD>9unCS^%{90BF@r(1KcOrX)23UrFiY^_ zE|w>HQQhO1zr<3E-L8;olmZ8?fuJ;|F9MoM`cl;%s;J@`0KgB^GJ}?YKmriY)B#L4 zH*MfH{UicRH>`nHGa(Fs3F90A827c?=NAxS2|^>G4w*KV)l2DJ_r-0r)}%U$DAVFn zJ14ewh=Q%+CAhS#A-TlcCu3yJO2)BX3xkp_zwG`BpFz4OooY6(ZcFMTj-;pgy+<7_TYxN?O_aP!FC|r_pdY zBlfP&BEAu>~@+qv%?V~UT^1N3&wXmp$yccLX#Y zoB$SeGGlaz6K86I1ca=B4f#S$-G=>F6Vm1WR?rdCX!G|P!c1)R-AA4eHxfXs8OUbP z=zZLwR~ow~77A)Y7fX=5R%rjk2VoilZ^`7Qh*4$wk zkwu6rWtaixX}I_K>%&%Z-+h380!F>GmS^W9xr8{CstSR@MdC1MrK@mEC_hd;;Bfz; zh+x2P)pVei(ueeK1Zm^~#y|svRpzhZ@l%F6^Wh<8Ci3hf@-52)@TSg3@~_kth9hc6 zixbh7YoyX3kBtlGCI|YsZdp$}pPqX?j{aB|iFSwc-M>PvCEH4x^i$jB=-@T*Es}r9 z$Tar}S9F%s)&)Ne(=8aqT=bNEAT&|PzF?A`VOXj`@dFbA*Zr)Z&)zV@*G`xljJrW z__yO)f@x}z5+Y7tx>(kGulwYx*bUei3~ubxSpPa=T$dZ&&mLdhWKZv{VxW9+!%751 z$_`lw=eau4)ykx~Pb?4~&w&SiO!cVZ%(6Py#a`y}gi=qTJkk%xre(TBV6l}xSrt6W zBfBSoltt<{QR}tO{DXQmO5?x-DsQD9Tnqkd z4PAD3wKr6iqlWJS-l;mTLbiy-3v~cEc;TaO7M?<1Zw=njA-{jOP6l*yGc@EUbX*Dj d&>5zNHaO5~#NxeTCqXU1%fn~+MR(%fKLM-Ja+v@C literal 0 HcmV?d00001 diff --git a/pi-drop/screenshot.png b/pi-drop/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..3b73217ca9b58d795ded48ec3af8ec3c4187bccf GIT binary patch literal 4769 zcmeHLdr;DM0RN(7YMPms4qtO~whknV5!}bLCWYam@`8@R6v64^%`@7@(Fl`lE~M+OGe8_q*SHAD{2%_4}UP zfkv5|+L{6Y%mcRtd&`#;&Q0&7Q?`X3nkp?iV&|t9w|i$@OOT)d#1qX}ITZ@Pa-_ zDzfd?EMihBB706InGo2a#pNd+tq#KU9;zOmd8{b38$fh*+tg)@A2e$VSVYP*If4OU z6K`l&?<}J)D=e%9x0Apg`Ixj$gqNIrvNyHaF_H(vCgu$zH%N9pIGQin=r~qn(&1%l zRLhwZSKcwAW;!?fr6^rnmW60Iy%Lf>Kx2$ExkU0c8fhl3)!D{?4HM2tVe7p+LSjib zu+{cbnY9;dqCn0}`4z4Z|B)OYBtqLGO^427FWKy+FVaKt0}~`m&Y5%_#ts!6W(Y%F zr_mY53WGg6`m4DXG-OXr-DetYS!%U4;2|5#d=f11!;?R+ZLKoD|8O>lDu7QMWE=8_ ztQ(ViWSe9t{_(;wOROZ=Vaa%e>h{oS*aOktya92FO(`LqpJ@M z^$CKuBQJQ1Dz8!7ij>|0T&To^G2Iv3gD#p%%nrsJcgS*+{f?@qF?bH@^Cn7gQd1P9 z4rZbqXrjHmU5J^u;yXh3zu+~#oW&q`F{3ytmCCXgUT14R>Z3fl6i$>&Z{!q>6Q~yO zyGh(2WfGrrk$67{&6Xv&$FVPeU+r!BNEyGutuA72$8$qRrsUo}daE)l3yR>WJfAK0bT{Q-RPw+f zyQ%R0(rBV&wWldq??1;B_4f|h7-F6WLb7EX(Z2PhfS;EXebLY&!Cg>dapy`>!?R#w zYqqh&5)i9BkU6s(Af)l^W^+hxJ!Sld4+}8EVr~hRX+6{rmpkB^+$L$1YVOnU<;K3| z&y5i8J{EaBo!BjdH93>75I&}Our*-Quo4Uv7CziGzBevm@J88w_McU}o$;NKc}Ruh z@fnzrptJ-C&!5*U7Z24~tMh-(Zt1NZicV!8gW6^SuI4N{t(U^X+6sUTEyj9`5K6N= zjoev0Gdbop_E3ivN#z~n^wVBK&+a*az65CfK6NcP4LhN?08d9pjvQ6w*CG%u%-qSi z3S<>7&V{vqD`dyJnB9Ep>w95hOx(pz6TsZ`Tx+j%6Mdqdx@rM%jQBC%&Dk-5QSM@x zylqkNywrxWO#t{PMl0OcE$@x0(?oFCNT?#e=t+`6N-6zCh|iPA8$;b*w<|#F`y_K3_wB%Q9d3JnGl4OXEkTEaBghQ7PMB_lu}^X?16$ z->Vg4lyVuptS7Yjl+P~<`CN{ACmeW>{)x}llnB-JF-UV4pkwbkEd7-jeWRRusdap~ u3HbkAjntFX_e&~O#gTQR>IWkYs|-P?E$i}KrwY9K4G7$d4k+7#&-@23-k8Dw literal 0 HcmV?d00001 diff --git a/pixel-painter/screenshot.png b/pixel-painter/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..53dd1d4dc1e1f16ed8796701e4cd4d17e64c6d25 GIT binary patch literal 3655 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^!c{Ew%LV@SoEH#ZJuGCPW} zT$Bvxvp?2ZRBQarS5-jgP4#lVg)grEN=_4FVEAy~@_G4w5W|d-mw}X1_l9+3r7A7g6O{2;NUx0kL(_r^phk?P))cEy8o6$%cO~s@6 zb+iB*Mn(0Gm9KYQTmJ1Hcf)&n)JX-w)$dsk{2hD+{b-^dMu~c~r8U~7q;Dhp!A?e7 zV{QfpCH~Ro{P67G*aLfk|Nk$N)+`1x*g*Y2W`>1fqMpKr5y1Wd1B0ilpUXO@geCx9 C>-XsZ literal 0 HcmV?d00001 diff --git a/plasma-grid/screenshot.png b/plasma-grid/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ccbd03b5fe0a8198c30306b6d8b602be910699aa GIT binary patch literal 8661 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%0APB+ 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 showSource(program)); actions.append(run, source); - card.append(badges, title, description, releaseDate, actions); + card.append(screenshot, badges, title, description, releaseDate, actions); return card; } diff --git a/public/styles.css b/public/styles.css index 38f8ae8..eca774f 100644 --- a/public/styles.css +++ b/public/styles.css @@ -37,10 +37,13 @@ h1 { margin: 0; font-size: clamp(2rem, 7vw, 4rem); line-height: 1; } .reset-button.filters-active { color: #75d7d0; } .reset-button:focus-visible { outline: 2px solid var(--c64-light-blue); outline-offset: 2px; } .program-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 1rem; } -.program-card { display: flex; min-height: 270px; flex-direction: column; align-items: flex-start; padding: 1.25rem; border: 1px solid #b8b1e733; border-radius: 14px; background: #211a59d9; color: #fff; cursor: pointer; transition: transform .15s, border-color .15s, background .15s; } +.program-card { display: flex; min-height: 270px; flex-direction: column; align-items: flex-start; padding: 1.25rem; overflow: hidden; border: 1px solid #b8b1e733; border-radius: 14px; background: #211a59d9; color: #fff; cursor: pointer; transition: transform .15s, border-color .15s, background .15s; } .program-card:hover { transform: translateY(-3px); border-color: var(--c64-light-blue); } .program-card:has(.card-action:hover), .program-card:has(.category-pill:hover) { transform: none; background: #29216bd9; } .card-badges, .program-badges { display: flex; flex-wrap: wrap; gap: .45rem; align-items: center; } +.card-screenshot { display: grid; width: calc(100% + 2.5rem); aspect-ratio: 4 / 3; margin: -1.25rem -1.25rem 1rem; place-items: center; overflow: hidden; border-bottom: 1px solid #b8b1e733; background: #080611; } +.card-screenshot img { width: 100%; height: 100%; object-fit: cover; } +.card-screenshot.placeholder { background: radial-gradient(circle, #40318d 0, #17113b 68%); color: #b8b1e755; font: 900 2.1rem/1 ui-monospace, monospace; letter-spacing: .12em; } .category-pill, .language-badge { display: inline-flex; gap: .4rem; align-items: center; min-height: 28px; padding: .3rem .65rem; border-radius: 999px; font-size: .72rem; font-weight: 800; letter-spacing: .04em; line-height: 1; text-transform: uppercase; } .category-pill { border: 1px solid #b8b1e755; background: #17113b99; color: #d8d3f4; text-decoration: none; transition: background .15s, color .15s, transform .15s; } .category-pill:hover { border-color: var(--c64-lightest-blue); background: var(--c64-light-blue); color: #fff; transform: translateY(-1px); } @@ -62,6 +65,7 @@ kbd { min-width: 1.8rem; padding: .25rem .45rem; border: 1px solid var(--c64-lig .code-icon { font: 800 .8rem ui-monospace, monospace; } .program-grid.list-view { grid-template-columns: 1fr; } .program-grid.list-view .program-card { min-height: 0; display: grid; grid-template-columns: minmax(180px, auto) minmax(150px, .8fr) minmax(220px, 1.5fr) minmax(110px, auto) minmax(180px, 1fr); align-items: center; gap: 1rem; } +.program-grid.list-view .card-screenshot { display: none; } .program-grid.list-view .program-title, .program-grid.list-view .program-description, .program-grid.list-view .release-date, .program-grid.list-view .program-actions { margin: 0; padding: 0; } .source-modal { width: min(900px, calc(100% - 2rem)); height: min(80vh, 760px); padding: 0; border: 1px solid #b8b1e744; border-radius: 14px; background: var(--c64-deep-blue); color: #fff; box-shadow: 0 30px 80px #000a; } .source-modal::backdrop { background: #090621d9; backdrop-filter: blur(5px); } diff --git a/raster-bars/screenshot.png b/raster-bars/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..e62e533b68b94ce5d382f59e1d1487423590d6ed GIT binary patch literal 4987 zcmeHLc{JPU8vcSNv=d7ejV-3FYMG(vXf3tObm}@v+Unf4Xff?bY9^$NgxZ3OS{ll| z*D^)jshX*+6&*Cz+Bzy^WI_=Vf>3)AAtE=HtM0$|%(=&z`QGPw-{*bb z*b9!f@V%;g0RV8jznr}UfMhKI5;SQE@d-_d!~sAa*qybx9Pn_W&!FH40a80#_4p^K z+ku>nzv%9KnA2Ls| z(z`|3i5Om0*&G^;Hejuev{yf`;Bppgy2V$?h45DjCpo7~0I0nsp(=&|fdddugaV?q zwIs0CdISMl@-#6TX$%0;QYQhB+Wl?k1%!>4TyBgvIEC}fvJByk`W>BmX->{CV(>d) z+!NnKEm8-h=|!uCR==v1w!~-7j!0PO3hhH35;iy<2!_j2yhQ{nxC~1e%ChG0F{7`2 zn_eE#79<}+=&ljm4E#?%{m1v1_qisTn)-x2GOt3XAsfnwvvLOee zAtMUgRP(U7&fKJxLY*n61WqwiJZ4N|$V0N4f*TVLx=cU)o=0_}$M>!Jat}N>PM3I= zxFNs##;d-=tmI;vdD{Y3uZ@S{{A{l;<@&xV;qaUt$`6Sl25#3Q_uHAC>MzvMZoUwf z>083j%&)Ril5R}o){p_$Zkv{IQzTsID?~r+ZhuE2zT=H>Nv^0~;e6opDM>g^dhJaS zI*Va^zR<_yRQx*(Gn(173q!k@z$>*Z1l)7m9-U`6ixKoiD#`tpm$v~|K;PZy8z z1+Dy4|MqgZ`S-a<4`<+Lzws~>=dxsbk&AQV9&D3wg&E%e$}=4+5j{S(ePxA=j#La? z?S?IeiO6oD??obSK*GGU`sNC|o?EQBZ;M6LlxT+0HI4OdZ}XttA;9bmzR#gwVawZB zUNAQKogKblEE;F~;ak&gwWupQ3*pWs<+Hi%S)F-GZ1u`HUS4nWxvdI$I!Y1%&4B~5 z0HoPj*9;kHKsA;Qy*ioCNDZ=4QE+sf`n$H~lm;Rj&zz5!wmoVwPzt&nwvm(~8|;(K zIWc@cEx@dyFOsns7ZTG(AM~^c{Fax;zKl#l_|SCK0%OPGulg#}l5aPo_u$@aq`psjPUz*nN_ojuYt-{aTMyzE=AGHmnC zlsaMayY;=JAhNdG(BQ7-3B883xjNsaT811&`yxlR>6()Xq*b#Z9f($@aQbR#z3CCCqQ z?t`)XhYSFSG5vu8`sB^SoBDG_)`uugXIPZqR9~V%7ab@=#ZJm)!8!JybYE~9!QD$- znYeFZnH`&dH+ft`NZISEx6Q^-X3(k-(oIs?ZC1JrL9S^ruC!f!^1wYrzTvPFt`yah z4EH~ZTpD{L97$wV1kIHBgv~7n|B|x8r?RS6yE5(YS!CgO-+3tanNV1L4SS_npAbxi zw54ykO^BIhR&cGJ0kNQIgFv#p5u|H`)F(3@yK1h)3 z7?aO4BZy;In`~EQ15Gpl-z%>;0+yuLZm&n_fh}!;4!esQaeq z*X=mzdV{~SUdHNvyyHRl2KEbL@1#iyb@BSyH03hm$%K9K5bqD*v>p*^ayx~vfWB%M zgzGrTke`!wXE09D)N#tfN5U_*>*A7==s67oAYn6fJEl<~<}XZI)leYr*mVnmL9hklUakodzLOA+EQ-jR2-h3Ex=14a zncU8T37{)8LDSwKU>MnJyVYW>A*~fx)}0hk0Ighz7dy+_h<;Ju;~FBO7e1PY%Ekhq wwOh~rq??>Nk=S@eqQ5#A4T(RXYxyDx`VdO$mypLu;uB!^rQ_M6&)p;b4Zy4x#{d8T literal 0 HcmV?d00001 diff --git a/reaction-timer/screenshot.png b/reaction-timer/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..5adbad027abca4dc5b6b6b9be72c45def53321c8 GIT binary patch literal 3770 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk17E(Ui(^Q|oHuti<{kO@X`)&Cd7!HK*KK$!f zr76&;&72tw3>+*b3=AwxQa}qBc^McQ8`u~c92}S#926EXGAIaKU|d}!|YUR$pxz#!(0lq?QZ7d{ebFjq&)H3_eb>^?r)Uf?4) zeO}%@=7w+~NN#$t@AbBK-P;%aPyH{=@Jtvfi+$L0a>nt+JG(90|EOhOVmO)7Yuw84 zM-%R%2amrc0dsNpL;pC2hWQa!q$iiPfBm{ymL#u_X7|y8ZM3u)%`!-Z*Jvd_+Ugl? z1|n76qwQkidjK2mG{_x43+zu!9BqdT)1H$ZBLl<#|D{5rqQGco19d%_8M@X4K&k)$ literal 0 HcmV?d00001 diff --git a/robot-maze/screenshot.png b/robot-maze/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..6efca5641ab718bdadd7a16afa25a9955bb1e63c GIT binary patch literal 4929 zcmeHL{a4cW9{(slv<%r<%X~=Fw7e@dZxclkwN-Pjsk648RIvF#>Xm3tAs;|p%hT@E z)4H}2P@Ah;(}@p>r6QW9xfHD%YN?>1plG7;B@hrUU3Vt`K)X9<{`xuZ^M0T6em%V3 zpZA4N!-C=KZPx<;;2|IFKLh}*4FKbTb;kOaflY-%0Nw^6`}clErq7M-Eu;+_2So%+ z4#XjgL*watyPkfQggo9gbkwD?r0dd`%`pQxK|wGOkXD zZYFc6tl$rqPHPK0TMc=4_-a`xgQB`N#ed$$q2krT{)66X-Cd5XupFkqXL(HQnV!dI@sQt_`ST8 zx-HGq)L$v57mhtZe8n29-Tj=Kzj5wWTyQ_%jty^ferMIkAlFxSjk`*uaaf_tY_=nU zbY_YHPrsIb=TuLGG;be^#|=+cRgOi&VdSWlq8Mme5G99jJ+DT?7ZStRC9>jzD2K^& zPfWw)0nrOFrP{CGJr-hssPJ;Od=_$PO-po_wuzv~ZjkXT95!9YqjTl)`_ z|C#WZR_3*g!OC<^3D=W*{jZCJeWFoO-8}SbE)sW{wbXep@9?6Pbd$W=8z)t{qEkez zUa7b4Tb_N0J5ogwaIeE0F_kwf=&*nWd*Ol&Q^|`vejEaPiM}Ov@brzhE60DetoABZ)LB;%{#}!?K z&=DR}VlXL|IHu;O-Abp*&gSFOIr$mVf|g#V-37joD@&WPN6oPS0V_aFySqDxQAuv; zL_(+^FQKoTTOw8yg@bm4n~bp&>Q3))CO3u}S<@7{r6JfId-~a@u1_Cq-MTMYT|#Of zhotP^;VTaIS*=FjA^S;XDQQ1fisxf0b9s=2F7q2m9i-?TsBcrBxQYaM zH#1VM(of!`6dYB5Vz%>lpIbE$(YW3~P!l#1g-kd!G~55-Dp&@V^(Ln?j?Zt+&Y&q2 zf)NiizI`TB355~d8z@iZX361*G@|eV#{)~ej+~9IkKH2AG8*Tt>$T# zSgcD#5k)~l!Pd+0LxmpA{9h(`x0}!ymc5%b%sXrYa5Zr9X?%UcV;#kOL$B{waiTMO zDoEr6E+3ZTYRJ=Tk78kuO}AOO(Gr%JRQoMU#My*M_vUNamTjn>OVRZ|nxebL5ACdI zGy;RafpQI=KP?jQ>Wagt-4l@f`ysbxzKSV`^P6dFH;>B%PE41G@~SLB_!f z{FyPeMXis0$dZ|n-Czw3Gno5 zfaLWp;IIB|A5Vj+^#5P|Wn-K3o9Ojly}y5k1CRy*fKJzbPi3J$gqgi&Jq+|=(pZF+ Snk@Yb2sse8zxsoiy#E5_&f~rS literal 0 HcmV?d00001 diff --git a/screenshots.json b/screenshots.json new file mode 100644 index 0000000..5de0751 --- /dev/null +++ b/screenshots.json @@ -0,0 +1,88 @@ +{ + "$default": { + "readyTimeoutMs": 60000, + "settleBeforeActionsMs": 5000, + "actions": [{ "key": "SPACE", "waitAfterMs": 2500 }], + "captureAfterMs": 1500 + }, + "meteor-dodge": {}, + "code-breaker": {}, + "submarine-hunt": {}, + "lunar-lander": {}, + "memory-matrix": {}, + "robot-maze": {}, + "music-box": {}, + "pixel-painter": {}, + "stock-trader": {}, + "starfield": {}, + "snake-trail": {}, + "dice-poker": {}, + "word-scramble": {}, + "nim-challenge": {}, + "reaction-timer": {}, + "morse-trainer": {}, + "kaleidoscope": {}, + "world-quiz": {}, + "life-lab": {}, + "cipher-wheel": {}, + "sprite-playground": {}, + "raster-bars": {}, + "sid-arpeggiator": {}, + "paddle-ball": {}, + "smooth-scroller": {}, + "fireworks": { + "actions": [ + { "key": "SPACE", "waitAfterMs": 2200 }, + { "key": "SPACE", "waitAfterMs": 450 } + ], + "captureAfterMs": 100 + }, + "jiffy-clock": {}, + "typing-sprint": {}, + "petscii-aquarium": { + "actions": [ + { "key": "SPACE", "waitAfterMs": 2500 }, + { "key": "F", "waitAfterMs": 700 } + ], + "captureAfterMs": 600 + }, + "fifteen-puzzle": {}, + "plasma-grid": {}, + "neon-rift": {}, + "infinite-road": {}, + "projectile-lab": { + "actions": [ + { "key": "SPACE", "waitAfterMs": 1000 }, + { "text": "20", "waitAfterMs": 150 }, + { "key": "RETURN", "waitAfterMs": 500 }, + { "text": "45", "waitAfterMs": 150 }, + { "key": "RETURN", "waitAfterMs": 5000 } + ], + "captureAfterMs": 1000 + }, + "prime-workshop": { + "actions": [ + { "key": "SPACE", "waitAfterMs": 1000 }, + { "text": "360", "waitAfterMs": 150 }, + { "key": "RETURN", "waitAfterMs": 3000 } + ], + "captureAfterMs": 500 + }, + "pi-drop": { "captureAfterMs": 15000 }, + "wave-composer": { "captureAfterMs": 12000 }, + "orbit-lab": {}, + "haunted-night": { + "actions": [ + { "key": "SPACE", "waitAfterMs": 2200 }, + { "key": "L", "waitAfterMs": 120 } + ], + "captureAfterMs": 120 + }, + "happy-birthday-beca": { + "actions": [ + { "key": "SPACE", "waitAfterMs": 2500 }, + { "key": "C", "waitAfterMs": 400 } + ], + "captureAfterMs": 250 + } +} diff --git a/server.mjs b/server.mjs index d4e1601..40cdf62 100644 --- a/server.mjs +++ b/server.mjs @@ -44,10 +44,15 @@ function fetchPrograms() { const slug = program.path.split('/')[0]; if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug) || slugs.has(slug)) throw new Error(`Invalid or duplicate program slug: ${slug}`); slugs.add(slug); + const screenshotPath = resolve(programDirectory, slug, 'screenshot.png'); + const screenshotStat = existsSync(screenshotPath) && statSync(screenshotPath).isFile() ? statSync(screenshotPath) : null; return { ...program, slug, - size: statSync(filePath).size + size: statSync(filePath).size, + screenshot: screenshotStat + ? `/api/screenshot?slug=${encodeURIComponent(slug)}&v=${Math.trunc(screenshotStat.mtimeMs)}` + : null }; }); } @@ -86,6 +91,22 @@ function sendSource(response, programPath, headOnly = false) { else createReadStream(filePath).pipe(response); } +function sendScreenshot(response, slug, headOnly = false) { + const program = fetchPrograms().find(entry => entry.slug === slug && entry.screenshot); + if (!program) { + sendJson(response, 404, { error: 'Program screenshot not found' }, headOnly); + return; + } + const filePath = resolve(programDirectory, slug, 'screenshot.png'); + response.writeHead(200, { + 'Content-Type': 'image/png', + 'Content-Length': statSync(filePath).size, + 'Cache-Control': 'public, max-age=31536000, immutable' + }); + if (headOnly) response.end(); + else createReadStream(filePath).pipe(response); +} + function serveStatic(response, pathname, headOnly = false) { const isProgramPage = /^\/program\/[a-z0-9]+(?:-[a-z0-9]+)*\/?$/.test(pathname); const isCategoryPage = /^\/category\/[a-z0-9]+(?:-[a-z0-9]+)*\/?$/.test(pathname); @@ -118,6 +139,8 @@ createServer(async (request, response) => { sendProgram(response, url.searchParams.get('path') || '', headOnly); } else if (url.pathname === '/api/source') { sendSource(response, url.searchParams.get('path') || '', headOnly); + } else if (url.pathname === '/api/screenshot') { + sendScreenshot(response, url.searchParams.get('slug') || '', headOnly); } else if (!serveStatic(response, url.pathname, headOnly)) { sendJson(response, 404, { error: 'Not found' }, headOnly); } diff --git a/sid-arpeggiator/screenshot.png b/sid-arpeggiator/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..2e4128b28da40a4bb15301bdc8fca805c3eab691 GIT binary patch literal 5452 zcmeHLc{JO57XJ}X%GlbDQnBf!mTDqW>Zqqzfu~!wfw3-qGuP8048eOE8 z_D!i;f-cb#X-lKAzk0+H5=0q6QrUz=CaE`1=A8L^&YU^^`<>tUoqNB(`&sV2-^>%9 zN7PmIQ~>}`cRT8O3IIUw06?*4gQE0Jk9N)k0B8Yjt_RLse>6MNnTs7zboPI9DK%XF9Xa1j7DDD43NB}F}HP6c%UNJ&-(lAWDFfb+KX&;Iucf`FEwS41pD z)Y8Hv?IF5w5JWj%A0G`VXiOKTbrI>phx(RtGj_Kdnc`ByzKr8GJsp+&fxM}*?=PQJuyeQ%iY?Nb+Ml5^l6#k>wv7LXdI-=r!EjxUf1%{ztCoMR%^*d3)T$Jq4j!kMykqiG~YU|3fG-YYyA?{XFtB2VCf zfkoRMyosAB&S-i(JM(AQU^m9q%4@!lV&gFqie(-r1JqRm#BHhcivf@HwIFB70; z@>#If#{+=w*PG2`9DlBL3g4RZUzbOb3~qrGc_A$r%<-`MvbUxs$ots&kv*Kej&A>; zUd%2eS>Fcz!gz~0@R)Wp=r%59?jWv8_RKj$tc65ha;jpT69pZv8dT8Sb)Wm+;on$!>Sb<|( zJ33oankX?wUFlPQgI6-hiw*b-v3*s$e+|W&qZi!x&zkvt+gZq!j27siGxDpJug(Ny zx*4rlFFW#4C9V+|(;S0qhGu$tsxHl7LeNldm>OxSCLlWFp~Pw~%{a7j)HyH2k?C$|)s&L3tiV|Xcgb-+ zmOfZ$)rzGSFFAw6MU1YY({_EP)AVMM=6kq)l$soF=qN?*gUzWNoPTrL=-Ws1{@#I} zzKxe~)id71lQ*Eq%k@XH6I3-;lp?G(xb(2a3tgK!Gz%Y-xT0f99QJ1mh2+-X9ratU|73WvGBcKFtqydo#+crT>G{@iUea9L`KKQtlB?qK_KeJ z_hLjjTo14MCpNVDZwe9f8JZz=LrJKG_ZG29k0;FH5)M6exHMM1yb{VJ86XcBu%lK} z#{X*d06*1XwA0IKu5V&b=v{Acc9PRkiwEBQRSazivPBxhlRuo-`_eeaiwvoLwlPmI zsk@uq_Q#186~rlpUHj*ep8e&Y8|=v{0u_7Gi+xG$O~2LP0JzGxPR8Y?Kd1o150mn} ztv(O>_p28RQZGl8oQ|MMkIG@`8bdB}CNDSLH4SVYVRm{V4(ru+!PVJ8@>yC%Cz?>0 zB6|tQZ#nvh*vGlrv1}57Rg|zv5HCj2SoThqB7gkT%e*!P!~1d;P&~(vLN*X9;iFtU zsoeI0UlzgQ3H!0KEo4;?qcL1+V7Tp$S&@2###MvJphzmpm9sBTbIz*qt5wDgoH=(B z25sR>Ha5G2$!|=>>Cwq+dR;TK&stJ5^CzB&MmFFvXv2;3$4FgsP)C$SI=(`wE)+EBA1Z(1jH-#OhH0`UjL>d5(phFy$Y2` jXg}a2(g$Uk0fK=3fQrzJJo5(W8^G<5r)%{=ztn#MU2-pL literal 0 HcmV?d00001 diff --git a/smooth-scroller/screenshot.png b/smooth-scroller/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..2db332ae6aee0103477531501a84cb3e760bd62e GIT binary patch literal 4394 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 AP5=M^ literal 0 HcmV?d00001 diff --git a/stock-trader/screenshot.png b/stock-trader/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..37f7057596848b21e2aa85e67f7aa64c7312f314 GIT binary patch literal 4952 zcmeHLdooZ2tD0bTY}d}%Do2G(MRZ1+T7zyMYz<}T8b(PJjUZ$=WoXgS zvt>kE<#=?unl^(Qnik9azQ?@ zBx^7Pz?w$g4XDn}aNzuIAq>2$->X_*I|Kl2&0_#)Y8+Di8-@Vj%bjWeeSpM{QVl#Rt`5!66>oY3L|^!^#XgolK-V!Y&^zG=wZ4LC7#U7{$Oeqf-q%* z_!%YcT2qFtA^MJ>E8e+kH3;(>EZIV|8Rx!2@AJ=p?`kMxF!KPi#vDoaW33P1qhddq z2Gn?qEj@?j*S8V_b1C=>!cI|I0e;;MHkD?e21-okop*Hb2Vm+Qh|seY{4nXjo|JI` z$?ORFs>C`g;oCCy*z9wX|1=U*zrQ)PiB88cSqTNEsUjSp9yXm9)zXEYEnyHAqGP8d zwelWv+Mv`u*C)ch^N+OScDd`{X<>d38eux>I*a4;OV(c|E5)rcONq&mTaJ}8v*Bg9 z=-99KuKvxc`^IqasrH;IJ6AY3xP^e&Gpehy-`sIBvcnLDb=QA8{zKKOR%?}g<2|3t zue;G5>~&M{r*WQ3H`8c^&$;6!*##2O(4$twVw9`i5__lSIg28;?-e8n@ZaK%Rm!M( zu_%AikaClE$p^poXizVvkA`EDpS4*S15=mzjCiR`d5nqHcxx(+`}P9x_%mHC0G6;Y1^WYOhF>IM zd>IL&S4TyX>QK`$X0@G&uLQoDRC7ZJZ+|v6XhDZY&ZOhX?1I$F&Xz{P=Q0;W){@+6 z4bF+1oFkY6V%XL}6q(Vv!#>>WIKt(~NgLjNqmhftUfx3anH!q0MiG<;_m!jH>qQ+r zHQ;C!{`5tfV0OjGn1dr-qamCWy2WINxr#f&f zuMyusC6T#@)1C~~gT~AsDYK7wLKVU8vq_1fS7l?gFb>BXYh)|f(Jv-ob7z09uc zb>=5#EjhT}Ws20t8VT|~-nQs%xQQkx5Rh(;qXfo4bT^w+y0OPjq0MrmbvInF;+(f% zNs1p}J23l>qoRB_OtX!F!*3zr5T@5Qcj=&` zJobS`HQD;Tlf%6C2XFz`noQikj4eND38M~f0Vr;GFwj>h90{+FdDF|wvFUS@_h5KW zl;RAFFN}(jsLYSyU>Bf7Lcnha#HH8dzfC;1MK?qCZW4 zMNNfItUlxhy8w{wx`l=?k<4ux@xLs4UMBQHEyj~9=KF`&OS%EzsXtDnC|)kkaK+ZX z)mq>WfgQIk1wEVjH>#~pI7RnzTd0V;hCF)l9JvEl=60ltHuGI)PX7=k#Hajn1tmA( zH``)jCA)F2M|lNK47IhRDMh>S4nekB%WP#jfhAltMeBgBU#g>Y_M|WIe(0p@Anljc8K2TS}g_<8V&D42HMd7dqA^3HWGaf^7e(T!a2kk4%yrY*UM1Q_vu0 zl^)JkjJ3AeulUKTxs%~F)3+p}MxfdN!l#hw{46EUme%UJ8wip&6*e_AWHxch$wMzl zmim-nPh*IlHI&Ntd~P~l!17ufpLu#QDUN=dnJ{_SOc&6|+I_i%4ZLt;H~MDd~Jz3FbHb5zz^zdgC5 z9xR1y?Oj%8^WpRT3E?2X2?AyIT|Zsv8Kdg76l2b4Oemuz?iQ^-vC(YycBm;>`cZ@J zf)E&eu6@dcY+TpZ%j?2R6~7?aWQFmK!LR=ztj2s-FNw}cSZ8sy$g8DwG(X~8DSI$` zeFdRPteyzll?p&*K%m41o6I6^W`!S literal 0 HcmV?d00001 diff --git a/submarine-hunt/screenshot.png b/submarine-hunt/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..29f96ef5182497535f9fcc717b61a40ca2b268a6 GIT binary patch literal 5123 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 literal 0 HcmV?d00001 diff --git a/tools/screenshots/Dockerfile b/tools/screenshots/Dockerfile new file mode 100644 index 0000000..f8fc8a3 --- /dev/null +++ b/tools/screenshots/Dockerfile @@ -0,0 +1,8 @@ +FROM mcr.microsoft.com/playwright:v1.56.1-noble + +WORKDIR /tool +COPY tools/screenshots/package*.json ./ +RUN npm ci --omit=dev +COPY tools/screenshots/capture.mjs ./ + +ENTRYPOINT ["node", "/tool/capture.mjs"] diff --git a/tools/screenshots/capture.mjs b/tools/screenshots/capture.mjs new file mode 100644 index 0000000..004119e --- /dev/null +++ b/tools/screenshots/capture.mjs @@ -0,0 +1,166 @@ +import { readFile, stat, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import process from 'node:process'; +import { chromium } from 'playwright'; +import { PNG } from 'pngjs'; + +const root = '/programs'; +const baseUrl = (process.env.SCREENSHOT_BASE_URL || 'http://c64-basic:8080').replace(/\/$/, ''); +const args = process.argv.slice(2); +const force = args.includes('--force'); +const requestedSlugs = args.filter(argument => !argument.startsWith('--')); +const programs = JSON.parse(await readFile(resolve(root, 'programs.json'), 'utf8')); +const recipes = JSON.parse(await readFile(resolve(root, 'screenshots.json'), 'utf8')); +const selected = programs.filter(program => { + const slug = program.path.split('/')[0]; + return requestedSlugs.length === 0 || requestedSlugs.includes(slug); +}); +const knownSlugs = new Set(programs.map(program => program.path.split('/')[0])); +const missingRecipes = [...knownSlugs].filter(slug => !Object.hasOwn(recipes, slug)); +const unknownRecipes = Object.keys(recipes).filter(slug => slug !== '$default' && !knownSlugs.has(slug)); + +if (missingRecipes.length || unknownRecipes.length) { + if (missingRecipes.length) console.error(`Missing screenshot recipes: ${missingRecipes.join(', ')}`); + if (unknownRecipes.length) console.error(`Unknown screenshot recipes: ${unknownRecipes.join(', ')}`); + process.exit(1); +} + +if (!selected.length) { + console.error('No matching programs. Pass a program slug or omit it to capture the full collection.'); + process.exit(1); +} + +const browser = await chromium.launch({ + headless: true, + args: ['--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist'] +}); +const context = await browser.newContext({ + viewport: { width: 1280, height: 1100 }, + deviceScaleFactor: 1, + acceptDownloads: false +}); +const page = await context.newPage(); +page.setDefaultTimeout(30_000); + +function slugOf(program) { + return program.path.split('/')[0]; +} + +function outputPath(program) { + return resolve(root, slugOf(program), 'screenshot.png'); +} + +function normalizeKey(key) { + const names = { SPACE: 'Space', RETURN: 'Enter', ENTER: 'Enter', ESC: 'Escape' }; + return names[key.toUpperCase()] || (key.length === 1 ? key.toLowerCase() : key); +} + +function imageStats(buffer) { + const png = PNG.sync.read(buffer); + const colors = new Set(); + let visiblePixels = 0; + for (let offset = 0; offset < png.data.length; offset += 16) { + const red = png.data[offset]; + const green = png.data[offset + 1]; + const blue = png.data[offset + 2]; + const alpha = png.data[offset + 3]; + if (alpha > 0 && red + green + blue > 18) visiblePixels++; + if (colors.size < 512) colors.add(`${red >> 3},${green >> 3},${blue >> 3},${alpha >> 5}`); + } + return { width: png.width, height: png.height, colors: colors.size, visiblePixels }; +} + +async function captureCanvas(canvas, path) { + const box = await canvas.boundingBox(); + if (!box) throw new Error('emulator canvas has no visible bounds'); + return page.screenshot({ + path, + type: 'png', + clip: { x: box.x, y: box.y, width: box.width, height: box.height } + }); +} + +async function waitForPicture(canvas, timeoutMs) { + const deadline = Date.now() + timeoutMs; + let lastStats; + while (Date.now() < deadline) { + const buffer = await captureCanvas(canvas); + lastStats = imageStats(buffer); + if (lastStats.colors >= 2 && lastStats.visiblePixels >= 100) return lastStats; + await page.waitForTimeout(750); + } + throw new Error(`emulator never produced a visible frame (${JSON.stringify(lastStats)})`); +} + +async function captureVisibleFrame(canvas, timeoutMs = 10_000) { + const deadline = Date.now() + timeoutMs; + let lastStats; + while (Date.now() < deadline) { + const buffer = await captureCanvas(canvas); + lastStats = imageStats(buffer); + if (lastStats.colors >= 2 && lastStats.visiblePixels >= 100) return { buffer, stats: lastStats }; + await page.waitForTimeout(250); + } + throw new Error(`captured frames look blank (${JSON.stringify(lastStats)})`); +} + +async function exists(path) { + try { + await stat(path); + return true; + } catch { + return false; + } +} + +let failures = 0; +for (const program of selected) { + const slug = slugOf(program); + const destination = outputPath(program); + const recipeOverride = recipes[slug]; + const recipe = recipes.$default ? { ...recipes.$default, ...recipeOverride } : recipeOverride; + if (!recipe || !recipeOverride) { + console.error(`FAIL ${slug}: no screenshot recipe`); + failures++; + continue; + } + if (!force && await exists(destination)) { + console.log(`SKIP ${slug}: screenshot.png already exists`); + continue; + } + + try { + console.log(`OPEN ${slug}`); + await page.goto(`${baseUrl}/program/${slug}`, { waitUntil: 'domcontentloaded', timeout: 60_000 }); + const canvas = page.locator('canvas.ejs_canvas'); + await canvas.waitFor({ state: 'visible', timeout: 60_000 }); + await waitForPicture(canvas, recipe.readyTimeoutMs || 60_000); + await page.waitForTimeout(recipe.settleBeforeActionsMs || 5000); + await canvas.click({ position: { x: 320, y: 240 } }); + await page.mouse.move(10, 10); + await page.waitForTimeout(1500); + + for (const action of recipe.actions || []) { + if (action.waitBeforeMs) await page.waitForTimeout(action.waitBeforeMs); + if (action.text !== undefined) await page.keyboard.type(String(action.text), { delay: 120 }); + else if (action.key) await page.keyboard.press(normalizeKey(action.key), { delay: 120 }); + else throw new Error('screenshot action must contain key or text'); + if (action.waitAfterMs) await page.waitForTimeout(action.waitAfterMs); + } + if (recipe.captureAfterMs) await page.waitForTimeout(recipe.captureAfterMs); + + const { buffer, stats } = await captureVisibleFrame(canvas); + await writeFile(destination, buffer); + console.log(`SAVED ${slug}/screenshot.png (${stats.width}x${stats.height}, ${stats.colors} sampled colors)`); + } catch (error) { + failures++; + console.error(`FAIL ${slug}: ${error.message}`); + } +} + +await browser.close(); +if (failures) { + console.error(`${failures} screenshot${failures === 1 ? '' : 's'} failed.`); + process.exit(1); +} +console.log(`Screenshot run complete: ${selected.length} program${selected.length === 1 ? '' : 's'} selected.`); diff --git a/tools/screenshots/package-lock.json b/tools/screenshots/package-lock.json new file mode 100644 index 0000000..fff5561 --- /dev/null +++ b/tools/screenshots/package-lock.json @@ -0,0 +1,66 @@ +{ + "name": "screenshots", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "playwright": "1.56.1", + "pngjs": "7.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.56.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + } + } +} diff --git a/tools/screenshots/package.json b/tools/screenshots/package.json new file mode 100644 index 0000000..e3db387 --- /dev/null +++ b/tools/screenshots/package.json @@ -0,0 +1,8 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "playwright": "1.56.1", + "pngjs": "7.0.0" + } +} diff --git a/typing-sprint/screenshot.png b/typing-sprint/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..27c53b88ea31764c976e1c7defdfc7e03678e475 GIT binary patch literal 4045 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk1AmjJi(^Q|oHutJi)IBd zG$dM2c79^=EGRO4VyWKMIcmpWC>i@@P5s2I^y>TCoX5h=EkB<(e%SK+;oq;>>zQd{Ws`oWhaH?$b8;lQuc)B<`4ezCli*2U753S zb#CwH@J~CF1E&8L-L_%%Oy#|YS+^gvT(>pm!8=Yw0{gWv_ld%%Q>W*C?rmRP6aDB# z3N!nH|F17CXOWq!RP@P`;f^6vaG&3F<>Q@Vbv7S~*?jW$S2jLdEUb7gwk7f7#_bs^ zbt_v}Ys<{NR`f1YJoeZA|MeG-uKjab?#`W5=dyDf8TH&C$yeN<-YV-{PshzEr*5sg zc5~8l$$gCXW_0KJ7v%B%aO$@9*nV=)g-iLf_Ns0WL9*I@OK*+#3$gukwk-R4-G5i> zkI#MG%vu}~w z9jBUe*JhlFKY4xEq+`uDuFU__qAQs&1C+xaTt8pGXMf#Wxwf6`3=Nj=zV_`=-&<=V zFE_h!H2;kjNTWqBQidF@i$*Kw(bmdny9}w~9c`#08djr)!yqUe`lT=Y{(G8_fkDZ5 z5V&TvH%Ei+?H@)4hX4PgPv^S;JLPPk-Z(SEj5P}l`VDuh0!0`+UHx3vIVCg!0NSzQ AmH+?% literal 0 HcmV?d00001 diff --git a/wave-composer/screenshot.png b/wave-composer/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..31aae34aea8e8185e10e403867b25c2a904da1f7 GIT binary patch literal 5171 zcmeHLdsI^S7XBHAHffJBS7YYm=1td(maprh1kAFmYlG#@(6nUIm8L0LCMrIlY3gQ? z)629pn%ip3>ttrjBo)L|C|_ARJ|KZaWqgu=0*Z1EXk!1(UH6Y#cl8JBto7rZv-ke? zx4*s5zOWDNWBAp&uK)msdv<&K0{~+Hu%uOg33SqGd|3d%DzL}f>p)W8NDnR3?zB1a z9g2S==N8qHON8&TF@km1ZCmPPV6b$xfvdwRuYE9Qn4`xP(sSm9$dmqI_F*-~$1moV za;Jud28A!#V}oObg(f^nQAU90DaeF1VL%svz1DYifaR)I$d-O20Q$Or13-7lI>-+l zLjXubI3RA@1_RqHzufj20wNvkr3Kh^+a^A3*hPOKBkVLap?b}wopxs0dZS7u z3ikVZ;_bSO0)PanI2&r0TvNX@Vw7^w%fAsG2Q zpnQ`AZ1$aF>UntE<%gaovClYpbxrbO8E>C};zFG`eYc$OZ*s`%JE^quX~jqT5d5vJ z44sdRJEOi$c_|SkrgIWwM?;va^}5&m_+Q4yTXqv8v;9i9&Q4$5@sf;v#Ob7#k9$)+3|1 zoh@>mhtyv-m!20r{Uw`PL9UwMG4IjF`2BAvV`(e?QhIigRJb9mE0uY-H%2TaBWe+A zw^e8DSBrp6{+w#w_*vSN?~y2O`xe>vJdd0<9Z$#P3y z@yB(O(acKLrQuF)#1spOYDW(F3o7~v?N`fFj6Ls`P4?Nc_X(=oedt|@rM)US)_9SZ zM`}>EZUabnq%iUPIt-$_U%vUYKl|$P)3^jKr-RUboaXFeQ}LpgETHfMVq|7L?T@F6 zIF8v$c5&&0V?xihh!cz77XHvGQs7fj?oph;i&v5_SG}tA9%o2nTxMUGt=RC&JtS#V zZy3+LhnD?j+#Q*9NETgC$%M6X4*x5Odu@G(IKZ%zdWB_wz^OmQsggD(CL=@*yVq0H zdE&ob20%`e?iZccmh%7F_(*LwI21lzGV|yTx=J>|${^gq-BFGX$Z5O`Iakq)K}{u0 z(Lmh1%tGC@=AIG50`BVqhYxRs!;7HnMK{gWjtlsad(|D+fpm`Jf;wp~YZUqUotl{r z73B{&{S4>#YW~%~bNQm7# zVxwv36UUa!6`(8kqd?As5Mtq)ic3vn{OUIC3++Vp+$sq5fm2OgKl zQ;L6oRKQyf<^)1B^S%E=_e5C9p)f|TA)xwyNNyCG<2EEv=*sPJxx(iv*SX^ZHp@Db15PD0g9lkS2W`(Rmhnmfd7NG#hd5L zYq5n);tf-`=nq(UX^9YvU&fl_KLRU=c4Y>=)&b=Vjj-CRo`vf2t-3ZDL~Y={?y1+^ z@ak${q+^~r5wLY4K`n`)o3fhX zofM9`unKgY(EwxEQ(kAJ7}}UZ`o~wYbCtPN~;r38)5B z;J!v9qnc;_3^ZwJN7BbRlG~n2DO*a=0fD<01RUZa=EOX&iJLw$ck9xl?r$P?>H)@g z^OopG(HnF@7iDpFinFrBa6q;E?}S|%qnIeix8P8!5n-EXrmtBDaZUAm+n|g-D_3?5 z-nJYJY)%cT1H4V_upR8l+28c!4KEn9=al_BS@rq8lQH3RR0`}}gdlzrVp7{?2fTP&} literal 0 HcmV?d00001 diff --git a/word-scramble/screenshot.png b/word-scramble/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..0fdb5d5c6c7ec5490864c729571b1917ef5eff16 GIT binary patch literal 4503 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