Nuevo dibujo (calle en perspectiva), New Rocker y naranja #ff7a1a

This commit is contained in:
hacklab 2026-10-13 12:52:18 +02:00
parent e8c59a3929
commit df4d68724d
10 changed files with 1690 additions and 389 deletions

View file

@ -1,6 +1,6 @@
# TAKE THE STREET # TAKE THE STREET
![¡A la calle!](docs/calle.svg) ![Calle en perspectiva con gente saliendo de los portales](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. **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.
@ -114,16 +114,14 @@ Las personas de enlace de cada ciudad no se ponen aquí: van en la sala cerrada
`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), pero con otras tipografías: `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), pero con otras tipografías:
- títulos: **Rubik Wet Paint** (pintura que chorrea), OFL - títulos: **New Rocker** (gótica de cartel de rock, sin pasarse), OFL
- etiquetas: **Special Elite** (máquina de escribir de fanzine), Apache 2.0 - etiquetas: **Special Elite** (máquina de escribir de fanzine), Apache 2.0
- botones: **Anton**, OFL, la misma que 26s - botones: **Anton**, OFL, la misma que 26s
El color es el rojo `#ff1f1f`. El dibujo `docs/calle.svg` se genera con `tools/calle.py`, que convierte el texto de la pancarta a trazos. Para regenerarlo: El color es el naranja `#ff7a1a`. El dibujo `docs/calle.svg` es una calle en perspectiva con gente saliendo de los portales. La calzada, el bordillo, la acera y el paso de cebra van con línea limpia; los edificios, a mano alzada; y la gente, con trazo suelto de spray. Se genera con `tools/calle.py`, sin dependencias. Para regenerarlo:
``` ```
python3 tools/calle.py RubikWetPaint-Regular.ttf docs/calle.svg python3 tools/calle.py docs/calle.svg
``` ```
Necesita `fonttools` y la fuente Rubik Wet Paint completa (TTF). Código AGPL-3.0. Textos y dibujo CC BY-SA 4.0. Tipografías: New Rocker y Anton (OFL), Special Elite (Apache 2.0).
Código AGPL-3.0. Textos y dibujo CC BY-SA 4.0. Tipografías: Rubik Wet Paint y Anton (OFL), Special Elite (Apache 2.0).

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Before After
Before After

View file

@ -1,13 +1,13 @@
@font-face { font-family: "Rubik Wet Paint"; src: url("../fonts/rubik-wet-paint.woff2") format("woff2"); font-display: swap; } @font-face { font-family: "New Rocker"; src: url("../fonts/new-rocker.woff2") format("woff2"); font-display: swap; }
@font-face { font-family: "Special Elite"; src: url("../fonts/special-elite.woff2") format("woff2"); font-display: swap; } @font-face { font-family: "Special Elite"; src: url("../fonts/special-elite.woff2") format("woff2"); font-display: swap; }
@font-face { font-family: "Anton"; src: url("../fonts/anton.woff2") format("woff2"); font-display: swap; } @font-face { font-family: "Anton"; src: url("../fonts/anton.woff2") format("woff2"); font-display: swap; }
:root { :root {
--bg: #fff; --bg: #fff;
--fg: #000; --fg: #000;
--neon: #ff1f1f; --neon: #ff7a1a;
--bd: 3px solid var(--fg); --bd: 3px solid var(--fg);
--tit: "Rubik Wet Paint", Impact, sans-serif; --tit: "New Rocker", "Old English Text MT", Georgia, serif;
--zine: "Special Elite", "Courier New", monospace; --zine: "Special Elite", "Courier New", monospace;
--bt: "Anton", Impact, "Arial Narrow", sans-serif; --bt: "Anton", Impact, "Arial Narrow", sans-serif;
--txt: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --txt: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
@ -19,7 +19,7 @@ a { color: inherit; }
.cab { .cab {
display: grid; display: grid;
grid-template-columns: 260px 1fr auto; grid-template-columns: 340px 1fr auto;
align-items: center; align-items: center;
gap: 20px; gap: 20px;
padding: 10px 20px; padding: 10px 20px;
@ -28,8 +28,7 @@ a { color: inherit; }
.cab img { width: 100%; height: auto; display: block; } .cab img { width: 100%; height: auto; display: block; }
h1 { h1 {
margin: 0; margin: 0;
font: 400 clamp(40px, 6vw, 84px)/1 var(--tit); font: 400 clamp(44px, 6.5vw, 96px)/1 var(--tit);
text-transform: uppercase;
text-shadow: .03em .03em 0 var(--neon), .06em .06em 0 var(--neon); text-shadow: .03em .03em 0 var(--neon), .06em .06em 0 var(--neon);
} }
.lema { margin: 22px 0 0; font: 18px var(--zine); } .lema { margin: 22px 0 0; font: 18px var(--zine); }

View file

@ -1,93 +1,96 @@
Copyright 2020 The Rubik Filtered Project Authors (https://https://github.com/NaN-xyz/Rubik-Filtered) Copyright (c) 2011, Pablo Impallari (www.impallari.com|impallari@gmail.com),
Copyright (c) 2011, Brenda Gallo (gbrenda1987@gmail.com),
This Font Software is licensed under the SIL Open Font License, Version 1.1. Copyright (c) 2011, Rodrigo Fuenzalida (www.rfuenzalida.com|hello@rfuenzalida.com),
This license is copied below, and is also available with a FAQ at: with Reserved Font Name 'New Rocker'
https://scripts.sil.org/OFL
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
----------------------------------------------------------- http://scripts.sil.org/OFL
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
-----------------------------------------------------------
PREAMBLE SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
The goals of the Open Font License (OFL) are to stimulate worldwide -----------------------------------------------------------
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and PREAMBLE
open framework in which fonts may be shared and improved in partnership The goals of the Open Font License (OFL) are to stimulate worldwide
with others. development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
The OFL allows the licensed fonts to be used, studied, modified and open framework in which fonts may be shared and improved in partnership
redistributed freely as long as they are not sold by themselves. The with others.
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved The OFL allows the licensed fonts to be used, studied, modified and
names are not used by derivative works. The fonts and derivatives, redistributed freely as long as they are not sold by themselves. The
however, cannot be released under any other type of license. The fonts, including any derivative works, can be bundled, embedded,
requirement for fonts to remain under this license does not apply redistributed and/or sold with any software provided that any reserved
to any document created using the fonts or their derivatives. names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
DEFINITIONS requirement for fonts to remain under this license does not apply
"Font Software" refers to the set of files released by the Copyright to any document created using the fonts or their derivatives.
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation. DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
"Reserved Font Name" refers to any names specified as such after the Holder(s) under this license and clearly marked as such. This may
copyright statement(s). include source files, build scripts and documentation.
"Original Version" refers to the collection of Font Software components as "Reserved Font Name" refers to any names specified as such after the
distributed by the Copyright Holder(s). copyright statement(s).
"Modified Version" refers to any derivative made by adding to, deleting, "Original Version" refers to the collection of Font Software components as
or substituting -- in part or in whole -- any of the components of the distributed by the Copyright Holder(s).
Original Version, by changing formats or by porting the Font Software to a
new environment. "Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
"Author" refers to any designer, engineer, programmer, technical Original Version, by changing formats or by porting the Font Software to a
writer or other person who contributed to the Font Software. new environment.
PERMISSION & CONDITIONS "Author" refers to any designer, engineer, programmer, technical
Permission is hereby granted, free of charge, to any person obtaining writer or other person who contributed to the Font Software.
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font PERMISSION & CONDITIONS
Software, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
1) Neither the Font Software nor any of its individual components, redistribute, and sell modified and unmodified copies of the Font
in Original or Modified Versions, may be sold by itself. Software, subject to the following conditions:
2) Original or Modified Versions of the Font Software may be bundled, 1) Neither the Font Software nor any of its individual components,
redistributed and/or sold with any software, provided that each copy in Original or Modified Versions, may be sold by itself.
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or 2) Original or Modified Versions of the Font Software may be bundled,
in the appropriate machine-readable metadata fields within text or redistributed and/or sold with any software, provided that each copy
binary files as long as those fields can be easily viewed by the user. contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
3) No Modified Version of the Font Software may use the Reserved Font in the appropriate machine-readable metadata fields within text or
Name(s) unless explicit written permission is granted by the corresponding binary files as long as those fields can be easily viewed by the user.
Copyright Holder. This restriction only applies to the primary font name as
presented to the users. 3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Copyright Holder. This restriction only applies to the primary font name as
Software shall not be used to promote, endorse or advertise any presented to the users.
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
permission. Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
5) The Font Software, modified or unmodified, in part or in whole, Copyright Holder(s) and the Author(s) or with their explicit written
must be distributed entirely under this license, and must not be permission.
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created 5) The Font Software, modified or unmodified, in part or in whole,
using the Font Software. must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
TERMINATION remain under this license does not apply to any document created
This license becomes null and void if any of the above conditions are using the Font Software.
not met.
TERMINATION
DISCLAIMER This license becomes null and void if any of the above conditions are
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, not met.
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT DISCLAIMER
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
OTHER DEALINGS IN THE FONT SOFTWARE. INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

View file

@ -8,9 +8,9 @@
</head> </head>
<body> <body>
<header class="cab"> <header class="cab">
<img src="../docs/calle.svg" alt="Gente con una pancarta «¡A la calle!» delante de las tiendas de una acampada"> <img src="../docs/calle.svg" alt="Calle en perspectiva con gente saliendo de los portales">
<div> <div>
<h1>Take the street</h1> <h1>Take the Street</h1>
<p class="lema">La vivienda es un derecho, no un negocio · red internacional de acampadas</p> <p class="lema">La vivienda es un derecho, no un negocio · red internacional de acampadas</p>
</div> </div>
<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> <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>

View file

@ -1,119 +1,156 @@
# Genera docs/calle.svg: ilustración stencil "a la calle" # Genera docs/calle.svg: calle en perspectiva, gente saliendo de los portales.
import sys, random # Calzada y acera con líneas limpias; edificios a mano alzada; gente con trazo difuso.
from fontTools.ttLib import TTFont import sys, random, math
from fontTools.pens.svgPathPen import SVGPathPen random.seed(1310)
from fontTools.pens.transformPen import TransformPen OUT = sys.argv[1]
random.seed(26)
FONT, OUT = sys.argv[1], sys.argv[2]
W, H = 1200, 640 W, H = 1200, 640
INK, RED = "#000", "#ff1f1f" VX, VY, F = 600, 205, 500 # punto de fuga y focal
out = [] EYE = 4.0 # cámara a 4 m (desde un balcón)
def add(s): out.append(s) ROAD, WALK, TOP = 3.4, 6.6, -16.0 # medio ancho calzada, fachada, altura
INK, ORA = "#111", "#ff7a1a"
o = []
add = o.append
def text_path(txt, x, y, size, fill, anchor="middle", font=FONT): def P(X, Y, Z): # Y hacia abajo, suelo en Y = EYE
f = TTFont(font); gs = f.getGlyphSet(); cmap = f.getBestCmap() return VX + X * F / Z, VY + Y * F / Z
upm = f["head"].unitsPerEm; k = size / upm def poly(pts, **a):
widths = [f["hmtx"][cmap[ord(c)]][0] for c in txt] d = "M" + " L".join(f"{x:.1f},{y:.1f}" for x, y in pts) + "Z"
total = sum(widths) * k add(f'<path d="{d}" ' + " ".join(f'{k.replace("_","-")}="{v}"' for k, v in a.items()) + "/>")
cx = x - total / 2 if anchor == "middle" else x def line(p, q, w, filt=None, col=INK, op=1):
d = [] f = f' filter="url(#{filt})"' if filt else ""
for c, w in zip(txt, widths): add(f'<line x1="{p[0]:.1f}" y1="{p[1]:.1f}" x2="{q[0]:.1f}" y2="{q[1]:.1f}" stroke="{col}" stroke-width="{w:.1f}" stroke-linecap="round" opacity="{op}"{f}/>')
pen = SVGPathPen(gs)
gs[cmap[ord(c)]].draw(TransformPen(pen, (k, 0, 0, -k, cx, y)))
d.append(pen.getCommands()); cx += w * k
return f'<path fill="{fill}" d="{" ".join(d)}"/>'
def blob(cx, cy, r, n, jit, fill, op=1):
import math
pts = []
for i in range(n):
a = 2 * math.pi * i / n
rr = r * (1 + random.uniform(-jit, jit))
if random.random() < .25: rr *= 1.25
pts.append((cx + rr * math.cos(a), cy + rr * math.sin(a)))
d = f"M{pts[0][0]:.0f},{pts[0][1]:.0f} " + " ".join(
f"Q{p[0]:.0f},{p[1]:.0f} {(p[0]+q[0])/2:.0f},{(p[1]+q[1])/2:.0f}"
for p, q in zip(pts, pts[1:] + pts[:1])) + "Z"
add(f'<path d="{d}" fill="{fill}" opacity="{op}"/>')
def splats(cx, cy, spread, n, rmax, fill):
for _ in range(n):
x = cx + random.gauss(0, spread); y = cy + random.gauss(0, spread * .6)
add(f'<circle cx="{x:.0f}" cy="{y:.0f}" r="{random.uniform(2, rmax):.1f}" fill="{fill}"/>')
def drips(x0, x1, y, fill, n):
for _ in range(n):
x = random.uniform(x0, x1); l = random.uniform(15, 70); w = random.uniform(5, 11)
add(f'<path d="M{x-w/2:.0f},{y} v{l:.0f} a{w/2:.1f},{w/2:.1f} 0 0 0 {w:.0f},0 v-{l:.0f}z" fill="{fill}"/>')
def tent(x, y, w, h):
add(f'<path d="M{x},{y} L{x+w/2},{y-h} L{x+w},{y} Z" fill="#fff" stroke="{INK}" stroke-width="8" stroke-linejoin="round"/>')
add(f'<path d="M{x+w/2},{y-h} L{x+w*.38},{y} L{x+w*.62},{y} Z" fill="{INK}"/>')
add(f'<path d="M{x+w/2},{y-h} l0,-22 l26,9 l-26,9" fill="{RED}" stroke="{INK}" stroke-width="4" stroke-linejoin="round"/>')
def fig(x, y, s=1.0, pose="down", step=1):
L = lambda *p: " ".join(f"{a:.0f}" for a in p)
sw = 19 * s
st = f'stroke="{INK}" stroke-width="{sw:.0f}" stroke-linecap="round" stroke-linejoin="round" fill="none"'
hipy = y - 78 * s; shy = y - 150 * s
# piernas (andando)
add(f'<path d="M{L(x-9*s, hipy)} L{L(x-9*s-24*s*step, y-38*s)} L{L(x-12*s-30*s*step, y)}" {st}/>')
add(f'<path d="M{L(x+9*s, hipy)} L{L(x+14*s+16*s*step, y-40*s)} L{L(x+20*s+26*s*step, y)}" {st}/>')
# torso (chupa)
add(f'<path d="M{L(x-31*s, shy)} L{L(x+31*s, shy)} L{L(x+24*s, hipy+6*s)} L{L(x-24*s, hipy+6*s)} Z" fill="{INK}" stroke="{INK}" stroke-width="{8*s:.0f}" stroke-linejoin="round"/>')
# cresta (detrás) + cabeza
if pose in ("fist", "cresta"):
for i in range(5):
dx = (i - 2) * 11 * s
add(f'<path d="M{L(x+dx-8*s, shy-40*s)} L{L(x+dx*1.9, shy-112*s)} L{L(x+dx+8*s, shy-40*s)} Z" fill="{RED}" stroke="{INK}" stroke-width="{5*s:.0f}" stroke-linejoin="round"/>')
add(f'<circle cx="{x:.0f}" cy="{shy-36*s:.0f}" r="{24*s:.0f}" fill="{INK}"/>')
# brazos
ls, rs = (x - 27 * s, shy + 8 * s), (x + 27 * s, shy + 8 * s)
if pose == "fist":
add(f'<path d="M{L(*rs)} L{L(x+52*s, shy-38*s)} L{L(x+50*s, shy-100*s)}" {st}/>')
add(f'<rect x="{x+33*s:.0f}" y="{shy-138*s:.0f}" width="{34*s:.0f}" height="{40*s:.0f}" rx="{10*s:.0f}" fill="{INK}"/>')
add(f'<path d="M{L(*ls)} L{L(x-46*s, shy+50*s)} L{L(x-40*s, shy+88*s)}" {st}/>')
elif pose.startswith("pole"):
side = -1 if pose == "poleL" else 1
px = x + side * 46 * s
add(f'<path d="M{L(*(rs if side>0 else ls))} L{L(px, shy-20*s)}" {st}/>')
add(f'<path d="M{L(*(ls if side>0 else rs))} L{L(x, shy+30*s)} L{L(px, shy+20*s)}" {st}/>')
else:
add(f'<path d="M{L(*ls)} L{L(x-44*s, shy+50*s)} L{L(x-38*s, shy+90*s)}" {st}/>')
add(f'<path d="M{L(*rs)} L{L(x+44*s, shy+50*s)} L{L(x+38*s, shy+90*s)}" {st}/>')
ZN, ZF = 4.4, 400.0
add(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" width="{W}" height="{H}">') add(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" width="{W}" height="{H}">')
add(f'<rect width="{W}" height="{H}" fill="#fff"/>') add('''<defs>
# mancha roja de spray detrás <filter id="lapiz" x="-5%" y="-5%" width="110%" height="110%"><feTurbulence type="fractalNoise" baseFrequency="0.035" numOctaves="2" seed="3"/><feDisplacementMap in="SourceGraphic" scale="4"/></filter>
blob(600, 300, 250, 22, .18, RED) <filter id="trazo" x="-30%" y="-30%" width="160%" height="160%"><feTurbulence type="fractalNoise" baseFrequency="0.13" numOctaves="2" seed="5" result="t"/><feDisplacementMap in="SourceGraphic" in2="t" scale="3.5" result="d"/><feGaussianBlur in="d" stdDeviation="0.45"/></filter>
splats(600, 300, 230, 70, 9, RED) <filter id="spray" x="-30%" y="-30%" width="160%" height="160%"><feTurbulence type="fractalNoise" baseFrequency="0.09" numOctaves="3" seed="8" result="t"/><feDisplacementMap in="SourceGraphic" in2="t" scale="9" result="d"/><feGaussianBlur in="d" stdDeviation="0.7"/></filter>
drips(420, 780, 470, RED, 9) <radialGradient id="luz"><stop offset="0" stop-color="#ff7a1a" stop-opacity=".9"/><stop offset="1" stop-color="#ff7a1a" stop-opacity="0"/></radialGradient>
# calle: asfalto + paso de cebra <clipPath id="lienzo"><rect width="1200" height="640"/></clipPath>
GY = 540 </defs>''')
add(f'<rect x="0" y="{GY}" width="{W}" height="{H-GY}" fill="{INK}"/>') add(f'<g clip-path="url(#lienzo)"><rect width="{W}" height="{H}" fill="#fff"/>')
for i in range(-2, 14):
x = i * 100 # luz naranja al fondo de la calle
add(f'<path d="M{x+30},{GY+14} L{x+80},{GY+14} L{x+60},{H-14} L{x-10},{H-14} Z" fill="#fff"/>') add(f'<ellipse cx="{VX}" cy="{VY-10}" rx="170" ry="120" fill="url(#luz)"/>')
# tiendas al fondo add(f'<g filter="url(#spray)">')
tent(40, GY, 190, 140); tent(190, GY, 140, 100) for _ in range(28):
tent(930, GY, 200, 150); tent(1070, GY, 120, 90) a = random.uniform(0, 2*math.pi); r = random.uniform(30, 190)
# pancarta entre dos palos add(f'<circle cx="{VX+r*math.cos(a)*1.2:.0f}" cy="{VY-10+r*math.sin(a)*.7:.0f}" r="{random.uniform(1.5,6):.1f}" fill="{ORA}"/>')
bx0, bx1, by = 300, 900, 50 add('</g>')
add(f'<path d="M{bx0},{by} L{bx1},{by} L{bx1},{by+115} Q{(bx0+bx1)/2},{by+140} {bx0},{by+115} Z" fill="#fff" stroke="{INK}" stroke-width="9" stroke-linejoin="round"/>')
add(text_path("¡A LA CALLE!", 600, by + 86, 74, INK)) # --- fachadas (a mano alzada) ---
drips(bx0 + 40, bx1 - 40, by + 122, INK, 6) def fachada(s):
add(f'<line x1="{bx0}" y1="{by-20}" x2="{bx0}" y2="{GY-150}" stroke="{INK}" stroke-width="12" stroke-linecap="round"/>') X = s * WALK
add(f'<line x1="{bx1}" y1="{by-20}" x2="{bx1}" y2="{GY-150}" stroke="{INK}" stroke-width="12" stroke-linecap="round"/>') # línea de cornisas / pisos
# gente for Y in (EYE - 3.2, EYE - 6.8, EYE - 10.4, EYE - 14.0):
fig(bx0 + 46, GY - 4, 1.05, "poleL", 1) line(P(X, Y, ZN), P(X, Y, ZF), 2.2, "lapiz")
fig(470, GY - 2, .95, "down", -1) # aristas verticales de los portales y ventanas
fig(600, GY, 1.15, "fist", 1) z = ZN + 1.2
fig(730, GY - 2, .95, "cresta", -1) i = 0
fig(bx1 - 46, GY - 4, 1.05, "poleR", 1) while z < 90:
# estrellas dz = 1.5
for (sx, sy, r) in [(120, 90, 26), (1080, 110, 32), (990, 300, 16), (200, 300, 14)]: # portal
import math poly([P(X, EYE, z), P(X, EYE - 2.6, z), P(X, EYE - 2.6, z + dz), P(X, EYE, z + dz)],
pts = " ".join(f"{sx + (r if i%2==0 else r*.42)*math.cos(math.pi/2 + i*math.pi/5):.0f},{sy - (r if i%2==0 else r*.42)*math.sin(math.pi/2 + i*math.pi/5):.0f}" for i in range(10)) fill=ORA if i % 2 == 0 else "#fff", stroke=INK, stroke_width=f"{max(1.2, 30/z):.1f}", filter="url(#lapiz)")
add(f'<polygon points="{pts}" fill="{INK}"/>') if i % 2 == 1:
add('</svg>') poly([P(X, EYE, z+.2), P(X, EYE - 2.4, z+.2), P(X, EYE - 2.4, z + dz-.2), P(X, EYE, z + dz-.2)], fill=INK, opacity=".85", filter="url(#lapiz)")
open(OUT, "w").write("\n".join(out)) # ventanas encima
for Yb in (EYE - 4.2, EYE - 7.8, EYE - 11.4):
poly([P(X, Yb, z+.15), P(X, Yb - 1.8, z+.15), P(X, Yb - 1.8, z + dz-.15), P(X, Yb, z + dz-.15)],
fill="none", stroke=INK, stroke_width=f"{max(.8, 18/z):.1f}", filter="url(#lapiz)")
z += dz + 2.2 + z * .12
i += 1
fachada(-1); fachada(1)
# --- acera y calzada (líneas limpias) ---
for s in (-1, 1):
# acera (gris claro) y bordillo
poly([P(s*ROAD, EYE-.15, ZN), P(s*WALK, EYE-.15, ZN), P(s*WALK, EYE-.15, ZF), P(s*ROAD, EYE-.15, ZF)], fill="#f2f2f2")
poly([P(s*ROAD, EYE-.15, ZN), P(s*ROAD, EYE, ZN), P(s*ROAD, EYE, ZF), P(s*ROAD, EYE-.15, ZF)], fill=INK)
line(P(s*ROAD, EYE-.15, ZN), P(s*ROAD, EYE-.15, ZF), 3)
line(P(s*WALK, EYE, ZN), P(s*WALK, EYE, ZF), 3)
# juntas de las baldosas
z = ZN
while z < 120:
line(P(s*ROAD, EYE-.15, z), P(s*WALK, EYE-.15, z), max(.6, 9/z))
z *= 1.12
for k in (1, 2):
Xk = s * (ROAD + (WALK - ROAD) * k / 3)
line(P(Xk, EYE-.15, ZN), P(Xk, EYE-.15, ZF), .8, op=.6)
# línea discontinua central
z = ZN
while z < 200:
poly([P(-.08, EYE, z), P(.08, EYE, z), P(.08, EYE, z+2.2), P(-.08, EYE, z+2.2)], fill=INK)
z += 2.2 + 2.6 + z * .08
# bordes de la calzada
for s in (-1, 1):
line(P(s*(ROAD-.25), EYE, ZN), P(s*(ROAD-.25), EYE, ZF), 2.4)
# paso de cebra delante
for i in range(-6, 7):
x0 = i * .52
poly([P(x0-.2, EYE, ZN+.2), P(x0+.2, EYE, ZN+.2), P(x0+.2, EYE, ZN+2.6), P(x0-.2, EYE, ZN+2.6)], fill=INK)
# --- gente (trazo difuso) ---
def persona(X, Z, col=INK, pose=None):
x, y = P(X, EYE, Z)
h = 1.72 * F / Z
if pose is None:
pose = random.choice(["anda", "anda", "puño", "brazos", "anda"])
paso = random.uniform(-1, 1)
w = max(1.1, .042 * h)
hd = (x + random.uniform(-.02, .02) * h, y - .9 * h)
nk, hip = (x, y - .8 * h), (x + paso * .01 * h, y - .48 * h)
sh_l, sh_r = (x - .11 * h, y - .78 * h), (x + .11 * h, y - .78 * h)
pie_l, pie_r = (x - .05*h - paso*.08*h, y), (x + .05*h + paso*.08*h, y)
rod_l, rod_r = (x - .04*h - paso*.04*h, y - .25*h), (x + .04*h + paso*.05*h, y - .25*h)
if pose == "puño":
m_l, c_l = (x - .14*h, y - .46*h), (x - .15*h, y - .62*h)
m_r, c_r = (x + .1*h, y - 1.18*h), (x + .17*h, y - .98*h)
elif pose == "brazos":
m_l, c_l = (x - .26*h, y - 1.12*h), (x - .2*h, y - .95*h)
m_r, c_r = (x + .26*h, y - 1.12*h), (x + .2*h, y - .95*h)
else:
m_l, c_l = (x - .15*h - paso*.05*h, y - .44*h), (x - .16*h, y - .6*h)
m_r, c_r = (x + .15*h + paso*.05*h, y - .44*h), (x + .16*h, y - .6*h)
def tr(*pts, ww):
d = "M" + " L".join(f"{a+random.uniform(-.6,.6):.1f},{b+random.uniform(-.6,.6):.1f}" for a, b in pts)
add(f'<path d="{d}" fill="none" stroke="{col}" stroke-width="{ww:.1f}" stroke-linecap="round" stroke-linejoin="round"/>')
for pas in range(2): # dos pasadas desplazadas = trazo suelto
ww = w * (1 if pas == 0 else .6)
dx, dy = (0, 0) if pas == 0 else (random.uniform(-.03, .03) * h, random.uniform(-.02, .02) * h)
add(f'<g opacity="{.92 if pas == 0 else .45}" transform="translate({dx:.1f},{dy:.1f})">')
tr(nk, hip, ww=ww * 1.5)
tr(hip, rod_l, pie_l, ww=ww)
tr(hip, rod_r, pie_r, ww=ww)
tr(sh_l, c_l, m_l, ww=ww * .85)
tr(sh_r, c_r, m_r, ww=ww * .85)
tr(sh_l, sh_r, ww=ww * 1.2)
add(f'<ellipse cx="{hd[0]:.1f}" cy="{hd[1]:.1f}" rx="{.062*h:.1f}" ry="{.072*h:.1f}" fill="{col}"/>')
add('</g>')
gente = []
# saliendo de los portales (en la acera)
for s in (-1, 1):
z = ZN + 1.2; i = 0
while z < 70:
if i % 2 == 0:
for k in range(random.randint(1, 3)):
gente.append((s * random.uniform(WALK - 1.6, WALK - .4), z + random.uniform(.2, 1.4), INK))
z += 1.5 + 2.2 + z * .12; i += 1
# bajando a la calzada, hacia el centro
for _ in range(42):
Z = math.exp(random.uniform(math.log(6.5), math.log(70)))
gente.append((random.uniform(-ROAD + .4, ROAD - .4), Z, ORA if random.random() < .22 else INK))
gente.sort(key=lambda g: -g[1])
add('<g filter="url(#trazo)">')
for X, Z, col in gente:
persona(X, Z, col)
add('</g>')
# salpicaduras sueltas
add('<g filter="url(#spray)">')
for _ in range(40):
add(f'<circle cx="{random.uniform(0,W):.0f}" cy="{random.uniform(H*.55,H):.0f}" r="{random.uniform(1,3.5):.1f}" fill="{INK}" opacity=".7"/>')
add('</g>')
add('</g></svg>')
open(OUT, "w").write("\n".join(o))