diff --git a/css/sideart.woff2 b/css/sideart.woff2 new file mode 100644 index 0000000..9c6af1d Binary files /dev/null and b/css/sideart.woff2 differ diff --git a/css/style.css b/css/style.css index 14db4f7..5aa831f 100644 --- a/css/style.css +++ b/css/style.css @@ -187,61 +187,77 @@ button { color: var(--muted); } -/* ── sky ──────────────────────────────────────────────────────────────── - Generated, not photographed. tools/background.py renders every style into - img/bg/; inc/header.php enumerates that directory and emits one custom - property per style, so --bg-img follows html[data-bg]. Only the matching - rule's url() is fetched. Each is Bayer ordered-dithered to a few grey - levels and upscaled with pixelated rendering so the cells stay crunchy. - Regenerate everything: python tools/background.py all */ -:root { - --bg: #060606; - /* opacity of the text-column scrim; raise for busier background styles */ - --scrim: .82; +/* ── side art ─────────────────────────────────────────────────────────── + Full-height sakura in both margins: colored ASCII art (inc/art/left.html + and right.html, 104x88 characters), mirrored so the branches hang in from + the outer corners. Hidden when there's no margin. */ + +/* The art is drawn for .6em-wide characters; system monospaces range from + .5em up, which squashes it. So it gets its own: JetBrains Mono's printable + ASCII (OFL), about 4.5 KB. */ +@font-face { + font-family: sideart; + src: url(sideart.woff2) format("woff2"); } -body { position: relative; } - -/* background layer */ -body::before { - content: ""; +.side { position: fixed; - inset: 0; + top: 0; z-index: -2; - background: #060606 var(--bg-img) center center / cover no-repeat; - image-rendering: pixelated; + margin: 0; + max-width: calc(50% - 360px); + overflow: hidden; + /* 88 rows at a 1.2 line fill the height */ + font: calc(100vh / 105.6) / 1.2 sideart, monospace; + opacity: .85; + pointer-events: none; + user-select: none; } +.side i { font-style: normal; } +.side-left { left: 0; } +/* rtl only so the right panel clips on its inner edge; the art itself stays + ltr, or the bidi algorithm reorders runs of punctuation */ +.side-right { right: 0; direction: rtl; } +.side-right > span { direction: ltr; unicode-bidi: isolate; } +.side .a0 { color: #8c6068; } +.side .a1 { color: #a07076; } +.side .a2 { color: #ffb0cc; } +.side .a3 { color: #ff96be; } +.side .a4 { color: #f678ac; } +.side .a5 { color: #ffd4e4; } +.side .a6 { color: #e86098; } +.side .a7 { color: #ffecbe; } -/* Readability scrim + diagonal hatch. - The first layer is a feathered vertical band, ~880px wide and centred on - the 680px text column, sitting above the artwork (z-index -2) but below - the content. It gives every background style a consistent dark bed to - read against instead of relying on each one being quiet in the middle, - and because the edges fade out there is no visible panel or card. */ -body::after { +/* the column's shadow: the art fades out under it instead of stopping dead + beside the text. Eased falloff, as wide as about a third of the margin. */ +body::before { content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none; - background: - linear-gradient(90deg, - transparent 0%, - rgba(4, 4, 4, var(--scrim)) 14%, - rgba(4, 4, 4, var(--scrim)) 86%, - transparent 100%), - radial-gradient(ellipse 70% 60% at 50% 38%, rgba(4, 4, 4, .32), transparent 82%), - repeating-linear-gradient(-45deg, rgba(240, 240, 240, .045) 0 1px, transparent 1px 8px); - background-size: 880px 100%, auto, auto; - background-position: center top, center, center; - background-repeat: no-repeat, no-repeat, repeat; + --l: calc(50% - 340px); + --r: calc(50% + 340px); + --fade: clamp(60px, (100vw - 680px) * .16, 180px); + background: linear-gradient(90deg, + transparent calc(var(--l) - var(--fade)), + color-mix(in srgb, var(--bg) 22%, transparent) calc(var(--l) - var(--fade) * .75), + color-mix(in srgb, var(--bg) 55%, transparent) calc(var(--l) - var(--fade) * .5), + color-mix(in srgb, var(--bg) 85%, transparent) calc(var(--l) - var(--fade) * .25), + var(--bg) var(--l) var(--r), + color-mix(in srgb, var(--bg) 85%, transparent) calc(var(--r) + var(--fade) * .25), + color-mix(in srgb, var(--bg) 55%, transparent) calc(var(--r) + var(--fade) * .5), + color-mix(in srgb, var(--bg) 22%, transparent) calc(var(--r) + var(--fade) * .75), + transparent calc(var(--r) + var(--fade))); } -/* cards sat on flat #161616 before; let the sky read through them */ -.status-card, -.spotify-card, -.post-body pre { background-color: rgba(6, 6, 6, .66); } -input, textarea { background: rgba(6, 6, 6, .66); } +@media (max-width: 1280px) { + .side, body::before { display: none; } +} + +@media print, (forced-colors: active) { + .side, body::before { display: none; } +} /* guestbook sign button */ .btn-sign { diff --git a/img/bg/forest-21.png b/img/bg/forest-21.png deleted file mode 100644 index 7da5c76..0000000 Binary files a/img/bg/forest-21.png and /dev/null differ diff --git a/img/bg/forest-4.png b/img/bg/forest-4.png deleted file mode 100644 index cbaca06..0000000 Binary files a/img/bg/forest-4.png and /dev/null differ diff --git a/img/bg/forest-57.png b/img/bg/forest-57.png deleted file mode 100644 index a4f080b..0000000 Binary files a/img/bg/forest-57.png and /dev/null differ diff --git a/img/bg/forest-93.png b/img/bg/forest-93.png deleted file mode 100644 index 6928e41..0000000 Binary files a/img/bg/forest-93.png and /dev/null differ diff --git a/inc/art/left.html b/inc/art/left.html new file mode 100644 index 0000000..a147e51 --- /dev/null +++ b/inc/art/left.html @@ -0,0 +1,88 @@ + -+.:===+=: : ,#@@@@@@@@@%@@@@@@@@@@@@@@@@@@%@@@@@@@@@@@@@@@@@@@@@@@@@@%@@@%@@@@@@@@%%o + ,+-===-, .**. .X@@@@@@@@o*o*@@@@@@%@@@@@@#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + =**=. , o@@@@@@@%###@@@@%#%@@@%xx=x@@@@@@X@#@X#@@@@@@@@@@@@@@@@@@@@%%%@@@@@@@@@@ +,,:+*+, -%: -%#@@@@@@@@@@@@@@%X+=@x@@-#+o%@@@X-: .-. ,@%#%*@@@@%@@@@@@@@@@@@@@@@@@@%#@ +==+***=----;;, :#@@@@@@@@@@@@@@@#,:xX*@@;==Xo@%@%= . ,*;x@@@%@%x@@@@@@@@@@@@@x+x= +--=***+++******=-:. ;+xX+@@@@@@@@@@@@%xo@@@@@@@@@%@@@@%x: ;=,*#@@@@xx@%@@%@@#@@@+, + .,,::;;-=++++++=;:xX;,,*x*.:=*o@@@@@@@@@@@#@@@@@@@@@@@@@@@%--. :*+@@@@@@@@@@X@@@@@@X:#-=: + .,;+**==+***++**+++=*Xx@@@@@@@@@@@@@@@@@@@@@@@@@@@X@+::;;;;-o@@@@@@@@@@@@@@@%+: -* + ,;===+=: ...:=o#@@@@@@@@@@@@@@@@@@@@@@oX%@xX;,.. =*o@@@@@#o@@@@@@@@@@@,,, + .*, .:+*+*=;. ;=.,*x@x@@@@@@@@@@@@@@@@#Xx. .;=-o*;:;. ;@@@@@X+,:=o@@@@@@@@#, + ,. . ;+***+-,:- ,*o+@@@@@@@@@@* :--#o ,;x@@@@@x=%@@@@@@@%%@+--o@@#@o*. + ,; *= :++-+**=;.:x*=@@@@@@@@@@#, :; :#@@@@@@@:o@@@@@@@@@@@@##@x*: + :- :- ,++: :=++o=#@@@@@@@@@@%-,.;. ,;;+@@@#%*.@@@@@@@oo+@@@@@@@@@@@@@@@o; + .o*, ,==. ;*%@@@@@@Xx#*:o=*@%@@@@@@@@@@@@:.;:*@@*o+ ;#x@%@@@@@@@+*#- + :++- o@@@@@@@@#@@@%-#%@@@@@@@@@@@#%##@*o#;;: .-,X@@@@@@@@##- + :; ,o++#x, x#@@@@@@@@@@@@#@@@@@@@@@@@@X%%%@@@@#%%. ;@@@@@@@@@##Xo;. +, .+. -- -++, , ,@@@@@@@@@@@@@@@@@@@@@%@#@@#@@@@@@@@%= *@@@@@@@%= . , +, -x: .++; :@#xX@@%@@@@@@@@@@@@@@@@@@@@%@%@@@@@@@@%%x,,,=@@@@@x=: ;:. .+=x#, +,,. .oX- -*= ;X%@@@@@@@@@@@@@@@@@@@@@@@@@@@@%#+;X=*@X::, -x@@@@@@%* +X. ,*+: + ,::;;;:, +-; -*- x+:X@@@@@@@@@@@@@@@@@@@@@@%o@@+*@@Xxo. .o@@@@@@@X- . +. .,:---;+=;.,-=: ,o+@@@@@@@@@@@@@@@@@@@##*.+@@@@@@@%; ,+o@@@@#+; ;#+. + , .+o===;. +@@@@@@@@@@@@@@@x%#x+; +@%@@@@@@@o :X*X*, ; +#%: :=+;. ,#@@@@@@@@@@@@@@+,.::++;:.X#x@@%#X. . +=. :+=: =o+=-@@@@@@@@@o+o=..:-=; :X; .@@@%* + .-=, ;+==. .:%@@@@@@@@@x-;-..-=:+*X@x==x+,=- . %@@@@@% + :=; . ,;==+- ;@@@@@@@@@@@@X@+, .;%@@@@@@@@@@o ,,%@-,.:;;,x@@@#- + .. ,--. ;%+. :=++=++. ,o@@@@@@@@@#@@%#@+#%@@@@@@@@@@@@- .-@@@@@@o=#*#%#:*@@ + ,@%:=x#;+: : .::::-++#x#o *++%*@@@@@@@@@@@@@@@@@@@@@@@@@@X: .:::::,,-@@@@o-#@@@@@@@@@ +#@@@@@@@@%- ,;-;,,;-==o@@@%o+@@@@@@x%@@@@@@@@@@@@@@@@@@@@@;;-ox#*:=-::;-;;--;:=:.=; :;@xxX@XX= +@@@@@@%@#o++x: .,;:,.:;;;=-=*X;%x=@@@@@@@@@@@@@@@@@@@@@@@@@@@%%-,,:*@@@@%@#=-:, :+ +@@@@@@@@#%@@@xX*:::,:;;-;;=++==#o. o@@@@@@@@@@@@@@@@@@@@@@X;-:::*X:.=#@@@@%#=, ;:,.xx,;+:,, x +*%@@@%#@@@@@@@x;::;-;::,.;=,o@@@#; ;-=X%@@@@@@@@@@@@@@@@@@X=-:=o%@#%* ;@@=*+,:-;.,,,,,;-@@@%%@@@@%@Xx# +x%@@%@@@@@@@#@o;:,. .=;.:@@@@Xx;-====o#@@@@@@@@@#X@@@@@@@@%o:=x@@@@=;-=+-====--==:::+%@@@@@@@@@@@#%oo +=+XX%@@@@@@@@@o...,;#@==*oXx@X#%;;;,,.,-@@@@@@@@@@@@@@@@@@@@@@x: x@@@@X:,,,,::;-;-==-,,;#@@@@@@@@%ox+,:+ +o*=-@@@@@@@@@@@@@@@@@@%@#@@@@@@%- -, x%@@@@@@@@%#X@@@#@@@@@@@@@=;=@@-=. ::. .,=-x@@@@@@@@%@@X-;=X +@@@@@@@@@@@@@@#o%%@@@@@@@@@@@@@@@=-@o *@##@@x%-,@@@@@@@@@@@@@@@@@@@%@@%o: ,;,:,.x@@@@@@@X%@%@@X#%@ +@@@@@@@@@@x%@@@@x-@@#@@xX@@@@@@@@@@@%X+x@@@o.;%@#@@@@@@@@@@@@@@@@@@@@@@@@@@X :-.,*%@@@@@@%@@@@@%%@@@ +@@@@@@@@X=,=@@@@@@@@@@@:=#X%@@#@@@%@X**x%@##*#@@@@@@X@@@@@@@@@@@@@@@@@@@@@@*:xo=%@@%%@@X@@@@@@@@@@@@@@@@ +@@@@@@@@@#@@@@@@@%@@@@+,.,+;+. :=.*%X@X,*@X*@@#@@@@@%@@@@@@@@@@@%#@@@@@@**-;-%@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@Xo#@@@@@=*%@+++: ,*. =%@@%@%#@@@@@@@@@@@@@@@@@@@@@@@@@@%X: :@@@@@@@%@@@@@@@@@@@@@@@@@@@@#% +@@@@@@#o*x@@@@@@@@@@%x;=:,=, =@@@@x#X%@@@@@@@@@@@@@@@@@@=o%@@@@@@#:,+#@@%%XXx%@@@@@@@@@@@@@@@@@@@@@ +@%@@@@@@@@%%@@@@@@X%# ,+,Xo: *@@@%*:=*%@#@@@%Xo:X@@@@@@@==#@@@@@@@@@%=:%x ;oX@%@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@+ + ;+-,. ,x%*+X* *#%@@@@@@o ;*@@@@@@@X: :,%@@@@@%x*+;. ,-.*o@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@#*, -X+@@, ,x@%@%xo; ;x@@@@@@@@=.=#@@@@@@%o- o@%X@@@+x%o, ;@@@@@@@@@@@@@@@@ +@@@@@@@@Xo%@@@@@@# :#@@@@X,-%@@@@@%*x@@@@@@@@@@%@*X@@@@@@Xo= .;=@@@@@@@@@@=. ;%@#@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@%#****@@@@x+X@@@@@@@@@@@@@@@@%=%@%@X#%@@@@%= +@@@@@@@@+--:,:-+%@@@@@@@@@@@@@@@ +@@@@%@@%-%@@@@%#@%%@@@%@@@@@@@@%o:@@@@@@@@@@@@@.;: #@o;; .*-+@@@@@%X= :=x@x%@X%@@@@@@@@@@@@@ +@@@@==; ,@@@@@@#@@@@@@@@@#@%.--.,,,o%Xx@@@@@@@x-%+ ,o##@#X-.X#; ;X@@@#+xo@@@@@@@@@@@@ +@@@X:,,,*@@@@@@@@@@@@%@@#xo@; :;x- ;#x%@@@#%@@%@+ .*@@@@@* -*; ,-+@#o=*@@@@@@@@@@@@@ +@@@@@@:;@@@@@@@@@@@@@@@@@@@@#+ .:. ::=;=o%:#@@@@o:.X%: X@@@@@@* ,, -*++-. ::+-=%@@@@@@@@@@@@ +@@@@@@X%@@@@@@@@@@@@@@@@@@%@X; :+,:: X%%@@@#@@%@@*+, :@@@@@@@@@%**: -o%%= .;**-@@@@@@%X@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@x..;*@%oo::;++*%@@@@@@@@@@#X; ,o@@@@@@@@@++* :=; ;+;:+o@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@%#@@@@@@@@X@@@@@@%x#@@@@@@@##@@@@@@@@;,.,. .oX;#@@@@@@@@o ,=*@o@+ ;=.+X@@@@@@@@ +@@@@@@@@@@@@@@@@@#@oX#@@@@@@@@@@@@@@@XX@@@@@@X%%%%@@@@@@@@@@@@@%. ..:o;=@%X#, .%%@@%; xXo@@@@@@@@@ +*%@@@@@@@@@@@@@@ :@@@@@@@@@@@@@@@@@#-o@@@@@@@:=@@x@@@@@@@@@@@@o:. ,, -, #x+X*X@@@o; %#@@@@@@@@@@@@ +:x%@@@@@@@@@@@@x. ;X@@@@@@@@@@@@@@@@@@@@@@@@@%%X@@@@@@@@@@@@@@@%#+ .: .X#%@@@@%%@@@#.*@@@@@@@@@@@#@@ +#@#@@@@@@@#@%*oX;.**@@@@@@@@@@@@@@@@@@@@@@@@@%:#@@@@@@@@@@@@%@@*%x ; .:;@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@%@%%@%X#@%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@Xo@@@@o*@@@@@@@o +@o#* .*@@@@@@@@@@@@@@#+%@@@@@@@ +@@@@@@@@@@@%xx@@@@@@@@@@@@@@@@@@@@@@@@@@ox@@%%@@@@@o,.;x#@@@@o x@@@@@@;:.-;,,,;,+-o+-x%@@@@@@@%@@@@@@@@@ +#@@@@@@@@@%#x+*::X@X#@@@@@@@@@@@@@%#@@@%; ;xx%@@@@@X ,#%+o,,:@#%@@@@x@%*= ;@@@@@@@#XX#%@@@@@@@ +-;:--@@@#x@X::-;;Xo;=%@@@@@@@@@@@@@@#= ,@%:;- ,:=#+ :, :@@@@@@@@@* =X@@@@@@* +#Xo@%X*: +++=--=*%@@@*-;=*;;::x@@@@@@@@@@@@@@x. -X=,::;:-o@@%@#;,x@@@@@@@@+. -=X+@@@@%X@* ,==+::- +@oo, ,@@@@@@@x,-=; ;*%@@@@@@@@@@@@@@o#o X@%@@X=:, :%@@@@@X##@@@@@@@@%X= ;: .*@@@@@@xx. ;=- . +x@@@+;*@@@@@@#x%@@@@@@%@@@@@@%X@@@@@@%=;,:;;:::+%@@@@@@# -@@@@@*X%@@%@%@@%#;=#*,,- x%@@@@@@@@XX ,+-. +@@@@@@@@@@@@@@@@@@@@@@@@@@%X x%@@%%@o.,;-:,%xx@@@@@@%#o:.xX%@#%oXx+@%*x*##=x#@@@@: +@o@@@@@@@@= ;=- +@@@@@@@@@@@@@@@@@@@@@@@@@@@@+ .:,;*.-+--::,X@@@@@@@@@@%X*,o* *x, . .:*@@@@* .,o+=*X%;-; .+: +++@@@@@@@@@@@@@@@@@@@@@@@@@*. *oox :-;,::=*@@@@@@@@@@#*x#@@@@=:%o+; ,X#o=- ,+ +%=..@X#@%@@@@@@@*o%o@@@@%-+: =#@@@%Xx:=@@X@@@@@@@@@@@+:@@@@@@+#%@%, ,:%#@%+-@X;. :x=. . +X, -%@@@@@@@@;. ,:-,+- , o@@@@%x==%@@@@@@@@@@@@@@@%@@@@@@@X:=. -*@@@@@@%@@@#+ .*: + .Xo+#*%@@@@@=: =o+@X., ,@@%@%@@X%@@@@@@@@@@@@@@@@@@@@@@@%; .=#@@@@@@@@@@%; ,, + :X@@@@*. ,o%@@@@%. ,x@@@%+ -@@@@@@@@@@#@%;@@@@@@@@@+ o#o@@@@@@@@@@= -%x +* + ;; .@@@@@@@@=x@%@@@@%@x XX;#@o,#@%%@@@@@@%*#o*%%@@%@#=#o ;o@@@@@@@@@@@X=- . ;*@@ + **. ,::@@@@@@@@@XX%X+:o=+-o#%@@@@@@@@%#@@@@@x+*x#@o+*,::, =o#@@@@@@@@@x=+ ;o@@ + x@@@%@@@@@o*@@#%@X%@X@@@@@@@@@x,=@@@@@@@%@%@@%= ,;;=X%@@@@@@@@@@@%:@xx* +%- ,o= + :- ,X@@@@@%@@@@@@@@@@@@@@o,+@@@@@@%@@x@@@@@@%@%%@= ;#@@@@@@@@@@@@X%%@@* ::. + :; :x*X@@@@@@@@@@@@@@@@-*X=*@x*-x%@@@@@@%@%@@@%@X= +@@@@@@@@@@@@X- #*-- .;. + .x%@@@@@@@@@@@@@%;. +@@@@@:oX@@@@@@@@@@@@@@#= -@o#@#@#+X@@@@@@%=;@x-@Xo- **. *@@ + ,#@@%o.-%@@@* . +@%@%o-#@@@@@@@@@@@@@@@@=,,#@@@@@%@x;x@@@@#==@@@@@@@@;..;=+@@x%%;o%% + .=;*+ .*=+= x@%@@@@@@@@@%@@@@@@@@%%+ +@@@@@@@;:@@@@@X@X@@x@o;=. x@@@@@@=,++* + :%@@@@@@@@@@@@@@@@@@@@@;** o%+@@@@@@@@@@@@@@@@%X= ,,,;#@@@@x@X + ,+o@@@@@@X%@@@@@%@@@@X;#@@@@@@@@x%@@@@@@@@@@@@@@%;#+.. ,;*%@@X, o + ,#@@@@@@@@==%@@@@@@@@::ox@X@@@@@*-#@@@@@@@@@@@@@@%@@@o ,;@@@@@- ;x + ,#@@@@=@@@@%- .x@@%@%o ;#%X@@@@@@@@@@@@#xX;X@%@@%@%%@, :%@@@@#- -* + ##+X:=@X@+ :..+- .. ,=@@@@@@@@@@@@@@@*@@%@x%@; x@X%@- -. + .. . ;X@@@@@@@@@@@@@@@@@@##%@@@%%- ,; :--%= + ., .:X@@@@@@@@@@@@@@%@%@xx#@@%@x: ++. + -@%. ,: o@@@@@@@@@@@@@@@%x--x; .-,:x= . + , ;##: X@@@#x@@@@@@@@@@#*; :%x, \ No newline at end of file diff --git a/inc/art/right.html b/inc/art/right.html new file mode 100644 index 0000000..87dfe8c --- /dev/null +++ b/inc/art/right.html @@ -0,0 +1,88 @@ +o%%@@@@@@@@%@@@%@@@@@@@@@@@@@@@@@@@@@@@@@@%@@@@@@@@@@@@@@@@@@%@@@@@@@@@#, : :=+===:.+- +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#@@@@@@%@@@@@@*o*o@@@@@@@@X. .**. ,-===-+, +@@@@@@@@@@%%%@@@@@@@@@@@@@@@@@@@@#X@#@X@@@@@@x=xx%@@@%#%@@@@###%@@@@@@@o , .=**= +@#%@@@@@@@@@@@@@@@@@@@%@@@@*%#%@, .-. :-X@@@%o+#-@@x@=+X%@@@@@@@@@@@@@@#%- :%- ,+*+:,, +=x+x@@@@@@@@@@@@@x%@%@@@x;*, . =%@%@oX==;@@*Xx:,#@@@@@@@@@@@@@@@#: ,;;----=***+== + ,+@@@#@@%@@%@xx@@@@#*,=; :x%@@@@%@@@@@@@@@ox%@@@@@@@@@@@@+Xx+; .:-=******+++***=-- + :=-#:X@@@@@@X@@@@@@@@@@+*: .--%@@@@@@@@@@@@@@@#@@@@@@@@@@@o*=:.*x*,,;Xx:;=++++++=-;;::,,. + *- :+%@@@@@@@@@@@@@@@o-;;;;::+@X@@@@@@@@@@@@@@@@@@@@@@@@@@@xX*=+++**++***+==**+;,. + ,,,@@@@@@@@@@@o#@@@@@o*= ..,;Xx@%Xo@@@@@@@@@@@@@@@@@@@@@@#o=:... :=+===;, + ,#@@@@@@@@o=:,+X@@@@@; .;:;*o-=;. .xX#@@@@@@@@@@@@@@@@x@x*,.=; .;=*+*+:. ,*. + .*o@#@@o--+@%%@@@@@@@%=x@@@@@x;, o#--: *@@@@@@@@@@+o*, -:,-+***+; . ., + :*x@##@@@@@@@@@@@@o:@@@@@@@#: ;: ,#@@@@@@@@@@=*x:.;=**+-++: =* ;, + ;o@@@@@@@@@@@@@@@+oo@@@@@@@.*%#@@@+;;, .;.,-%@@@@@@@@@@#=o++=: :++, -: -: + -#*+@@@@@@@%@x#; +o*@@*:;.:@@@@@@@@@@@@%@*=o:*#xX@@@@@@%*; .==, ,*o. + -##@@@@@@@@X,-. :;;#o*@##%#@@@@@@@@@@@%#-%@@@#@@@@@@@@o -++: + .;oX##@@@@@@@@@; .%%#@@@@%%%X@@@@@@@@@@@@#@@@@@@@@@@@@#x ,x#++o, ;: + , . =%@@@@@@@* =%@@@@@@@@#@@#@%@@@@@@@@@@@@@@@@@@@@@, , ,++- -- .+. , + ,#x=+. .:; :=x@@@@@=,,,x%%@@@@@@@@%@%@@@@@@@@@@@@@@@@@@@@%@@Xx#@: ;++. :x- , + :+*, .X+ *%@@@@@@x- ,::X@*=X;+#%@@@@@@@@@@@@@@@@@@@@@@@@@@@@%X; =*- -Xo. .,, + . -X@@@@@@@o. .oxX@@*+@@o%@@@@@@@@@@@@@@@@@@@@@@X:+x -*- ;-+ ,:;;;::, + .+#; ;+#@@@@o+, ;%@@@@@@@+.*##@@@@@@@@@@@@@@@@@@@+o, :=-,.;=+;---:,. . + ; ,*X*X: o@@@@@@@%@+ ;+x#%x@@@@@@@@@@@@@@@+ .;===o+. , + . .X#%@@x#X.:;++::.,+@@@@@@@@@@@@@@#, .;+=: :%# +*%@@@. ;X: ;=-:..=o+o@@@@@@@@@-=+o= :=+: .= +%@@@@@% . -=,+x==x@X*+:=-..-;-x@@@@@@@@@%:. .==+; ,=-. +-#@@@x,;;:.,-@%,, o@@@@@@@@@@%;. ,+@X@@@@@@@@@@@@; -+==;, . ;=: +@@*:#%#*#=o@@@@@@-. -@@@@@@@@@@@@%#+@#%@@#@@@@@@@@@o, .++=++=: .+%; .--, .. +@@@@@@@@@#-o@@@@-,,:::::. :X@@@@@@@@@@@@@@@@@@@@@@@@@@*%++* o#x#++-::::. : :+;#x=:%@, +=XX@Xxx@;: ;=.:=:;--;;-;::-=:*#xo-;;@@@@@@@@@@@@@@@@@@@@@%x@@@@@@+o%@@@o==-;,,;-;, -%@@@@@@@@# + +: ,:-=#@%@@@@*:,,-%%@@@@@@@@@@@@@@@@@@@@@@@@@@@=x%;X*=-=;;;:.,:;,. :x++o#@%@@@@@@ +x ,,:+;,xx.,:; ,=#%@@@@#=.:X*:::-;X@@@@@@@@@@@@@@@@@@@@@@o .o#==++=;;-;;:,:::*Xx@@@%#@@@@@@@@ +#xX@%@@@@%%@@@-;,,,,,.;-:,+*=@@; *%#@%o=:-=X@@@@@@@@@@@@@@@@@@%X=-; ;#@@@o,=;.,::;-;::;x@@@@@@@#%@@@%* +oo%#@@@@@@@@@@@%+:::==--====-+=-;=@@@@x=:o%@@@@@@@@X#@@@@@@@@@#o====-;xX@@@@:.;=. .,:;o@#@@@@@@@%@@%x ++:,+xo%@@@@@@@@#;,,-==-;-;::,,,,:X@@@@x :x@@@@@@@@@@@@@@@@@@@@@@-,.,,;;;%#X@xXo*==@#;,...o@@@@@@@@@%XX+= +X=;-X@@%@@@@@@@@x-=,. .:: .=-@@=;=@@@@@@@@@#@@@X#%@@@@@@@@%x ,- -%@@@@@@#@%@@@@@@@@@@@@@@@@@@-=*o +@%#X@@%@%X@@@@@@@x.,:,;, :o%@@%@@@@@@@@@@@@@@@@@@@,-%x@@##@* o@-=@@@@@@@@@@@@@@@%%o#@@@@@@@@@@@@@@ +@@@%%@@@@@%@@@@@@%*,.-: X@@@@@@@@@@@@@@@@@@@@@@@@@@#@%;.o@@@x+X%@@@@@@@@@@@Xx@@#@@-x@@@@%x@@@@@@@@@@ +@@@@@@@@@@@@@@@@X@@%%@@%=ox:*@@@@@@@@@@@@@@@@@@@@@@X@@@@@@#*##@%x**X@%@@@#@@%X#=:@@@@@@@@@@@=,=X@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@%-;-**@@@@@@#%@@@@@@@@@@@%@@@@@#@@*X@*,X@X%*.=: .+;+,.,+@@@@%@@@@@@@#@@@@@@@@@ +%#@@@@@@@@@@@@@@@@@@@@%@@@@@@@: :X%@@@@@@@@@@@@@@@@@@@@@@@@@@#%@%@@%= .*, :+++@%*=@@@@@#oX@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@%xXX%%@@#+,:#@@@@@@%o=@@@@@@@@@@@@@@@@@@%X#x@@@@= ,=,:=;x%@@@@@@@@@@x*o#@@@@@@ +@@@@@@@@@@@@@@@@@@%@Xo; x%:=%@@@@@@@@@#==@@@@@@@X:oX%@@@#@%*=:*%@@@* :oX,+, #%X@@@@@@%%@@@@@@@@%@ +@@@@@@@@@@@@@@@@o*.-, .;+*x%@@@@@%,: :X@@@@@@@*; o@@@@@@%#* *X+*%x, .,-+; + +@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@; ,o%x+@@@X%@o -o%@@@@@@#=.=@@@@@@@@x; ;ox%@%@x, ,@@+X- ,*#@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@#@%; .=@@@@@@@@@@=;. =oX@@@@@@X*@%@@@@@@@@@@x*%@@@@@%-,X@@@@#: #@@@@@@%oX@@@@@@@@ +@@@@@@@@@@@@@@@%+-:,:--+@@@@@@@@+ =%@@@@%#X@%@%=%@@@@@@@@@@@@@@@@X+x@@@@****#%@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@%X@%x@x=: =X%@@@@@+-*. ;;o@# :;.@@@@@@@@@@@@@:o%@@@@@@@@%@@@%%@#%@@@@%-%@@%@@@@ +@@@@@@@@@@@@ox+#@@@X; ;#X.-X#@##o, +%-x@@@@@@@xX%o,,,.--.%@#@@@@@@@@@#@@@@@@, ;==@@@@ +@@@@@@@@@@@@@*=o#@+-, ;*- *@@@@@*. +@%@@%#@@@%x#; -x;: ;@ox#@@%@@@@@@@@@@@@*,,,:X@@@ +@@@@@@@@@@@@%=-+:: .-++*- ,, *@@@@@@X :%X.:o@@@@#:%o=;=:: .:. +#@@@@@@@@@@@@@@@@@@@@;:@@@@@@ +@@@X%@@@@@@-**;. =%%o- :**%@@@@@@@@@: ,+*@@%@@#@@@%%X ::,+: ;X@%@@@@@@@@@@@@@@@@@@%X@@@@@@ +@@@@@@@@o+:;+; ;=: *++@@@@@@@@@o, ;X#@@@@@@@@@@%*++;::oo%@*;..x@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@X+.=; +@o@*=, o@@@@@@@@#;Xo. .,.,;@@@@@@@@##@@@@@@@#x%@@@@@@X@@@@@@@@#%@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@oXx ;%@@%%. ,#X%@=;o:.. .%@@@@@@@@@@@@@%%%%X@@@@@@XX@@@@@@@@@@@@@@@#Xo@#@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@#% ;o@@@X*X+x# ,- ,, .:o@@@@@@@@@@@@x@@=:@@@@@@@o-#@@@@@@@@@@@@@@@@@: @@@@@@@@@@@@@@%* +@@#@@@@@@@@@@@*.#@@@%%@@@@%#X. :. +#%@@@@@@@@@@@@@@@X%%@@@@@@@@@@@@@@@@@@@@@@@@@X; .x@@@@@@@@@@@@%x: +@@@@@@@@@@@@@@@@@@@@@@@@@@;:. ; x%*@@%@@@@@@@@@@@@#:%@@@@@@@@@@@@@@@@@@@@@@@@@**.;Xo*%@#@@@@@@@#@# +@@@@@@@%+#@@@@@@@@@@@@@@*. *#o@+ o@@@@@@@*o@@@@oX@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%@#X%@%%@%@@@@@@@ +@@@@@@@@@%@@@@@@@%x-+o-+,;,,,;-.:;@@@@@@x o@@@@#x;.,o@@@@@%%@@xo@@@@@@@@@@@@@@@@@@@@@@@@@@xx%@@@@@@@@@@@ +@@@@@@@%#XX#@@@@@@@; =*%@x@@@@%#@:,,o+%#, X@@@@@%xx; ;%@@@#%@@@@@@@@@@@@@#X@X::*+x#%@@@@@@@@@# +:*X%@oX#+ *@@@@@@X= *@@@@@@@@@: ,: +#=:, -;:%@, =#@@@@@@@@@@@@@@%=;oX;;-::X@x#@@@--:;- +-::+==, *@X%@@@@+X=- .+@@@@@@@@x,;#@%@@o-:;::,=X- .x@@@@@@@@@@@@@@x::;;*=;-*@@@%*=--=++ +. -=; .xx@@@@@@*. :; =X%@@@@@@@@##X@@@@@%: ,:=X@@%@X o#o@@@@@@@@@@@@@@%*; ;=-,x@@@@@@@, ,oo@ +.-+, XX@@@@@@@@%x -,,*#=;#%@@%@%@@%X*@@@@@- #@@@@@@%+:::;;:,;=%@@@@@@X%@@@@@@%@@@@@@%x#@@@@@@*;+@@@x +-=; =@@@@@@@@o@+ :@@@@#x=##*x*%@+xXo%#@%Xx.:o#%@@@@@@xx%,:-;,.o@%%@@%x X%@@@@@@@@@@@@@@@@@@@@@@@@@@ +:+. ;-;%X*=+o,. *@@@@*:. . ,x* *o,*X%@@@@@@@@@@X,::--+-.*;,:. +@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ++, -=o#X, ;+o%:=@@@@#x*#@@@@@@@@@@*=::,;-: xoo* .*@@@@@@@@@@@@@@@@@@@@@@@@@++ +. .=x: .;X@-+%@#%:, ,%@%#+@@@@@@:+@@@@@@@@@@@X@@=:xX%@@@#= :+-%@@@@o%o*@@@@@@@%@#X@..=% + :*. +#@@@%@@@@@@*- .=:X@@@@@@@%@@@@@@@@@@@@@@@%==x%@@@@o , -+,-:, .;@@@@@@@@%- ,X + ,, ;%@@@@@@@@@@#=. ;%@@@@@@@@@@@@@@@@@@@@@@@%X@@%@%@@, ,.X@+o= :=@@@@@%*#+oX. +*+ x%- =@@@@@@@@@@o#o +@@@@@@@@@;%@#@@@@@@@@@@- +%@@@x, .%@@@@%o, .*@@@@X: +@@*; . -=X@@@@@@@@@@@o; o#=#@%@@%%*o#*%@@@@@@%%@#,o@#;XX x@%@@@@%@x=@@@@@@@@. ;; +@@o; +=x@@@@@@@@@#o= ,::,*+o@#x*+x@@@@@#%@@@@@@@@%#o-+=o:+X%XX@@@@@@@@@::, .** +=o, -%+ *xx@:%@@@@@@@@@@@%X=;;, =%@@%@%@@@@@@@=,x@@@@@@@@@X@%X@%#@@*o@@@@@%@@@x + .:: *@@%%X@@@@@@@@@@@@#; =@%%@%@@@@@@x@@%@@@@@@+,o@@@@@@@@@@@@@@%@@@@@X, -: +.;. --*# -X@@@@@@@@@@@@+ =X@%@@@%@%@@@@@@%x-*x@*=X*-@@@@@@@@@@@@@@@@X*x: ;: +@@* .** -oX@-x@;=%@@@@@@X+#@#@#o@- =#@@@@@@@@@@@@@@Xo:@@@@@+ .;%@@@@@@@@@@@@@%x. +%%o;%%x@@+=;..;@@@@@@@@==#@@@@x;x@%@@@@@#,,=@@@@@@@@@@@@@@@@#-o%@%@+ . *@@@%-.o%@@#, +*++,=@@@@@@x .=;o@x@@X@X@@@@@:;@@@@@@@+ +%%@@@@@@@@%@@@@@@@@@%@x =+=*. +*;=. + X@x@@@@#;,,, =X%@@@@@@@@@@@@@@@@+%o **;@@@@@@@@@@@@@@@@@@@@@%: +o ,X@@%*;, ..+#;%@@@@@@@@@@@@@@%x@@@@@@@@#;X@@@@%@@@@@%X@@@@@@o+, +x; -@@@@@;, o@@@%@@@@@@@@@@@@@@#-*@@@@@X@xo::@@@@@@@@%==@@@@@@@@#, +*- -#@@@@%: ,@%%@%@@%@X;Xx#@@@@@@@@@@@@X%#; o%@%@@x. -%@@@@=@@@@#, + .- -@%X@x ;@%x@%@@*@@@@@@@@@@@@@@@=, .. -+..: +@X@=:X+## + =%--: ;, -%%@@@%##@@@@@@@@@@@@@@@@@@X; . .. + .++ :x@%@@#xx@%@%@@@@@@@@@@@@@@X:. ,. + . =x:,-. ;x--x%@@@@@@@@@@@@@@@o :, .%@- + ,x%: ;*#@@@@@@@@@@x#@@@X :##; , \ No newline at end of file diff --git a/inc/backgrounds.php b/inc/backgrounds.php deleted file mode 100644 index f467ace..0000000 --- a/inc/backgrounds.php +++ /dev/null @@ -1,31 +0,0 @@ - pathinfo($f, PATHINFO_FILENAME), $files); - sort($names); - return $names; -} - -function bg_default(): string -{ - $all = bg_styles(); - return in_array('flow', $all, true) ? 'flow' : ($all[0] ?? ''); -} - -/** A different style on every page load. */ -function bg_pick(): string -{ - $all = bg_styles(); - return $all ? $all[random_int(0, count($all) - 1)] : ''; -} diff --git a/inc/footer.php b/inc/footer.php index d6d7f11..7b4742a 100644 --- a/inc/footer.php +++ b/inc/footer.php @@ -10,21 +10,7 @@ $build = preg_match('/[0-9a-f]{7,40}/i', $build, $m) ? $m[0] : ($build ?: 'dev') © · build: - - 1): ?> - - + + diff --git a/inc/header.php b/inc/header.php index f5e36a6..f7cf66d 100644 --- a/inc/header.php +++ b/inc/header.php @@ -1,30 +1,14 @@ -> + <?= htmlspecialchars($page_title ?? SITE_TITLE) ?> - - - diff --git a/tools/__pycache__/background.cpython-313.pyc b/tools/__pycache__/background.cpython-313.pyc deleted file mode 100644 index 00fcbe0..0000000 Binary files a/tools/__pycache__/background.cpython-313.pyc and /dev/null differ diff --git a/tools/background.py b/tools/background.py deleted file mode 100644 index 792eb93..0000000 --- a/tools/background.py +++ /dev/null @@ -1,299 +0,0 @@ -#!/usr/bin/env python3 -"""Generate the dithered backgrounds for 0xa0.dev. - - python tools/background.py all # every style -> img/bg/ - python tools/background.py attractor # just one - python tools/background.py flow out.png # explicit destination - python tools/background.py forest img/bg/forest-2.png 21 # pick a seed - -All procedural, no source photos. Each is Bayer ordered-dithered to a few -grey levels so it matches the terminal aesthetic and stays small. Drop a new -PNG into img/bg/ and the site picks it up automatically -- inc/header.php -enumerates that directory, so nothing else needs editing. -""" -import numpy as np -from PIL import Image -import sys, os - -W, H = 1400, 875 -OUT = 'img/bg' - - -# -- helpers ---------------------------------------------------------- -def vnoise(res, seed, w=W, h=H): - r = np.random.default_rng(seed).random((res + 1, res + 1)) - im = Image.fromarray((r * 255).astype(np.uint8), 'L').resize((w, h), Image.BICUBIC) - return np.asarray(im, dtype=np.float64) / 255.0 - - -def fbm(octaves=5, base=3, seed=1, w=W, h=H): - o = np.zeros((h, w)); a = 1.0; t = 0.0 - for i in range(octaves): - o += a * vnoise(base * 2 ** i, seed + i * 101, w, h); t += a; a *= 0.5 - return o / t - - -def bayer(n): - m = np.array([[0.0]]); s = 1 - while s < n: - m = np.block([[4 * m, 4 * m + 2], [4 * m + 3, 4 * m + 1]]); s *= 2 - return (m + 0.5) / (s * s) - - -def calm(strength=0.55, width=0.32, shape=None): - """Damp the middle of the frame, where the text column sits.""" - h, w = shape or (H, W) - _, xx = np.mgrid[0:h, 0:w] - return (1 - strength) + strength * np.clip(np.abs(xx / w - 0.5) / width, 0, 1) ** 1.3 - - -def seg(c, x0, y0, x1, y1, val=1.0): - h, w = c.shape - n = int(max(abs(x1 - x0), abs(y1 - y0))) + 1 - xs = np.linspace(x0, x1, n).astype(int); ys = np.linspace(y0, y1, n).astype(int) - ok = (xs >= 0) & (xs < w) & (ys >= 0) & (ys < h) - np.add.at(c, (ys[ok], xs[ok]), val) - - -def dither(v, levels=4, mn=4, lo=6, hi=196, mode='bayer'): - """Quantise to `levels` greys. - - mode 'bayer': ordered dither against an n x n Bayer matrix. The matrix - size caps how many densities are distinguishable -- 4x4 gives 17 steps, - 8x8 gives 65 -- so bigger matrices band far less on smooth gradients at - the cost of a finer, less chunky texture. - - mode 'fs': Floyd-Steinberg error diffusion. No repeating pattern at all, - so gradients are smooth and organic, but it loses the woven/print look. - """ - v = np.clip(v, 0, 1) - h, w = v.shape - if mode == 'fs': - a = v.copy() * (levels - 1) - for yy in range(h): - for xx in range(w): - old = a[yy, xx] - new = round(min(max(old, 0), levels - 1)) - err = old - new - a[yy, xx] = new - if xx + 1 < w: a[yy, xx + 1] += err * 7 / 16 - if yy + 1 < h: - if xx: a[yy + 1, xx - 1] += err * 3 / 16 - a[yy + 1, xx] += err * 5 / 16 - if xx + 1 < w: a[yy + 1, xx + 1] += err * 1 / 16 - out = np.clip(a, 0, levels - 1) / (levels - 1) - else: - t = bayer(mn); th = np.tile(t, (h // mn + 1, w // mn + 1))[:h, :w] - s = v * (levels - 1); low = np.floor(s) - out = np.clip(low + (s - low > th), 0, levels - 1) / (levels - 1) - g = (lo + out * (hi - lo)).astype(np.uint8) - return Image.fromarray(g, 'L').convert('P', palette=Image.ADAPTIVE, colors=levels) - - -# -- styles ----------------------------------------------------------- -def flow(seed=29): - """Particles advected through a noise vector field; the bright filaments - are where the field converges and thousands of paths pile up.""" - rng = np.random.default_rng(seed) - ang = fbm(4, 3, seed) * np.pi * 3.0 - c = np.zeros((H, W)); n = 22000 - px = rng.random(n) * W; py = rng.random(n) * H - for _ in range(240): - a = ang[np.clip(py.astype(int), 0, H - 1), np.clip(px.astype(int), 0, W - 1)] - px += np.cos(a) * 1.3; py += np.sin(a) * 1.3 - ok = (px >= 0) & (px < W) & (py >= 0) & (py < H) - np.add.at(c, (py[ok].astype(int), px[ok].astype(int)), 1.0) - return np.clip(c / np.percentile(c, 99.5), 0, 1) ** 0.9 * calm() * 0.92, {} - - -def attractor(a=-1.7, b=1.8, cc=-1.9, d=-0.4, seed=3): - """Clifford attractor: x' = sin(a*y) + c*cos(a*x). The bright edges are - density caustics where orbits crowd together.""" - rng = np.random.default_rng(seed) - n = 260000; x = rng.uniform(-2, 2, n); y = rng.uniform(-2, 2, n) - c = np.zeros((H, W)) - for i in range(70): - x, y = np.sin(a * y) + cc * np.cos(a * x), np.sin(b * x) + d * np.cos(b * y) - if i > 8: - px = ((x + 2.6) / 5.2 * W).astype(int) - py = ((y + 2.6) / 5.2 * H).astype(int) - ok = (px >= 0) & (px < W) & (py >= 0) & (py < H) - np.add.at(c, (py[ok], px[ok]), 1.0) - return np.clip(c / np.percentile(c, 99.6), 0, 1) ** 0.75 * 0.95, {} - - -def truchet(cell=46, seed=5): - """Quarter-arc maze tiles. Regular and quiet; genuinely tileable.""" - c = np.zeros((H, W)); rng = np.random.default_rng(seed) - yy, xx = np.mgrid[0:H, 0:W] - for gy in range(0, H, cell): - for gx in range(0, W, cell): - sl = (slice(gy, min(gy + cell, H)), slice(gx, min(gx + cell, W))) - ly = yy[sl] - gy; lx = xx[sl] - gx - if rng.random() < 0.5: - d1 = np.hypot(lx, ly); d2 = np.hypot(lx - cell, ly - cell) - else: - d1 = np.hypot(lx - cell, ly); d2 = np.hypot(lx, ly - cell) - r = cell / 2.0 - c[sl] = np.maximum(np.clip(1 - np.abs(d1 - r) / 2.0, 0, 1), - np.clip(1 - np.abs(d2 - r) / 2.0, 0, 1)) - return c * 0.78, {'levels': 3} - - -def circuit(seed=13, g=16, traces=150): - """PCB-style routed traces at 45/90 degrees, with vias.""" - c = np.zeros((H, W)); rng = np.random.default_rng(seed) - dirs = [(g, 0), (-g, 0), (0, g), (0, -g), (g, g), (-g, g), (g, -g), (-g, -g)] - yy, xx = np.mgrid[0:H, 0:W] - for _ in range(traces): - x, y = int(rng.integers(0, W // g)) * g, int(rng.integers(0, H // g)) * g - dx, dy = dirs[int(rng.integers(len(dirs)))] - for _ in range(int(rng.integers(8, 30))): - nx, ny = x + dx, y + dy - if not (0 <= nx < W and 0 <= ny < H): - break - seg(c, x, y, nx, ny, 1.0); x, y = nx, ny - if rng.random() < 0.28: - dx, dy = dirs[int(rng.integers(len(dirs)))] - if rng.random() < 0.45: - c += np.clip(1 - np.abs(np.hypot(xx - x, yy - y) - 3.5) / 1.6, 0, 1) * 0.9 - return np.clip(c, 0, 1) * calm(0.5) * 0.72, {'levels': 3} - - -def reaction(seed=9, steps=3800): - """Gray-Scott reaction-diffusion. Striking but dense, so it is knocked - well back in tone to stay readable behind text.""" - gw, gh = 450, 282 - U = np.ones((gh, gw)); V = np.zeros((gh, gw)) - rng = np.random.default_rng(seed) - for _ in range(26): - sx, sy = int(rng.integers(12, gw - 12)), int(rng.integers(12, gh - 12)) - U[sy - 5:sy + 5, sx - 5:sx + 5] = 0.5 - V[sy - 5:sy + 5, sx - 5:sx + 5] = 0.25 - Du, Dv, F, k = 0.16, 0.08, 0.0545, 0.062 - - def lap(Z): - return (np.roll(Z, 1, 0) + np.roll(Z, -1, 0) - + np.roll(Z, 1, 1) + np.roll(Z, -1, 1) - 4 * Z) - - for _ in range(steps): - uvv = U * V * V - U += Du * lap(U) - uvv + F * (1 - U) - V += Dv * lap(V) + uvv - (F + k) * V - v = np.clip(V / max(V.max(), 1e-6), 0, 1) - v = np.asarray(Image.fromarray((v * 255).astype(np.uint8), 'L') - .resize((W, H), Image.BICUBIC)) / 255.0 - return v ** 1.1 * calm(0.45) * 0.42, {} - - -def ridge(seed=900, lines=40): - """Stacked ridgelines, Joy Division style.""" - c = np.zeros((H, W)) - for i in range(lines): - base = H * 0.10 + i * (H * 0.84 / lines) - prof = fbm(5, 4, seed + i * 7, w=W, h=8)[0] - amp = (H * 0.84 / lines) * 11.0 * (0.25 + 0.75 * np.exp(-((i / lines - 0.5) ** 2) / 0.055)) - ys = base - (prof - prof.mean()) * amp - for x in range(W - 1): - seg(c, x, ys[x], x + 1, ys[x + 1], 1.0) - return np.clip(c, 0, 1) * 0.85, {'levels': 3} - - -def topo(seed=11, bands=16): - """Iso-contour map of a fractal-noise height field.""" - h = fbm(6, 3, seed) - d = np.abs((h * bands) % 1.0 - 0.5) - return (np.clip(1 - d * 7.0, 0, 1) ** 2.2 * 0.7 + h * 0.12) * calm(0.4), {} - - -def _pine(c, cx, base, h, w, val): - """One tiered conifer, drawn as rows of spans so the edges stay blocky.""" - fh, fw = c.shape - top = int(base - h) - rows = np.arange(max(top, 0), min(int(base), fh)) - if rows.size == 0: - return - t = (rows - top) / max(h, 1.0) - tier = (t * 3.0) % 1.0 # three stacked skirts - hw = w * (0.10 + 0.90 * t) * (0.55 + 0.45 * tier) - for r, half in zip(rows, hw): - x0 = max(int(cx - half), 0); x1 = min(int(cx + half) + 1, fw) - if x1 > x0: - c[r, x0:x1] = val - - -def forest(seed=4, layers=4): - """1-bit pixel-art conifer forest. - - Composition follows the knitwear reference: dark starry sky across the - top (where the text column sits), a dithered mist band at the horizon, - then receding layers of pines. Trees are drawn bright against the dark - sky -- each gets a dark outline pass first, so overlapping trees stay - separate instead of merging into one white mass. Rendered small and - upscaled by CSS with image-rendering: pixelated, so it reads as sprite - art rather than a photo. - """ - fw, fh = 760, 475 - rng = np.random.default_rng(seed) - v = np.zeros((fh, fw)) - y = np.arange(fh)[:, None] / fh - ones = np.ones((1, fw)) - horizon = 0.66 - - # stars, denser toward the top of the sky - n = 440 - sx = rng.integers(0, fw, n) - sy = (rng.random(n) ** 2.3 * fh * horizon).astype(int) - v[sy, sx] = 1.0 - - # mist: a narrow glow hugging the horizon, not a wash over the whole - # middle. The ordered dither turns this band into the scattered - # transition that the knitwear reference has. - v = np.maximum(v, np.exp(-((y - horizon) ** 2) / 0.0040) * 0.26 * ones) - - # receding tree layers, far to near. Each tree gets a slightly larger - # dark pass first so overlapping trees stay separate; the outline hugs - # the shape rather than overshooting it. - for d in range(layers): - f = d / max(layers - 1, 1) - base = fh * (horizon + 0.02 + 0.34 * f ** 1.2) - count = int(120 * (1 - 0.80 * f)) + 8 - val = 0.38 + 0.36 * f - for _ in range(count): - cx = rng.random() * (fw + 140) - 70 - hgt = (20 + 74 * f ** 1.3) * (0.72 + 0.56 * rng.random()) - wid = hgt * (0.27 + 0.11 * rng.random()) - jitter = (rng.random() - 0.5) * 12 * (0.3 + f) - _pine(v, cx, base + jitter, hgt * 1.04 + 2, wid * 1.16 + 1.2, 0.0) - _pine(v, cx, base + jitter, hgt, wid, val) - - # a little ground haze at the very bottom for the nearest trees to sit on - v = np.maximum(v, np.clip((y - 0.96) / 0.04, 0, 1) * 0.30 * ones) - return v, {'levels': 2, 'mn': 8, 'hi': 205} - - -STYLES = {'flow': flow, 'attractor': attractor, 'truchet': truchet, - 'circuit': circuit, 'reaction': reaction, 'ridge': ridge, - 'topo': topo, 'forest': forest} - - -def render(name, dest=None, seed=None): - v, opts = STYLES[name](seed=seed) if seed is not None else STYLES[name]() - img = dither(v, **{**{'levels': 4, 'mn': 4}, **opts}) - dest = dest or os.path.join(OUT, name + '.png') - os.makedirs(os.path.dirname(dest) or '.', exist_ok=True) - img.save(dest, optimize=True) - a = np.asarray(img.convert('L')) - print(" %-10s -> %-22s %3dKB mean=%.0f" - % (name, dest, os.path.getsize(dest) // 1024, a.mean())) - - -if __name__ == '__main__': - what = sys.argv[1] if len(sys.argv) > 1 else 'all' - if what == 'all': - for n in STYLES: - render(n) - else: - dest = sys.argv[2] if len(sys.argv) > 2 else None - seed = int(sys.argv[3]) if len(sys.argv) > 3 else None - render(what, dest, seed)