Add Rippr source snapshot and full-history bundle
Two copies for two jobs. rippr-src/ is a browsable git archive export of the tracked tree at 2f76983 - no build outputs, no local.properties, no nested .git - which is convenient to read in gitea but carries no history and will drift. rippr-full-history.bundle is the real backup: all 18 commits, verified as "records a complete history" and test-cloned before committing. This matters because ~/dojo/rippr has no git remote and otherwise exists only on one machine. rippr-src/SNAPSHOT.md explains the difference and how to restore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
262
rippr-src/design/build_preview.py
Normal file
262
rippr-src/design/build_preview.py
Normal file
@@ -0,0 +1,262 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Builds the logo concept review page, inlining each generated SVG."""
|
||||
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
import gen_logos
|
||||
|
||||
HERE = pathlib.Path(__file__).parent
|
||||
LOGOS = HERE / "logos"
|
||||
|
||||
NOTES = {
|
||||
"apex": (
|
||||
"Strongest speed signal in the set, and the gauge reads as an instrument cluster "
|
||||
"rather than a generic dial. Verified weakness: the tick marks disappear by 24px "
|
||||
"and the needle loses its point, so the status-bar version needs the ticks removed."
|
||||
),
|
||||
"route": (
|
||||
"Describes what the app records rather than what it rides on — the most specific "
|
||||
"idea of the three. The accent segment reads as recording progress. "
|
||||
"Ambiguous at 24px — the trace could be almost anything."
|
||||
),
|
||||
"heading": (
|
||||
"Cleanest silhouette of the outlined concepts and the second-best small-size "
|
||||
"performer. The accent arc marks where the arrow points, so ring and arrow read as "
|
||||
"one object, and it reduces to a single flat color more cleanly than the other two — which makes it the natural pick for the status-bar icon."
|
||||
),
|
||||
}
|
||||
|
||||
# The status-bar icon must be a flat single-color silhouette; Android discards
|
||||
# color and alpha nuance. Worth previewing since it rules some concepts out.
|
||||
MONO_CSS = """
|
||||
.mono svg *, .mono svg { fill: #fff !important; stroke: #fff !important; }
|
||||
"""
|
||||
|
||||
|
||||
def inner_svg(key: str) -> str:
|
||||
raw = (LOGOS / f"rippr-{key}.svg").read_text()
|
||||
return re.sub(r"^<svg[^>]*>|</svg>$", "", raw).strip()
|
||||
|
||||
|
||||
def card(letter: str, key: str, name: str, desc: str) -> str:
|
||||
body = inner_svg(key)
|
||||
|
||||
def mark(size, extra_class="", guide=False):
|
||||
g = '<span class="safe"></span>' if guide else ""
|
||||
return (
|
||||
f'<span class="mark {extra_class}" style="--s:{size}px">'
|
||||
f'<svg viewBox="0 0 108 108" width="{size}" height="{size}" '
|
||||
f'aria-hidden="true">{body}</svg>{g}</span>'
|
||||
)
|
||||
|
||||
sizes = "".join(
|
||||
f'<div class="spec"><div class="tile sm">{mark(n)}</div>'
|
||||
f'<span class="cap">{n}px</span></div>'
|
||||
for n in (48, 32, 24)
|
||||
)
|
||||
|
||||
return f"""
|
||||
<article class="card">
|
||||
<header class="card-hd">
|
||||
<span class="letter">{letter}</span>
|
||||
<div>
|
||||
<h2>{name}</h2>
|
||||
<p class="desc">{desc}</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="stage">{mark(168, guide=True)}</div>
|
||||
|
||||
<div class="specs">
|
||||
<div class="spec">
|
||||
<div class="tile mask-circle">{mark(72)}</div>
|
||||
<span class="cap">circle mask</span>
|
||||
</div>
|
||||
<div class="spec">
|
||||
<div class="tile mask-squircle">{mark(72)}</div>
|
||||
<span class="cap">squircle</span>
|
||||
</div>
|
||||
<div class="spec">
|
||||
<div class="tile sm mono">{mark(24)}</div>
|
||||
<span class="cap">status bar</span>
|
||||
</div>
|
||||
{sizes}
|
||||
</div>
|
||||
|
||||
<p class="note">{NOTES[key]}</p>
|
||||
</article>"""
|
||||
|
||||
|
||||
def main():
|
||||
gen_logos.main()
|
||||
cards = "".join(
|
||||
card(chr(65 + i), key, name, desc)
|
||||
for i, (key, (name, desc, _)) in enumerate(gen_logos.CONCEPTS.items())
|
||||
)
|
||||
html = TEMPLATE.replace("{{CARDS}}", cards).replace("{{MONO_CSS}}", MONO_CSS)
|
||||
out = HERE / "logo-review.html"
|
||||
out.write_text(html)
|
||||
print(f"wrote {out}")
|
||||
|
||||
|
||||
TEMPLATE = """<title>Rippr — logo concepts</title>
|
||||
<style>
|
||||
:root {
|
||||
--ground: #F2F3F5;
|
||||
--surface: #FFFFFF;
|
||||
--line: #D8DCE1;
|
||||
--ink: #161A1F;
|
||||
--ink-dim: #5C6874;
|
||||
--accent: #E8480F;
|
||||
--tile: #101418;
|
||||
--tile-line: #2A323B;
|
||||
--radius: 14px;
|
||||
}
|
||||
:root:not([data-theme="light"]) { color-scheme: light dark; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
--ground: #0C0F13;
|
||||
--surface: #151A20;
|
||||
--line: #262E37;
|
||||
--ink: #E8EDF2;
|
||||
--ink-dim: #8B98A6;
|
||||
--accent: #FF5722;
|
||||
--tile: #06080A;
|
||||
--tile-line: #222A33;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"] {
|
||||
--ground: #0C0F13;
|
||||
--surface: #151A20;
|
||||
--line: #262E37;
|
||||
--ink: #E8EDF2;
|
||||
--ink-dim: #8B98A6;
|
||||
--accent: #FF5722;
|
||||
--tile: #06080A;
|
||||
--tile-line: #222A33;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--ground);
|
||||
color: var(--ink);
|
||||
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
line-height: 1.55;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
.wrap { max-width: 1080px; margin: 0 auto; padding: 56px 24px 80px; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 11px; letter-spacing: .18em; text-transform: uppercase;
|
||||
color: var(--accent); margin: 0 0 12px;
|
||||
}
|
||||
h1 {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: clamp(30px, 5vw, 46px); font-weight: 700; letter-spacing: -.02em;
|
||||
margin: 0 0 14px; text-wrap: balance;
|
||||
}
|
||||
.lede { max-width: 62ch; color: var(--ink-dim); font-size: 17px; margin: 0; }
|
||||
.lede strong { color: var(--ink); font-weight: 600; }
|
||||
|
||||
.grid { display: grid; gap: 22px; margin-top: 44px; }
|
||||
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 22px;
|
||||
display: grid; gap: 18px;
|
||||
}
|
||||
.card-hd { display: flex; gap: 14px; align-items: flex-start; }
|
||||
.letter {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 12px; font-weight: 700; color: var(--accent);
|
||||
border: 1px solid var(--accent); border-radius: 6px;
|
||||
width: 26px; height: 26px; display: grid; place-items: center; flex: none;
|
||||
}
|
||||
.card h2 { font-size: 19px; margin: 0; letter-spacing: -.01em; }
|
||||
.desc { margin: 2px 0 0; color: var(--ink-dim); font-size: 14px; }
|
||||
|
||||
.stage {
|
||||
background: var(--tile);
|
||||
border: 1px solid var(--tile-line);
|
||||
border-radius: 10px;
|
||||
display: grid; place-items: center;
|
||||
padding: 30px 0;
|
||||
}
|
||||
.mark { position: relative; display: grid; place-items: center; }
|
||||
.mark svg { display: block; }
|
||||
/* Adaptive-icon safe zone: content outside the centre 66/108 circle can be cropped. */
|
||||
.safe {
|
||||
position: absolute; inset: 0; margin: auto;
|
||||
width: calc(var(--s) * 0.611); height: calc(var(--s) * 0.611);
|
||||
border: 1px dashed var(--tile-line); border-radius: 50%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.specs { display: flex; flex-wrap: wrap; gap: 14px; }
|
||||
.spec { display: grid; gap: 6px; justify-items: center; }
|
||||
.tile {
|
||||
background: var(--tile); border: 1px solid var(--tile-line);
|
||||
width: 88px; height: 88px; display: grid; place-items: center; border-radius: 10px;
|
||||
}
|
||||
.tile.sm { width: 64px; height: 64px; }
|
||||
.mask-circle { border-radius: 50%; overflow: hidden; }
|
||||
.mask-squircle { border-radius: 26%; overflow: hidden; }
|
||||
.cap {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
|
||||
color: var(--ink-dim); font-variant-numeric: tabular-nums;
|
||||
}
|
||||
{{MONO_CSS}}
|
||||
|
||||
.note {
|
||||
margin: 0; font-size: 14.5px; color: var(--ink-dim);
|
||||
border-left: 2px solid var(--line); padding-left: 14px; max-width: 68ch;
|
||||
}
|
||||
|
||||
.foot {
|
||||
margin-top: 44px; padding-top: 24px; border-top: 1px solid var(--line);
|
||||
color: var(--ink-dim); font-size: 14.5px; max-width: 68ch;
|
||||
}
|
||||
.foot h3 { color: var(--ink); font-size: 15px; margin: 0 0 8px; }
|
||||
.foot code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 12.5px; background: var(--ground); border: 1px solid var(--line);
|
||||
padding: 1px 5px; border-radius: 4px; color: var(--ink);
|
||||
}
|
||||
@media (min-width: 760px) { .grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
</style>
|
||||
|
||||
<div class="wrap">
|
||||
<p class="eyebrow">Rippr · icon direction</p>
|
||||
<h1>Three marks for a ride recorder</h1>
|
||||
<p class="lede">
|
||||
Each concept is drawn on Android's <strong>108-unit adaptive-icon canvas</strong>, so
|
||||
whichever you pick converts straight to a vector drawable with no redrawing. The dashed
|
||||
circle marks the safe zone — anything outside it can be cropped by an OEM mask. The
|
||||
small tiles are the real test: a launcher icon is seen at 48px and the status-bar icon
|
||||
is a flat 24px silhouette.
|
||||
</p>
|
||||
|
||||
<div class="grid">{{CARDS}}</div>
|
||||
|
||||
<div class="foot">
|
||||
<h3>What happens after you pick one</h3>
|
||||
<p>
|
||||
The chosen mark becomes <code>ic_launcher_foreground.xml</code> as a vector drawable,
|
||||
paired with <code>#101418</code> as the adaptive background, replacing the placeholder
|
||||
compass. Its monochrome variant replaces
|
||||
<code>android.R.drawable.ic_menu_compass</code> in the recording notification. Source
|
||||
SVGs live in <code>design/logos/</code>; regenerate with
|
||||
<code>python3 design/gen_logos.py</code>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user