import math
import pathlib
OUT = pathlib.Path(__file__).resolve().parent
LIGHT = {"ink": "#12161C", "track": "#CBD3DE", "tick": "#9AA5B5",
"amber": "#F0A02A", "red": "#E5484D"}
DARK = {"ink": "#F2F5F9", "track": "#333D4B", "tick": "#6C7788",
"amber": "#F0A02A", "red": "#E5484D"}
SOLID = {"ink": "#F2F5F9", "track": "#3E4959", "tick": "#6C7788",
"amber": "#F0A02A", "red": "#E5484D"}
FIELD = "#0F131A"
class Theme:
def __init__(self, palette, adaptive=False):
self.palette, self.adaptive = palette, adaptive
def paint(self, name, prop="stroke"):
if not self.adaptive:
return f'{prop}="{self.palette[name]}"'
cls = name if prop == "stroke" else f"{name}-f"
return f'class="{cls}" {prop}="{LIGHT[name]}"'
@property
def style(self):
if not self.adaptive:
return ""
rules = " ".join(f".{k}{{stroke:{v}}} .{k}-f{{fill:{v}}}"
for k, v in DARK.items() if v != LIGHT[k])
return (" <style>\n"
f" @media (prefers-color-scheme:dark){{{rules}}}\n"
" </style>\n")
ADAPTIVE = Theme(LIGHT, adaptive=True)
REVERSED = Theme(SOLID)
def f(x):
return f"{x:.2f}".rstrip("0").rstrip(".")
def P(cx, cy, r, deg):
a = math.radians(deg)
return cx + r * math.cos(a), cy - r * math.sin(a)
def arc(t, name, cx, cy, r, a0, a1, w, cap="round"):
x0, y0 = P(cx, cy, r, a0)
x1, y1 = P(cx, cy, r, a1)
large = 1 if (a0 - a1) > 180 else 0
return (f'<path d="M {f(x0)} {f(y0)} A {f(r)} {f(r)} 0 {large} 1 {f(x1)} {f(y1)}"'
f' {t.paint(name)} fill="none" stroke-width="{f(w)}" stroke-linecap="{cap}"/>')
def ticks(t, name, cx, cy, a0, a1, n, r_in, r_out, w):
out = []
for i in range(n):
a = a0 + (a1 - a0) * i / (n - 1)
x0, y0 = P(cx, cy, r_in, a)
x1, y1 = P(cx, cy, r_out, a)
out.append(f'<line x1="{f(x0)}" y1="{f(y0)}" x2="{f(x1)}" y2="{f(y1)}"'
f' {t.paint(name)} stroke-width="{f(w)}" stroke-linecap="round"/>')
return "\n ".join(out)
def needle(t, cx, cy, deg, length, base_w):
a = math.radians(deg)
ux, uy = math.cos(a), -math.sin(a)
px, py = -uy, ux
return (f'<path d="M {f(cx + ux * length)} {f(cy + uy * length)}'
f' L {f(cx + px * base_w / 2)} {f(cy + py * base_w / 2)}'
f' L {f(cx - px * base_w / 2)} {f(cy - py * base_w / 2)} Z"'
f' {t.paint("ink", "fill")}/>')
def svg(t, w, h, body, label="tak"):
return (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}"'
f' width="{w}" height="{h}" role="img" aria-label="{label}">\n'
f'{t.style}{body}\n</svg>\n')
CX, CY, R, W = 128, 154, 106, 13
A0, A1 = 210, -30 NEEDLE, REDLINE = 44, 22
def dial(t, simple=False, tiny=False):
r, w = (96, 30) if tiny else (103.5, 18) if simple else (R, W)
cy = 154
b = [arc(t, "track", CX, cy, r, A0, A1, w),
arc(t, "red", CX, cy, r, REDLINE, A1, w),
arc(t, "amber", CX, cy, r, A0, NEEDLE, w)]
if not (simple or tiny):
b.append(ticks(t, "tick", CX, cy, A0, A1, 7, r - 34, r - 20, 7))
nl, nw, hub, dot = ((r - 18, 40, 29, 12) if tiny else
(r - 22, 30, 22, 8.5) if simple else
(r - 30, 22, 17, 6.5))
b += [needle(t, CX, cy, NEEDLE, nl, nw),
f'<circle cx="{CX}" cy="{f(cy)}" r="{hub}" {t.paint("ink", "fill")}/>',
f'<circle cx="{CX}" cy="{f(cy)}" r="{dot}" {t.paint("amber", "fill")}/>']
return " " + "\n ".join(b)
def mark(simple=False, tiny=False):
return svg(ADAPTIVE, 256, 256, dial(ADAPTIVE, simple, tiny))
def tile(simple=False, tiny=False, radius=58, scale=0.82):
body = (f' <rect width="256" height="256" rx="{radius}" fill="{FIELD}"/>\n'
f' <g transform="translate(128,128) scale({scale}) translate(-128,-128)">\n'
f'{dial(REVERSED, simple, tiny)}\n </g>')
return svg(REVERSED, 256, 256, body)
def square(simple=False, tiny=False):
return tile(simple, tiny, radius=0, scale=0.94 if tiny else 0.88)
SW, BASE, XTOP, ASC = 14, 150, 78, 44
ACX, ACY, AR = 110, 114, 36
TX, KX = 26, 188
CAPS = f'fill="none" stroke-width="{SW}" stroke-linecap="round" stroke-linejoin="round"'
def letters(t, live=True):
ink = f'{t.paint("ink")} {CAPS}'
b = [ f'<path d="M {TX} {ASC} L {TX} {BASE - 16} Q {TX} {BASE} {TX + 16} {BASE}" {ink}/>',
f'<line x1="{TX - 20}" y1="{XTOP}" x2="{TX + 26}" y2="{XTOP}" {ink}/>']
if live:
b.append(arc(t, "ink", ACX, ACY, AR, 34, -282, SW, cap="butt"))
b.append(arc(t, "red", ACX, ACY, AR, 78, 34, SW, cap="butt"))
else:
b.append(f'<circle cx="{ACX}" cy="{ACY}" r="{AR}" {ink}/>')
b.append(f'<line x1="{ACX + AR}" y1="{XTOP}" x2="{ACX + AR}" y2="{BASE}" {ink}/>')
if live:
b.append(needle(t, ACX, ACY, 56, AR - 12, 11))
b.append(f'<circle cx="{ACX}" cy="{ACY}" r="6.5" {t.paint("ink", "fill")}/>')
b += [ f'<line x1="{KX}" y1="{ASC}" x2="{KX}" y2="{BASE}" {ink}/>',
f'<line x1="{KX + 46}" y1="{XTOP}" x2="{KX}" y2="{BASE - 30}" {ink}/>',
f'<line x1="{KX + 17}" y1="{BASE - 47}" x2="{KX + 48}" y2="{BASE}" {ink}/>']
return "\n ".join(b)
WM_W, WM_H = KX + 48 + 7 + 8, 194
def wordmark(live=True, t=ADAPTIVE):
return svg(t, WM_W, WM_H, f' <g transform="translate(8,0)">{letters(t, live)}</g>')
M_L, M_T, M_W, M_H = 15.5, 41.5, 225.0, 172.0
def lockup(t=ADAPTIVE):
s = (BASE - ASC) / M_H dx, dy = -M_L * s, ASC - M_T * s
gap = 24
x = M_W * s + gap + 7 body = (f' <g transform="translate({f(dx)},{f(dy)}) scale({f(s)})">\n'
f'{dial(t)}\n </g>\n'
f' <g transform="translate({f(x)},0)">{letters(t, False)}</g>')
return svg(t, int(x + KX + 48 + 7), WM_H, body)
files = {"tak-mark.svg": mark(),
"tak-mark-small.svg": mark(simple=True),
"tak-icon-tile.svg": tile(),
"tak-icon-tile-small.svg": tile(simple=True),
"tak-icon-square.svg": square(),
"tak-icon-square-small.svg": square(simple=True),
"tak-icon-square-tiny.svg": square(tiny=True),
"tak-wordmark.svg": wordmark(True),
"tak-wordmark-plain.svg": wordmark(False),
"tak-lockup.svg": lockup(),
"tak-lockup-light.svg": lockup(Theme(LIGHT)),
"tak-lockup-dark.svg": lockup(Theme(DARK))}
for name, content in files.items():
(OUT / name).write_text(content)
print(f"{name:28} {len(content):5}b")