feat(website): logo showcase on /logo/, poll link + current feature list on the homepage
Both marks now appear everywhere they would have to work: five sizes down to the real 16 px tab icon, horizontal and stacked lockups, browser tab + site header, phone tiles, console banner + boot splash, social card, and four colour grounds — generated from one geometry source by website/tools/logo-assets.py (outputs committed under data/logo and static/img/logo). The homepage links to the vote and its 'what works today' list catches up with 26.8–26.17: password login, online app catalog, app dependencies, opt-in HTTPS. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MScAinbyMdeNc7H2BZdnnG
|
|
@ -75,6 +75,10 @@ layouts/ Custom inline theme — no external theme or framework
|
|||
index.html Home-only layout with editorial hero
|
||||
assets/css/main.css Stylesheet (fingerprinted + minified on build)
|
||||
assets/js/poll.js Vote script for /logo/ — plain fetch, no cookies, no storage
|
||||
data/logo/{m,r}.json Generated: mark SVGs, lockups, 16 px icons, console banner per proposal
|
||||
static/img/logo/ Generated: tiles, splash and social card per proposal
|
||||
tools/logo-assets.py Generator for the two lines above (cairosvg + Pillow + fontTools);
|
||||
run on the dev box, commit the output. Font: tools/FONT-PROVENANCE.md
|
||||
static/favicon.svg Gate mark in crimson
|
||||
deploy.sh Manual rsync + remote Hugo build (over SSH, for off-CI pushes)
|
||||
deploy-ci.sh Local rsync + Hugo build — runs on forge-runner-01 from CI
|
||||
|
|
|
|||
|
|
@ -458,50 +458,14 @@ main.container {
|
|||
background: var(--card-bg);
|
||||
transition: border-color 160ms, box-shadow 160ms;
|
||||
}
|
||||
.poll-card.is-yours {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px var(--accent-glow);
|
||||
}
|
||||
.is-yours { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
||||
.poll-card h2 { margin: 0; font-size: 1.25rem; letter-spacing: -0.01em; }
|
||||
.poll-desc { margin: 0; color: var(--fg-muted); font-size: 0.95rem; }
|
||||
.poll-art {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 2.2rem 1rem;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
.poll-art--m { background: var(--bg-subtle); color: var(--accent); }
|
||||
.poll-art--r { background: #151515; color: #af6428; }
|
||||
.poll-art { display: grid; place-items: center; padding: 2.2rem 1rem; border-radius: 0.75rem; }
|
||||
.poll-art--m { background: var(--bg-subtle); }
|
||||
.poll-art--r { background: #151515; }
|
||||
.poll-art .pm { height: 9rem; width: auto; }
|
||||
.pm { display: block; }
|
||||
.pm--big { height: 9rem; width: auto; }
|
||||
.pm--head { height: 1.1em; width: auto; }
|
||||
.poll-ctx {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.6rem;
|
||||
align-items: center;
|
||||
}
|
||||
.poll-ctx-head, .poll-ctx-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
padding: 0.35rem 0.7rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.5rem;
|
||||
background: var(--bg);
|
||||
font-size: 0.82rem;
|
||||
color: var(--fg-muted);
|
||||
}
|
||||
.poll-ctx-head { color: var(--accent); }
|
||||
.poll-ctx-head--r { color: #af6428; }
|
||||
.poll-ctx-name {
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.7rem;
|
||||
color: var(--fg);
|
||||
}
|
||||
.poll-ctx-tab img { image-rendering: pixelated; }
|
||||
.poll-btn {
|
||||
margin-top: auto;
|
||||
padding: 0.85rem 1.2rem;
|
||||
|
|
@ -522,9 +486,76 @@ main.container {
|
|||
.poll-bar i { display: block; height: 100%; width: 0; background: var(--accent); transition: width 500ms ease; }
|
||||
.poll--voted .poll-bar { display: block; }
|
||||
.poll-count { margin: 0; min-height: 1.4em; font-size: 0.85rem; color: var(--fg-muted); font-variant-numeric: tabular-nums; }
|
||||
.poll-status { margin: 1.5rem 0 0; min-height: 1.5em; color: var(--fg-muted); }
|
||||
.poll-status { margin: 1.25rem 0 0; min-height: 1.5em; color: var(--fg-muted); }
|
||||
.poll-note { margin-top: 1rem; max-width: var(--measure); font-size: 0.85rem; color: var(--fg-muted); }
|
||||
.poll-note a { color: var(--accent); }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.poll-bar i, .poll-btn { transition: none; }
|
||||
|
||||
/* showcase */
|
||||
.show { display: flex; flex-direction: column; gap: 3rem; margin-top: 4rem; }
|
||||
.show-intro { margin: 0; }
|
||||
.show-row { display: flex; flex-direction: column; gap: 0.6rem; }
|
||||
.show-h { margin: 0; font-size: 1.35rem; letter-spacing: -0.01em; }
|
||||
.show-p { margin: 0 0 0.6rem; max-width: var(--measure); color: var(--fg-muted); font-size: 0.95rem; }
|
||||
.show-pair { display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); gap: 1.25rem; }
|
||||
.show-cell {
|
||||
display: flex; flex-direction: column; gap: 0.9rem;
|
||||
padding: 1.1rem; border: 1px solid var(--card-border); border-radius: 0.9rem; background: var(--card-bg);
|
||||
}
|
||||
.show-cell--m { color: var(--accent); }
|
||||
.show-cell--r { color: #af6428; }
|
||||
.show-tag { font-size: 0.7rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--fg-muted); }
|
||||
.sizes { display: flex; align-items: flex-end; gap: 1.1rem; flex-wrap: wrap; }
|
||||
.sizes figure { margin: 0; display: flex; flex-direction: column; align-items: center; gap: 0.35rem; }
|
||||
.sizes figcaption { font-family: var(--font-mono); font-size: 0.65rem; color: var(--fg-muted); }
|
||||
.sz { display: block; }
|
||||
.sz .pm { width: auto; }
|
||||
.sz-96 .pm { height: 96px; } .sz-48 .pm { height: 48px; } .sz-32 .pm { height: 32px; } .sz-24 .pm { height: 24px; }
|
||||
.px { image-rendering: pixelated; }
|
||||
.lock { display: grid; place-items: center; padding: 1rem; border-radius: 0.6rem; background: var(--bg-subtle); }
|
||||
.lock-h .pm { height: 3rem; width: auto; max-width: 100%; }
|
||||
.lock-s .pm { height: 7rem; width: auto; max-width: 100%; }
|
||||
.mock { border: 1px solid var(--border); border-radius: 0.7rem; overflow: hidden; background: var(--bg); color: var(--fg); font-size: 0.8rem; }
|
||||
.tabbar { background: var(--bg-subtle); padding: 0.55rem 0.65rem; }
|
||||
.tab { display: inline-flex; align-items: center; gap: 0.45rem; padding: 0.3rem 0.65rem; border: 1px solid var(--border); border-radius: 0.4rem; background: var(--bg); color: var(--fg-muted); }
|
||||
.urlbar { padding: 0.45rem 0.75rem; border-top: 1px solid var(--border); font-family: var(--font-mono); font-size: 0.72rem; color: var(--fg-muted); }
|
||||
.uihead { display: flex; align-items: center; gap: 0.5rem; padding: 0.8rem 0.9rem; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
|
||||
.uihead-mark { display: inline-flex; color: inherit; }
|
||||
.show-cell--m .uihead-mark { color: var(--accent); }
|
||||
.show-cell--r .uihead-mark { color: #af6428; }
|
||||
.uihead-mark .pm { height: 1.25rem; width: auto; }
|
||||
.uihead-name { font-weight: 600; letter-spacing: 0.13em; text-transform: uppercase; font-size: 0.72rem; color: var(--fg); }
|
||||
.uihead-nav { margin-left: auto; display: flex; gap: 0.7rem; color: var(--fg-muted); }
|
||||
.uibody { padding: 0.9rem; display: flex; flex-direction: column; gap: 0.45rem; }
|
||||
.uibody i { display: block; height: 0.45rem; border-radius: 3px; background: var(--bg-subtle); }
|
||||
.uibody i.s { width: 45%; }
|
||||
.tiles { display: flex; gap: 1rem; justify-content: center; padding: 1.2rem; border-radius: 0.6rem; background: var(--bg-subtle); }
|
||||
.tiles img { width: 6rem; height: 6rem; }
|
||||
.term {
|
||||
margin: 0; padding: 0.9rem 1rem; border-radius: 0.6rem; overflow-x: auto;
|
||||
background: #100f0c; color: #d8d2c2; font-family: var(--font-mono); font-size: 0.6rem; line-height: 1.3;
|
||||
}
|
||||
.term i, .term b { font-style: normal; font-weight: 400; color: inherit; }
|
||||
.show-cell--m .term i { color: #ff6b56; } .show-cell--m .term b { color: #ff6b56; }
|
||||
.show-cell--r .term i { color: #c9803f; } .show-cell--r .term b { color: #c9803f; }
|
||||
.shot { display: block; width: 100%; height: auto; border-radius: 0.6rem; }
|
||||
.grounds { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.6rem; }
|
||||
.ground { display: grid; place-items: center; padding: 1.2rem; border-radius: 0.6rem; border: 1px solid var(--border); }
|
||||
.ground--fg { background: var(--bg-subtle); color: var(--fg); }
|
||||
.ground .pm { height: 3.2rem; width: auto; }
|
||||
.poll-final { margin-top: 3.5rem; display: flex; flex-direction: column; gap: 1rem; }
|
||||
.poll-final-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 1.25rem; }
|
||||
.poll-final-card {
|
||||
display: flex; flex-direction: column; gap: 0.8rem; align-items: stretch;
|
||||
padding: 1.1rem; border: 1px solid var(--card-border); border-radius: 0.9rem; background: var(--card-bg);
|
||||
}
|
||||
.poll-final-mark { display: grid; place-items: center; padding: 0.8rem; }
|
||||
.poll-final-mark .pm { height: 3.5rem; width: auto; }
|
||||
.home-poll {
|
||||
display: inline-flex; align-items: center; gap: 0.6rem; margin-top: 1.4rem;
|
||||
padding: 0.55rem 0.95rem; border: 1px solid var(--border); border-radius: 999px;
|
||||
background: var(--card-bg); color: var(--fg); text-decoration: none; font-size: 0.92rem;
|
||||
}
|
||||
.home-poll:hover { border-color: var(--accent); }
|
||||
.home-poll .pm { height: 1.1rem; width: auto; color: var(--accent); }
|
||||
.home-poll .pm + .pm { color: #af6428; }
|
||||
@media (prefers-reduced-motion: reduce) { .poll-bar i, .poll-btn { transition: none; } }
|
||||
|
|
|
|||
|
|
@ -10,20 +10,22 @@
|
|||
var busy = false;
|
||||
|
||||
function pct(n, total) { return total ? Math.round((n / total) * 100) : 0; }
|
||||
function each(sel, fn) { Array.prototype.forEach.call(root.querySelectorAll(sel), fn); }
|
||||
|
||||
function render(d) {
|
||||
var total = d.total || 0;
|
||||
root.classList.toggle('poll--voted', !!d.yours);
|
||||
Object.keys(d.counts).forEach(function (k) {
|
||||
var card = root.querySelector('[data-choice="' + k + '"]');
|
||||
if (!card) return;
|
||||
var n = d.counts[k], p = pct(n, total);
|
||||
card.classList.toggle('is-yours', d.yours === k);
|
||||
card.querySelector('.poll-bar i').style.width = p + '%';
|
||||
card.querySelector('[data-count]').textContent = d.yours ? p + ' % · ' + n : '';
|
||||
var b = card.querySelector('[data-vote]');
|
||||
b.disabled = d.yours === k;
|
||||
b.textContent = d.yours === k ? t.tVoted : (d.yours ? t.tChange : b.dataset.label);
|
||||
var n = d.counts[k], p = pct(n, total), mine = d.yours === k;
|
||||
each('[data-choice="' + k + '"]', function (card) {
|
||||
card.classList.toggle('is-yours', mine);
|
||||
var bar = card.querySelector('.poll-bar i'); if (bar) bar.style.width = p + '%';
|
||||
var c = card.querySelector('[data-count]'); if (c) c.textContent = d.yours ? p + ' % · ' + n : '';
|
||||
});
|
||||
each('[data-vote="' + k + '"]', function (b) {
|
||||
b.disabled = mine;
|
||||
b.textContent = mine ? t.tVoted : (d.yours ? t.tChange : b.dataset.label);
|
||||
});
|
||||
});
|
||||
status.textContent = d.yours ? t.tTotal.replace('{n}', total) : '';
|
||||
}
|
||||
|
|
@ -35,7 +37,7 @@
|
|||
.catch(function () { status.textContent = t.tError; });
|
||||
}
|
||||
|
||||
buttons.forEach(function (b) {
|
||||
Array.prototype.forEach.call(buttons, function (b) {
|
||||
b.dataset.label = b.textContent;
|
||||
b.addEventListener('click', function () {
|
||||
if (busy) return;
|
||||
|
|
@ -46,7 +48,7 @@
|
|||
body: JSON.stringify({ choice: b.dataset.vote })
|
||||
})
|
||||
.then(function (r) { if (!r.ok) throw r; return r.json(); })
|
||||
.then(render)
|
||||
.then(function (d) { render(d); status.scrollIntoView({ block: 'nearest' }); })
|
||||
.catch(function () { status.textContent = t.tError; b.disabled = false; })
|
||||
.then(function () { busy = false; });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
title: "Furtka"
|
||||
description: "Offenes Heimserver-Betriebssystem — einfach genug für alle."
|
||||
status: "<span class=\"mono\">26.20-alpha</span> — in Arbeit"
|
||||
poll_link: "/de/logo/"
|
||||
poll_text: "Wir suchen ein Logo — hilf uns beim Aussuchen"
|
||||
# features_today / features_next müssen index-parallel zu content/_index.md bleiben.
|
||||
intro: |
|
||||
**Furtka** ist ein offenes Heimserver-Betriebssystem.
|
||||
|
|
@ -19,11 +21,15 @@ features_today:
|
|||
- "Apps mit einem Klick installieren und entfernen"
|
||||
- "Eine installierte App mit einem Klick aktualisieren (holt das neueste Container-Image)"
|
||||
- "Furtka selbst mit einem Klick aktualisieren — keine Neuinstallation mehr für neue Features"
|
||||
- "Die Bedienseite fragt nach einem Passwort — niemand im WLAN kommt einfach so rein"
|
||||
- "Neue Apps kommen aus einem Online-Katalog, ohne auf ein Furtka-Update zu warten"
|
||||
- "Apps können auf anderen Apps aufbauen — was eine braucht, wird mit installiert"
|
||||
- "Verschlüsselte Verbindung im Heimnetz, in den Einstellungen einschaltbar"
|
||||
features_next_label: "Was als Nächstes kommt"
|
||||
features_next:
|
||||
- "Apps für Fotos, Dateien, Smarthome, Spiele-Streaming und Medien"
|
||||
- "Einfachere Sprache im Einrichtungs-Assistenten"
|
||||
- "Sichere Verbindung im Heimnetz (ohne Warnmeldung im Browser)"
|
||||
- "Den Server auch von unterwegs erreichen"
|
||||
- "Mehrere Server zusammenschalten"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
title: "Furtka"
|
||||
description: "Open-source home server OS — simple enough for everyone."
|
||||
status: "<span class=\"mono\">26.20-alpha</span> — work in progress"
|
||||
poll_link: "/logo/"
|
||||
poll_text: "We're picking a logo — help us choose"
|
||||
# Keep features_today / features_next index-aligned with content/_index.de.md.
|
||||
intro: |
|
||||
**Furtka** is an open-source home server OS.
|
||||
|
|
@ -19,11 +21,15 @@ features_today:
|
|||
- "Install and remove apps with one click"
|
||||
- "Update an installed app with one click (pulls the newest container image)"
|
||||
- "Update Furtka itself with one click — no reinstalling for new features"
|
||||
- "The control page asks for a password — nobody on the Wi-Fi can just walk in"
|
||||
- "New apps arrive from an online catalog, without waiting for a Furtka update"
|
||||
- "Apps can build on other apps — what one needs gets installed alongside it"
|
||||
- "Encrypted connection on your home network, switched on in settings"
|
||||
features_next_label: "What's coming next"
|
||||
features_next:
|
||||
- "Apps for photos, files, smart home, game streaming and media"
|
||||
- "Plainer language in the setup wizard"
|
||||
- "Secure connection on your home network (no browser warning)"
|
||||
- "Reaching your server from outside the house"
|
||||
- "Linking several servers together"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,32 @@ poll: logo
|
|||
sitemap:
|
||||
priority: 0.5
|
||||
m:
|
||||
short: "M"
|
||||
title: "M — das f im Tor"
|
||||
text: "Ein kleines f, aus einem Rundbogentor ausgestanzt. Der Stamm ist der Torpfosten, die zwei Balken sind die Querriegel."
|
||||
r:
|
||||
short: "R"
|
||||
title: "R — Fenster, Rad und F"
|
||||
text: "Ein Bogenfenster mit einem Speichenrad dahinter; in der oberen rechten Scheibe wird die Speiche zum F."
|
||||
show:
|
||||
intro: "Noch unentschieden? Hier ist jedes Zeichen überall dort, wo es funktionieren muss."
|
||||
sizes: "Groß und klein"
|
||||
sizes_text: "Vom Poster bis zum 16-Pixel-Symbol im Browser-Tab. Unter 32 Pixeln wechseln beide auf eine vereinfachte Zeichnung — M verliert nichts, R behält Fenster, Kreuz und Nabe."
|
||||
name: "Mit dem Namen"
|
||||
name_text: "Nebeneinander und gestapelt. Die Wortmarke ist bei beiden dieselbe, also entscheidet nur das Zeichen."
|
||||
browser: "Im Browser"
|
||||
browser_text: "Das Tab-Symbol in echter Größe und die Kopfzeile von furtka.org."
|
||||
nav1: "Doku"
|
||||
nav2: "Blog"
|
||||
phone: "Auf dem Handy"
|
||||
phone_text: "Startbildschirm-Symbol — eckig wie auf Android, rund wie manche Launcher es beschneiden."
|
||||
box: "Auf dem Server"
|
||||
box_text: "Der Text-Willkommensbildschirm, den der Server am Monitor zeigt, und der Startbildschirm beim Booten."
|
||||
shared: "Geteilt"
|
||||
shared_text: "Die Vorschaukarte, die erscheint, wenn jemand einen Link zu furtka.org postet."
|
||||
colour: "In Farbe"
|
||||
colour_text: "Auf hell, auf dunkel, auf der eigenen Farbe und in normaler Textfarbe — jedes Zeichen ist eine Form und nimmt die Farbe an, die es umgibt."
|
||||
final: "Genug gesehen?"
|
||||
labels:
|
||||
vote: "Dieses hier"
|
||||
voted: "Deine Wahl"
|
||||
|
|
@ -26,5 +47,6 @@ labels:
|
|||
*Furtka* ist polnisch für die kleine Gartenpforte — die Tür im Zaun, durch
|
||||
die man hineinkommt, ohne das große Tor aufzumachen. Beide Zeichen spielen
|
||||
damit: das eine zeichnet den Buchstaben ins Tor, das andere schaut durchs
|
||||
Tor auf ein Rad. Unter jedem Zeichen siehst du, wie es in der Kopfzeile
|
||||
der Seite sitzt und als echtes 16-Pixel-Symbol im Browser-Tab.
|
||||
Tor auf ein Rad. Stimm direkt hier ab — oder scroll erst weiter und sieh
|
||||
beide Zeichen überall, wo sie funktionieren müssen, vom Poster bis zum
|
||||
winzigen Symbol im Browser-Tab.
|
||||
|
|
|
|||
|
|
@ -9,11 +9,32 @@ poll: logo
|
|||
sitemap:
|
||||
priority: 0.5
|
||||
m:
|
||||
short: "M"
|
||||
title: "M — the f in the gate"
|
||||
text: "A lowercase f cut out of an arched gate. The stem is the gate post, the two rails are the crossbars."
|
||||
r:
|
||||
short: "R"
|
||||
title: "R — window, wheel and F"
|
||||
text: "An arched window with a wheel of spokes behind it; in the top-right pane the spoke becomes an F."
|
||||
show:
|
||||
intro: "Not sure yet? Here is each mark everywhere it would have to work."
|
||||
sizes: "Big and small"
|
||||
sizes_text: "From a poster to the 16-pixel icon in a browser tab. Below 32 pixels both marks switch to a simplified drawing — M drops nothing, R keeps the window, the cross and the hub."
|
||||
name: "With the name"
|
||||
name_text: "Side by side and stacked. The wordmark is the same for both, so only the mark decides."
|
||||
browser: "In the browser"
|
||||
browser_text: "The tab icon at its real size, and the site header of furtka.org."
|
||||
nav1: "Docs"
|
||||
nav2: "Blog"
|
||||
phone: "On the phone"
|
||||
phone_text: "Home-screen icon — square as on Android, round as some launchers cut it."
|
||||
box: "On the box"
|
||||
box_text: "The text-mode welcome screen your server shows on a monitor, and the boot splash."
|
||||
shared: "Shared"
|
||||
shared_text: "The preview card that appears when someone posts a link to furtka.org."
|
||||
colour: "In colour"
|
||||
colour_text: "On light, on dark, on its own colour, and in plain text colour — each mark is one shape and takes whatever colour is around it."
|
||||
final: "Seen enough?"
|
||||
labels:
|
||||
vote: "This one"
|
||||
voted: "Your pick"
|
||||
|
|
@ -27,5 +48,5 @@ labels:
|
|||
*Furtka* is Polish for a small garden gate — the little door in the fence
|
||||
that lets you in without opening the whole thing. Both marks play with that:
|
||||
one draws the letter into the gate, the other looks through the gate at a
|
||||
wheel. Below each mark you see how it sits in the site header and as a
|
||||
real 16-pixel browser-tab icon.
|
||||
wheel. Vote right here — or scroll on first and see both marks everywhere
|
||||
they would have to work, from a poster to the tiny icon in a browser tab.
|
||||
|
|
|
|||
12
website/data/logo/m.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"display": "<svg class=\"pm\" viewBox=\"0 0 64 64\" aria-hidden=\"true\" focusable=\"false\"><defs><linearGradient id=\"gmd\" x1=\".15\" y1=\"0\" x2=\".7\" y2=\"1\"><stop offset=\"0\" stop-color=\"#d95a45\"/><stop offset=\".55\" stop-color=\"#c03a28\"/><stop offset=\"1\" stop-color=\"#8a2618\"/></linearGradient><clipPath id=\"cmd\" clip-rule=\"evenodd\"><path d=\"M6 60V28a26 26 0 0 1 52 0v32Z M20.0 60V26.5A10.5 10.5 0 0 1 30.5 16H45.0v8.6H32.5a3.5 3.5 0 0 0-3.5 3.5V32h16v8.6H29.0V60Z\"/></clipPath></defs><path fill=\"url(#gmd)\" fill-rule=\"evenodd\" d=\"M6 60V28a26 26 0 0 1 52 0v32Z M20.0 60V26.5A10.5 10.5 0 0 1 30.5 16H45.0v8.6H32.5a3.5 3.5 0 0 0-3.5 3.5V32h16v8.6H29.0V60Z\"/><g clip-path=\"url(#cmd)\"><path fill=\"#fff\" opacity=\".14\" d=\"M6 28a26 26 0 0 1 52 0v4.5a26 26 0 0 0-52 0Z\"/></g></svg>",
|
||||
"flat": "<svg class=\"pm\" viewBox=\"0 0 64 64\" aria-hidden=\"true\" focusable=\"false\"><path fill=\"currentColor\" fill-rule=\"evenodd\" d=\"M6 60V28a26 26 0 0 1 52 0v32Z M20.0 60V26.5A10.5 10.5 0 0 1 30.5 16H45.0v8.6H32.5a3.5 3.5 0 0 0-3.5 3.5V32h16v8.6H29.0V60Z\"/></svg>",
|
||||
"min": "<svg class=\"pm\" viewBox=\"0 0 64 64\" aria-hidden=\"true\" focusable=\"false\"><path fill=\"currentColor\" fill-rule=\"evenodd\" d=\"M5 60V27a27 27 0 0 1 54 0v33Z M19.5 60V26A10 10 0 0 1 29.5 16H46v10H32a2.5 2.5 0 0 0-2.5 2.5V32H46v10H29.5v18Z\"/></svg>",
|
||||
"lockup_h": "<svg class=\"pm\" viewBox=\"0 -4 232.44 64.00\" aria-hidden=\"true\" focusable=\"false\"><g transform=\"scale(1.0000) translate(-6 -2)\"><path fill=\"currentColor\" fill-rule=\"evenodd\" d=\"M6 60V28a26 26 0 0 1 52 0v32Z M20.0 60V26.5A10.5 10.5 0 0 1 30.5 16H45.0v8.6H32.5a3.5 3.5 0 0 0-3.5 3.5V32h16v8.6H29.0V60Z\"/></g><g fill=\"currentColor\"><path transform=\"translate(68.24 58.00) scale(0.05354 -0.05354)\" d=\"M115 0V494H30V600H115V637Q115 687 132.5 724.0Q150 761 192.0 781.5Q234 802 305 802Q333 802 358 799V695Q344 698 324 698Q287 698 273.5 682.0Q260 666 260 632V600H358V494H260V0Z\"/><path transform=\"translate(87.21 58.00) scale(0.05354 -0.05354)\" d=\"M271 -15Q169 -15 119.0 48.5Q69 112 69 218V600H218V244Q218 179 241.5 143.0Q265 107 316 107Q379 107 413.0 156.0Q447 205 447 276V600H596V0H462V90H438Q423 44 382.5 14.5Q342 -15 271 -15Z\"/><path transform=\"translate(121.45 58.00) scale(0.05354 -0.05354)\" d=\"M76 0V600H211V492H235Q241 521 257.0 547.0Q273 573 307.0 589.5Q341 606 399 606H428V471H387Q301 471 263.0 427.0Q225 383 225 294V0Z\"/><path transform=\"translate(144.17 58.00) scale(0.05354 -0.05354)\" d=\"M162 0Q139 27 125.5 65.0Q112 103 112 179V494H30V600H114V750H259V600H358V494H260V196Q260 116 278.0 74.5Q296 33 321 6V0Z\"/><path transform=\"translate(163.14 58.00) scale(0.05354 -0.05354)\" d=\"M78 0V800H226V366H274L449 600H608V594L397 320L632 6V0H457L274 254H226V0Z\"/><path transform=\"translate(195.88 58.00) scale(0.05354 -0.05354)\" d=\"M280 -13Q198 -13 144.0 27.5Q90 68 63.5 138.5Q37 209 37 301Q37 395 64.0 465.0Q91 535 145.0 574.0Q199 613 280 613Q344 613 384.5 587.5Q425 562 445 522H469V600H606V0H469V78H445Q425 41 385.0 14.0Q345 -13 280 -13ZM317 95Q381 95 419.5 151.0Q458 207 458 300Q458 394 419.5 449.5Q381 505 317 505Q257 505 223.5 453.0Q190 401 190 300Q190 199 223.5 147.0Q257 95 317 95Z\"/></g></svg>",
|
||||
"lockup_s": "<svg class=\"pm\" viewBox=\"0 0 164.20 125.89\" aria-hidden=\"true\" focusable=\"false\"><g transform=\"translate(56.10 0) scale(1.0000) translate(-6 -2)\"><path fill=\"currentColor\" fill-rule=\"evenodd\" d=\"M6 60V28a26 26 0 0 1 52 0v32Z M20.0 60V26.5A10.5 10.5 0 0 1 30.5 16H45.0v8.6H32.5a3.5 3.5 0 0 0-3.5 3.5V32h16v8.6H29.0V60Z\"/></g><g fill=\"currentColor\"><path transform=\"translate(0.00 111.76) scale(0.05354 -0.05354)\" d=\"M115 0V494H30V600H115V637Q115 687 132.5 724.0Q150 761 192.0 781.5Q234 802 305 802Q333 802 358 799V695Q344 698 324 698Q287 698 273.5 682.0Q260 666 260 632V600H358V494H260V0Z\"/><path transform=\"translate(18.97 111.76) scale(0.05354 -0.05354)\" d=\"M271 -15Q169 -15 119.0 48.5Q69 112 69 218V600H218V244Q218 179 241.5 143.0Q265 107 316 107Q379 107 413.0 156.0Q447 205 447 276V600H596V0H462V90H438Q423 44 382.5 14.5Q342 -15 271 -15Z\"/><path transform=\"translate(53.21 111.76) scale(0.05354 -0.05354)\" d=\"M76 0V600H211V492H235Q241 521 257.0 547.0Q273 573 307.0 589.5Q341 606 399 606H428V471H387Q301 471 263.0 427.0Q225 383 225 294V0Z\"/><path transform=\"translate(75.93 111.76) scale(0.05354 -0.05354)\" d=\"M162 0Q139 27 125.5 65.0Q112 103 112 179V494H30V600H114V750H259V600H358V494H260V196Q260 116 278.0 74.5Q296 33 321 6V0Z\"/><path transform=\"translate(94.90 111.76) scale(0.05354 -0.05354)\" d=\"M78 0V800H226V366H274L449 600H608V594L397 320L632 6V0H457L274 254H226V0Z\"/><path transform=\"translate(127.64 111.76) scale(0.05354 -0.05354)\" d=\"M280 -13Q198 -13 144.0 27.5Q90 68 63.5 138.5Q37 209 37 301Q37 395 64.0 465.0Q91 535 145.0 574.0Q199 613 280 613Q344 613 384.5 587.5Q425 562 445 522H469V600H606V0H469V78H445Q425 41 385.0 14.0Q345 -13 280 -13ZM317 95Q381 95 419.5 151.0Q458 207 458 300Q458 394 419.5 449.5Q381 505 317 505Q257 505 223.5 453.0Q190 401 190 300Q190 199 223.5 147.0Q257 95 317 95Z\"/></g></svg>",
|
||||
"tab16": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABJklEQVR42sWRvUoDURCFv9msf1hoZyFCGmEjWks2iIkSi9gIIZ2FQWx9BX0Gn0GsrUwjCSmMaB1ICBEsxMJGK8F13bHYmFzWJQQi+HX3zsyZc88VIlyvry7YCf8YZQ9huXfdUeRyEu8s03h4MfvFPFTd1K5FcA4yTzyvIrK/edO6+iVQTa9sWxJUgAmG4wXKztZtu94XqGWT0+JNdUCWGI3H9znfKVS6HzaAeDMlUGNY7hUOc41W05yquc6pwAmQnH2zi8CFFZaCvNHn69dnMTocRYU8gN3buGjUnnN33SeAetpxEdkYDGkG/QkvdBwKKJbxH4Gx5Qj0YLDWcKCSMBzEk220y0A5JoM+FmMy1EE9kyqgrMVlMJKAqpaA2Az+7An/LzA232GMWfyHzvuOAAAAAElFTkSuQmCC",
|
||||
"full16": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABJUlEQVR42sWRz0oCYRTFf3dmkDbSbqCwTQQzPUGkgRriE6TQrnqGwKB1u54jwjbtIxzB0hdIC7fppjb92SQ1t0WMfZlOgUFnd7977uGc8wlj0Mh4yYFK0UaWABDt2ujZ2sXN0yhXzKFawnb7fgVlD0iOcB9BDrKXnUOB8JtAtYTt9vxjYIM4qFSzzc5mJGJF727fr/x4/BGnXE8v735x0Mh4yVeVnmH7NlRr6z7VDsonvEXkYNWvIeSAB03YqXxw9ewADFSKlpFZQ9leb7XPY3zM8hIWgFMHwBI89HN7t9Cp0YJ62ttXZMc4nDfK8AEcAFUS5ndEtlWlgLA4vgtmhgKTkGte583Z6GAIiykR6yBI+0fAyvgOfiGAMjexg7+K8P8CU+Md2cpUHzmzh4cAAAAASUVORK5CYII=",
|
||||
"banner": " ▄▄██████▄▄\n ▄████████████▄\n ██████▀▀▀▀▀▀████\n ████▀ ████\n █████ ██████████\n █████ █████\n █████ ▄▄▄▄▄█████\n █████ ██████████\n █████ ██████████\n ▀▀▀▀ ▀▀▀▀▀▀▀▀▀",
|
||||
"color": "#c03a28",
|
||||
"ink": "#0d0d0f"
|
||||
}
|
||||
12
website/data/logo/r.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"display": "<svg class=\"pm\" viewBox=\"0 0 104 114\" aria-hidden=\"true\" focusable=\"false\"><defs><clipPath id=\"crd\" clip-rule=\"evenodd\"><path clip-rule=\"evenodd\" d=\"M2 112V42A40 40 0 0 1 42 2H62A40 40 0 0 1 102 42V112Z M63 65a11 11 0 1 0-22 0a11 11 0 1 0 22 0Z\"/></clipPath><linearGradient id=\"grd\" gradientUnits=\"userSpaceOnUse\" x1=\"0\" y1=\"2\" x2=\"0\" y2=\"112\"><stop offset=\"0\" stop-color=\"#c9803f\"/><stop offset=\".6\" stop-color=\"#af6428\"/><stop offset=\"1\" stop-color=\"#9a561f\"/></linearGradient></defs><g fill=\"none\" stroke=\"url(#grd)\" stroke-width=\"8\" stroke-linejoin=\"round\"><path d=\"M6 108V42A36 36 0 0 1 42 6H62A36 36 0 0 1 98 42V108Z\"/><path d=\"M52 8V106 M8 65H96 M52 65L4 31.1 M52 65L4 98.9 M52 65L100 98.9\" clip-path=\"url(#crd)\"/><path d=\"M52 22H79M52 37H69\" stroke-linecap=\"round\"/><circle cx=\"52\" cy=\"65\" r=\"15\"/></g></svg>",
|
||||
"flat": "<svg class=\"pm\" viewBox=\"0 0 104 114\" aria-hidden=\"true\" focusable=\"false\"><defs><clipPath id=\"c\" clip-rule=\"evenodd\"><path clip-rule=\"evenodd\" d=\"M2 112V42A40 40 0 0 1 42 2H62A40 40 0 0 1 102 42V112Z M63 65a11 11 0 1 0-22 0a11 11 0 1 0 22 0Z\"/></clipPath></defs><g fill=\"none\" stroke=\"currentColor\" stroke-width=\"8\" stroke-linejoin=\"round\"><path d=\"M6 108V42A36 36 0 0 1 42 6H62A36 36 0 0 1 98 42V108Z\"/><path d=\"M52 8V106 M8 65H96 M52 65L4 31.1 M52 65L4 98.9 M52 65L100 98.9\" clip-path=\"url(#c)\"/><path d=\"M52 22H79M52 37H69\" stroke-linecap=\"round\"/><circle cx=\"52\" cy=\"65\" r=\"15\"/></g></svg>",
|
||||
"min": "<svg class=\"pm\" viewBox=\"0 0 104 114\" aria-hidden=\"true\" focusable=\"false\"><g fill=\"none\" stroke=\"currentColor\" stroke-width=\"11\" stroke-linejoin=\"round\"><path d=\"M6 108V42A36 36 0 0 1 42 6H62A36 36 0 0 1 98 42V108Z\"/><path d=\"M52 8V106M8 65H96\"/><path d=\"M52 23H78M52 39H68\" stroke-linecap=\"round\"/></g><circle cx=\"52\" cy=\"65\" r=\"19\" fill=\"currentColor\"/></svg>",
|
||||
"lockup_h": "<svg class=\"pm\" viewBox=\"0 -4 233.17 64.00\" aria-hidden=\"true\" focusable=\"false\"><g transform=\"scale(0.5273) translate(-2 -2)\"><defs><clipPath id=\"c\" clip-rule=\"evenodd\"><path clip-rule=\"evenodd\" d=\"M2 112V42A40 40 0 0 1 42 2H62A40 40 0 0 1 102 42V112Z M63 65a11 11 0 1 0-22 0a11 11 0 1 0 22 0Z\"/></clipPath></defs><g fill=\"none\" stroke=\"currentColor\" stroke-width=\"8\" stroke-linejoin=\"round\"><path d=\"M6 108V42A36 36 0 0 1 42 6H62A36 36 0 0 1 98 42V108Z\"/><path d=\"M52 8V106 M8 65H96 M52 65L4 31.1 M52 65L4 98.9 M52 65L100 98.9\" clip-path=\"url(#c)\"/><path d=\"M52 22H79M52 37H69\" stroke-linecap=\"round\"/><circle cx=\"52\" cy=\"65\" r=\"15\"/></g></g><g fill=\"currentColor\"><path transform=\"translate(68.97 58.00) scale(0.05354 -0.05354)\" d=\"M115 0V494H30V600H115V637Q115 687 132.5 724.0Q150 761 192.0 781.5Q234 802 305 802Q333 802 358 799V695Q344 698 324 698Q287 698 273.5 682.0Q260 666 260 632V600H358V494H260V0Z\"/><path transform=\"translate(87.94 58.00) scale(0.05354 -0.05354)\" d=\"M271 -15Q169 -15 119.0 48.5Q69 112 69 218V600H218V244Q218 179 241.5 143.0Q265 107 316 107Q379 107 413.0 156.0Q447 205 447 276V600H596V0H462V90H438Q423 44 382.5 14.5Q342 -15 271 -15Z\"/><path transform=\"translate(122.17 58.00) scale(0.05354 -0.05354)\" d=\"M76 0V600H211V492H235Q241 521 257.0 547.0Q273 573 307.0 589.5Q341 606 399 606H428V471H387Q301 471 263.0 427.0Q225 383 225 294V0Z\"/><path transform=\"translate(144.90 58.00) scale(0.05354 -0.05354)\" d=\"M162 0Q139 27 125.5 65.0Q112 103 112 179V494H30V600H114V750H259V600H358V494H260V196Q260 116 278.0 74.5Q296 33 321 6V0Z\"/><path transform=\"translate(163.87 58.00) scale(0.05354 -0.05354)\" d=\"M78 0V800H226V366H274L449 600H608V594L397 320L632 6V0H457L274 254H226V0Z\"/><path transform=\"translate(196.60 58.00) scale(0.05354 -0.05354)\" d=\"M280 -13Q198 -13 144.0 27.5Q90 68 63.5 138.5Q37 209 37 301Q37 395 64.0 465.0Q91 535 145.0 574.0Q199 613 280 613Q344 613 384.5 587.5Q425 562 445 522H469V600H606V0H469V78H445Q425 41 385.0 14.0Q345 -13 280 -13ZM317 95Q381 95 419.5 151.0Q458 207 458 300Q458 394 419.5 449.5Q381 505 317 505Q257 505 223.5 453.0Q190 401 190 300Q190 199 223.5 147.0Q257 95 317 95Z\"/></g></svg>",
|
||||
"lockup_s": "<svg class=\"pm\" viewBox=\"0 0 164.20 125.89\" aria-hidden=\"true\" focusable=\"false\"><g transform=\"translate(55.74 0) scale(0.5273) translate(-2 -2)\"><defs><clipPath id=\"c\" clip-rule=\"evenodd\"><path clip-rule=\"evenodd\" d=\"M2 112V42A40 40 0 0 1 42 2H62A40 40 0 0 1 102 42V112Z M63 65a11 11 0 1 0-22 0a11 11 0 1 0 22 0Z\"/></clipPath></defs><g fill=\"none\" stroke=\"currentColor\" stroke-width=\"8\" stroke-linejoin=\"round\"><path d=\"M6 108V42A36 36 0 0 1 42 6H62A36 36 0 0 1 98 42V108Z\"/><path d=\"M52 8V106 M8 65H96 M52 65L4 31.1 M52 65L4 98.9 M52 65L100 98.9\" clip-path=\"url(#c)\"/><path d=\"M52 22H79M52 37H69\" stroke-linecap=\"round\"/><circle cx=\"52\" cy=\"65\" r=\"15\"/></g></g><g fill=\"currentColor\"><path transform=\"translate(0.00 111.76) scale(0.05354 -0.05354)\" d=\"M115 0V494H30V600H115V637Q115 687 132.5 724.0Q150 761 192.0 781.5Q234 802 305 802Q333 802 358 799V695Q344 698 324 698Q287 698 273.5 682.0Q260 666 260 632V600H358V494H260V0Z\"/><path transform=\"translate(18.97 111.76) scale(0.05354 -0.05354)\" d=\"M271 -15Q169 -15 119.0 48.5Q69 112 69 218V600H218V244Q218 179 241.5 143.0Q265 107 316 107Q379 107 413.0 156.0Q447 205 447 276V600H596V0H462V90H438Q423 44 382.5 14.5Q342 -15 271 -15Z\"/><path transform=\"translate(53.21 111.76) scale(0.05354 -0.05354)\" d=\"M76 0V600H211V492H235Q241 521 257.0 547.0Q273 573 307.0 589.5Q341 606 399 606H428V471H387Q301 471 263.0 427.0Q225 383 225 294V0Z\"/><path transform=\"translate(75.93 111.76) scale(0.05354 -0.05354)\" d=\"M162 0Q139 27 125.5 65.0Q112 103 112 179V494H30V600H114V750H259V600H358V494H260V196Q260 116 278.0 74.5Q296 33 321 6V0Z\"/><path transform=\"translate(94.90 111.76) scale(0.05354 -0.05354)\" d=\"M78 0V800H226V366H274L449 600H608V594L397 320L632 6V0H457L274 254H226V0Z\"/><path transform=\"translate(127.64 111.76) scale(0.05354 -0.05354)\" d=\"M280 -13Q198 -13 144.0 27.5Q90 68 63.5 138.5Q37 209 37 301Q37 395 64.0 465.0Q91 535 145.0 574.0Q199 613 280 613Q344 613 384.5 587.5Q425 562 445 522H469V600H606V0H469V78H445Q425 41 385.0 14.0Q345 -13 280 -13ZM317 95Q381 95 419.5 151.0Q458 207 458 300Q458 394 419.5 449.5Q381 505 317 505Q257 505 223.5 453.0Q190 401 190 300Q190 199 223.5 147.0Q257 95 317 95Z\"/></g></svg>",
|
||||
"tab16": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAQCAYAAADJViUEAAAB4klEQVR42o2TPWhTYRSGn/Pdm7Qd/NnqpoPYqIvVguAP7SwUmlRrhw5qrx1dHF0c7OoiFElTOyhOpjFrJ6uIgihSKKYloiBCwSJ00bTJva9DbtubEKHveM77nPN+Bz4joYUgk3PGLYl+oCfR+gt8FJrLFVbLO0UDmL9xrPuQ3/3cIGuwLrQko8dhQxF6ZXI10CDQK3jR3dOYuPKouuUADqe68gYjwP3tTe9otrA6jngicRBRk6Jvkp4iKxtc3aqlZgDs5dSp84qi94iH2bnK3Z1IxckTgTM3S6veGXwQ3AEGXKToOlCv400nXV7kfgBImjazXD3tHcgWKhe2w8YDoCFj3JnUB1Ydm1v5nYQjz44DmNk9SQt+PZwCGJuv/gK+msg4sBSm7SQoMCPqjW/6zAsZ2qzXZtg7cw3o8umgUtCXNyyIR02EHn9uFr4vtfusFGQWDU5j9raZl1RkGrGkCYSsjKPenKeLghU/jplGOtd0mmdtGwSG0Y8UxqU0gB+P/pmdrZzZjT158g2mSwn4da7wZXC3fzvzGYHr9GbfC7OCUkwWUy4a7eTrCA/n1zYs4nEM54fzaxv7hvcrKwWZReAsUGw5kunI3sew9TZuFPjkA8vAAHCtZaoMQWjY5f8sXv4HrfW+WZX2x04AAAAASUVORK5CYII=",
|
||||
"full16": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAQCAYAAADJViUEAAAChUlEQVR42nWTT4jUZRjHP993fr/ZSSSKoFu4UDC/TQNLErrUVQndSaigw2o0oyRG7KkgFAKxS9TFIpp1W4I6eHCmdutSHcwgoaK10FndFkwIRNSD5u7On9/77TA7y4T1PT087/u8z/f7fd5HDKH58paHnHT3AtuwNg7yhtvgX9LEM7s+uvTXIB8GQaNaftWF3gLWdqMzwrPAVuFZxA9BeqrXC5ca1Wz/oEYAjVq2D/OeCOOVqQtnAOZeeuz+7obuTQhvS14BIHrE8qTFoT31i5/p5MHNG9NOfkVmb+XEwuzg1VMT2QMqcl1SE+j2+XseqWW7vqFd3JQUO/HNiEuBpDWsX6GUwyo2p2X+7uRh7oWZ81cBmtXs2HKpsyNER7CWovL5Rq38+qA4KRaSNbvGLb84Enpj6wbKP2E9mgQFsH9Emoxm5lQ1e6aU9w6s5Ms7giDEMLV7+sLnAq+zilpFjKy7Xam3vk1D3CpZ7UJ6OYT4LvBHVP5Oszr2Af+BZG2Oo83a2Bu5QdG/Wq6EGHaOn2h9//VrWx7uLPcWm7XsKqgN4OhRwbU1XdwLbOvrCSWItNPiEkAnp2Mh9c/bQ/f7nQW/Veqt/QY1atmk7GfTbvurL2rln2Ontx3II5x+rt56X+DmK9nHAGpWx45iPyglhyPdadDmQs6hPPFOrIOI47a+FP4Q/Gck31dwcgTpWuhTZTTSOy+FO2maPr77k4U5uve8BUC3dHjPVOubEP0EaClQ+B14BCCJuCx4GnmiUl84yf9gfPribeBAo1b+zvCp8Y2koLAYo88B9/3706+UjK4rWZloVLNVhlYM61whaDGB/CzSk8Dzw53c35l5o1130ZBvQTz7D8VtHXvXpMGjAAAAAElFTkSuQmCC",
|
||||
"banner": " ▄▄████████▄▄\n ▄██▀ ██▄▄▄███▄\n▄█▀ ██▀▀▀▀ ▀█▄\n██ ████▀ ██\n██ ▄████▄ ██\n██████████████████\n██ ██████ ██\n██ ██ ██\n██ ██ ██\n██████████████████",
|
||||
"color": "#af6428",
|
||||
"ink": "#151515"
|
||||
}
|
||||
|
|
@ -12,6 +12,9 @@
|
|||
<span aria-hidden="true">→</span>
|
||||
</a>
|
||||
</p>
|
||||
{{ with .Params.poll_link }}
|
||||
<p class="reveal"><a class="home-poll" href="{{ . }}">{{ partial "mark-m.html" (dict "class" "" "id" "hm") }}{{ partial "mark-r.html" (dict "class" "" "id" "hr") }}<span>{{ $.Params.poll_text }} →</span></a></p>
|
||||
{{ end }}
|
||||
</header>
|
||||
|
||||
{{ with .Params.intro }}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
{{ define "main" }}
|
||||
{{- $p := .Params -}}
|
||||
{{- $M := hugo.Data.logo.m -}}
|
||||
{{- $R := hugo.Data.logo.r -}}
|
||||
{{- $s := $p.show -}}
|
||||
<article class="poll" data-poll="{{ $p.poll }}" data-api="/api/poll/{{ $p.poll }}">
|
||||
<header class="page-header">
|
||||
<h1>{{ .Title }}</h1>
|
||||
|
|
@ -7,40 +10,156 @@
|
|||
</header>
|
||||
<div class="prose">{{ .Content }}</div>
|
||||
|
||||
<div class="poll-grid">
|
||||
<section class="poll-card" data-choice="m" aria-labelledby="poll-m-title">
|
||||
<div class="poll-art poll-art--m">{{ partial "mark-m.html" (dict "class" "pm--big" "id" "m") }}</div>
|
||||
<div class="poll-ctx">
|
||||
<span class="poll-ctx-head">{{ partial "mark-m.html" (dict "class" "pm--head" "id" "mh") }}<span class="poll-ctx-name">Furtka</span></span>
|
||||
<span class="poll-ctx-tab"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABL0lEQVQ4jcWRP0tCYRTGf+d6+0dDbQ0RuAQaNYdXIi1ssCUQt4YkWvsK9Rn6DNHclEsoDt2oWVDEoCEaWmoKutk9Dd6rLzczwaHf9r7nPM953vMKEa7XVxfsWOcYZQ9hObhuKXI5iXeWdh9ezH4xDxUnuWvhn4PMR40DXkVkf/OmcfXDoJJa2bbELwMTv4hDPF/Z2bpt1noG1Ux8WrypFsjSH+KQx/e5TiJfbn/YAOLNFEENsdwrHGbdRt1UVZ3EqcAJEJ99swvAhdUt+Tmjr6Nfn4WoOIoKOQA7mLho1J6zd+0ngFoq4SCy0RdpGg0UQeKugWIZ/+EbU45AD/pjjQQqMSPBYDJuswSUBuyghzXMYBSGJqilk3mUtfBs7mAkA1UtAgN3EDL2E/7fYGy+AWGMWfwfY1MaAAAAAElFTkSuQmCC" width="16" height="16" alt="">Furtka</span>
|
||||
</div>
|
||||
<h2 id="poll-m-title">{{ $p.m.title }}</h2>
|
||||
<p class="poll-desc">{{ $p.m.text }}</p>
|
||||
<button type="button" class="poll-btn" data-vote="m">{{ $p.labels.vote }}</button>
|
||||
{{/* ── Quick vote ─────────────────────────────────────────── */}}
|
||||
<div class="poll-grid" id="vote">
|
||||
{{ range $k, $d := (dict "m" $M "r" $R) }}
|
||||
{{ $c := index $p $k }}
|
||||
<section class="poll-card" data-choice="{{ $k }}" aria-labelledby="poll-{{ $k }}-title">
|
||||
<div class="poll-art poll-art--{{ $k }}">{{ $d.display | safeHTML }}</div>
|
||||
<h2 id="poll-{{ $k }}-title">{{ $c.title }}</h2>
|
||||
<p class="poll-desc">{{ $c.text }}</p>
|
||||
<button type="button" class="poll-btn" data-vote="{{ $k }}">{{ $p.labels.vote }}</button>
|
||||
<div class="poll-bar" aria-hidden="true"><i></i></div>
|
||||
<p class="poll-count" data-count="m"></p>
|
||||
<p class="poll-count" data-count="{{ $k }}"></p>
|
||||
</section>
|
||||
|
||||
<section class="poll-card" data-choice="r" aria-labelledby="poll-r-title">
|
||||
<div class="poll-art poll-art--r">{{ partial "mark-r.html" (dict "class" "pm--big" "id" "r") }}</div>
|
||||
<div class="poll-ctx">
|
||||
<span class="poll-ctx-head poll-ctx-head--r">{{ partial "mark-r.html" (dict "class" "pm--head" "id" "rh") }}<span class="poll-ctx-name">Furtka</span></span>
|
||||
<span class="poll-ctx-tab"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAQCAYAAADJViUEAAAABmJLR0QA/wD/AP+gvaeTAAAB30lEQVQokY2TPUhVYRzGf8855/ox9LHZVkPkrZYsIegDnQPBey1zcKi8ObY0tjTk2hJEXK85FE3pzdUpiyiIIgRJxSiIQEgCl7p6Pedp8GhHuYHP+P8/v+d93hdekdFkKV8MxA2bDqA1s/oDfDAeK1YWpraGAhi/dqTlQNTyTFAQLBvPWLQGqDvBL+WgBu4C2gzPW1o3Bi89WFoLAA7mmsuCXuDu+mp4uFBZGMA8ttmPqdnJV9tPsKYEl9dquYcAejF84qyT5B3mfmFs/vZWpYmhY6VAwSg79Vbw3nAL6AwSJ1eBep1wJOsKk+A7gO0RScV6U7ivUJk/tx5v3AM2LAYC2e2gpf6xuV9ZOAl1FEDSHduTUT0eBugfX/oJfJHJB6Ac8noWNEgkbembPg1julfrtYfbBlEDmiMaqFpqLwuV0qjBOOT39cq3md0+VUv5acFJpDebfcklcq+yJjDWFAH1zTyfN8xFac0m7DObToXadYJBiA7sOB01AURp9I/C6Pyp7dpDx18jX8jAr4qVz13b+5v5TxiCRneOwrhgqKbkRC5I+hr5GsI95cUVJTxK4XJPeXFlz/BepWopPw2cBiayC8uH/n0MLe/i+oCPETALdAJXdqRaGGKhi/85ePYvrfW+Wa23Y78AAAAASUVORK5CYII=" width="15" height="16" alt="">Furtka</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<h2 id="poll-r-title">{{ $p.r.title }}</h2>
|
||||
<p class="poll-desc">{{ $p.r.text }}</p>
|
||||
<button type="button" class="poll-btn" data-vote="r">{{ $p.labels.vote }}</button>
|
||||
<div class="poll-bar" aria-hidden="true"><i></i></div>
|
||||
<p class="poll-count" data-count="r"></p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<p class="poll-status" role="status" aria-live="polite"
|
||||
data-t-voted="{{ $p.labels.voted }}"
|
||||
data-t-total="{{ $p.labels.total }}"
|
||||
data-t-error="{{ $p.labels.error }}"
|
||||
data-t-nojs="{{ $p.labels.nojs }}"
|
||||
data-t-change="{{ $p.labels.change }}">{{ $p.labels.nojs }}</p>
|
||||
|
||||
{{/* ── Showcase ───────────────────────────────────────────── */}}
|
||||
<div class="show">
|
||||
<p class="section-eyebrow show-intro">{{ $s.intro }}</p>
|
||||
|
||||
<section class="show-row">
|
||||
<h2 class="show-h">{{ $s.sizes }}</h2>
|
||||
<p class="show-p">{{ $s.sizes_text }}</p>
|
||||
<div class="show-pair">
|
||||
{{ range $k, $d := (dict "m" $M "r" $R) }}
|
||||
<div class="show-cell show-cell--{{ $k }}">
|
||||
<span class="show-tag">{{ index $p $k "short" }}</span>
|
||||
<div class="sizes">
|
||||
<figure><span class="sz sz-96">{{ $d.flat | safeHTML }}</span><figcaption>96</figcaption></figure>
|
||||
<figure><span class="sz sz-48">{{ $d.flat | safeHTML }}</span><figcaption>48</figcaption></figure>
|
||||
<figure><span class="sz sz-32">{{ $d.min | safeHTML }}</span><figcaption>32</figcaption></figure>
|
||||
<figure><span class="sz sz-24">{{ $d.min | safeHTML }}</span><figcaption>24</figcaption></figure>
|
||||
<figure><img class="px" src="{{ $d.tab16 | safeURL }}" width="16" height="16" alt=""><figcaption>16</figcaption></figure>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="show-row">
|
||||
<h2 class="show-h">{{ $s.name }}</h2>
|
||||
<p class="show-p">{{ $s.name_text }}</p>
|
||||
<div class="show-pair">
|
||||
{{ range $k, $d := (dict "m" $M "r" $R) }}
|
||||
<div class="show-cell show-cell--{{ $k }}">
|
||||
<span class="show-tag">{{ index $p $k "short" }}</span>
|
||||
<div class="lock lock-h">{{ $d.lockup_h | safeHTML }}</div>
|
||||
<div class="lock lock-s">{{ $d.lockup_s | safeHTML }}</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="show-row">
|
||||
<h2 class="show-h">{{ $s.browser }}</h2>
|
||||
<p class="show-p">{{ $s.browser_text }}</p>
|
||||
<div class="show-pair">
|
||||
{{ range $k, $d := (dict "m" $M "r" $R) }}
|
||||
<div class="show-cell show-cell--{{ $k }}">
|
||||
<span class="show-tag">{{ index $p $k "short" }}</span>
|
||||
<div class="mock">
|
||||
<div class="tabbar"><span class="tab"><img class="px" src="{{ $d.tab16 | safeURL }}" width="16" height="16" alt="">Furtka</span></div>
|
||||
<div class="urlbar">https://furtka.org</div>
|
||||
<div class="uihead"><span class="uihead-mark">{{ $d.flat | safeHTML }}</span><span class="uihead-name">Furtka</span><span class="uihead-nav"><span>{{ $s.nav1 }}</span><span>{{ $s.nav2 }}</span></span></div>
|
||||
<div class="uibody"><i></i><i class="s"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="show-row">
|
||||
<h2 class="show-h">{{ $s.phone }}</h2>
|
||||
<p class="show-p">{{ $s.phone_text }}</p>
|
||||
<div class="show-pair">
|
||||
{{ range $k, $d := (dict "m" $M "r" $R) }}
|
||||
<div class="show-cell show-cell--{{ $k }}">
|
||||
<span class="show-tag">{{ index $p $k "short" }}</span>
|
||||
<div class="tiles"><img src="/img/logo/{{ $k }}/tile.png" width="96" height="96" alt=""><img src="/img/logo/{{ $k }}/tile-round.png" width="96" height="96" alt=""></div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="show-row">
|
||||
<h2 class="show-h">{{ $s.box }}</h2>
|
||||
<p class="show-p">{{ $s.box_text }}</p>
|
||||
<div class="show-pair">
|
||||
{{ range $k, $d := (dict "m" $M "r" $R) }}
|
||||
<div class="show-cell show-cell--{{ $k }}">
|
||||
<span class="show-tag">{{ index $p $k "short" }}</span>
|
||||
<pre class="term"><i>{{ $d.banner }}</i>
|
||||
|
||||
<b>Furtka is ready.</b> Open http://furtka.local</pre>
|
||||
<img class="shot" src="/img/logo/{{ $k }}/splash.png" width="960" height="540" alt="">
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="show-row">
|
||||
<h2 class="show-h">{{ $s.shared }}</h2>
|
||||
<p class="show-p">{{ $s.shared_text }}</p>
|
||||
<div class="show-pair">
|
||||
{{ range $k, $d := (dict "m" $M "r" $R) }}
|
||||
<div class="show-cell show-cell--{{ $k }}">
|
||||
<span class="show-tag">{{ index $p $k "short" }}</span>
|
||||
<img class="shot" src="/img/logo/{{ $k }}/social.png" width="600" height="315" alt="">
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="show-row">
|
||||
<h2 class="show-h">{{ $s.colour }}</h2>
|
||||
<p class="show-p">{{ $s.colour_text }}</p>
|
||||
<div class="show-pair">
|
||||
{{ range $k, $d := (dict "m" $M "r" $R) }}
|
||||
<div class="show-cell show-cell--{{ $k }}">
|
||||
<span class="show-tag">{{ index $p $k "short" }}</span>
|
||||
<div class="grounds">
|
||||
<span class="ground" style="background:#f7f6f3;color:{{ $d.color }}">{{ $d.flat | safeHTML }}</span>
|
||||
<span class="ground" style="background:{{ $d.ink }};color:{{ $d.color }}">{{ $d.flat | safeHTML }}</span>
|
||||
<span class="ground" style="background:{{ $d.color }};color:#f7f6f3">{{ $d.flat | safeHTML }}</span>
|
||||
<span class="ground ground--fg">{{ $d.flat | safeHTML }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{{/* ── Vote again at the end ─────────────────────────────── */}}
|
||||
<section class="poll-final">
|
||||
<h2 class="show-h">{{ $s.final }}</h2>
|
||||
<div class="poll-final-row">
|
||||
{{ range $k, $d := (dict "m" $M "r" $R) }}
|
||||
<div class="poll-final-card" data-choice="{{ $k }}">
|
||||
<span class="poll-final-mark show-cell--{{ $k }}">{{ $d.flat | safeHTML }}</span>
|
||||
<button type="button" class="poll-btn" data-vote="{{ $k }}">{{ $p.labels.vote }} — {{ index $p $k "short" }}</button>
|
||||
<div class="poll-bar" aria-hidden="true"><i></i></div>
|
||||
<p class="poll-count" data-count="{{ $k }}"></p>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
<p class="poll-note">{{ $p.labels.note | safeHTML }}</p>
|
||||
</article>
|
||||
{{ $js := resources.Get "js/poll.js" | minify | fingerprint }}
|
||||
|
|
|
|||
BIN
website/static/img/logo/m/social.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
website/static/img/logo/m/splash.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
website/static/img/logo/m/tile-round.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
website/static/img/logo/m/tile.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
website/static/img/logo/r/social.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
website/static/img/logo/r/splash.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
website/static/img/logo/r/tile-round.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
website/static/img/logo/r/tile.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
9
website/tools/FONT-PROVENANCE.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Build-time font
|
||||
|
||||
`FamiljenGrotesk-SemiBold.ttf` — Familjen Grotesk, weight 600, fetched from
|
||||
Google Fonts (fonts.gstatic.com, family `Familjen+Grotesk:wght@600`) on
|
||||
2026-08-26. Licence: SIL Open Font License 1.1.
|
||||
|
||||
It is used **only** by `tools/logo-assets.py` to outline the wordmark into
|
||||
SVG paths and to render the splash / social-card PNGs. The site itself ships
|
||||
no webfonts.
|
||||
BIN
website/tools/FamiljenGrotesk-SemiBold.ttf
Normal file
254
website/tools/logo-assets.py
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate the logo-poll showcase assets for /logo/ from one geometry source.
|
||||
|
||||
python3 website/tools/logo-assets.py
|
||||
|
||||
Writes website/static/img/logo/{m,r}/*.png and website/data/logo/{m,r}.json.
|
||||
Needs: cairosvg, Pillow, fontTools (pip). Run it on the dev box and commit
|
||||
the output — the site build itself needs nothing beyond Hugo.
|
||||
|
||||
Mark M: lowercase f knocked out of a gate, 64-unit grid (even-odd fill).
|
||||
Mark R: window, wheel and F, 104x114 grid, stroke 8 (from Robert's sketch).
|
||||
"""
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import cairosvg
|
||||
from fontTools.pens.svgPathPen import SVGPathPen
|
||||
from fontTools.ttLib import TTFont
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SITE = os.path.dirname(HERE)
|
||||
FONT = os.path.join(HERE, "FamiljenGrotesk-SemiBold.ttf")
|
||||
XMLNS = 'xmlns="http://www.w3.org/2000/svg"'
|
||||
|
||||
# ---------------------------------------------------------------- geometry
|
||||
M_FLAT = (
|
||||
"M6 60V28a26 26 0 0 1 52 0v32Z M20.0 60V26.5A10.5 10.5 0 0 1 30.5 16H45.0v8.6"
|
||||
"H32.5a3.5 3.5 0 0 0-3.5 3.5V32h16v8.6H29.0V60Z"
|
||||
)
|
||||
M_MIN = (
|
||||
"M5 60V27a27 27 0 0 1 54 0v33Z M19.5 60V26A10 10 0 0 1 29.5 16H46v10H32"
|
||||
"a2.5 2.5 0 0 0-2.5 2.5V32H46v10H29.5v18Z"
|
||||
)
|
||||
R_FRAME = "M6 108V42A36 36 0 0 1 42 6H62A36 36 0 0 1 98 42V108Z"
|
||||
R_OUTER = "M2 112V42A40 40 0 0 1 42 2H62A40 40 0 0 1 102 42V112Z"
|
||||
R_HOLE = "M63 65a11 11 0 1 0-22 0a11 11 0 1 0 22 0Z"
|
||||
R_SPOKES = "M52 8V106 M8 65H96 M52 65L4 31.1 M52 65L4 98.9 M52 65L100 98.9"
|
||||
R_ARMS = "M52 22H79M52 37H69"
|
||||
|
||||
MARKS = {
|
||||
"m": {
|
||||
"vb": (0, 0, 64, 64),
|
||||
"box": (6, 2, 52, 58), # x, y, w, h of the drawn mark inside the viewBox
|
||||
"color": "#c03a28",
|
||||
"ink": "#0d0d0f",
|
||||
"grad": ("#d95a45", "#c03a28", "#8a2618"),
|
||||
},
|
||||
"r": {
|
||||
"vb": (0, 0, 104, 114),
|
||||
"box": (2, 2, 100, 110),
|
||||
"color": "#af6428",
|
||||
"ink": "#151515",
|
||||
"grad": ("#c9803f", "#af6428", "#9a561f"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def body(k, tier, color="currentColor", uid=""):
|
||||
"""SVG inner markup for mark k at tier display | flat | min."""
|
||||
if k == "m":
|
||||
if tier == "display":
|
||||
g = MARKS["m"]["grad"]
|
||||
return (
|
||||
f'<defs><linearGradient id="g{uid}" x1=".15" y1="0" x2=".7" y2="1">'
|
||||
f'<stop offset="0" stop-color="{g[0]}"/><stop offset=".55" stop-color="{g[1]}"/>'
|
||||
f'<stop offset="1" stop-color="{g[2]}"/></linearGradient>'
|
||||
f'<clipPath id="c{uid}" clip-rule="evenodd"><path d="{M_FLAT}"/></clipPath></defs>'
|
||||
f'<path fill="url(#g{uid})" fill-rule="evenodd" d="{M_FLAT}"/>'
|
||||
f'<g clip-path="url(#c{uid})"><path fill="#fff" opacity=".14" '
|
||||
f'd="M6 28a26 26 0 0 1 52 0v4.5a26 26 0 0 0-52 0Z"/></g>'
|
||||
)
|
||||
path = M_MIN if tier == "min" else M_FLAT
|
||||
return f'<path fill="{color}" fill-rule="evenodd" d="{path}"/>'
|
||||
if tier == "min":
|
||||
return (
|
||||
f'<g fill="none" stroke="{color}" stroke-width="11" stroke-linejoin="round">'
|
||||
f'<path d="{R_FRAME}"/><path d="M52 8V106M8 65H96"/>'
|
||||
f'<path d="M52 23H78M52 39H68" stroke-linecap="round"/></g>'
|
||||
f'<circle cx="52" cy="65" r="19" fill="{color}"/>'
|
||||
)
|
||||
defs = (
|
||||
f'<clipPath id="c{uid}" clip-rule="evenodd"><path clip-rule="evenodd" '
|
||||
f'd="{R_OUTER} {R_HOLE}"/></clipPath>'
|
||||
)
|
||||
if tier == "display":
|
||||
g = MARKS["r"]["grad"]
|
||||
defs += (
|
||||
f'<linearGradient id="g{uid}" gradientUnits="userSpaceOnUse" x1="0" y1="2" x2="0" y2="112">'
|
||||
f'<stop offset="0" stop-color="{g[0]}"/><stop offset=".6" stop-color="{g[1]}"/>'
|
||||
f'<stop offset="1" stop-color="{g[2]}"/></linearGradient>'
|
||||
)
|
||||
color = f"url(#g{uid})"
|
||||
return (
|
||||
f"<defs>{defs}</defs>"
|
||||
f'<g fill="none" stroke="{color}" stroke-width="8" stroke-linejoin="round">'
|
||||
f'<path d="{R_FRAME}"/><path d="{R_SPOKES}" clip-path="url(#c{uid})"/>'
|
||||
f'<path d="{R_ARMS}" stroke-linecap="round"/><circle cx="52" cy="65" r="15"/></g>'
|
||||
)
|
||||
|
||||
|
||||
def svg(k, tier, color="currentColor", uid="", cls="pm", standalone=False):
|
||||
x, y, w, h = MARKS[k]["vb"]
|
||||
ns = f" {XMLNS}" if standalone else ""
|
||||
return (
|
||||
f'<svg class="{cls}"{ns} viewBox="{x} {y} {w} {h}" aria-hidden="true" focusable="false">'
|
||||
f"{body(k, tier, color, uid)}</svg>"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- raster
|
||||
def raster(svgtxt, w, h):
|
||||
png = cairosvg.svg2png(bytestring=svgtxt.encode(), output_width=w, output_height=h)
|
||||
return Image.open(io.BytesIO(png)).convert("RGBA")
|
||||
|
||||
|
||||
def mark_png(k, tier, height, color=None):
|
||||
_, _, w, h = MARKS[k]["vb"]
|
||||
return raster(svg(k, tier, color or MARKS[k]["color"], standalone=True), round(height * w / h), height)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- wordmark
|
||||
_font = TTFont(FONT)
|
||||
_upm = _font["head"].unitsPerEm
|
||||
_cap = _font["OS/2"].sCapHeight or int(_upm * 0.7)
|
||||
_glyphs = _font.getGlyphSet()
|
||||
_cmap = _font.getBestCmap()
|
||||
|
||||
|
||||
def wordmark(text, size, x0=0.0, baseline=0.0, tracking=-0.028):
|
||||
"""Outline text as one SVG path; returns (path_d, advance_width)."""
|
||||
s = size / _upm
|
||||
parts, x = [], x0
|
||||
for ch in text:
|
||||
name = _cmap[ord(ch)]
|
||||
pen = SVGPathPen(_glyphs)
|
||||
g = _glyphs[name]
|
||||
g.draw(pen)
|
||||
d = pen.getCommands()
|
||||
if d:
|
||||
parts.append(f'<path transform="translate({x:.2f} {baseline:.2f}) scale({s:.5f} {-s:.5f})" d="{d}"/>')
|
||||
x += g.width * s + tracking * size
|
||||
return "".join(parts), x - x0 - tracking * size
|
||||
|
||||
|
||||
def lockup(k, stacked=False):
|
||||
"""Mark + 'furtka', outlined, currentColor. Returns an inline SVG string."""
|
||||
bx, by, bw, bh = MARKS[k]["box"]
|
||||
H = 58.0 # mark height in lockup units
|
||||
sc = H / bh
|
||||
mw = bw * sc
|
||||
size = H * 0.72 * _upm / _cap # cap height = 72 % of the mark height
|
||||
if stacked:
|
||||
paths, tw = wordmark("furtka", size)
|
||||
gap = 12
|
||||
total_w = max(mw, tw)
|
||||
total_h = H + gap + size * _cap / _upm + size * 0.22
|
||||
mx = (total_w - mw) / 2
|
||||
tx = (total_w - tw) / 2
|
||||
base = H + gap + size * _cap / _upm
|
||||
paths, _ = wordmark("furtka", size, x0=tx, baseline=base)
|
||||
inner = (
|
||||
f'<g transform="translate({mx:.2f} 0) scale({sc:.4f}) translate({-bx} {-by})">{body(k, "flat")}</g>'
|
||||
f'<g fill="currentColor">{paths}</g>'
|
||||
)
|
||||
return f'<svg class="pm" viewBox="0 0 {total_w:.2f} {total_h:.2f}" aria-hidden="true" focusable="false">{inner}</svg>'
|
||||
gap = H * 0.28
|
||||
paths, tw = wordmark("furtka", size, x0=mw + gap, baseline=H)
|
||||
total_w = mw + gap + tw
|
||||
inner = (
|
||||
f'<g transform="scale({sc:.4f}) translate({-bx} {-by})">{body(k, "flat")}</g>'
|
||||
f'<g fill="currentColor">{paths}</g>'
|
||||
)
|
||||
return f'<svg class="pm" viewBox="0 -4 {total_w:.2f} {H + 6:.2f}" aria-hidden="true" focusable="false">{inner}</svg>'
|
||||
|
||||
|
||||
def lockup_png(k, W, Hpx, stacked, mark_h):
|
||||
"""Rasterise the lockup on the mark's ink ground (splash / social card)."""
|
||||
ink = MARKS[k]["ink"]
|
||||
col = MARKS[k]["color"]
|
||||
inner = lockup(k, stacked).replace("currentColor", col)
|
||||
inner = inner.replace('<svg class="pm"', f"<svg {XMLNS}")
|
||||
vb = inner.split('viewBox="')[1].split('"')[0].split()
|
||||
vw, vh = float(vb[2]), float(vb[3])
|
||||
scale = mark_h / 58.0
|
||||
w, h = round(vw * scale), round(vh * scale)
|
||||
im = Image.new("RGBA", (W, Hpx), ink)
|
||||
im.alpha_composite(raster(inner, w, h), ((W - w) // 2, (Hpx - h) // 2))
|
||||
return im
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- tiles etc.
|
||||
def tile(k, size, pad=0.18, radius=None):
|
||||
im = Image.new("RGBA", (size, size), MARKS[k]["ink"])
|
||||
m = mark_png(k, "flat", int(size * (1 - 2 * pad)))
|
||||
im.alpha_composite(m, ((size - m.width) // 2, (size - m.height) // 2))
|
||||
if radius:
|
||||
mask = Image.new("L", (size, size), 0)
|
||||
ImageDraw.Draw(mask).rounded_rectangle([0, 0, size - 1, size - 1], radius, fill=255)
|
||||
im.putalpha(mask)
|
||||
return im
|
||||
|
||||
|
||||
def banner(k):
|
||||
"""Half-block console art from the minimum tier, ~18 columns wide."""
|
||||
a = mark_png(k, "min", 20, "#000").split()[3]
|
||||
cols, rows = a.size
|
||||
lines = []
|
||||
for y in range(0, rows - 1, 2):
|
||||
s = ""
|
||||
for x in range(cols):
|
||||
t = a.getpixel((x, y)) > 110
|
||||
b = a.getpixel((x, y + 1)) > 110
|
||||
s += "█" if t and b else "▀" if t else "▄" if b else " "
|
||||
lines.append(s.rstrip())
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def b64png(im):
|
||||
buf = io.BytesIO()
|
||||
im.save(buf, "PNG", optimize=True)
|
||||
return "data:image/png;base64," + base64.b64encode(buf.getvalue()).decode()
|
||||
|
||||
|
||||
def main():
|
||||
for k in MARKS:
|
||||
out = os.path.join(SITE, "static", "img", "logo", k)
|
||||
os.makedirs(out, exist_ok=True)
|
||||
tile(k, 192, radius=42).save(os.path.join(out, "tile.png"), optimize=True)
|
||||
tile(k, 192, radius=96).save(os.path.join(out, "tile-round.png"), optimize=True)
|
||||
lockup_png(k, 960, 540, True, 190).save(os.path.join(out, "splash.png"), optimize=True)
|
||||
lockup_png(k, 600, 315, False, 130).save(os.path.join(out, "social.png"), optimize=True)
|
||||
data = {
|
||||
"display": svg(k, "display", uid=f"{k}d"),
|
||||
"flat": svg(k, "flat"),
|
||||
"min": svg(k, "min"),
|
||||
"lockup_h": lockup(k),
|
||||
"lockup_s": lockup(k, stacked=True),
|
||||
"tab16": b64png(mark_png(k, "min", 16)),
|
||||
"full16": b64png(mark_png(k, "flat", 16)),
|
||||
"banner": banner(k),
|
||||
"color": MARKS[k]["color"],
|
||||
"ink": MARKS[k]["ink"],
|
||||
}
|
||||
with open(os.path.join(SITE, "data", "logo", f"{k}.json"), "w") as f:
|
||||
json.dump(data, f, indent=1, ensure_ascii=False)
|
||||
print(k, "ok")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||