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:
2026-08-11 08:30:25 -05:00
parent 4debdfa518
commit 280fd7f988
106 changed files with 10377 additions and 0 deletions

View 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()