Logotipo en Grindy Brush (negro), naranja más rojizo y dibujo nuevo

- logotipo: Grindy Brush todo en negro (o blanco en negativo), convertido a
  trazados; fuera las alternativas y las versiones con STREET en naranja.
  README: la fuente es demo de uso personal, hay que comprar la licencia
- naranja #ff7a1a -> #f04e23, solo en detalles
- docs/calle.svg: calle corta en diagonal, solo planta baja que se desvanece;
  de cada portal sale gente que avanza hacia la calzada (presencias con alguna
  zancada o puño en alto)
This commit is contained in:
hacklab 2026-10-13 13:28:18 +02:00
parent 69d84e5a3d
commit 987893d33c
18 changed files with 341 additions and 1296 deletions

View file

@ -1,8 +1,8 @@
# TAKE THE STREET
![Take the Street](docs/marca/logotipo-naranja.svg)
![Take the Street](docs/marca/logotipo.svg)
![Una multitud en la calle forma una persona grande con el puño en alto](docs/calle.svg)
![Gente que sale de los portales y avanza hacia la calle](docs/calle.svg)
**takethestreet.net / takethestreet.org**: red internacional de las acampadas por la vivienda que nacieron en la Puerta del Sol el 26 de septiembre de 2026 (26S). La vivienda es un derecho, no un negocio.
@ -118,30 +118,28 @@ Tres piezas, cada una con su uso:
![Hoja de marca](docs/marca/hoja.png)
- **Símbolo** (`docs/marca/simbolo*.svg`): una calle en perspectiva con una cabeza encima. La calle es el cuerpo de una persona: la gente es la calle. Repite la forma de las presencias del dibujo (cabeza y cuerpo afilado). Sirve para el favicon, los avatares de redes y las pegatinas, y se lee hasta a 16 px. Tres versiones: positivo, negativo y sobre naranja.
- **Logotipo** (`docs/marca/logotipo*.svg`): «TAKE THE STREET» en **Dracutaz**, de pincel, como la calle pintada. Va convertido a trazados, así que no hace falta la fuente. Hay versión en una línea y en dos, en negro, con STREET en naranja y en negativo.
- **Símbolo** (`docs/marca/simbolo*.svg`): una calle en perspectiva con una cabeza naranja encima. La calle es el cuerpo de una persona: la gente es la calle. Repite la forma de las presencias del dibujo (cabeza y cuerpo afilado). Sirve para el favicon, los avatares de redes y las pegatinas, y se lee hasta a 16 px. Tres versiones: positivo, negativo y sobre naranja.
- **Logotipo** (`docs/marca/logotipo*.svg`): «TAKE THE STREET» en **Grindy Brush**, todo en negro (o en blanco sobre negro). Va convertido a trazados, así que no hace falta la fuente. Hay versión en una línea y en dos.
- **Firma**: símbolo + logotipo. Es lo que va arriba a la izquierda de la web.
- **Ilustración** (`docs/calle.svg`): la imagen principal de la portada, nunca en la cabecera. Ver el apartado 6.
Color: naranja `#ff7a1a`, negro `#111` y blanco.
Color: negro `#111` y blanco. El naranja `#f04e23` (más rojizo que el de antes) va solo en detalles: la cabeza del símbolo, alguna línea del dibujo, la sombra de los botones y el directo.
Tipografías:
- logotipo: **Dracutaz** (Dhabee Studio, freeware, uso comercial permitido), solo convertida a trazados
- logotipo: **Grindy Brush** (Sronstudio), solo convertida a trazados
- titulares, botones y etiquetas: **League Gothic**, OFL (`mockup/fonts/`)
- texto: la fuente del sistema
### Alternativas para el logotipo
### Licencia del logotipo
![Alternativas de pincel](docs/marca/alternativas.png)
Grindy Brush, Eagle Horizon, Brushed, Higher Jump y Future Outrun (1001fonts) son **solo para uso personal**. Para usarlas en el logotipo habría que comprar la licencia o pedir permiso a sus autores. Dracutaz no tiene ese problema.
Grindy Brush es una versión demo **solo para uso personal**. Antes de usar el logotipo en público (web, redes, carteles, camisetas) hay que comprar la licencia en https://sronstudio.com/product/grindy-brush-font/ o pedir permiso a su autor (sronstudio@gmail.com). Su nota avisa de que el uso comercial sin licencia se cobra a diez veces el precio de la licencia corporativa.
## 6. Dibujo
![Una multitud en la calle forma una persona grande con el puño en alto](docs/calle.svg)
![Gente que sale de los portales y avanza hacia la calle](docs/calle.svg)
Una calle pintada a brochazos que se disuelve hacia arriba y hacia los lados, sin edificios. Encima, cientos de «presencias» (cabeza, cuerpo afilado y una sombra en diagonal), a la manera del mural *Presencias/Ausencias* de Suso33 en Tetuán: juntas forman una persona grande con el puño en alto. Unas pocas van en naranja. Se genera con `tools/calle.py`, sin dependencias:
Una calle corta vista en diagonal, con el punto de fuga a la derecha. Solo se ve la planta baja: la fachada se desvanece hacia arriba, sin pisos, y la calle también se pierde por la derecha. De cada portal sale gente que avanza hacia la calzada y hacia quien mira. Son «presencias» abstractas a la manera de Suso33 (el mural *Presencias/Ausencias* de Tetuán): una mancha afilada con la cabeza, y algunas con zancada o con el puño en alto. Todo en negro; el naranja va solo en líneas de detalle (el bordillo, algún dintel, alguna estela). Se genera con `tools/calle.py`, sin dependencias:
```
python3 tools/calle.py docs/calle.svg
@ -149,6 +147,6 @@ python3 tools/calle.py docs/calle.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, y debajo el deck.Capturas al principio de este README.
`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.
Código AGPL-3.0. Textos y dibujo CC BY-SA 4.0. Tipografías: League Gothic (OFL); logotipo dibujado con Dracutaz (freeware).
Código AGPL-3.0. Textos y dibujo CC BY-SA 4.0. Tipografías: League Gothic (OFL); logotipo dibujado con Grindy Brush (demo de uso personal, ver «Licencia del logotipo»).

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before After
Before After

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Before After
Before After

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 185 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 177 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Before After
Before After

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 177 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Before After
Before After

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 177 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Before After
Before After

View file

@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100"><rect width="100" height="100" fill="#ff7a1a"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100"><rect width="100" height="100" fill="#f04e23"/>
<circle cx="50" cy="19" r="11.5" fill="#fff"/>
<path d="M46.5,37 L53.5,37 L84,92 L16,92 Z" fill="#111"/>
<path d="M49.4,46 h1.2 l.3,7 h-1.8 Z M48.9,60 h2.2 l.5,10 h-3.2 Z M48.2,77 h3.6 l.7,15 h-5 Z" fill="#ff7a1a"/>
<path d="M49.4,46 h1.2 l.3,7 h-1.8 Z M48.9,60 h2.2 l.5,10 h-3.2 Z M48.2,77 h3.6 l.7,15 h-5 Z" fill="#f04e23"/>
</svg>

Before

Width:  |  Height:  |  Size: 358 B

After

Width:  |  Height:  |  Size: 358 B

Before After
Before After

View file

@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100"><rect width="100" height="100" fill="#111"/>
<circle cx="50" cy="19" r="11.5" fill="#ff7a1a"/>
<circle cx="50" cy="19" r="11.5" fill="#f04e23"/>
<path d="M46.5,37 L53.5,37 L84,92 L16,92 Z" fill="#fff"/>
<path d="M49.4,46 h1.2 l.3,7 h-1.8 Z M48.9,60 h2.2 l.5,10 h-3.2 Z M48.2,77 h3.6 l.7,15 h-5 Z" fill="#111"/>
</svg>

Before

Width:  |  Height:  |  Size: 355 B

After

Width:  |  Height:  |  Size: 355 B

Before After
Before After

View file

@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
<circle cx="50" cy="19" r="11.5" fill="#ff7a1a"/>
<circle cx="50" cy="19" r="11.5" fill="#f04e23"/>
<path d="M46.5,37 L53.5,37 L84,92 L16,92 Z" fill="#111"/>
<path d="M49.4,46 h1.2 l.3,7 h-1.8 Z M48.9,60 h2.2 l.5,10 h-3.2 Z M48.2,77 h3.6 l.7,15 h-5 Z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 311 B

After

Width:  |  Height:  |  Size: 311 B

Before After
Before After

View file

@ -3,7 +3,7 @@
:root {
--bg: #fff;
--fg: #111;
--neon: #ff7a1a;
--neon: #f04e23;
--bd: 3px solid var(--fg);
--bt: "League Gothic", "Arial Narrow", Impact, sans-serif;
--txt: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
@ -29,9 +29,9 @@ a { color: inherit; }
.idiomas a[aria-current] { background: var(--fg); color: var(--bg); box-shadow: 3px 3px 0 var(--neon); }
.portada { padding: 36px 0 0; border-bottom: var(--bd); text-align: center; }
.calle { display: block; width: 100%; max-width: 1100px; height: auto; margin: -10px auto 0; }
.calle { display: block; width: 100%; max-width: 1200px; height: auto; margin: 10px auto 0; }
.lema { margin: 0; font: 400 clamp(40px, 6vw, 80px)/.95 var(--bt); text-transform: uppercase; }
.sub { margin: 10px 0 0; font: 20px var(--bt); letter-spacing: .12em; text-transform: uppercase; color: var(--neon); }
.sub { margin: 10px 0 0; font: 20px var(--bt); letter-spacing: .12em; text-transform: uppercase; }
.tags { display: flex; gap: 8px; flex-wrap: wrap; padding: 10px 20px; border-bottom: var(--bd); font: 18px var(--bt); letter-spacing: .03em; }
.tags span { border: 2px dashed var(--fg); padding: 1px 8px; }

View file

@ -9,14 +9,14 @@
</head>
<body>
<header class="cab">
<a class="firma" href="#"><img class="simbolo" src="../docs/marca/simbolo.svg" alt=""><img class="logotipo" src="../docs/marca/logotipo-naranja.svg" alt="Take the Street"></a>
<a class="firma" href="#"><img class="simbolo" src="../docs/marca/simbolo.svg" alt=""><img class="logotipo" src="../docs/marca/logotipo.svg" alt="Take the Street"></a>
<nav class="idiomas"><a href="#" aria-current="page">ES</a><a href="#">EN</a><a href="#">PT</a><a href="#">IT</a><a href="#">FR</a></nav>
</header>
<section class="portada">
<p class="lema">La vivienda es un derecho,<br>no un negocio</p>
<p class="sub">Red internacional de acampadas · 26S</p>
<img class="calle" src="../docs/calle.svg" alt="Una multitud en la calle forma una persona grande con el puño en alto">
<img class="calle" src="../docs/calle.svg" alt="Gente que sale de los portales y avanza hacia la calle">
</section>
<div class="tags"><span>#takethestreet</span><span>#26s</span><span>#acampadaporto</span><span>#generazionesenzacasa</span><span>#11N</span><span>#viviendaya</span></div>

View file

@ -1,108 +1,111 @@
# Genera docs/calle.svg: calle pintada que se disuelve, con «presencias» que,
# juntas, forman una figura grande con el puño en alto (a la manera de Suso33).
# Genera docs/calle.svg: una calle corta vista en diagonal. De cada portal sale
# gente que avanza hacia la calzada (presencias abstractas, a la manera de Suso33).
# Arriba no hay pisos: la fachada se desvanece, y la calle también hacia la derecha.
import sys, random, math
random.seed(26)
OUT = sys.argv[1]
W, H = 1600, 900
VX, VY = 800, 70 # punto de fuga
INK, ORA = "#111", "#ff7a1a"
W, H = 1600, 700
VX, VY, F, EYE = 1150, 210, 520, 1.6 # punto de fuga a la derecha, focal, ojo a 1,6 m
FX, KX = -5.0, -2.6 # fachada y bordillo (m)
INK, ORA = "#111", "#f04e23"
o = []
add = o.append
def esc(y): # tamaño según la distancia
return max(0.0, (y - VY) / (H - VY))
def P(X, Yh, Z): # Yh = altura sobre el suelo
return VX + X * F / Z, VY + (EYE - Yh) * F / Z
def pts(*ps):
return " ".join(f"{x:.1f},{y:.1f}" for x, y in ps)
def brush(a, b, w, n=4, col=INK, op=.9):
for _ in range(n):
j = lambda: random.uniform(-w, w) * .35
add(f'<line x1="{a[0]+j():.1f}" y1="{a[1]+j():.1f}" x2="{b[0]+j():.1f}" y2="{b[1]+j():.1f}" stroke="{col}" '
f'stroke-width="{random.uniform(w*.3, w*.65):.1f}" stroke-linecap="round" opacity="{random.uniform(op*.55, op):.2f}"/>')
def edge(xb, y): # x de una recta que va del punto de fuga a (xb, H)
return VX + (xb - VX) * esc(y)
Y0 = 210 # lo de arriba queda en blanco: se recorta
add(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 {Y0} {W} {H - Y0}" width="{W}" height="{H - Y0}">')
HV = 570 # se recorta el suelo vacío de abajo
add(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {HV}" width="{W}" height="{HV}">')
add('''<defs>
<filter id="brocha" x="-10%" y="-10%" width="120%" height="120%"><feTurbulence type="fractalNoise" baseFrequency="0.015 0.15" numOctaves="3" seed="4" result="t"/><feDisplacementMap in="SourceGraphic" in2="t" scale="6"/></filter>
<filter id="pintura" x="-20%" y="-20%" width="140%" height="140%"><feTurbulence type="fractalNoise" baseFrequency="0.6" numOctaves="2" seed="9" result="t"/><feDisplacementMap in="SourceGraphic" in2="t" scale="2.2"/></filter>
<filter id="sombra" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="1.2"/></filter>
<filter id="grano"><feTurbulence type="fractalNoise" baseFrequency="0.012 0.05" numOctaves="4" seed="2"/><feColorMatrix values="0 0 0 0 0.07 0 0 0 0 0.07 0 0 0 0 0.07 0 0 0 -2.2 1.25"/></filter>
<linearGradient id="fv" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#000"/><stop offset=".3" stop-color="#000"/><stop offset=".62" stop-color="#fff"/></linearGradient>
<radialGradient id="fh" cx=".5" cy=".95" r=".75"><stop offset=".55" stop-color="#fff"/><stop offset="1" stop-color="#000"/></radialGradient>
<linearGradient id="gl"><stop offset="0" stop-color="#000"/><stop offset=".12" stop-color="#fff"/><stop offset=".88" stop-color="#fff"/><stop offset="1" stop-color="#000"/></linearGradient>
<mask id="lados" maskUnits="userSpaceOnUse" x="0" y="0" width="1600" height="900"><rect width="1600" height="900" fill="url(#gl)"/></mask>
<mask id="borde" maskUnits="userSpaceOnUse" x="0" y="0" width="1600" height="900"><rect width="1600" height="900" fill="url(#fh)"/></mask>
<mask id="disipa" maskUnits="userSpaceOnUse" x="0" y="0" width="1600" height="900"><g mask="url(#borde)"><rect width="1600" height="900" fill="url(#fv)"/></g></mask>
<filter id="brocha" x="-10%" y="-10%" width="120%" height="120%"><feTurbulence type="fractalNoise" baseFrequency="0.02 0.12" numOctaves="3" seed="4" result="t"/><feDisplacementMap in="SourceGraphic" in2="t" scale="7"/></filter>
<filter id="pintura" x="-20%" y="-20%" width="140%" height="140%"><feTurbulence type="fractalNoise" baseFrequency="0.5" numOctaves="2" seed="9" result="t"/><feDisplacementMap in="SourceGraphic" in2="t" scale="5" result="d"/><feGaussianBlur in="d" stdDeviation=".6"/></filter>
<filter id="sombra" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="2"/></filter>
<linearGradient id="gv" x1="0" y1="0" x2="0" y2="1"><stop offset=".04" stop-color="#000"/><stop offset=".32" stop-color="#fff"/></linearGradient>
<linearGradient id="gh" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#000"/><stop offset=".07" stop-color="#fff"/><stop offset=".6" stop-color="#fff"/><stop offset=".93" stop-color="#000"/></linearGradient>
<linearGradient id="hueco" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#111"/><stop offset="1" stop-color="#111" stop-opacity=".75"/></linearGradient>
<mask id="arriba" maskUnits="userSpaceOnUse" x="0" y="0" width="1600" height="700"><rect width="1600" height="700" fill="url(#gv)"/></mask>
<mask id="derecha" maskUnits="userSpaceOnUse" x="0" y="0" width="1600" height="700"><rect width="1600" height="700" fill="url(#gh)"/></mask>
</defs>''')
# --- calle pintada: todo dentro de la máscara que la disipa ---
add('<g mask="url(#lados)"><g mask="url(#disipa)">')
road = [(VX, VY), (edge(-150, H), H), (edge(1750, H), H)]
add(f'<path d="M{VX},{VY} L-150,{H} L1750,{H}Z" fill="#f1efe9"/>')
def brush(x0, y0, x1, y1, w, n, col=INK, op=.85):
for _ in range(n):
j = lambda: random.uniform(-w, w) * .5
add(f'<line x1="{x0+j():.1f}" y1="{y0+j():.1f}" x2="{x1+j():.1f}" y2="{y1+j():.1f}" '
f'stroke="{col}" stroke-width="{random.uniform(w*.25, w*.6):.1f}" stroke-linecap="round" opacity="{random.uniform(op*.5, op):.2f}"/>')
ZN, ZF = 1.5, 70
# --- calle: suelo, bordillo y carretera; se va a blanco por la derecha ---
add('<g mask="url(#derecha)">')
add('<g filter="url(#brocha)">')
for xb, w in ((-150, 26), (1750, 26)): # bordillos
y0 = VY + 40
brush(edge(xb, y0), y0, xb, H + 30, w, 7)
add('</g></g></g>')
add(f'<polygon points="{pts(P(FX,0,ZN), P(FX,0,ZF), P(9,0,ZF), P(9,0,ZN))}" fill="#f2f0eb"/>')
add(f'<polygon points="{pts(P(FX,0,ZN), P(FX,0,ZF), P(KX,0,ZF), P(KX,0,ZN))}" fill="#e6e3dc"/>') # acera
brush(P(KX, 0, ZN), P(KX, 0, 40), 9, 5) # bordillo
brush(P(KX + .25, 0, ZN), P(KX + .25, 0, 34), 3, 2, col=ORA) # línea naranja de detalle
add('</g></g>')
# --- figura grande: siluetas sencillas en coordenadas de la imagen ---
def cap(px, py, ax, ay, bx, by, r): # cápsula (segmento con radio)
dx, dy = bx - ax, by - ay
t = max(0, min(1, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)))
return math.hypot(px - (ax + t * dx), py - (ay + t * dy)) < r
K, YT = .8, 250 # escala y altura de la figura
def dentro(x, y):
x, y = 800 + (x - 800) / K, 60 + (y - YT) / K
return (math.hypot(x - 772, (y - 205) * .9) < 56 or # cabeza
cap(x, y, 790, 372, 815, 560, 88) or # tronco
cap(x, y, 735, 360, 590, 270, 34) or cap(x, y, 590, 270, 560, 150, 32) or # brazo en alto
math.hypot(x - 556, y - 128) < 46 or # puño
cap(x, y, 880, 360, 960, 470, 32) or cap(x, y, 960, 470, 935, 585, 30) or # brazo bajo
cap(x, y, 770, 590, 700, 735, 44) or cap(x, y, 700, 735, 650, 870, 40) or # pierna
cap(x, y, 860, 590, 940, 720, 44) or cap(x, y, 940, 720, 1010, 860, 40)) # pierna
# --- fachada solo a la altura de los portales: arriba se desvanece ---
add('<g mask="url(#arriba)"><g filter="url(#brocha)">')
add(f'<polygon points="{pts(P(FX,0,ZN), P(FX,3.4,ZN), P(FX,3.4,ZF), P(FX,0,ZF))}" fill="#f7f6f2"/>')
brush(P(FX, 0, ZN), P(FX, 0, ZF), 7, 4) # pie de la fachada
DOORS = [4.0, 7.5, 12.0, 18.5, 28.0]
for zd in DOORS:
d = [P(FX, 0, zd), P(FX, 2.5, zd), P(FX, 2.5, zd + 1.5), P(FX, 0, zd + 1.5)]
add(f'<polygon points="{pts(*d)}" fill="url(#hueco)"/>')
w = 90 / zd
brush(d[0], d[1], w, 3); brush(d[1], d[2], w, 3) # marco
if zd in (4.0, 12.0): # dintel en naranja
brush(P(FX, 2.7, zd - .1), P(FX, 2.7, zd + 1.6), w * .5, 2, col=ORA, op=.8)
add('</g></g>')
# --- presencias ---
def presencia(x, y, s, col):
h = 46 * s # alto de la figura
r = h * random.uniform(.10, .13) # cabeza
lean = random.uniform(-.12, .12) * h
w = h * random.uniform(.13, .19) # hombros
fx = x + lean # pies
# sombra hacia abajo a la derecha
L = h * random.uniform(.9, 1.3)
add(f'<path d="M{fx-w*.25:.1f},{y:.1f} L{fx+L*.85:.1f},{y+L*.42:.1f} L{fx+L*.85+w*.25:.1f},{y+L*.42-w*.2:.1f} L{fx+w*.3:.1f},{y-w*.15:.1f}Z" fill="{INK}" opacity=".22" filter="url(#sombra)"/>')
sx, sy = x, y - h + 2 * r # hombros
split = random.random() < .35 # piernas abiertas al andar
if split:
d = (f'M{sx-w/2:.1f},{sy:.1f} Q{sx-w*.55:.1f},{sy+h*.35:.1f} {fx-w*.32:.1f},{y:.1f} '
f'L{fx-w*.12:.1f},{y:.1f} L{fx:.1f},{y-h*.3:.1f} L{fx+w*.12:.1f},{y:.1f} L{fx+w*.32:.1f},{y:.1f} '
f'Q{sx+w*.55:.1f},{sy+h*.35:.1f} {sx+w/2:.1f},{sy:.1f} Z')
else:
d = (f'M{sx-w/2:.1f},{sy:.1f} Q{sx-w*.45:.1f},{sy+h*.45:.1f} {fx-w*.1:.1f},{y:.1f} '
f'L{fx+w*.1:.1f},{y:.1f} Q{sx+w*.45:.1f},{sy+h*.45:.1f} {sx+w/2:.1f},{sy:.1f} Z')
add(f'<path d="{d}" fill="{col}"/>')
add(f'<ellipse cx="{sx+lean*.1:.1f}" cy="{sy-r*.9:.1f}" rx="{r:.1f}" ry="{r*1.12:.1f}" fill="{col}"/>')
# --- presencias: salen de los portales hacia la calzada y hacia quien mira ---
def presencia(X, Z, paso, brazo):
fx, fy = P(X, 0, Z)
h = 1.75 * F / Z # alto en pantalla
lean = h * .09 # inclinados hacia delante (derecha)
sx, sy = fx + lean, fy - h * .80 # hombros
r = h * .095
w = h * .17
# sombra en el suelo, hacia atrás a la izquierda
add(f'<polygon points="{pts((fx-w*.3,fy),(fx-h*.75,fy-h*.16),(fx-h*.7,fy-h*.2),(fx+w*.3,fy-2))}" fill="{INK}" opacity=".18" filter="url(#sombra)"/>')
if paso: # zancada: tronco y dos piernas de pincel
hip = (fx + lean * .4, fy - h * .45)
add(f'<path d="M{sx-w/2:.1f},{sy:.1f} Q{sx-w*.45:.1f},{sy+h*.2:.1f} {hip[0]-w*.3:.1f},{hip[1]:.1f} '
f'L{hip[0]+w*.3:.1f},{hip[1]:.1f} Q{sx+w*.5:.1f},{sy+h*.2:.1f} {sx+w/2:.1f},{sy:.1f} Z" fill="{INK}"/>')
for fxx, fyy in ((fx - h * .16, fy), (fx + h * .17, fy - h * .03)):
add(f'<path d="M{hip[0]-w*.28:.1f},{hip[1]-2:.1f} Q{(hip[0]+fxx)/2-w*.12:.1f},{(hip[1]+fyy)/2:.1f} {fxx-w*.07:.1f},{fyy:.1f} '
f'L{fxx+w*.09:.1f},{fyy:.1f} Q{(hip[0]+fxx)/2+w*.14:.1f},{(hip[1]+fyy)/2:.1f} {hip[0]+w*.28:.1f},{hip[1]-2:.1f} Z" fill="{INK}"/>')
else: # cuerpo afilado
add(f'<path d="M{sx-w/2:.1f},{sy:.1f} Q{sx-w*.55:.1f},{sy+h*.45:.1f} {fx-w*.22:.1f},{fy:.1f} L{fx+w*.22:.1f},{fy:.1f} '
f'Q{sx+w*.55:.1f},{sy+h*.4:.1f} {sx+w/2:.1f},{sy:.1f} Z" fill="{INK}"/>')
if brazo == "arriba": # brazo en alto
hx, hy = sx + h * .14, sy - h * .30
add(f'<path d="M{sx+w*.15:.1f},{sy+h*.04:.1f} Q{sx+h*.12:.1f},{sy-h*.06:.1f} {hx:.1f},{hy:.1f}" stroke="{INK}" stroke-width="{h*.045:.1f}" stroke-linecap="round" fill="none"/>')
add(f'<circle cx="{hx:.1f}" cy="{hy:.1f}" r="{h*.035:.1f}" fill="{INK}"/>')
elif brazo == "atras": # brazo que se balancea hacia atrás
add(f'<path d="M{sx-w*.2:.1f},{sy+h*.05:.1f} Q{sx-h*.12:.1f},{sy+h*.2:.1f} {sx-h*.2:.1f},{sy+h*.32:.1f}" stroke="{INK}" stroke-width="{h*.04:.1f}" stroke-linecap="round" fill="none"/>')
add(f'<ellipse cx="{sx+lean*.15:.1f}" cy="{sy-r*.75:.1f}" rx="{r:.1f}" ry="{r*1.15:.1f}" fill="{INK}"/>')
if random.random() < .15: # estela de movimiento en naranja
for k in range(2):
y = sy + h * (.15 + .22 * k)
add(f'<line x1="{sx-w*1.6:.1f}" y1="{y:.1f}" x2="{sx-w*1.6-h*.16:.1f}" y2="{y+h*.03:.1f}" stroke="{ORA}" stroke-width="{max(1.2,h*.012):.1f}" stroke-linecap="round" opacity=".9"/>')
pts = []
def libre(x, y, s):
return all(math.hypot(x - a, (y - b) * 1.6) > 17 * max(s, t) for a, b, t in pts)
for _ in range(200000): # la figura grande, densa
x, y = random.uniform(400, 1150), random.uniform(240, 890)
if dentro(x, y):
s = .62 + .38 * esc(y)
if libre(x, y, s * 1.35):
pts.append((x, y, s))
grande = len(pts)
for _ in range(900): # poca gente suelta, por la calle
y = random.uniform(300, 880)
x = random.uniform(edge(-150, y), edge(1750, y))
s = .62 + .38 * esc(y)
if not dentro(x, y) and libre(x, y, s * 5) and random.random() < .35:
pts.append((x, y, s))
pts.sort(key=lambda p: p[1]) # de lejos a cerca
add('<g filter="url(#pintura)" mask="url(#lados)">')
for x, y, s in pts:
presencia(x, y, s, ORA if random.random() < .05 else INK)
gente = []
for zd in DOORS:
n = random.randint(7, 10) if zd < 20 else 5
for i in range(n):
t = (i + random.uniform(.1, .9)) / n # 0 en el portal, 1 en la calzada
X = FX + .5 + t * random.uniform(4, 9)
Z = zd + .75 - t * zd * random.uniform(.3, .5)
if Z < 2.6 or any(abs(Z - g[0]) < .35 and abs(X - g[1]) < .45 for g in gente):
continue
paso = random.random() < .3
brazo = random.choices(["arriba", "atras", None], [.15, .2, .65])[0]
gente.append((Z, X, paso, brazo))
gente.sort(reverse=True) # de lejos a cerca
add('<g filter="url(#pintura)">')
for Z, X, paso, brazo in gente:
presencia(X, Z, paso, brazo)
add('</g></svg>')
open(OUT, "w").write("\n".join(o))
print(f"{len(pts)} presencias ({grande} en la figura grande)", file=sys.stderr)
print(f"{len(gente)} presencias", file=sys.stderr)