Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b9d72c878 | |||
| 2d0635e710 | |||
| 768d1b23d4 | |||
| 11f0f79866 | |||
| b8f5c35045 | |||
| 9e6cf6e5b7 | |||
| 4e115086e6 |
@@ -72,6 +72,17 @@ Don't get ahead of the phases. GEDCOM lands before the assistant (so AI writes t
|
||||
|
||||
Provenance is **source-available** under **BUSL-1.1** (see [LICENSE](LICENSE)): free for personal/family/non-commercial use, no third-party commercial hosting, and each release converts to **AGPL-3.0** four years after it ships. The DCO sign-off keeps the licensing chain clean so the maintainer can manage that conversion and a possible future hosted offering. Don't add code under an incompatible license, and don't vendor dependencies whose licenses conflict with eventual AGPL distribution.
|
||||
|
||||
## Brand
|
||||
|
||||
Visual identity lives in [docs/brand/](docs/brand/) (see its README for full guidance). Use these as the frontend's design tokens:
|
||||
|
||||
- **Ink** (primary text/marks): `#1A1A17` light / `#F2EEE6` dark
|
||||
- **Bronze** (accent, constant): `#A06A42`
|
||||
- **Paper** (knockout on bronze, constant): `#F7F3EC`
|
||||
- **Muted** (secondary text): `#6B6862` light / `#9A968E` dark
|
||||
|
||||
Wordmark is a serif (heritage register); UI body/secondary text is a humanist sans. Logo lockup: `docs/brand/provenance-logo.svg`; app icon/favicon: `docs/brand/provenance-icon.svg` and `favicon.svg`. Don't recolor outside the palette or add gradients/shadows — the look is flat and warm.
|
||||
|
||||
## Owner & contact
|
||||
|
||||
Maintainer: **Justin Paul** (`justin@jpaul.io`). This deployment targets a home lab: Authentik at `auth.jpaul.io` for auth, `mail.jpaul.io` for SMTP, behind Caddy + Cloudflare Tunnel.
|
||||
|
||||
@@ -30,9 +30,10 @@ S3_REGION=us-east-1
|
||||
# tunnel forwards plain HTTP to caddy:80.
|
||||
PROVENANCE_SITE_ADDRESS=:80
|
||||
|
||||
# --- Cloudflare Tunnel (optional) ---
|
||||
# Enable by setting COMPOSE_PROFILES=tunnel and supplying the connector token
|
||||
# from the Cloudflare dashboard. Public hostname -> http://caddy:80.
|
||||
# --- Deploy-host services (optional, selected via COMPOSE_PROFILES) ---
|
||||
# 'tunnel' -> cloudflared connector (needs CLOUDFLARE_TUNNEL_TOKEN; public hostname -> http://caddy:80)
|
||||
# Auto-deploy is handled by the host's global Watchtower (watches the
|
||||
# watchtower-enabled backend/frontend labels) — no profile needed here.
|
||||
CLOUDFLARE_TUNNEL_TOKEN=
|
||||
COMPOSE_PROFILES=
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ services:
|
||||
|
||||
backend:
|
||||
image: git.jpaul.io/justin/provenance-backend:${IMAGE_TAG:-test-main}
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
environment:
|
||||
APP_ENV: ${APP_ENV:-development}
|
||||
DATABASE_URL: ${DATABASE_URL:-postgresql+asyncpg://provenance:provenance@postgres:5432/provenance}
|
||||
@@ -62,6 +64,8 @@ services:
|
||||
|
||||
frontend:
|
||||
image: git.jpaul.io/justin/provenance-frontend:${IMAGE_TAG:-test-main}
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
depends_on:
|
||||
@@ -104,6 +108,12 @@ services:
|
||||
profiles:
|
||||
- tunnel
|
||||
|
||||
# Auto-deploy is handled by the host's global Watchtower (a single
|
||||
# nickfedor/watchtower instance watches every container labelled
|
||||
# `com.centurylinklabs.watchtower.enable=true` across all stacks). The backend
|
||||
# and frontend carry that label above, so a new :test-main image is pulled and
|
||||
# the container recreated automatically — no per-stack Watchtower needed.
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
miniodata:
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Provenance — Brand
|
||||
|
||||
Draft v0.1. The visual identity for Provenance: an **Origin mark** (primary logo) and a **monogram tile** (app icon / favicon), in a warm ink-and-bronze palette.
|
||||
|
||||
## Concept
|
||||
|
||||
The mark is a survey-datum / origin point: a ring with cardinal ticks (a surveyor's monument — the **land / property** side of the product) enclosing four nodes connected to a center point (a **family graph** — the **people** side). One mark for both halves of what Provenance does. The bronze echoes survey-marker disks and aged document seals — heritage without sepia cliché.
|
||||
|
||||
## Palette
|
||||
|
||||
| Role | Light | Dark | Hex notes |
|
||||
|------|-------|------|-----------|
|
||||
| Ink (primary text/marks) | `#1A1A17` | `#F2EEE6` | warm near-black / warm off-white |
|
||||
| Bronze (accent) | `#A06A42` | `#A06A42` | constant in both modes |
|
||||
| Paper (knockout on bronze) | `#F7F3EC` | `#F7F3EC` | constant |
|
||||
| Muted (tagline/secondary) | `#6B6862` | `#9A968E` | |
|
||||
|
||||
The SVG assets carry an embedded `prefers-color-scheme` rule, so ink and muted tones auto-adapt to light/dark; bronze and paper are intentionally fixed (bronze is mid-tone and reads on both).
|
||||
|
||||
## Typography
|
||||
|
||||
- **Wordmark:** a refined transitional **serif** (the heritage/archival register). The wordmark in these assets is **outlined to vector paths**, so the files render identically everywhere with no font dependency. For production UI headings, pair with a comparable licensed serif (e.g. a Times/Garamond-class face).
|
||||
- **Tagline & secondary:** a clean humanist/grotesque **sans**.
|
||||
- **Tagline:** *where it came from matters* — sentence case, never title case.
|
||||
|
||||
## Assets
|
||||
|
||||
| File | Use |
|
||||
|------|-----|
|
||||
| `provenance-logo.svg` | Primary horizontal lockup — mark + wordmark + tagline |
|
||||
| `provenance-logo-plain.svg` | Lockup without tagline (tight spaces, headers) |
|
||||
| `provenance-mark.svg` | Mark only — square, for avatars, small placements, loading states |
|
||||
| `provenance-icon.svg` | App icon — 512px bronze monogram tile |
|
||||
| `favicon.svg` | Favicon — 48px monogram tile |
|
||||
| `generate.py` | Reproducible generator for all of the above |
|
||||
|
||||
## Usage notes
|
||||
|
||||
- **Clear space:** keep padding around the lockup at least the height of the mark's center-to-tick distance (≈ the ring radius). Don't crowd it.
|
||||
- **Minimum size:** the full lockup stays legible down to ~140px wide; below that, use the mark or the icon.
|
||||
- **Don't:** recolor outside the palette, add gradients/shadows, stretch, or rotate. Don't put the ink lockup on a busy or mid-tone background where it loses contrast — use the icon tile instead.
|
||||
- **Backgrounds:** the lockup and mark are transparent and adapt to light/dark. The icon tile supplies its own bronze background.
|
||||
|
||||
## Regenerating
|
||||
|
||||
```sh
|
||||
python3 docs/brand/generate.py
|
||||
```
|
||||
|
||||
Requires `matplotlib` and the Liberation Serif/Sans fonts (or edit the font paths at the top of the script). Outputs all SVGs into this directory.
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" role="img" aria-label="Provenance icon">
|
||||
<title>Provenance icon</title>
|
||||
<rect x="0" y="0" width="48" height="48" rx="8.64" fill="#A06A42"/>
|
||||
<g transform="translate(16.32 14.26)"><path d="M12.47 5.77Q12.47 3.37 11.35 2.34Q10.23 1.31 7.58 1.31L6.16 1.31L6.16 10.54L7.67 10.54Q10.13 10.54 11.29 9.42Q12.47 8.3 12.47 5.77ZM6.16 11.84L6.16 18.33L9.26 18.72L9.26 19.49L1.05 19.49L1.05 18.72L3.36 18.33L3.36 1.15L0.86 0.77L0.86 0.0L8.21 0.0Q15.36 0.0 15.36 5.74Q15.36 8.73 13.55 10.29Q11.74 11.84 8.36 11.84L6.16 11.84Z" fill="#F7F3EC"/></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 625 B |
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate Provenance brand assets as portable, font-independent SVGs."""
|
||||
import os
|
||||
from matplotlib.textpath import TextPath
|
||||
from matplotlib.font_manager import FontProperties
|
||||
from matplotlib.path import Path
|
||||
|
||||
SERIF = "/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf"
|
||||
SANS = "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"
|
||||
|
||||
INK = "#1A1A17"
|
||||
INK_DARK = "#F2EEE6"
|
||||
BRONZE = "#A06A42"
|
||||
PAPER = "#F7F3EC"
|
||||
MUTED = "#6B6862"
|
||||
MUTED_DARK = "#9A968E"
|
||||
|
||||
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)))
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
|
||||
|
||||
def text_to_path(s, font_path, size):
|
||||
"""Return (d_string, width, height). Coordinates: top-left origin, y down."""
|
||||
fp = FontProperties(fname=font_path)
|
||||
tp = TextPath((0, 0), s, size=size, prop=fp)
|
||||
verts, codes = tp.vertices, tp.codes
|
||||
xs = verts[:, 0]
|
||||
ys = verts[:, 1]
|
||||
xmin, xmax = xs.min(), xs.max()
|
||||
ymin, ymax = ys.min(), ys.max()
|
||||
|
||||
def tx(x):
|
||||
return round(float(x - xmin), 2)
|
||||
|
||||
def ty(y):
|
||||
return round(float(ymax - y), 2)
|
||||
|
||||
d = []
|
||||
i = 0
|
||||
n = len(codes)
|
||||
while i < n:
|
||||
c = codes[i]
|
||||
if c == Path.MOVETO:
|
||||
x, y = verts[i]
|
||||
d.append(f"M{tx(x)} {ty(y)}")
|
||||
i += 1
|
||||
elif c == Path.LINETO:
|
||||
x, y = verts[i]
|
||||
d.append(f"L{tx(x)} {ty(y)}")
|
||||
i += 1
|
||||
elif c == Path.CURVE3:
|
||||
x1, y1 = verts[i]
|
||||
x2, y2 = verts[i + 1]
|
||||
d.append(f"Q{tx(x1)} {ty(y1)} {tx(x2)} {ty(y2)}")
|
||||
i += 2
|
||||
elif c == Path.CURVE4:
|
||||
x1, y1 = verts[i]
|
||||
x2, y2 = verts[i + 1]
|
||||
x3, y3 = verts[i + 2]
|
||||
d.append(f"C{tx(x1)} {ty(y1)} {tx(x2)} {ty(y2)} {tx(x3)} {ty(y3)}")
|
||||
i += 3
|
||||
elif c == Path.CLOSEPOLY:
|
||||
d.append("Z")
|
||||
i += 1
|
||||
else:
|
||||
i += 1
|
||||
return "".join(d), round(float(xmax - xmin), 2), round(float(ymax - ymin), 2)
|
||||
|
||||
|
||||
def origin_mark(cx, cy, R, conn_sw=1.5, tick_sw=2):
|
||||
"""Origin/survey-datum mark primitives as an SVG fragment string.
|
||||
Ink elements get class='ink'; bronze elements class='br'."""
|
||||
import math
|
||||
diag = R * 0.7071
|
||||
nodes = [(cx + diag, cy - diag), (cx - diag, cy - diag),
|
||||
(cx + diag, cy + diag), (cx - diag, cy + diag)]
|
||||
parts = []
|
||||
# ring (bronze)
|
||||
parts.append(f'<circle cx="{cx}" cy="{cy}" r="{R}" class="br-s" '
|
||||
f'fill="none" stroke-width="{tick_sw}"/>')
|
||||
# cardinal ticks (bronze) crossing the ring
|
||||
t_in, t_out = R - 4, R + 4
|
||||
for (dx, dy) in [(0, -1), (0, 1), (1, 0), (-1, 0)]:
|
||||
x1, y1 = cx + dx * t_in, cy + dy * t_in
|
||||
x2, y2 = cx + dx * t_out, cy + dy * t_out
|
||||
parts.append(f'<line x1="{round(x1,2)}" y1="{round(y1,2)}" '
|
||||
f'x2="{round(x2,2)}" y2="{round(y2,2)}" class="br-s" '
|
||||
f'stroke-width="{tick_sw}" stroke-linecap="round"/>')
|
||||
# connectors (ink) center -> diagonal nodes
|
||||
for (nx, ny) in nodes:
|
||||
parts.append(f'<line x1="{cx}" y1="{cy}" x2="{round(nx,2)}" '
|
||||
f'y2="{round(ny,2)}" class="ink-s" fill="none" '
|
||||
f'stroke-width="{conn_sw}"/>')
|
||||
# diagonal nodes (ink)
|
||||
nr = max(2.0, R * 0.105)
|
||||
for (nx, ny) in nodes:
|
||||
parts.append(f'<circle cx="{round(nx,2)}" cy="{round(ny,2)}" '
|
||||
f'r="{round(nr,2)}" class="ink"/>')
|
||||
# center dot (ink)
|
||||
parts.append(f'<circle cx="{cx}" cy="{cy}" r="{round(R*0.15,2)}" '
|
||||
f'class="ink"/>')
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
STYLE = f""".ink{{fill:{INK}}}.ink-s{{stroke:{INK}}}.br{{fill:{BRONZE}}}.br-s{{stroke:{BRONZE}}}.muted{{fill:{MUTED}}}
|
||||
@media (prefers-color-scheme:dark){{.ink{{fill:{INK_DARK}}}.ink-s{{stroke:{INK_DARK}}}.muted{{fill:{MUTED_DARK}}}}}"""
|
||||
|
||||
# ---- 1. Primary horizontal lockup ----
|
||||
WM_SIZE = 64
|
||||
TAG_SIZE = 15
|
||||
wm_d, wm_w, wm_h = text_to_path("Provenance", SERIF, WM_SIZE)
|
||||
tag_d, tag_w, tag_h = text_to_path("where it came from matters", SANS, TAG_SIZE)
|
||||
|
||||
pad = 28
|
||||
R = 30
|
||||
mark_box = 2 * (R + 6)
|
||||
gap = 26
|
||||
block_gap = 12
|
||||
text_block_h = wm_h + block_gap + tag_h
|
||||
content_h = max(mark_box, text_block_h)
|
||||
H = round(pad * 2 + content_h, 2)
|
||||
mark_cx = pad + (R + 6)
|
||||
mark_cy = round(H / 2, 2)
|
||||
text_x = pad + mark_box + gap
|
||||
W = round(text_x + max(wm_w, tag_w) + pad, 2)
|
||||
# vertically center the text block
|
||||
block_top = (H - text_block_h) / 2
|
||||
wm_y = round(block_top, 2)
|
||||
tag_y = round(block_top + wm_h + block_gap, 2)
|
||||
|
||||
logo = f'''<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}" viewBox="0 0 {W} {H}" role="img" aria-label="Provenance">
|
||||
<title>Provenance</title>
|
||||
<style>{STYLE}</style>
|
||||
{origin_mark(mark_cx, mark_cy, R)}
|
||||
<g transform="translate({text_x} {wm_y})"><path d="{wm_d}" class="ink"/></g>
|
||||
<g transform="translate({round(text_x+0.5,2)} {tag_y})"><path d="{tag_d}" class="muted"/></g>
|
||||
</svg>
|
||||
'''
|
||||
open(f"{OUT}/provenance-logo.svg", "w").write(logo)
|
||||
|
||||
# ---- 1b. Lockup without tagline ----
|
||||
H2 = round(pad * 2 + max(mark_box, wm_h), 2)
|
||||
mcy2 = round(H2 / 2, 2)
|
||||
wm_y2 = round((H2 - wm_h) / 2, 2)
|
||||
W2 = round(text_x + wm_w + pad, 2)
|
||||
logo2 = f'''<svg xmlns="http://www.w3.org/2000/svg" width="{W2}" height="{H2}" viewBox="0 0 {W2} {H2}" role="img" aria-label="Provenance">
|
||||
<title>Provenance</title>
|
||||
<style>{STYLE}</style>
|
||||
{origin_mark(mark_cx, mcy2, R)}
|
||||
<g transform="translate({text_x} {wm_y2})"><path d="{wm_d}" class="ink"/></g>
|
||||
</svg>
|
||||
'''
|
||||
open(f"{OUT}/provenance-logo-plain.svg", "w").write(logo2)
|
||||
|
||||
# ---- 2. Mark only (square) ----
|
||||
S = 96
|
||||
c = S / 2
|
||||
markonly = f'''<svg xmlns="http://www.w3.org/2000/svg" width="{S}" height="{S}" viewBox="0 0 {S} {S}" role="img" aria-label="Provenance mark">
|
||||
<title>Provenance mark</title>
|
||||
<style>{STYLE}</style>
|
||||
{origin_mark(c, c, 34, conn_sw=1.6, tick_sw=2.2)}
|
||||
</svg>
|
||||
'''
|
||||
open(f"{OUT}/provenance-mark.svg", "w").write(markonly)
|
||||
|
||||
# ---- 3. App icon / monogram tile (square) ----
|
||||
def monogram(side, radius_ratio=0.22):
|
||||
p_size = side * 0.62
|
||||
pd, pw, ph = text_to_path("P", SERIF, p_size)
|
||||
px = round((side - pw) / 2, 2)
|
||||
py = round((side - ph) / 2, 2)
|
||||
rx = round(side * radius_ratio, 2)
|
||||
return f'''<svg xmlns="http://www.w3.org/2000/svg" width="{side}" height="{side}" viewBox="0 0 {side} {side}" role="img" aria-label="Provenance icon">
|
||||
<title>Provenance icon</title>
|
||||
<rect x="0" y="0" width="{side}" height="{side}" rx="{rx}" fill="{BRONZE}"/>
|
||||
<g transform="translate({px} {py})"><path d="{pd}" fill="{PAPER}"/></g>
|
||||
</svg>
|
||||
'''
|
||||
|
||||
open(f"{OUT}/provenance-icon.svg", "w").write(monogram(512))
|
||||
open(f"{OUT}/favicon.svg", "w").write(monogram(48, radius_ratio=0.18))
|
||||
|
||||
print("WROTE:")
|
||||
for f in sorted(os.listdir(OUT)):
|
||||
p = os.path.join(OUT, f)
|
||||
print(f" {f} ({os.path.getsize(p)} bytes)")
|
||||
print(f"\nlogo lockup: {W} x {H}")
|
||||
print(f"wordmark path size: w={wm_w} h={wm_h}")
|
||||
print(f"tagline path size: w={tag_w} h={tag_h}")
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" role="img" aria-label="Provenance icon">
|
||||
<title>Provenance icon</title>
|
||||
<rect x="0" y="0" width="512" height="512" rx="112.64" fill="#A06A42"/>
|
||||
<g transform="translate(174.08 152.06)"><path d="M132.98 61.55Q132.98 35.96 121.02 25.0Q109.12 13.99 80.9 13.99L65.72 13.99L65.72 112.39L81.84 112.39Q108.03 112.39 120.48 100.49Q132.98 88.54 132.98 61.55ZM65.72 126.33L65.72 195.47L98.75 199.64L98.75 207.87L11.16 207.87L11.16 199.64L35.81 195.47L35.81 12.25L9.13 8.23L9.13 0.0L87.59 0.0Q163.83 0.0 163.83 61.26Q163.83 93.15 144.53 109.76Q125.24 126.33 89.13 126.33L65.72 126.33Z" fill="#F7F3EC"/></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 689 B |
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="450.31" height="128" viewBox="0 0 450.31 128" role="img" aria-label="Provenance">
|
||||
<title>Provenance</title>
|
||||
<style>.ink{fill:#1A1A17}.ink-s{stroke:#1A1A17}.br{fill:#A06A42}.br-s{stroke:#A06A42}.muted{fill:#6B6862}
|
||||
@media (prefers-color-scheme:dark){.ink{fill:#F2EEE6}.ink-s{stroke:#F2EEE6}.muted{fill:#9A968E}}</style>
|
||||
<circle cx="64" cy="64.0" r="30" class="br-s" fill="none" stroke-width="2"/>
|
||||
<line x1="64" y1="38.0" x2="64" y2="30.0" class="br-s" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="64" y1="90.0" x2="64" y2="98.0" class="br-s" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="90" y1="64.0" x2="98" y2="64.0" class="br-s" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="38" y1="64.0" x2="30" y2="64.0" class="br-s" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="64" y1="64.0" x2="85.21" y2="42.79" class="ink-s" fill="none" stroke-width="1.5"/>
|
||||
<line x1="64" y1="64.0" x2="42.79" y2="42.79" class="ink-s" fill="none" stroke-width="1.5"/>
|
||||
<line x1="64" y1="64.0" x2="85.21" y2="85.21" class="ink-s" fill="none" stroke-width="1.5"/>
|
||||
<line x1="64" y1="64.0" x2="42.79" y2="85.21" class="ink-s" fill="none" stroke-width="1.5"/>
|
||||
<circle cx="85.21" cy="42.79" r="3.15" class="ink"/>
|
||||
<circle cx="42.79" cy="42.79" r="3.15" class="ink"/>
|
||||
<circle cx="85.21" cy="85.21" r="3.15" class="ink"/>
|
||||
<circle cx="42.79" cy="85.21" r="3.15" class="ink"/>
|
||||
<circle cx="64" cy="64.0" r="4.5" class="ink"/>
|
||||
<g transform="translate(126 42.73)"><path d="M26.81 12.41Q26.81 7.25 24.4 5.04Q22.0 2.82 16.31 2.82L13.25 2.82L13.25 22.66L16.5 22.66Q21.78 22.66 24.29 20.26Q26.81 17.85 26.81 12.41ZM13.25 25.47L13.25 39.41L19.91 40.25L19.91 41.91L2.25 41.91L2.25 40.25L7.22 39.41L7.22 2.47L1.84 1.66L1.84 0.0L17.66 0.0Q33.03 0.0 33.03 12.35Q33.03 18.78 29.14 22.13Q25.25 25.47 17.97 25.47L13.25 25.47ZM56.34 11.75L56.34 19.69L55.0 19.69L53.18 16.25Q51.62 16.25 49.48 16.68Q47.34 17.1 45.78 17.78L45.78 39.72L50.81 40.5L50.81 41.91L36.87 41.91L36.87 40.5L40.59 39.72L40.59 14.72L36.87 13.94L36.87 12.53L45.43 12.53L45.72 16.19Q47.59 14.63 50.79 13.19Q54.0 11.75 55.87 11.75L56.34 11.75ZM86.47 27.07Q86.47 42.54 72.72 42.54Q66.1 42.54 62.72 38.57Q59.35 34.6 59.35 27.07Q59.35 19.63 62.72 15.69Q66.1 11.75 72.97 11.75Q79.66 11.75 83.06 15.61Q86.47 19.47 86.47 27.07ZM80.85 27.07Q80.85 20.32 78.88 17.29Q76.91 14.25 72.72 14.25Q68.63 14.25 66.8 17.16Q64.97 20.07 64.97 27.07Q64.97 34.16 66.83 37.12Q68.69 40.07 72.72 40.07Q76.85 40.07 78.85 37.01Q80.85 33.94 80.85 27.07ZM106.32 42.54L104.0 42.54L91.91 14.72L88.91 13.94L88.91 12.53L102.6 12.53L102.6 13.94L97.94 14.78L106.5 35.07L114.69 14.72L110.04 13.94L110.04 12.53L120.91 12.53L120.91 13.94L118.1 14.6L106.32 42.54ZM129.04 27.13L129.04 27.69Q129.04 32.0 129.99 34.39Q130.94 36.78 132.92 38.03Q134.91 39.28 138.13 39.28Q139.82 39.28 142.13 39.0Q144.44 38.72 145.94 38.38L145.94 40.13Q144.44 41.1 141.86 41.82Q139.29 42.54 136.6 42.54Q129.75 42.54 126.58 38.85Q123.41 35.16 123.41 27.0Q123.41 19.32 126.63 15.54Q129.85 11.75 135.82 11.75Q147.1 11.75 147.1 24.57L147.1 27.13L129.04 27.13ZM135.82 14.25Q132.57 14.25 130.83 16.88Q129.1 19.5 129.1 24.63L141.66 24.63Q141.66 19.03 140.22 16.64Q138.79 14.25 135.82 14.25ZM159.44 14.91Q161.84 13.53 164.56 12.64Q167.28 11.75 169.09 11.75Q172.9 11.75 174.84 13.97Q176.78 16.19 176.78 20.41L176.78 39.72L180.34 40.5L180.34 41.91L167.69 41.91L167.69 40.5L171.59 39.72L171.59 20.97Q171.59 18.38 170.32 16.9Q169.06 15.41 166.4 15.41Q163.59 15.41 159.5 16.32L159.5 39.72L163.47 40.5L163.47 41.91L150.78 41.91L150.78 40.5L154.31 39.72L154.31 14.72L150.78 13.94L150.78 12.53L159.15 12.53L159.44 14.91ZM195.84 11.88Q200.65 11.88 202.92 13.85Q205.19 15.82 205.19 19.88L205.19 39.72L208.84 40.5L208.84 41.91L200.78 41.91L200.19 38.97Q196.62 42.54 191.09 42.54Q183.56 42.54 183.56 33.78Q183.56 30.85 184.7 28.93Q185.84 27.0 188.34 25.99Q190.84 24.97 195.59 24.88L200.0 24.75L200.0 20.16Q200.0 17.13 198.89 15.69Q197.78 14.25 195.47 14.25Q192.34 14.25 189.75 15.72L188.69 19.38L186.94 19.38L186.94 12.97Q192.0 11.88 195.84 11.88ZM200.0 26.94L195.9 27.07Q191.72 27.22 190.23 28.69Q188.75 30.16 188.75 33.6Q188.75 39.1 193.22 39.1Q195.34 39.1 196.89 38.62Q198.44 38.13 200.0 37.38L200.0 26.94ZM219.85 14.91Q222.25 13.53 224.97 12.64Q227.69 11.75 229.5 11.75Q233.31 11.75 235.25 13.97Q237.19 16.19 237.19 20.41L237.19 39.72L240.75 40.5L240.75 41.91L228.1 41.91L228.1 40.5L232.0 39.72L232.0 20.97Q232.0 18.38 230.73 16.9Q229.47 15.41 226.81 15.41Q224.0 15.41 219.91 16.32L219.91 39.72L223.88 40.5L223.88 41.91L211.19 41.91L211.19 40.5L214.72 39.72L214.72 14.72L211.19 13.94L211.19 12.53L219.56 12.53L219.85 14.91ZM268.16 40.13Q266.63 41.25 263.94 41.9Q261.25 42.54 258.44 42.54Q244.16 42.54 244.16 27.0Q244.16 19.66 247.8 15.71Q251.44 11.75 258.22 11.75Q262.44 11.75 267.44 12.72L267.44 20.91L265.72 20.91L264.38 15.72Q261.78 14.25 258.16 14.25Q249.78 14.25 249.78 27.0Q249.78 33.63 252.33 36.46Q254.88 39.28 260.22 39.28Q264.78 39.28 268.16 38.25L268.16 40.13ZM278.25 27.13L278.25 27.69Q278.25 32.0 279.2 34.39Q280.16 36.78 282.13 38.03Q284.12 39.28 287.35 39.28Q289.04 39.28 291.35 39.0Q293.66 38.72 295.16 38.38L295.16 40.13Q293.66 41.1 291.07 41.82Q288.5 42.54 285.81 42.54Q278.97 42.54 275.8 38.85Q272.62 35.16 272.62 27.0Q272.62 19.32 275.85 15.54Q279.06 11.75 285.04 11.75Q296.31 11.75 296.31 24.57L296.31 27.13L278.25 27.13ZM285.04 14.25Q281.79 14.25 280.05 16.88Q278.31 19.5 278.31 24.63L290.88 24.63Q290.88 19.03 289.44 16.64Q288.0 14.25 285.04 14.25Z" class="ink"/></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96" role="img" aria-label="Provenance mark">
|
||||
<title>Provenance mark</title>
|
||||
<style>.ink{fill:#1A1A17}.ink-s{stroke:#1A1A17}.br{fill:#A06A42}.br-s{stroke:#A06A42}.muted{fill:#6B6862}
|
||||
@media (prefers-color-scheme:dark){.ink{fill:#F2EEE6}.ink-s{stroke:#F2EEE6}.muted{fill:#9A968E}}</style>
|
||||
<circle cx="48.0" cy="48.0" r="34" class="br-s" fill="none" stroke-width="2.2"/>
|
||||
<line x1="48.0" y1="18.0" x2="48.0" y2="10.0" class="br-s" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<line x1="48.0" y1="78.0" x2="48.0" y2="86.0" class="br-s" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<line x1="78.0" y1="48.0" x2="86.0" y2="48.0" class="br-s" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<line x1="18.0" y1="48.0" x2="10.0" y2="48.0" class="br-s" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<line x1="48.0" y1="48.0" x2="72.04" y2="23.96" class="ink-s" fill="none" stroke-width="1.6"/>
|
||||
<line x1="48.0" y1="48.0" x2="23.96" y2="23.96" class="ink-s" fill="none" stroke-width="1.6"/>
|
||||
<line x1="48.0" y1="48.0" x2="72.04" y2="72.04" class="ink-s" fill="none" stroke-width="1.6"/>
|
||||
<line x1="48.0" y1="48.0" x2="23.96" y2="72.04" class="ink-s" fill="none" stroke-width="1.6"/>
|
||||
<circle cx="72.04" cy="23.96" r="3.57" class="ink"/>
|
||||
<circle cx="23.96" cy="23.96" r="3.57" class="ink"/>
|
||||
<circle cx="72.04" cy="72.04" r="3.57" class="ink"/>
|
||||
<circle cx="23.96" cy="72.04" r="3.57" class="ink"/>
|
||||
<circle cx="48.0" cy="48.0" r="5.1" class="ink"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1,14 +1,31 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* Brand palette (docs/brand): warm ink + bronze + paper. */
|
||||
@theme {
|
||||
--color-bronze: #a06a42;
|
||||
--color-bronze-deep: #8a5836;
|
||||
--color-paper: #f7f3ec;
|
||||
--color-ink: #1a1a17;
|
||||
|
||||
--font-serif: Georgia, "Times New Roman", "Liberation Serif", ui-serif, serif;
|
||||
}
|
||||
|
||||
/* Adaptive tokens (ink/paper flip for light/dark; bronze + paper are constant). */
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #0a0a0a;
|
||||
--background: #f7f3ec; /* paper */
|
||||
--foreground: #1a1a17; /* ink */
|
||||
--muted: #6b6862;
|
||||
--surface: #fbf8f2;
|
||||
--border: #e4dccb;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
--background: #1a1a17; /* warm near-black */
|
||||
--foreground: #f2eee6; /* warm off-white */
|
||||
--muted: #9a968e;
|
||||
--surface: #232019;
|
||||
--border: #3a352c;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,3 +34,11 @@ body {
|
||||
color: var(--foreground);
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
|
||||
/* Headings use the heritage serif register. */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
.font-serif {
|
||||
font-family: var(--font-serif);
|
||||
}
|
||||
|
||||
@@ -6,28 +6,35 @@ import "./globals.css";
|
||||
export const metadata: Metadata = {
|
||||
title: "Provenance",
|
||||
description: "Where it came from matters — family and land, every fact sourced.",
|
||||
icons: { icon: "/favicon.svg" },
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<header className="border-b border-neutral-200">
|
||||
<body className="flex min-h-screen flex-col">
|
||||
<header className="border-b border-[var(--border)]">
|
||||
<div className="mx-auto flex max-w-3xl items-center justify-between px-4 py-3">
|
||||
<Link href="/" className="font-semibold">
|
||||
Provenance
|
||||
<Link href="/" className="flex items-center" aria-label="Provenance — home">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src="/provenance-logo-plain.svg" alt="Provenance" className="h-7 w-auto" />
|
||||
</Link>
|
||||
<nav className="flex gap-4 text-sm">
|
||||
<Link href="/trees" className="hover:underline">
|
||||
<nav className="flex gap-5 text-sm">
|
||||
<Link href="/trees" className="text-[var(--muted)] transition-colors hover:text-bronze">
|
||||
Trees
|
||||
</Link>
|
||||
<Link href="/login" className="hover:underline">
|
||||
<Link href="/login" className="text-[var(--muted)] transition-colors hover:text-bronze">
|
||||
Sign in
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<main className="mx-auto max-w-3xl px-4 py-8">{children}</main>
|
||||
<main className="mx-auto w-full max-w-3xl flex-1 px-4 py-10">{children}</main>
|
||||
<footer className="border-t border-[var(--border)]">
|
||||
<div className="mx-auto max-w-3xl px-4 py-6 text-sm italic text-[var(--muted)]">
|
||||
where it came from matters
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -62,9 +62,9 @@ export default function LoginPage() {
|
||||
{loading ? "Signing in…" : "Sign in"}
|
||||
</Button>
|
||||
</form>
|
||||
<p className="mt-4 text-sm text-neutral-600">
|
||||
<p className="mt-4 text-sm text-[var(--muted)]">
|
||||
No account?{" "}
|
||||
<Link href="/register" className="underline">
|
||||
<Link href="/register" className="text-bronze underline">
|
||||
Create one
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
@@ -4,15 +4,17 @@ import { Button } from "@/components/ui/button";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-3xl font-bold">Provenance</h1>
|
||||
<p className="text-neutral-600">
|
||||
Trace where you come from — your family and your land — with every fact linked to a
|
||||
source, on infrastructure you control.
|
||||
<div className="space-y-8 py-4">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||
Where it came from matters
|
||||
</h1>
|
||||
<p className="max-w-prose text-lg text-[var(--muted)]">
|
||||
Trace where you come from — your family <span className="text-bronze">and</span> your
|
||||
land — with every fact linked to a source, on infrastructure you control.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Link href="/register">
|
||||
<Button>Create an account</Button>
|
||||
</Link>
|
||||
|
||||
@@ -70,9 +70,9 @@ export default function RegisterPage() {
|
||||
{loading ? "Creating…" : "Create account"}
|
||||
</Button>
|
||||
</form>
|
||||
<p className="mt-4 text-sm text-neutral-600">
|
||||
<p className="mt-4 text-sm text-[var(--muted)]">
|
||||
Already have an account?{" "}
|
||||
<Link href="/login" className="underline">
|
||||
<Link href="/login" className="text-bronze underline">
|
||||
Sign in
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
@@ -52,11 +52,11 @@ export default function TreeDetailPage() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!ready) return <p className="text-neutral-500">Loading…</p>;
|
||||
if (!ready) return <p className="text-[var(--muted)]">Loading…</p>;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Link href="/trees" className="text-sm text-neutral-500 hover:underline">
|
||||
<Link href="/trees" className="text-sm text-[var(--muted)] hover:underline">
|
||||
← All trees
|
||||
</Link>
|
||||
|
||||
@@ -76,14 +76,14 @@ export default function TreeDetailPage() {
|
||||
<div>
|
||||
<h2 className="mb-2 text-lg font-semibold">People</h2>
|
||||
{persons.length === 0 ? (
|
||||
<p className="text-neutral-500">No people yet.</p>
|
||||
<p className="text-[var(--muted)]">No people yet.</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{persons.map((person) => (
|
||||
<li key={person.id}>
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
{person.primary_name ?? <span className="text-neutral-400">Unnamed</span>}
|
||||
{person.primary_name ?? <span className="text-[var(--muted)]">Unnamed</span>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</li>
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function TreesPage() {
|
||||
router.push("/login");
|
||||
}
|
||||
|
||||
if (!ready) return <p className="text-neutral-500">Loading…</p>;
|
||||
if (!ready) return <p className="text-[var(--muted)]">Loading…</p>;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -75,16 +75,16 @@ export default function TreesPage() {
|
||||
</Card>
|
||||
|
||||
{trees.length === 0 ? (
|
||||
<p className="text-neutral-500">No trees yet — create your first one above.</p>
|
||||
<p className="text-[var(--muted)]">No trees yet — create your first one above.</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{trees.map((tree) => (
|
||||
<li key={tree.id}>
|
||||
<Link href={`/trees/${tree.id}`}>
|
||||
<Card className="transition-colors hover:bg-neutral-50">
|
||||
<Card className="transition-colors hover:border-bronze/50">
|
||||
<CardContent className="flex items-center justify-between p-4">
|
||||
<span className="font-medium">{tree.name}</span>
|
||||
<span className="text-xs uppercase tracking-wide text-neutral-400">
|
||||
<span className="text-xs uppercase tracking-wide text-bronze">
|
||||
{tree.visibility}
|
||||
</span>
|
||||
</CardContent>
|
||||
|
||||
@@ -4,13 +4,15 @@ import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 disabled:pointer-events-none disabled:opacity-50",
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bronze focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-neutral-900 text-white hover:bg-neutral-700",
|
||||
outline: "border border-neutral-300 bg-transparent hover:bg-neutral-100",
|
||||
ghost: "hover:bg-neutral-100",
|
||||
// Bronze is the brand accent; paper reads cleanly on it.
|
||||
default: "bg-bronze text-paper hover:bg-bronze-deep",
|
||||
outline:
|
||||
"border border-bronze text-bronze bg-transparent hover:bg-bronze hover:text-paper",
|
||||
ghost: "text-[var(--foreground)] hover:bg-bronze/10",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
|
||||
@@ -5,7 +5,10 @@ import { cn } from "@/lib/utils";
|
||||
export function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn("rounded-lg border border-neutral-200 bg-white/50 shadow-sm", className)}
|
||||
className={cn(
|
||||
"rounded-lg border border-[var(--border)] bg-[var(--surface)] shadow-sm",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -16,7 +19,7 @@ export function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDiv
|
||||
}
|
||||
|
||||
export function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
|
||||
return <h3 className={cn("text-lg font-semibold", className)} {...props} />;
|
||||
return <h3 className={cn("font-serif text-lg font-semibold", className)} {...props} />;
|
||||
}
|
||||
|
||||
export function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
|
||||
@@ -7,7 +7,7 @@ export const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttribute
|
||||
<input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-neutral-300 bg-transparent px-3 py-2 text-sm placeholder:text-neutral-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 disabled:opacity-50",
|
||||
"flex h-10 w-full rounded-md border border-[var(--border)] bg-[var(--surface)] px-3 py-2 text-sm placeholder:text-[var(--muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bronze disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" role="img" aria-label="Provenance icon">
|
||||
<title>Provenance icon</title>
|
||||
<rect x="0" y="0" width="48" height="48" rx="8.64" fill="#A06A42"/>
|
||||
<g transform="translate(16.32 14.26)"><path d="M12.47 5.77Q12.47 3.37 11.35 2.34Q10.23 1.31 7.58 1.31L6.16 1.31L6.16 10.54L7.67 10.54Q10.13 10.54 11.29 9.42Q12.47 8.3 12.47 5.77ZM6.16 11.84L6.16 18.33L9.26 18.72L9.26 19.49L1.05 19.49L1.05 18.72L3.36 18.33L3.36 1.15L0.86 0.77L0.86 0.0L8.21 0.0Q15.36 0.0 15.36 5.74Q15.36 8.73 13.55 10.29Q11.74 11.84 8.36 11.84L6.16 11.84Z" fill="#F7F3EC"/></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 625 B |
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="450.31" height="128" viewBox="0 0 450.31 128" role="img" aria-label="Provenance">
|
||||
<title>Provenance</title>
|
||||
<style>.ink{fill:#1A1A17}.ink-s{stroke:#1A1A17}.br{fill:#A06A42}.br-s{stroke:#A06A42}.muted{fill:#6B6862}
|
||||
@media (prefers-color-scheme:dark){.ink{fill:#F2EEE6}.ink-s{stroke:#F2EEE6}.muted{fill:#9A968E}}</style>
|
||||
<circle cx="64" cy="64.0" r="30" class="br-s" fill="none" stroke-width="2"/>
|
||||
<line x1="64" y1="38.0" x2="64" y2="30.0" class="br-s" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="64" y1="90.0" x2="64" y2="98.0" class="br-s" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="90" y1="64.0" x2="98" y2="64.0" class="br-s" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="38" y1="64.0" x2="30" y2="64.0" class="br-s" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="64" y1="64.0" x2="85.21" y2="42.79" class="ink-s" fill="none" stroke-width="1.5"/>
|
||||
<line x1="64" y1="64.0" x2="42.79" y2="42.79" class="ink-s" fill="none" stroke-width="1.5"/>
|
||||
<line x1="64" y1="64.0" x2="85.21" y2="85.21" class="ink-s" fill="none" stroke-width="1.5"/>
|
||||
<line x1="64" y1="64.0" x2="42.79" y2="85.21" class="ink-s" fill="none" stroke-width="1.5"/>
|
||||
<circle cx="85.21" cy="42.79" r="3.15" class="ink"/>
|
||||
<circle cx="42.79" cy="42.79" r="3.15" class="ink"/>
|
||||
<circle cx="85.21" cy="85.21" r="3.15" class="ink"/>
|
||||
<circle cx="42.79" cy="85.21" r="3.15" class="ink"/>
|
||||
<circle cx="64" cy="64.0" r="4.5" class="ink"/>
|
||||
<g transform="translate(126 42.73)"><path d="M26.81 12.41Q26.81 7.25 24.4 5.04Q22.0 2.82 16.31 2.82L13.25 2.82L13.25 22.66L16.5 22.66Q21.78 22.66 24.29 20.26Q26.81 17.85 26.81 12.41ZM13.25 25.47L13.25 39.41L19.91 40.25L19.91 41.91L2.25 41.91L2.25 40.25L7.22 39.41L7.22 2.47L1.84 1.66L1.84 0.0L17.66 0.0Q33.03 0.0 33.03 12.35Q33.03 18.78 29.14 22.13Q25.25 25.47 17.97 25.47L13.25 25.47ZM56.34 11.75L56.34 19.69L55.0 19.69L53.18 16.25Q51.62 16.25 49.48 16.68Q47.34 17.1 45.78 17.78L45.78 39.72L50.81 40.5L50.81 41.91L36.87 41.91L36.87 40.5L40.59 39.72L40.59 14.72L36.87 13.94L36.87 12.53L45.43 12.53L45.72 16.19Q47.59 14.63 50.79 13.19Q54.0 11.75 55.87 11.75L56.34 11.75ZM86.47 27.07Q86.47 42.54 72.72 42.54Q66.1 42.54 62.72 38.57Q59.35 34.6 59.35 27.07Q59.35 19.63 62.72 15.69Q66.1 11.75 72.97 11.75Q79.66 11.75 83.06 15.61Q86.47 19.47 86.47 27.07ZM80.85 27.07Q80.85 20.32 78.88 17.29Q76.91 14.25 72.72 14.25Q68.63 14.25 66.8 17.16Q64.97 20.07 64.97 27.07Q64.97 34.16 66.83 37.12Q68.69 40.07 72.72 40.07Q76.85 40.07 78.85 37.01Q80.85 33.94 80.85 27.07ZM106.32 42.54L104.0 42.54L91.91 14.72L88.91 13.94L88.91 12.53L102.6 12.53L102.6 13.94L97.94 14.78L106.5 35.07L114.69 14.72L110.04 13.94L110.04 12.53L120.91 12.53L120.91 13.94L118.1 14.6L106.32 42.54ZM129.04 27.13L129.04 27.69Q129.04 32.0 129.99 34.39Q130.94 36.78 132.92 38.03Q134.91 39.28 138.13 39.28Q139.82 39.28 142.13 39.0Q144.44 38.72 145.94 38.38L145.94 40.13Q144.44 41.1 141.86 41.82Q139.29 42.54 136.6 42.54Q129.75 42.54 126.58 38.85Q123.41 35.16 123.41 27.0Q123.41 19.32 126.63 15.54Q129.85 11.75 135.82 11.75Q147.1 11.75 147.1 24.57L147.1 27.13L129.04 27.13ZM135.82 14.25Q132.57 14.25 130.83 16.88Q129.1 19.5 129.1 24.63L141.66 24.63Q141.66 19.03 140.22 16.64Q138.79 14.25 135.82 14.25ZM159.44 14.91Q161.84 13.53 164.56 12.64Q167.28 11.75 169.09 11.75Q172.9 11.75 174.84 13.97Q176.78 16.19 176.78 20.41L176.78 39.72L180.34 40.5L180.34 41.91L167.69 41.91L167.69 40.5L171.59 39.72L171.59 20.97Q171.59 18.38 170.32 16.9Q169.06 15.41 166.4 15.41Q163.59 15.41 159.5 16.32L159.5 39.72L163.47 40.5L163.47 41.91L150.78 41.91L150.78 40.5L154.31 39.72L154.31 14.72L150.78 13.94L150.78 12.53L159.15 12.53L159.44 14.91ZM195.84 11.88Q200.65 11.88 202.92 13.85Q205.19 15.82 205.19 19.88L205.19 39.72L208.84 40.5L208.84 41.91L200.78 41.91L200.19 38.97Q196.62 42.54 191.09 42.54Q183.56 42.54 183.56 33.78Q183.56 30.85 184.7 28.93Q185.84 27.0 188.34 25.99Q190.84 24.97 195.59 24.88L200.0 24.75L200.0 20.16Q200.0 17.13 198.89 15.69Q197.78 14.25 195.47 14.25Q192.34 14.25 189.75 15.72L188.69 19.38L186.94 19.38L186.94 12.97Q192.0 11.88 195.84 11.88ZM200.0 26.94L195.9 27.07Q191.72 27.22 190.23 28.69Q188.75 30.16 188.75 33.6Q188.75 39.1 193.22 39.1Q195.34 39.1 196.89 38.62Q198.44 38.13 200.0 37.38L200.0 26.94ZM219.85 14.91Q222.25 13.53 224.97 12.64Q227.69 11.75 229.5 11.75Q233.31 11.75 235.25 13.97Q237.19 16.19 237.19 20.41L237.19 39.72L240.75 40.5L240.75 41.91L228.1 41.91L228.1 40.5L232.0 39.72L232.0 20.97Q232.0 18.38 230.73 16.9Q229.47 15.41 226.81 15.41Q224.0 15.41 219.91 16.32L219.91 39.72L223.88 40.5L223.88 41.91L211.19 41.91L211.19 40.5L214.72 39.72L214.72 14.72L211.19 13.94L211.19 12.53L219.56 12.53L219.85 14.91ZM268.16 40.13Q266.63 41.25 263.94 41.9Q261.25 42.54 258.44 42.54Q244.16 42.54 244.16 27.0Q244.16 19.66 247.8 15.71Q251.44 11.75 258.22 11.75Q262.44 11.75 267.44 12.72L267.44 20.91L265.72 20.91L264.38 15.72Q261.78 14.25 258.16 14.25Q249.78 14.25 249.78 27.0Q249.78 33.63 252.33 36.46Q254.88 39.28 260.22 39.28Q264.78 39.28 268.16 38.25L268.16 40.13ZM278.25 27.13L278.25 27.69Q278.25 32.0 279.2 34.39Q280.16 36.78 282.13 38.03Q284.12 39.28 287.35 39.28Q289.04 39.28 291.35 39.0Q293.66 38.72 295.16 38.38L295.16 40.13Q293.66 41.1 291.07 41.82Q288.5 42.54 285.81 42.54Q278.97 42.54 275.8 38.85Q272.62 35.16 272.62 27.0Q272.62 19.32 275.85 15.54Q279.06 11.75 285.04 11.75Q296.31 11.75 296.31 24.57L296.31 27.13L278.25 27.13ZM285.04 14.25Q281.79 14.25 280.05 16.88Q278.31 19.5 278.31 24.63L290.88 24.63Q290.88 19.03 289.44 16.64Q288.0 14.25 285.04 14.25Z" class="ink"/></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96" role="img" aria-label="Provenance mark">
|
||||
<title>Provenance mark</title>
|
||||
<style>.ink{fill:#1A1A17}.ink-s{stroke:#1A1A17}.br{fill:#A06A42}.br-s{stroke:#A06A42}.muted{fill:#6B6862}
|
||||
@media (prefers-color-scheme:dark){.ink{fill:#F2EEE6}.ink-s{stroke:#F2EEE6}.muted{fill:#9A968E}}</style>
|
||||
<circle cx="48.0" cy="48.0" r="34" class="br-s" fill="none" stroke-width="2.2"/>
|
||||
<line x1="48.0" y1="18.0" x2="48.0" y2="10.0" class="br-s" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<line x1="48.0" y1="78.0" x2="48.0" y2="86.0" class="br-s" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<line x1="78.0" y1="48.0" x2="86.0" y2="48.0" class="br-s" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<line x1="18.0" y1="48.0" x2="10.0" y2="48.0" class="br-s" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<line x1="48.0" y1="48.0" x2="72.04" y2="23.96" class="ink-s" fill="none" stroke-width="1.6"/>
|
||||
<line x1="48.0" y1="48.0" x2="23.96" y2="23.96" class="ink-s" fill="none" stroke-width="1.6"/>
|
||||
<line x1="48.0" y1="48.0" x2="72.04" y2="72.04" class="ink-s" fill="none" stroke-width="1.6"/>
|
||||
<line x1="48.0" y1="48.0" x2="23.96" y2="72.04" class="ink-s" fill="none" stroke-width="1.6"/>
|
||||
<circle cx="72.04" cy="23.96" r="3.57" class="ink"/>
|
||||
<circle cx="23.96" cy="23.96" r="3.57" class="ink"/>
|
||||
<circle cx="72.04" cy="72.04" r="3.57" class="ink"/>
|
||||
<circle cx="23.96" cy="72.04" r="3.57" class="ink"/>
|
||||
<circle cx="48.0" cy="48.0" r="5.1" class="ink"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |