Cinco bocetos de monigotes de un trazo en zigzag, en piña de colores

docs/bocetos/boceto-1..5.svg y su generador tools/bocetos.py: contenedor,
la piña sale a la calle, el portal, calle en diagonal y ausencias.
This commit is contained in:
hacklab 2026-10-13 13:37:34 +02:00
parent 987893d33c
commit efedefe535
7 changed files with 190 additions and 0 deletions

View file

@ -145,6 +145,30 @@ Una calle corta vista en diagonal, con el punto de fuga a la derecha. Solo se ve
python3 tools/calle.py docs/calle.svg
```
### Bocetos nuevos (a la manera de Suso33)
Cinco bocetos para sustituir el dibujo de la portada. Cada monigote es un solo trazo de spray en zigzag: una espiral para la cabeza y un cuerpo alto que se afila hacia los pies, como en las *Ausencias* y en el contenedor de Suso33. Van en piña: el centro en colores de fuego (blanco, amarillo, naranja, rojo) y alrededor fríos (morados, azules, blanco y negro). Se generan con `python3 tools/bocetos.py docs/bocetos`.
1. **Contenedor**: la piña de fuego sobre una pared gris.
![Boceto 1](docs/bocetos/boceto-1.svg)
2. **La piña sale a la calle**: la masa avanza hacia la derecha y se va soltando.
![Boceto 2](docs/bocetos/boceto-2.svg)
3. **El portal**: del hueco sale una fila de gente y el fuego va delante.
![Boceto 3](docs/bocetos/boceto-3.svg)
4. **Calle en diagonal**: la piña baja por la calle hacia quien mira.
![Boceto 4](docs/bocetos/boceto-4.svg)
5. **Ausencias**: todos en negro y gris, y solo el corazón de la piña en fuego.
![Boceto 5](docs/bocetos/boceto-5.svg)
## 7. Maqueta
`mockup/index.html` es HTML y CSS, sin JavaScript. Se abre directamente en el navegador. Sigue el estilo de acampada26s.net (blanco y negro, bordes de 3 px, sombra de color): firma en la cabecera, portada con el lema y el dibujo debajo, y debajo el deck.Capturas al principio de este README.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 58 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 141 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 35 KiB

136
tools/bocetos.py Normal file
View file

@ -0,0 +1,136 @@
# Genera docs/bocetos/boceto-1..5.svg. Monigotes de un solo trazo de spray en zigzag,
# altos y juntos, en piña: el centro en colores de fuego y alrededor fríos
# (a la manera del contenedor y las «Ausencias» de Suso33).
import sys, random, math, os
OUT = sys.argv[1]
W, H = 1600, 700
FUEGO = ["#fffbe6", "#ffe066", "#ffb627", "#ff7b1c", "#f04e23", "#c8102e"] # del centro hacia fuera
FRIO = ["#5b2a86", "#8e44c9", "#3b5bdb", "#6c7ae0", "#e9e6f5", "#111", "#b07cd8"]
DEFS = '''<defs>
<filter id="spray" x="-30%" y="-10%" width="160%" height="120%"><feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="2" seed="3" result="t"/><feDisplacementMap in="SourceGraphic" in2="t" scale="2" result="d"/><feGaussianBlur in="d" stdDeviation=".7" result="b"/><feGaussianBlur in="SourceGraphic" stdDeviation="4" result="n"/><feComponentTransfer in="n" result="n2"><feFuncA type="linear" slope=".35"/></feComponentTransfer><feMerge><feMergeNode in="n2"/><feMergeNode in="b"/></feMerge></filter>
<filter id="brocha" x="-10%" y="-10%" width="120%" height="120%"><feTurbulence type="fractalNoise" baseFrequency="0.02 0.15" numOctaves="3" seed="4" result="t"/><feDisplacementMap in="SourceGraphic" in2="t" scale="6"/></filter>
<filter id="halo" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="40"/></filter>
<filter id="sombra"><feGaussianBlur stdDeviation="3"/></filter>
</defs>'''
def fuego(d): # d = 0 en el centro de la piña, 1 en el borde
i = min(len(FUEGO) - 1, int(max(0, d + random.uniform(-.12, .12)) * len(FUEGO)))
return FUEGO[i]
def monigote(o, x, y, h, col, lean=0.0, paso=False, brazo=False, sombra=False):
"""Un trazo: cabeza en zigzag apretado y cuerpo en zigzag alto. (x, y) = pies."""
sw = max(1.1, h * .011)
dy = sw * 2.0 # renglones del zigzag
if sombra:
o.append(f'<path d="M{x-h*.05:.1f},{y:.1f} L{x+h*.6:.1f},{y+h*.14:.1f} L{x+h*.64:.1f},{y+h*.11:.1f} L{x+h*.05:.1f},{y-2:.1f}Z" fill="#111" opacity=".14" filter="url(#sombra)"/>')
top = y - h
pts = []
yy = top
i = 0
while yy <= y:
t = (yy - top) / h
cx = x + lean * h * (1 - t)
if t < .13: # cabeza
w = h * .05 * math.sin(math.pi * t / .13) ** .6 + sw * .3
elif t < .16: # cuello
w = h * .008
else: # cuerpo: hombros y se afila hacia los pies
u = (t - .16) / .84
w = h * (.062 * (1 - u) ** .8 + .014)
if paso and u > .5: # zancada: el zigzag se abre en dos
k = (u - .5) / .5
cx += (-1 if (i // 2) % 2 else 1) * h * .035 * k
w *= .6
s = -1 if i % 2 else 1
pts.append((cx + s * w + random.uniform(-.3, .3) * sw, yy))
yy += dy * random.uniform(.85, 1.15) * (.55 if t < .13 else 1)
i += 1
d = "M" + " L".join(f"{px:.1f},{py:.1f}" for px, py in pts)
o.append(f'<path d="{d}" fill="none" stroke="{col}" stroke-width="{sw:.1f}" stroke-linejoin="round" stroke-linecap="round" filter="url(#spray)"/>')
if brazo: # brazo en alto
bx, by = x + lean * h * .84 + h * .05, top + h * .2
p, n = [], 10
for j in range(n):
t = j / (n - 1)
p.append((bx + t * h * .06 + (-1 if j % 2 else 1) * h * .012, by - t * h * .30))
d = "M" + " L".join(f"{px:.1f},{py:.1f}" for px, py in p)
o.append(f'<path d="{d}" fill="none" stroke="{col}" stroke-width="{sw:.1f}" stroke-linecap="round" filter="url(#spray)"/>')
def portal(o, x, y, w, h, fondo="#111"):
o.append(f'<g filter="url(#brocha)"><rect x="{x}" y="{y-h}" width="{w}" height="{h}" fill="{fondo}" opacity=".9"/>'
f'<path d="M{x-8},{y} L{x-8},{y-h-8} L{x+w+8},{y-h-8} L{x+w+8},{y}" fill="none" stroke="#111" stroke-width="7" stroke-linecap="round"/></g>')
def svg(cuerpo, fondo="#fff"):
return (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" width="{W}" height="{H}">{DEFS}'
f'<rect width="{W}" height="{H}" fill="{fondo}"/>' + "".join(cuerpo) + "</svg>\n")
def fila(o, xs, y0, h0, centro, radio, frio=FRIO, lean=0.0, jit=12, hvar=.15, paso=.2, brazo=.08):
"""Una piña: monigotes muy juntos; los cercanos al centro, en fuego."""
for x in xs:
d = abs(x - centro) / radio
col = fuego(d) if d < 1 else random.choice(frio)
h = h0 * random.uniform(1 - hvar, 1 + hvar) * (1.05 if d < 1 else 1)
monigote(o, x, y0 + random.uniform(-jit, jit), h, col, lean, random.random() < paso, random.random() < brazo)
# 1 · Contenedor: piña de fuego en una pared gris, como el contenedor
def b1():
random.seed(1); o = []
o.append('<circle cx="800" cy="400" r="190" fill="#ffb627" opacity=".35" filter="url(#halo)"/>')
xs = sorted(random.uniform(90, 1510) for _ in range(70))
fila(o, xs[::2], 610, 470, 800, 260)
fila(o, xs[1::2], 650, 500, 800, 230)
return o, "#9aa0a8"
# 2 · Piña que sale a la calle: en blanco, la masa avanza hacia la derecha
def b2():
random.seed(2); o = []
o.append('<g filter="url(#brocha)"><path d="M40,640 L1560,632" stroke="#111" stroke-width="7" stroke-linecap="round"/></g>')
xs = sorted(random.gauss(820, 300) for _ in range(55))
xs = [x for x in xs if 80 < x < 1520]
fila(o, xs, 630, 400, 1000, 230, lean=.12, paso=.45, brazo=.12)
return o, "#fff"
# 3 · El portal: del hueco sale la gente y el fuego va delante
def b3():
random.seed(3); o = []
o.append('<g filter="url(#brocha)"><path d="M40,610 L1560,602" stroke="#111" stroke-width="7" stroke-linecap="round"/></g>')
portal(o, 150, 610, 190, 380)
o.append('<circle cx="330" cy="430" r="130" fill="#ffb627" opacity=".35" filter="url(#halo)"/>')
for i in range(30):
t = i / 29
x = 250 + t * 1250 + random.uniform(-25, 25)
d = abs(t - .32) / .25
col = fuego(d) if d < 1 else random.choice(FRIO)
monigote(o, x, 610 + t * 70 + random.uniform(-8, 8), 300 + t * 120, col, .1, random.random() < .45, random.random() < .12)
return o, "#fff"
# 4 · Calle en diagonal: la piña baja por la calle hacia quien mira
def b4():
random.seed(4); o = []
o.append('<g filter="url(#brocha)"><path d="M40,690 L1180,300" stroke="#111" stroke-width="9" stroke-linecap="round"/>'
'<path d="M330,700 L1260,305" stroke="#f04e23" stroke-width="3" stroke-linecap="round" opacity=".9"/></g>')
ps = []
for _ in range(1500):
t = random.random()
x = 1240 - t * 700 + random.gauss(0, 40 + t * 150)
y = 300 + t * 390 + random.gauss(0, 10)
if 290 < y < 695 and all(math.hypot(x - a, (y - b) * 3) > 22 + t * 50 for a, b, _ in ps):
ps.append((x, y, t))
for x, y, t in sorted(ps, key=lambda p: p[1]):
d = math.hypot(x - 820, (y - 520) * 1.5) / 230
col = fuego(d) if d < 1 else random.choice(FRIO)
monigote(o, x, y, 60 + t * 230, col, -.1, random.random() < .35, random.random() < .1)
return o, "#fff"
# 5 · Ausencias: todos en negro y gris, y solo el corazón de la piña en fuego
def b5():
random.seed(5); o = []
xs = sorted(random.uniform(100, 1500) for _ in range(42))
fila(o, xs, 650, 430, 900, 120, frio=["#111", "#111", "#333", "#555", "#888"], lean=.06, hvar=.12, paso=.3, brazo=.1)
return o, "#fff"
os.makedirs(OUT, exist_ok=True)
for n, f in enumerate([b1, b2, b3, b4, b5], 1):
cuerpo, fondo = f()
open(f"{OUT}/boceto-{n}.svg", "w").write(svg(cuerpo, fondo))