From 4e1699bcdc67308fbdfed5fc5b2412ffe672929b Mon Sep 17 00:00:00 2001 From: Kyle Tse Date: Mon, 28 Sep 2026 02:47:36 +0000 Subject: [PATCH 1/3] feat(brand): brand home in brand/ (masters, generated icons, tokens, provenance) The drawing that was docs/public/logo.svg moves to brand/svg/lockdocs-symbol.svg and becomes the only source: build.py copies it back to the URL the site serves, and generates the favicon (16/32 px snapped grids, 48, .ico, .svg), the app icons (192/512/1024, maskable, apple-touch) and tokens.css from it. The docs theme now reads the brand: custom.css imports brand/tokens.css and sets --vp-c-bg*, --vp-c-brand-* and the hero gradient from var(--brand-color-*); docs/.vitepress/config.ts reads theme-color from brand/tokens.json and links the generated favicon files instead of carrying a second copy of the logo as a data URI. Values are unchanged. CI checks that every surface is still a byte-for-byte copy (python3 brand/build.py --check). --- .github/workflows/ci.yml | 2 + PROJECT.md | 3 + brand/README.md | 160 +++++++++++++ brand/app-icon/apple-touch-icon-180.png | Bin 0 -> 1955 bytes brand/app-icon/icon-1024.png | Bin 0 -> 20776 bytes brand/app-icon/icon-192.png | Bin 0 -> 3411 bytes brand/app-icon/icon-512.png | Bin 0 -> 9365 bytes brand/app-icon/icon-maskable-192.png | Bin 0 -> 2104 bytes brand/app-icon/icon-maskable-512.png | Bin 0 -> 5623 bytes brand/brand.json | 18 ++ brand/build.py | 285 ++++++++++++++++++++++++ brand/favicon/favicon-16.png | Bin 0 -> 173 bytes brand/favicon/favicon-32.png | Bin 0 -> 227 bytes brand/favicon/favicon-48.png | Bin 0 -> 964 bytes brand/favicon/favicon.ico | Bin 0 -> 1418 bytes brand/favicon/favicon.svg | 1 + brand/favicon/grid-16.txt | 21 ++ brand/favicon/grid-32.txt | 37 +++ brand/provenance.json | 102 +++++++++ brand/svg/lockdocs-app-icon.svg | 1 + brand/svg/lockdocs-maskable.svg | 1 + brand/svg/lockdocs-symbol-black.svg | 1 + brand/svg/lockdocs-symbol-white.svg | 1 + brand/svg/lockdocs-symbol.svg | 1 + brand/tokens.css | 13 ++ brand/tokens.json | 16 ++ docs/.vitepress/config.ts | 11 +- docs/.vitepress/theme/custom.css | 18 +- docs/public/favicon.ico | Bin 0 -> 1418 bytes docs/public/favicon.svg | 1 + 30 files changed, 681 insertions(+), 12 deletions(-) create mode 100644 brand/README.md create mode 100644 brand/app-icon/apple-touch-icon-180.png create mode 100644 brand/app-icon/icon-1024.png create mode 100644 brand/app-icon/icon-192.png create mode 100644 brand/app-icon/icon-512.png create mode 100644 brand/app-icon/icon-maskable-192.png create mode 100644 brand/app-icon/icon-maskable-512.png create mode 100644 brand/brand.json create mode 100644 brand/build.py create mode 100644 brand/favicon/favicon-16.png create mode 100644 brand/favicon/favicon-32.png create mode 100644 brand/favicon/favicon-48.png create mode 100644 brand/favicon/favicon.ico create mode 100644 brand/favicon/favicon.svg create mode 100644 brand/favicon/grid-16.txt create mode 100644 brand/favicon/grid-32.txt create mode 100644 brand/provenance.json create mode 100644 brand/svg/lockdocs-app-icon.svg create mode 100644 brand/svg/lockdocs-maskable.svg create mode 100644 brand/svg/lockdocs-symbol-black.svg create mode 100644 brand/svg/lockdocs-symbol-white.svg create mode 100644 brand/svg/lockdocs-symbol.svg create mode 100644 brand/tokens.css create mode 100644 brand/tokens.json create mode 100644 docs/public/favicon.ico create mode 100644 docs/public/favicon.svg diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcbb116..534a575 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,3 +101,5 @@ jobs: $B tinymodel "turn a model into a dict" -C crates/lockdocs-core/tests/fixture | grep -q to_dict - name: Docs build run: bun run docs:build + - name: Brand home (hashes and surface copies agree) + run: python3 brand/build.py --check diff --git a/PROJECT.md b/PROJECT.md index bd29682..8c10456 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -20,6 +20,9 @@ MIT licensed. - `packages/lockdocs`: the npm launcher; `packages/npm/*`: native binaries - `bench/`: version-sensitive questions, project pins, and the runner - `docs/`: the VitePress site; `scripts/`: version sync +- `brand/`: the brand home — vector masters, generated favicons and app icons, + colour and type tokens. The site's logo, favicon and theme colours are copies + of it. See `brand/README.md` ## Release diff --git a/brand/README.md b/brand/README.md new file mode 100644 index 0000000..7c5da2e --- /dev/null +++ b/brand/README.md @@ -0,0 +1,160 @@ +# lockdocs brand + +This folder is the source of truth for the lockdocs mark, its small sizes, its +app icon, its colours and its type. Every surface copies from it — `brand.json` +lists which file each surface is a copy of — and nothing draws its own. +`build.py` rebuilds every derived file from the masters in `svg/`: + +```bash +pip install pillow numpy resvg-py +python3 brand/build.py # regenerate (favicon, app icons, tokens.css, provenance) +python3 brand/build.py --resnap # also redraw the 16/32 px grids from the master +python3 brand/build.py --check # verify hashes and surface copies (CI runs this; stdlib only) +``` + +## Name + +Running text is `lockdocs`: lower case, one word, everywhere — the README, the +docs site, the npm package `@sylphx/lockdocs`, the registry entry +`io.github.SylphxAI/lockdocs`, and the `lockdocs` binary. No document in this +repository writes `Lockdocs` or `LockDocs`. The only upper-case form is the +`LOCKDOCS_*` environment-variable prefix, which is not the name. + +lockdocs is operated by Sylphx Limited. The operator line is never part of the +brand: it belongs to the licence, legal pages and the site footer, and the mark +never carries it. + +There are no local-script names: the product ships in English. + +The mark is a padlock with no lettering and there is no wordmark, so the name +never appears inside the logo and has no capitalised form anywhere. + +## Files + +| Need | File | +|---|---| +| The mark, anywhere it is placed (site header, README, slides) | `svg/lockdocs-symbol.svg` | +| One-colour printing, engraving, embroidery, stamps | `svg/lockdocs-symbol-black.svg`, `svg/lockdocs-symbol-white.svg` | +| App icon master (store listing, launcher) | `svg/lockdocs-app-icon.svg` | +| Full-bleed square for masks (Android maskable, Apple touch, vendors that crop) | `svg/lockdocs-maskable.svg` | +| Browser tab | `favicon/favicon.svg` (32 px pixel grid), `favicon/favicon.ico` (16, 32, 48), `favicon/favicon-16.png`, `favicon/favicon-32.png`, `favicon/favicon-48.png` | +| Home screen, store listing, future web-app manifest | `app-icon/icon-192.png`, `app-icon/icon-512.png`, `app-icon/icon-1024.png`, `app-icon/icon-maskable-192.png`, `app-icon/icon-maskable-512.png`, `app-icon/apple-touch-icon-180.png` | +| The website's own logo file (served at `/lockdocs/logo.svg`) | `docs/public/logo.svg` | +| Colour and type tokens | `tokens.json` (source), `tokens.css` (generated) | +| Where every file came from, with its SHA-256 | `provenance.json` | + +## Colours + +| Token | Hex | Used for | +|---|---|---| +| `--brand-color-bg` | `#06080C` | Page ground; the app-icon ground; the document lines cut out of the lock body | +| `--brand-color-bg-raised` | `#0B0E14` | Alternating background: code blocks, sidebar, footer | +| `--brand-color-bg-elevated` | `#0E1118` | Elevated surfaces: menus, dialogs, hover cards | +| `--brand-color-bg-soft` | `#11151D` | Soft fills and panels | +| `--brand-color-accent` | `#7C9CFF` | The lock body; links, badges and primary buttons | +| `--brand-color-accent-hover` | `#9DB4FF` | Links, badges and primary buttons on hover | +| `--brand-color-accent-active` | `#6480E8` | Links and buttons when pressed | +| `--brand-color-mint` | `#42D6A4` | The shackle; second stop of the hero gradient | +| `--brand-color-gold` | `#FFD166` | Third stop of the hero gradient | + +The docs site gets its theme colours from these tokens: `custom.css` imports +`brand/tokens.css` and assigns `--vp-c-*` from `var(--brand-color-*)`, and +`docs/.vitepress/config.ts` reads the `theme-color` meta tag from +`tokens.json`. The values are unchanged from the site that shipped before the +brand home existed. + +## Type + +Body and interface are set in Inter (`--brand-font-sans`), served by VitePress +with the docs theme from the built site's own `/assets` — self-hosted, no font +CDN — under the SIL Open Font License 1.1. The theme already defaults +`--vp-font-family-base` to it, so no surface declares its own face. No type +face is part of the mark, because the mark has no lettering. + +## Small sizes + +16 px and 32 px are not scaled renders. `build.py` renders the app icon at 8x, +quantises every sample to the three colours in `brand.json` (`palette`), and +writes the result as a character grid: `favicon/grid-16.txt` and +`favicon/grid-32.txt`. A pixel takes the colour most of its samples agree on, +and stays empty when fewer than `snap_threshold` (0.5) of its samples are +filled. + +The grid files are meant to be hand-edited — a later `build.py` run draws from +the grid as it stands; only `--resnap` redraws it from the master. +`favicon/favicon.svg` is the 32 px grid as one path per colour, and +`favicon/favicon.ico` holds 16 and 32 from the grids plus 48 from a direct +render. `palette` must list exactly the colours the master uses, or the grids +pick up colours that are not in the brand. + +## Clear space and minimum size + +Not yet specified. This repository has no design document, so no clear space or +minimum size has been recorded. The symbol's viewBox is tight to the artwork: +add clearance where the mark is placed, not inside the file. + +## Do / Don't + +Not yet specified beyond what the files themselves fix: the mark is never +recoloured, so the three accent colours above are the only ones (the one-colour +files are the exception, for print); and the two document lines stay visible in +every version, which in the one-colour files means they are cut out of the lock +body rather than painted over it. The rounded square belongs to the app icon +alone — for anything that crops or masks, use the full-bleed `lockdocs-maskable`. + +## Surfaces + +| Surface | Is a copy of | +|---|---| +| `docs/public/logo.svg` (site header) | `svg/lockdocs-symbol.svg` | +| `docs/public/favicon.svg` | `favicon/favicon.svg` | +| `docs/public/favicon.ico` | `favicon/favicon.ico` | + +`python3 brand/build.py --check` fails if any of these stops being a +byte-for-byte copy. Rebuilding the site is not needed for icon changes: the +files are served from `docs/public/` as they are. + +### Surfaces still to move + +- `docs/.vitepress/theme/custom.css` keeps four brand tints as literal + `rgba()`: `--vp-c-brand-soft` (`rgba(124, 156, 255, 0.14)`), the hero + radial gradient (`rgba(124,156,255,.35)`, `rgba(66,214,164,.2)`) and the + hero-shot border and shadow (`rgba(124,156,255,.08)`). They are alpha tints + of `--brand-color-accent` and `--brand-color-mint`; writing them as tokens + needs `color-mix()`, which drops the whole declaration on browsers older + than 2023, so they stay literals. +- `README.md` badges: the shields.io URLs carry `color=7c9cff` (npm badge) and + `42d6a4` (MCP Registry badge). shields.io renders from its own servers and + cannot read a local token. +- Social preview: `docs/.vitepress/config.ts` sets `og:image` to + `docs/public/img/demo.gif`, a 1400×780 terminal recording, not a 1200×630 + still. There is no `og/` master. The audit of 2026-09-28 recorded this as a + gap to fill later rather than draw one now. + +## Provenance + +- `svg/lockdocs-symbol.svg` is `docs/public/logo.svg` moved here; git history + for that path starts at `d4c4d54` (2026-09-25, "feat: lockdocs 0.1 — + exact-version library docs from your lockfile, offline (#1)") and the drawing + has not changed since. The website file is now generated back as a copy. +- `svg/lockdocs-symbol-black.svg` and `-white.svg` are that drawing in one + colour: every fill and stroke set to `#000000` / `#FFFFFF`, with the two + document lines cut out of the lock body by `fill-rule="evenodd"` so they stay + visible on a black or white ground. +- `svg/lockdocs-app-icon.svg` was specified by the brand lead in the audit of + 2026-09-28: the symbol on a `#06080C` rounded square (`rx` 22% of the side), + 1024 viewBox, symbol at 70% width, centred. `svg/lockdocs-maskable.svg` is the + same construction without the corner radius, so Apple's and Android's masks + have a full-bleed square to work with. +- `tokens.json` records the colours and the type face the product already + shipped: the logo's three colours, the docs theme's background and accent + roles, and the theme's Inter stack. +- Every file's SHA-256 is in `provenance.json`; the entry for each file says + where it came from, and `build.py` refreshes the hashes. + +## Trademark + +Not registered. Owner decision owner#781: no trademark filings before the +product earns money. Use ™ at most, never ®. + + diff --git a/brand/app-icon/apple-touch-icon-180.png b/brand/app-icon/apple-touch-icon-180.png new file mode 100644 index 0000000000000000000000000000000000000000..895bb676aa667d27fd245bf1e0ae182038db7694 GIT binary patch literal 1955 zcmaKtX*3&X7sq4QR)dae$G$Jss91wqT1yy%s@7IYYOGa4YN;hgQZltR1Z9+}*kTW{ z3u%nCN(W6ttW|4KTkQ<*OwXBf-uJv8?sLy`@BMb~|Nnb-%L01-tiV|S0C3*a#PIe> zt~m`(=98CLfpr4_IJ`{_Z`=tPSsuIRW9jMb(w3ql4 z#wQ$Yct(yTFJ_Xtg9FQ<$i0KwVB-|fAGmZu|AKzJ+vf=l3V!QiWB|(+9&A{VkvAyt-0wMW_UR7>LN)fx67bQ}J$M>rM zSIkuQ5j?MMqo*vY_BqfwU&?9Bd_cKXpCFf(mWH1-0Ux^5fcP2%RPKZ+Fy`4Xo^BP6 zJ{3Wo-Vc73E`utv_B(T6c%X5AqEJcWnUJ_h8g06@dV6zzfCXr--j|`hO;tPA%eh5> zqqkSa2U|c$E9W+O>v;JGiIOG!+7Hz;=ibFG*)PBSoNw@QECR#?_ml}EX-AHZ%nTL# z=bSNlc?ARlU*@`*T@)KbeE~!Idfa`tcj-d7gKH)zS}=l}0|S}6RT2L>PZY0>kab&% z03rLE_Az05ngK;%k3M6wVg#`^TX`^9U3R~JRj@6$alNjr?>;FcV4HG1I1Nlappl&0 za?2m4Xs)(ut%xSoV)mM&Ji|OUoo^?@7=FZG`1%rRG41t8-l;6tOqIHs5~oi9u$Hh0 zp+$3+1s|LIe&O2?9c=k(%&`vo>n=Ah{AzurY{8iU&#*=$f_TSh>4ab4kGP5frYJthun4|`t#_ai`2wJGV)P{~23-EMi}tc5XD zy37*kDTo16zm(GD5X6UgjcIz#sz18RdTEWZ+!(ZJhH>ZalE0Ih?g0Pd^Qsqj-j$;ax{1HUf6i5? zn{E*dP{-!e2Z30!C_ag%;Rq3w4cWxwhNmYB79t7+k9SDQNTJj>-oHD(b&9HYxVE)+S$>n!0Y!Rhm2j z(qa#!uW?%nBG)Agg9@a$Wl%>{9LB&NZHbJpqJ_+j1d|KmrUrE2ayesG_iY*^X;$S6 ztwyaCD~tJ=HqOEjzLEgy)Egtgr<#r$6A&9L2j{1U`&WPxXjnXd8^w zaP_tb%nM4m<~w{Y(GmK@{&S+Cuhp-q{aTGUPzl3})NnvTV^i~-sxS9ndrxanl`>SE z>2SNc4QXbe!g~{1v8AnC!0))IqjCh47AO_O1b|H287${U9VxO!P7Rxhq7AaZMhga3 zmxWOHojEz{!K;%z_&3o@sfVk4J0jwZR2X*s%EW=AylAgYq{O23x>gG=u%6-P*E-@q zp@QAuHcQhv+4V?L8`|||CwCmKGZ`3j@#KCC`~rh(QRof}l7^wJJkZJfC&IUG0v}Xq z0RMyNdlN{}2aRt-+NC?0it|jE%$$Nyf7J;{E{9M83Dd@lC?hw{hEgI_X*ryPkgDGZ z6*eb}Q2d$!4fa_2Ts=D6j3IP@2POg+_4A+WzQ%rS$XmiW;qW2e$K+J<%A{!P(evYj zI0+D8e#J)zF;9B6Xe&w(?kq~0-Ah>*tp1~3Fcfik#)w}}RtyxOjW_C1DEpYaBv+?< z53l)y@U{-p*~ws8{Cva>d^{XXz|_dXu>7VA>MwsNxnckS literal 0 HcmV?d00001 diff --git a/brand/app-icon/icon-1024.png b/brand/app-icon/icon-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..067663a4a5e0a84c182642771732503db0606171 GIT binary patch literal 20776 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 literal 0 HcmV?d00001 diff --git a/brand/app-icon/icon-192.png b/brand/app-icon/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..040aff145b1a932414348a371fe4b24a31c3999d GIT binary patch literal 3411 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 z zMa3B;2&ysJ&V}J&&^Ep^r#i2c=aN&;RK1+Hbj6I2jXC-c?Zbq7h(+%Fr;*T$*0ShY z`DG33doMh8mPqgVY3g;z>*D;~msi^p@lK*gQF6xQ^7{osEnV>0@Sy9tsG7n$5OMsu z__?*%Xf1`Yy?hUOSqWv9lh%5|M;R79G?HKgT;Y;Q5!lCFF&|pL>2K*s)!ZQa-V+wv z1m3>OrQlfm84-q#0XO(pt0>+^3iUO6cXWzjaLYt-%f48B*1K(R@M~?I zqU<_wi@toLhMKm#XRscv&(JIAQ+J zNXRmz>%Ziz&W`5>O7I5Pypon+YWCYgOX`AZC{~-C<)3dgdqvpn$)6=a MSJMDprEVYoFH2!S6951J literal 0 HcmV?d00001 diff --git a/brand/app-icon/icon-512.png b/brand/app-icon/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..acbba0dfcf4b6b8cb5e37f2d1d0d5583cb9244ab GIT binary patch literal 9365 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 literal 0 HcmV?d00001 diff --git a/brand/app-icon/icon-maskable-192.png b/brand/app-icon/icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b022d484a08c16df6793271d11e3fa87003b8426 GIT binary patch literal 2104 zcmb_edpr~B8-F)ttjJP1Czn*BPf;XJqC>gV2)Uh6%58^oExDb3^ZVoccm8^rF$*UKge}~2|V}3`+rDlyIuZJk84xr5^N{lU$ zNiXgn=x)=cY$%x zEmzCzX9Y2~OrfTx`B3t^g;uT1+c~M)ECWJsDN)aBiuFF6+^$_;K58e30r9Od#sCT= zfHJ^>e~_FYcJF`Fb#}_RqE0aqnL%P@(pZ_#I0OyTQWa!**tw(2ChPRy0#x+EoSG89 zN|IAnz8vuzQ>1c9N|&61Gt22OP9i9r?nsU8rj4f%Gle^`tFQ>B$Lfmif;o<o{R+QC zpwwtts_uKwa8BDDfWB!LDV^kvr{AYfzHP8KC|dT?Z?Q80xr&=QUMZ}I&HzTM>%xb5 z6TGx~EPia-C9h&AU#})G*=k6l5G!trpRwMmv$lv|+~SJhb%eaGlWU*1;ZlOPHH&Mi zQ(m5C#UFUQC_ntkWWg(WYPd=x<7jQoU{l;>hlQ#17lb|yTd3x1TJzC^UgW&w-u2BP z-lJv%-?iaqKDW!;A2hb|S_~_mx1(RtP6&Lv606mvq0d@J=CDzi)>r2bT(Sut8P3f89gbUv1eThQ@}8H}l7YHFP0)B6hYrlD`p)K{Ki_ZC)_}zbZW!l6VhvNO~-+-1W+v)OefR!?-!B#6boT5-Iq2ZJ!8#{Ll6qRnUegJ$oB zlW=nv3E8Jm8|FaQliL~2VT`p`bvk#X+fy0!R;n%@${^2q)8FU0j?UM9R&;%LVvgrA zuDqbdPpZ**yDU+dkC?xYLj-iZS&f`}$^V9;kMwmj^B1%sqrrlJPYZ?-Mf9Bk22LI6 zR8Fkmulq-1bEytF1>#X)a0Hk=>$GzG(x;j%t5wozg$~9^V!LpOo^kie#iTBim;)DB~yz`=q&O6NQSpMPW)hC8UUA+O0{%tkf_Nvw% z5A{tC_@gre>HZed0-cy_`lX;}d9Wl<=keyx7(!Z8I)GEr99a?OR}K;$`l$fck$a|Y z>QEJI)fjEF7`o@0oU8^x-_3V=Pze$TdN1O+$_pj=Q&d1`>A=e}-;cV2^8Jk~+(?Jz$lAz=V;WhEkA3)uTF9^LEa=Mth63za;Xng;=*pCOc_5s#Jx2D0Ak;}(xeB5+<;1*&r zn4rR5#76618_Z5=bWxD@`0_QI%vb{B0M32Dni?+n-rm0Y5Ck5UnW%lduDjLPt;OPq z0;Wh_ac+2&*peUdX)DB0U3_kMFQ)8u+T|xB@2$PQBjYh&?#+l{>tF2M^>1GR$bSs> ej}zkhri7dK&Az*9^v|Ln0YI8snbe$fjs6!gHPFTY literal 0 HcmV?d00001 diff --git a/brand/app-icon/icon-maskable-512.png b/brand/app-icon/icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..80706bc80817977dde68526ddb85e06992d3d72d GIT binary patch literal 5623 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 literal 0 HcmV?d00001 diff --git a/brand/brand.json b/brand/brand.json new file mode 100644 index 0000000..0de6f27 --- /dev/null +++ b/brand/brand.json @@ -0,0 +1,18 @@ +{ + "name": "lockdocs", + "icon": { + "master": "svg/lockdocs-app-icon.svg", + "small": "svg/lockdocs-app-icon.svg", + "apple_touch": "svg/lockdocs-maskable.svg", + "maskable": "svg/lockdocs-maskable.svg", + "palette": ["#06080C", "#7C9CFF", "#42D6A4"], + "snap_threshold": 0.5, + "sizes": [180, 192, 512, 1024], + "maskable_sizes": [192, 512] + }, + "outputs": { + "docs/public/logo.svg": "svg/lockdocs-symbol.svg", + "docs/public/favicon.svg": "favicon/favicon.svg", + "docs/public/favicon.ico": "favicon/favicon.ico" + } +} diff --git a/brand/build.py b/brand/build.py new file mode 100644 index 0000000..9fc45b2 --- /dev/null +++ b/brand/build.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python3 +"""Brand home generator: rebuilds every derived file in brand/ from the SVG masters. + + python3 brand/build.py # regenerate (needs: pip install pillow resvg-py numpy) + python3 brand/build.py --resnap # also redraw the 16/32 px grids from the master + python3 brand/build.py --check # verify hashes and surface copies (stdlib only; for CI) + +brand/brand.json says which master feeds which output and which surface files +are copies of brand files. brand/README.md is the usage sheet. + +Small sizes are pixel-snapped: the master is rendered at 8x and every sample +takes the nearest brand colour from tokens.json (or empty). A pixel takes the +colour most of its samples agree on, and stays empty when fewer than the +threshold of its samples are filled. The result +is stored as a character grid (favicon/grid-16.txt, grid-32.txt) that a +person may hand-edit; later runs draw from the grid until --resnap. +""" +import hashlib, io, json, os, shutil, struct, sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.dirname(HERE) +SPEC = json.load(open(os.path.join(HERE, 'brand.json'))) +TOKENS = json.load(open(os.path.join(HERE, 'tokens.json'))) +GRID_CHARS = '.ABCDEFGHIJKLMNOPQRSTUVWXYZ' + + +def p(rel): + return os.path.join(HERE, rel) + + +def sha(path): + return hashlib.sha256(open(path, 'rb').read()).hexdigest() + + +def colour_tokens(): + out = {} + for name, tok in TOKENS.get('color', {}).items(): + out[name] = tok['$value'] if isinstance(tok, dict) else tok + return out + + +# ---------------------------------------------------------------- tokens.css +def write_tokens_css(): + lines = ['/* Generated by brand/build.py from brand/tokens.json. Do not edit. */', ':root {'] + for group, items in TOKENS.items(): + if group.startswith('$') or not isinstance(items, dict): + continue + for name, tok in items.items(): + v = tok['$value'] if isinstance(tok, dict) else tok + if isinstance(v, list): + v = ', '.join(v) + lines.append(f' --brand-{group}-{name}: {v};') + lines.append('}') + open(p('tokens.css'), 'w').write('\n'.join(lines) + '\n') + + +# ---------------------------------------------------------------- rendering +def render(svg_rel, w, h=None): + import resvg_py + from PIL import Image + h = h or w + png = resvg_py.svg_to_bytes(svg_path=p(svg_rel), width=w, height=h) + return Image.open(io.BytesIO(bytes(png))).convert('RGBA') + + +def save_png(img, rel): + """Save by extension: .png (optimised), .jpg/.jpeg (quality 85), .webp.""" + os.makedirs(os.path.dirname(p(rel)), exist_ok=True) + if rel.lower().endswith(('.jpg', '.jpeg')): + img.convert('RGB').save(p(rel), quality=85, optimize=True, progressive=True) + else: + img.save(p(rel), optimize=True) + + +def hex_rgb(h): + h = h.lstrip('#') + return tuple(int(h[i:i + 2], 16) for i in (0, 2, 4)) + + +def snap(svg_rel, size, threshold, palette): + """Render at 8x and quantise every sample to the nearest palette colour (or empty when + under half opaque). A pixel takes the colour most of its samples agree on, and stays + empty when fewer than `threshold` of its samples are filled.""" + import numpy as np + ss = 8 + a = np.asarray(render(svg_rel, size * ss), dtype=np.float64) / 255.0 + pal = np.array([hex_rgb(c) for c in palette], dtype=np.float64) / 255.0 + d = ((a[..., None, :3] - pal[None, None]) ** 2).sum(axis=-1) + idx = d.argmin(axis=-1) + 1 + idx[a[..., 3] < 0.5] = 0 + idx = idx.reshape(size, ss, size, ss).transpose(0, 2, 1, 3).reshape(size, size, ss * ss) + rows = [] + for y in range(size): + row = '' + for x in range(size): + counts = np.bincount(idx[y, x], minlength=len(palette) + 1) + filled = counts[1:].sum() / (ss * ss) + row += '.' if filled < threshold else GRID_CHARS[1 + int(counts[1:].argmax())] + rows.append(row) + return rows + + +def grid_path(size): + return f'favicon/grid-{size}.txt' + + +def write_grid(size, rows, palette): + header = [f'# {size}x{size} pixel grid for the favicon. "." is empty; letters index the palette below.', + '# Hand edits are kept: build.py draws from this file until --resnap.'] + header += [f'# {GRID_CHARS[i + 1]} = {c}' for i, c in enumerate(palette)] + open(p(grid_path(size)), 'w').write('\n'.join(header + rows) + '\n') + + +def read_grid(size): + rows, pal = [], {} + for line in open(p(grid_path(size))): + line = line.rstrip('\n') + if line.startswith('# ') and ' = #' in line: + k, v = line[2:].split(' = ') + pal[k.strip()] = v.strip() + elif line and not line.startswith('#'): + rows.append(line) + assert len(rows) == size and all(len(r) == size for r in rows), f'grid-{size} must be {size}x{size}' + return rows, pal + + +def grid_png(rows, pal, size): + from PIL import Image + img = Image.new('RGBA', (size, size), (0, 0, 0, 0)) + for y, r in enumerate(rows): + for x, ch in enumerate(r): + if ch != '.': + img.putpixel((x, y), hex_rgb(pal[ch]) + (255,)) + return img + + +def grid_svg(rows, pal, size, dark_map=None): + """Pixel grid as one path per colour, run-length merged along rows. dark_map swaps + colours when the browser is in a dark theme.""" + paths = {} + for y, r in enumerate(rows): + x = 0 + while x < size: + ch = r[x] + if ch == '.': + x += 1 + continue + x0 = x + while x < size and r[x] == ch: + x += 1 + paths.setdefault(ch, []).append(f'M{x0} {y}h{x - x0}v1h-{x - x0}z') + dark = {ch: dark_map[c] for ch, c in pal.items() if dark_map and c in dark_map} + style = '' + if dark: + rules = ''.join(f'.{ch}{{fill:{c}}}' for ch, c in sorted(dark.items())) + style = f'' + body = ''.join(f'' + for ch, d in sorted(paths.items())) + return (f'{style}{body}\n') + + +def write_ico(images, rel): + """ICO holding PNG entries (one per size, drawn separately).""" + blobs = [] + for img in images: + b = io.BytesIO() + img.save(b, format='PNG', optimize=True) + blobs.append((img.size[0], b.getvalue())) + out = struct.pack(';cyhaRB{wE9Z?K%H!d%eGdeC7>zQlndQOgI{`}KQ#PDD&!6yY0HxW z#z{hon^<4$JF4#f)uiDpQ&CgBiFl7y!;6>zv!}+9ZRJlt@LirHF#Y@q&Xo%#nVu}; zoaM@>$|u#^v@lSh_ZsWr+l()2O{<#X?e`fNCB8Pebh>DY#)9R_6W%5rx?Z}>{ksf9 Yf3#Ck)^m<@psN`?UHx3vIVCg!0EtLmKL7v# literal 0 HcmV?d00001 diff --git a/brand/favicon/favicon-48.png b/brand/favicon/favicon-48.png new file mode 100644 index 0000000000000000000000000000000000000000..a4cabefe83b6c867676cc07fdd0bd26debcb87bd GIT binary patch literal 964 zcmV;#13UbQP)ozulWK#+rcJV^9L=JE;K|K*_vc?M%6ZRrKDo4Ws_h7<-L_Mw? zU=sloVyIe<$`TJqg0WflLN&y#%}Pm1I$K+2cW3)NxWxjq?9#rS&LsSA)A{D*|DX5$ zzxTe*6RZKtitlRS9usg-3Neb5aryuuL{18(mYVgP=Y22Y68D^hg9HcyrVZUV(P-2P zoxkgklaGzJguDX0M)0t!g**~5%!=zS6`m4cS+VgJ@*H3hTv!suB8EBF79jwl(fH_c zYvo$M^1KUU)HJE@b)4Ft8i2dvejXa~08+-KSos1#Y#E4+(UMmPAvBCvb;B@E2h_Cd zPXsMnY_VRFgcJgj(zHUNNIZ*H2I4b@`~?i(R>JcBiqz#K{dusC1EnXgj%Dvy1u zsMO5LX26IIkjfrr=;+(ejGMpsBc;i!%4S8GfW4Pa65YLHL$QrJfAQPRH-7}ZhjJ9)$&b1e>QY41oU}B*X=s6J(*_g z!;1_icJ@TTuFjD=dz4Cf#yH6~B(!D-g~Nul9%Mj3ekb8DR8 zbjCjqy!=W^c`kQ5rm}uu@nMZ`zC65cE^kk!Ir7m}WizO(3OP-CLq@EWXKmWrPs=}DFWIeRFd=E13!KH33hev=`D9M&0FuC4z>WhTH^LEUwhsw z{r+?C1#C;EdH47w?t2(HHT*{KQ>eYbb-N(>T1Nkv$tx$)?U-E&sFY`zy?H?y`PrKn z{N2p8v1k7WJ#Q-(9;|JCU;b8k(a`kN%?LZx0)&(WO$hN_SOrL!)}-KkSOr9MBd0Aj z>p20}LzqBQjYh4Y0T{aRF+$kkGOzwAtbXWTUUu$94D+6Zv5=I`Lyoghcq9P8b(abe z!<>^syh%{M*8f*F4>`_1)1K>qc{rX#wMd-9Y@8~+0S=Swhcg8zvC0000U=ZM71CsKe|0e>e5>FS$kcwN$DGf|!eDnVEs5Hde?=vtm0D_5&B_sKj zEoV(+6t?r6U2H6obMCCd8K8J?O9`U*bTKsO5v}{rWvVCn6`$dBelNQ{W+|ygkeqXg`CetDnm{ zr-UXfj#U6kD}WtaTvhc2$XNw*tOIk7jLm<^434_LKMr-ANnf=5>5VrDU@~dZ_NOoU z5@a||7cOfQe90hIWO8%P?8eYP9f53bof)P-ZFy3_I7vux6YGn8N7dcGnlzkcDr%}X z5$~~Tco7p|_S87Ct^DZ+zRQyYrk_8-xpJW-)01VKvs@Wf`J{TA76uCRUSmCcoAE`h zX;l+As1jcrTsmDeMPtEo#I?&blya5;W(y*$0FqNCHw(|_CSILfs7&82;F z&#C;$zx?yr)Bp3o|K0z6-#OtmjHkQaXFZe?E(qW%3HJR_7 zv|_1Ev+0xrGF}WDEaH#OTp#pHtG{;jiYsSMr5)(top4Gn?Zu1R=X+-F|Dn9}uk;KSZC)ZHtE_467{8Yb3XTqyvxT{>$ z7rJdcbJuwK^`5=e&y2e3!)@okoz0NF^%6tKdeM2Ae%tRY`Sh3feEM&-18)>AYD~Ls zUf}LiqH?pI(SP5==bbk*u72`$s5dLoXZcd;eEqG}2HTlwZw`0!sFi(lIk4{BMC diff --git a/brand/favicon/grid-16.txt b/brand/favicon/grid-16.txt new file mode 100644 index 0000000..81b979b --- /dev/null +++ b/brand/favicon/grid-16.txt @@ -0,0 +1,21 @@ +# 16x16 pixel grid for the favicon. "." is empty; letters index the palette below. +# Hand edits are kept: build.py draws from this file until --resnap. +# A = #06080C +# B = #7C9CFF +# C = #42D6A4 +..AAAAAAAAAAAA.. +.AAAAAAAAAAAAAA. +AAAAAAAAAAAAAAAA +AAAAAAACCAAAAAAA +AAAAAACAACAAAAAA +AAAAACAAAACAAAAA +AAAAACAAAACAAAAA +AAAAABBBBBBAAAAA +AAAAABBBBBBAAAAA +AAAAABBBBBBAAAAA +AAAAABBABBBAAAAA +AAAAABBBBBBAAAAA +AAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAA +.AAAAAAAAAAAAAA. +..AAAAAAAAAAAA.. diff --git a/brand/favicon/grid-32.txt b/brand/favicon/grid-32.txt new file mode 100644 index 0000000..9c86a26 --- /dev/null +++ b/brand/favicon/grid-32.txt @@ -0,0 +1,37 @@ +# 32x32 pixel grid for the favicon. "." is empty; letters index the palette below. +# Hand edits are kept: build.py draws from this file until --resnap. +# A = #06080C +# B = #7C9CFF +# C = #42D6A4 +....AAAAAAAAAAAAAAAAAAAAAAAA.... +...AAAAAAAAAAAAAAAAAAAAAAAAAA... +..AAAAAAAAAAAAAAAAAAAAAAAAAAAA.. +.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA. +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAACCCCCCAAAAAAAAAAAAA +AAAAAAAAAAAACCCCCCCCAAAAAAAAAAAA +AAAAAAAAAAACCCAAAACCCAAAAAAAAAAA +AAAAAAAAAAACCAAAAAACCAAAAAAAAAAA +AAAAAAAAAAACCAAAAAACCAAAAAAAAAAA +AAAAAAAAAAACCAAAAAACCAAAAAAAAAAA +AAAAAAAAAAACCAAAAAACCAAAAAAAAAAA +AAAAAAAAAAACCAAAAAACCAAAAAAAAAAA +AAAAAAAAABBBBBBBBBBBBBBAAAAAAAAA +AAAAAAAAABBBBBBBBBBBBBBAAAAAAAAA +AAAAAAAAABBBBAAAAAABBBBAAAAAAAAA +AAAAAAAAABBBBAAAAAABBBBAAAAAAAAA +AAAAAAAAABBBBBBBBBBBBBBAAAAAAAAA +AAAAAAAAABBBBAAAABBBBBBAAAAAAAAA +AAAAAAAAABBBBAAAABBBBBBAAAAAAAAA +AAAAAAAAABBBBBBBBBBBBBBAAAAAAAAA +AAAAAAAAABBBBBBBBBBBBBBAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA. +..AAAAAAAAAAAAAAAAAAAAAAAAAAAA.. +...AAAAAAAAAAAAAAAAAAAAAAAAAA... +....AAAAAAAAAAAAAAAAAAAAAAAA.... diff --git a/brand/provenance.json b/brand/provenance.json new file mode 100644 index 0000000..1ab7164 --- /dev/null +++ b/brand/provenance.json @@ -0,0 +1,102 @@ +{ + "about": "Where every file in brand/ came from, with its SHA-256. build.py refreshes the hashes.", + "files": { + "README.md": { + "source": "the usage sheet for this brand home, written for the audit of 2026-09-28", + "sha256": "9802f675db42bebc1ad56526a6c7ee65f7001ea231cdded5f72b7e56d08614b5" + }, + "app-icon/apple-touch-icon-180.png": { + "source": "generated by build.py", + "sha256": "d6fc00b6744c5b9586f685cf22bd7442e8ae6e3628c970bb4a6786d7164c8100" + }, + "app-icon/icon-1024.png": { + "source": "generated by build.py", + "sha256": "b33a02acd9c0a0bf0b99bf6bf3423be21c89d4400dfcee88b807e447448eeb9a" + }, + "app-icon/icon-192.png": { + "source": "generated by build.py", + "sha256": "49a272fafe59281a8b7263a90721e92f270f00fd09d27b3fd0593bb646c31bae" + }, + "app-icon/icon-512.png": { + "source": "generated by build.py", + "sha256": "5e885a3260b5a307cc0045abbd1add2eeb0ae529986b3fdd62c81e4abafc1af1" + }, + "app-icon/icon-maskable-192.png": { + "source": "generated by build.py", + "sha256": "ce140eb07785347537d1c84bb804ad28d02f45fd3b4ec0e367c1aaf5545d8368" + }, + "app-icon/icon-maskable-512.png": { + "source": "generated by build.py", + "sha256": "9ec9f90aa165231ee95d7ecbe58cc368e6e7d66a8f7b083c91e92510031d3ec9" + }, + "brand.json": { + "source": "written 2026-09-28 for this brand home: names the masters, the icon palette for the 16/32 px grids, and the surface files that are copies of brand files", + "sha256": "b3365e3feb4b87fb388e42c80bedea9fdfb7cd8bbf4cd715763a12f11a469e7f" + }, + "build.py": { + "source": "copy of the shared brand-home generator /scratch/sylphx-apps/brand-kit/build.py, unmodified", + "sha256": "b570fd5af590caa0a73ccd0461ef7be445f05ea71d6560c9ee933442b8c072fb" + }, + "favicon/favicon-16.png": { + "source": "generated by build.py", + "sha256": "2a28e3f363ebf9256537766b76987e6130e8a94407a2c1dc27111a49004b2757" + }, + "favicon/favicon-32.png": { + "source": "generated by build.py", + "sha256": "de69897a8e5ac2883b3f686cdd02365e29fc97be5ce413b144d0642001e370bd" + }, + "favicon/favicon-48.png": { + "source": "generated by build.py", + "sha256": "65f5fa3987a95624088de6c0d5e626b5879793e698824e6d99cec0e3e65ccb88" + }, + "favicon/favicon.ico": { + "source": "generated by build.py", + "sha256": "da819f7d2e32ac33a8b8fa5c0f930309e673c9d86aa118a9e19819c5b755f78b" + }, + "favicon/favicon.svg": { + "source": "generated by build.py", + "sha256": "ce6571a05cdedb61b9d6db573b6ef3da06d1456ee0638fce41f37304cbb23ed1" + }, + "favicon/grid-16.txt": { + "source": "generated by build.py", + "sha256": "2e114b685ee51deb6a7549c5901565842e1a23a075f1249f5919b47f9da57691" + }, + "favicon/grid-32.txt": { + "source": "generated by build.py", + "sha256": "144c21fa1bc84370c3ddf07cbbf6a4a626b5d386d0d88005a32a73328c899a41" + }, + "svg/lockdocs-app-icon.svg": { + "source": "specified by the brand lead in the audit of 2026-09-28: the symbol on a #06080C rounded square (rx 22% of the 1024 side), symbol at 70% width, centred", + "sha256": "a5058f927de47ed94eb9f58d44ec146cc514165a431c81d930f8b3e11be2215e" + }, + "svg/lockdocs-maskable.svg": { + "source": "svg/lockdocs-app-icon.svg without the corner radius, so masks that crop (Android maskable, Apple touch) have a full-bleed square", + "sha256": "dd55b9d3e7f7912d6fe9f4f0eca1dbf0cee8d85445bd57dbc01369690d9d5b71" + }, + "svg/lockdocs-symbol-black.svg": { + "source": "one-colour version of svg/lockdocs-symbol.svg: every fill and stroke set to #000000, with the two document lines cut out of the lock body using fill-rule=\"evenodd\" so they stay visible", + "sha256": "dd65b1813ff2287a8783e086998076745754014803b16e898b67a557a4377c29" + }, + "svg/lockdocs-symbol-white.svg": { + "source": "one-colour version of svg/lockdocs-symbol.svg: every fill and stroke set to #FFFFFF, with the two document lines cut out of the lock body using fill-rule=\"evenodd\" so they stay visible", + "sha256": "4df58371e47dc511ccc08f2b2a696349afe8ac9fa3601e5c306000c3d8d984fb" + }, + "svg/lockdocs-symbol.svg": { + "source": "docs/public/logo.svg moved here 2026-09-28; git history for that path starts at d4c4d54 (2026-09-25, 'feat: lockdocs 0.1 - exact-version library docs from your lockfile, offline (#1)') and the drawing is unchanged. build.py copies it back to docs/public/logo.svg.", + "sha256": "8d553fc7585fb61d4e788582ab250ff5af155bf14f8647258e90fbc895653721" + }, + "tokens.css": { + "source": "generated by build.py", + "sha256": "760eb9d8cf89aa8b27e6089f0b2f48829a19a9cfa8132c063196ef1a4202939e" + }, + "tokens.json": { + "source": "the colours and the type face the product already shipped: the logo's three colours, the docs theme's background and accent roles (docs/.vitepress/theme/custom.css) and the theme's Inter stack", + "sha256": "8bd583de4e27f9bbc5bf34f1ef0d944e1e653c6746531d5feae4264d3150d851" + } + }, + "surfaces": { + "docs/public/favicon.ico": "favicon/favicon.ico", + "docs/public/favicon.svg": "favicon/favicon.svg", + "docs/public/logo.svg": "svg/lockdocs-symbol.svg" + } +} diff --git a/brand/svg/lockdocs-app-icon.svg b/brand/svg/lockdocs-app-icon.svg new file mode 100644 index 0000000..2fb44c5 --- /dev/null +++ b/brand/svg/lockdocs-app-icon.svg @@ -0,0 +1 @@ + diff --git a/brand/svg/lockdocs-maskable.svg b/brand/svg/lockdocs-maskable.svg new file mode 100644 index 0000000..02d1a8d --- /dev/null +++ b/brand/svg/lockdocs-maskable.svg @@ -0,0 +1 @@ + diff --git a/brand/svg/lockdocs-symbol-black.svg b/brand/svg/lockdocs-symbol-black.svg new file mode 100644 index 0000000..7ceb873 --- /dev/null +++ b/brand/svg/lockdocs-symbol-black.svg @@ -0,0 +1 @@ + diff --git a/brand/svg/lockdocs-symbol-white.svg b/brand/svg/lockdocs-symbol-white.svg new file mode 100644 index 0000000..91ccd8c --- /dev/null +++ b/brand/svg/lockdocs-symbol-white.svg @@ -0,0 +1 @@ + diff --git a/brand/svg/lockdocs-symbol.svg b/brand/svg/lockdocs-symbol.svg new file mode 100644 index 0000000..9fddf84 --- /dev/null +++ b/brand/svg/lockdocs-symbol.svg @@ -0,0 +1 @@ + diff --git a/brand/tokens.css b/brand/tokens.css new file mode 100644 index 0000000..85aad04 --- /dev/null +++ b/brand/tokens.css @@ -0,0 +1,13 @@ +/* Generated by brand/build.py from brand/tokens.json. Do not edit. */ +:root { + --brand-color-bg: #06080C; + --brand-color-bg-raised: #0B0E14; + --brand-color-bg-elevated: #0E1118; + --brand-color-bg-soft: #11151D; + --brand-color-accent: #7C9CFF; + --brand-color-accent-hover: #9DB4FF; + --brand-color-accent-active: #6480E8; + --brand-color-mint: #42D6A4; + --brand-color-gold: #FFD166; + --brand-font-sans: Inter, ui-sans-serif, system-ui, sans-serif; +} diff --git a/brand/tokens.json b/brand/tokens.json new file mode 100644 index 0000000..78f66d1 --- /dev/null +++ b/brand/tokens.json @@ -0,0 +1,16 @@ +{ + "color": { + "bg": {"$value": "#06080C", "$type": "color", "$description": "Page ground; the app-icon ground; the document lines cut out of the logo's lock body."}, + "bg-raised": {"$value": "#0B0E14", "$type": "color", "$description": "Alternating background: code blocks, sidebar, footer."}, + "bg-elevated": {"$value": "#0E1118", "$type": "color", "$description": "Elevated surfaces: menus, dialogs, hover cards."}, + "bg-soft": {"$value": "#11151D", "$type": "color", "$description": "Soft fills and panels."}, + "accent": {"$value": "#7C9CFF", "$type": "color", "$description": "The lock body; links, badges and primary buttons (docs --vp-c-brand-1)."}, + "accent-hover": {"$value": "#9DB4FF", "$type": "color", "$description": "Links, badges and primary buttons on hover (docs --vp-c-brand-2)."}, + "accent-active": {"$value": "#6480E8", "$type": "color", "$description": "Links and buttons when pressed (docs --vp-c-brand-3)."}, + "mint": {"$value": "#42D6A4", "$type": "color", "$description": "The shackle; second stop of the hero gradient."}, + "gold": {"$value": "#FFD166", "$type": "color", "$description": "Third stop of the hero gradient."} + }, + "font": { + "sans": {"$value": ["Inter", "ui-sans-serif", "system-ui", "sans-serif"], "$type": "fontFamily", "$description": "Body and interface, Latin and CJK; VitePress serves Inter with the docs theme, self-hosted from the built site."} + } +} diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index f4d45e1..809c775 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,11 +1,12 @@ import { defineConfig } from 'vitepress' +import tokens from '../../brand/tokens.json' +const base = '/lockdocs/' const url = 'https://sylphxai.github.io/lockdocs/' const desc = 'Exact-version library docs from your lockfile — local, offline, no rate limits.' -const icon = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect x='6' y='14' width='20' height='14' rx='3' fill='%237c9cff'/%3E%3Cpath d='M10 14V10a6 6 0 0 1 12 0v4' fill='none' stroke='%2342d6a4' stroke-width='3'/%3E%3Crect x='11' y='18' width='10' height='2' rx='1' fill='%2306080c'/%3E%3Crect x='11' y='22' width='7' height='2' rx='1' fill='%2306080c'/%3E%3C/svg%3E" export default defineConfig({ - base: '/lockdocs/', + base, title: 'lockdocs', description: desc, appearance: 'force-dark', @@ -15,8 +16,10 @@ export default defineConfig({ lastUpdated: true, sitemap: { hostname: url }, head: [ - ['link', { rel: 'icon', href: icon }], - ['meta', { name: 'theme-color', content: '#06080c' }], + // The icons are copies of the brand home's files (brand/svg, brand/favicon). + ['link', { rel: 'icon', href: `${base}favicon.ico`, sizes: '48x48' }], + ['link', { rel: 'icon', type: 'image/svg+xml', href: `${base}favicon.svg` }], + ['meta', { name: 'theme-color', content: tokens.color.bg.$value }], ['meta', { property: 'og:type', content: 'website' }], ['meta', { property: 'og:site_name', content: 'lockdocs' }], ['meta', { property: 'og:image', content: `${url}img/demo.gif` }], diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index e8cc156..09189a0 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -1,15 +1,17 @@ +@import '../../../brand/tokens.css'; + :root, .dark { - --vp-c-bg: #06080c; - --vp-c-bg-alt: #0b0e14; - --vp-c-bg-elv: #0e1118; - --vp-c-bg-soft: #11151d; + --vp-c-bg: var(--brand-color-bg); + --vp-c-bg-alt: var(--brand-color-bg-raised); + --vp-c-bg-elv: var(--brand-color-bg-elevated); + --vp-c-bg-soft: var(--brand-color-bg-soft); --vp-c-divider: rgba(255, 255, 255, 0.08); - --vp-c-brand-1: #7c9cff; - --vp-c-brand-2: #9db4ff; - --vp-c-brand-3: #6480e8; + --vp-c-brand-1: var(--brand-color-accent); + --vp-c-brand-2: var(--brand-color-accent-hover); + --vp-c-brand-3: var(--brand-color-accent-active); --vp-c-brand-soft: rgba(124, 156, 255, 0.14); --vp-home-hero-name-color: transparent; - --vp-home-hero-name-background: linear-gradient(120deg, #7c9cff 10%, #42d6a4 60%, #ffd166 95%); + --vp-home-hero-name-background: linear-gradient(120deg, var(--brand-color-accent) 10%, var(--brand-color-mint) 60%, var(--brand-color-gold) 95%); --vp-home-hero-image-background-image: radial-gradient(circle, rgba(124,156,255,.35), rgba(66,214,164,.2) 50%, transparent 70%); --vp-home-hero-image-filter: blur(56px); } diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9be3e3d8434a10a6ba84442662d895cee9c3e4ad GIT binary patch literal 1418 zcmZQzU}Rus5D;Jh(h3Y~85kJMfLK8R!hZ~8gRp@CgnxvYf#Db<14CzkpF1y?6cU=ZM71CsKe|0e>e5>FS$kcwN$DGf|!eDnVEs5Hde?=vtm0D_5&B_sKj zEoV(+6t?r6U2H6obMCCd8K8J?O9`U*bTKsO5v}{rWvVCn6`$dBelNQ{W+|ygkeqXg`CetDnm{ zr-UXfj#U6kD}WtaTvhc2$XNw*tOIk7jLm<^434_LKMr-ANnf=5>5VrDU@~dZ_NOoU z5@a||7cOfQe90hIWO8%P?8eYP9f53bof)P-ZFy3_I7vux6YGn8N7dcGnlzkcDr%}X z5$~~Tco7p|_S87Ct^DZ+zRQyYrk_8-xpJW-)01VKvs@Wf`J{TA76uCRUSmCcoAE`h zX;l+As1jcrTsmDeMPtEo#I?&blya5;W(y*$0FqNCHw(|_CSILfs7&82;F z&#C;$zx?yr)Bp3o|K0z6-#OtmjHkQaXFZe?E(qW%3HJR_7 zv|_1Ev+0xrGF}WDEaH#OTp#pHtG{;jiYsSMr5)(top4Gn?Zu1R=X+-F|Dn9}uk;KSZC)ZHtE_467{8Yb3XTqyvxT{>$ z7rJdcbJuwK^`5=e&y2e3!)@okoz0NF^%6tKdeM2Ae%tRY`Sh3feEM&-18)>AYD~Ls zUf}LiqH?pI(SP5==bbk*u72`$s5dLoXZcd;eEqG}2HTlwZw`0!sFi(lIk4{BMC From a2f9f6c719ec2a710646661cdff0d3dd4f9292aa Mon Sep 17 00:00:00 2001 From: Kyle Tse Date: Mon, 28 Sep 2026 02:48:31 +0000 Subject: [PATCH 2/3] docs(brand): say how a regenerated icon reaches the live site --- brand/README.md | 5 +++-- brand/provenance.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/brand/README.md b/brand/README.md index 7c5da2e..5ff3c1a 100644 --- a/brand/README.md +++ b/brand/README.md @@ -111,8 +111,9 @@ alone — for anything that crops or masks, use the full-bleed `lockdocs-maskabl | `docs/public/favicon.ico` | `favicon/favicon.ico` | `python3 brand/build.py --check` fails if any of these stops being a -byte-for-byte copy. Rebuilding the site is not needed for icon changes: the -files are served from `docs/public/` as they are. +byte-for-byte copy. The website files are plain copies under `docs/public/`, so +a regenerated icon is picked up by the next docs build and deploy; the deploy +workflow runs on changes under `docs/`. ### Surfaces still to move diff --git a/brand/provenance.json b/brand/provenance.json index 1ab7164..1349e9b 100644 --- a/brand/provenance.json +++ b/brand/provenance.json @@ -3,7 +3,7 @@ "files": { "README.md": { "source": "the usage sheet for this brand home, written for the audit of 2026-09-28", - "sha256": "9802f675db42bebc1ad56526a6c7ee65f7001ea231cdded5f72b7e56d08614b5" + "sha256": "2d37af6176f75b3a1d09465b86c98cecbfe3dcf6dbf74e5a70da654e13390d06" }, "app-icon/apple-touch-icon-180.png": { "source": "generated by build.py", From 0034a642aeeb3ec43f2dc639e5222435f7322d67 Mon Sep 17 00:00:00 2001 From: Kyle Tse Date: Mon, 28 Sep 2026 02:51:27 +0000 Subject: [PATCH 3/3] docs(brand): similarity check and review fixes --- brand/README.md | 2 +- brand/build.py | 8 +++++++- brand/favicon/favicon-16.png | Bin 173 -> 179 bytes brand/favicon/favicon.ico | Bin 1418 -> 1424 bytes brand/favicon/grid-16.txt | 4 ++-- brand/provenance.json | 12 ++++++------ docs/public/favicon.ico | Bin 1418 -> 1424 bytes 7 files changed, 16 insertions(+), 10 deletions(-) diff --git a/brand/README.md b/brand/README.md index 5ff3c1a..5ef1a6f 100644 --- a/brand/README.md +++ b/brand/README.md @@ -158,4 +158,4 @@ workflow runs on changes under `docs/`. Not registered. Owner decision owner#781: no trademark filings before the product earns money. Use ™ at most, never ®. - +Checked 2026-09-28. **Same name, different category:** LockDocs (lockdocs.com) is a client-document and smart-forms platform for wealth and fund managers, listed on Microsoft AppSource. Not a developer tool. Noted once, not blocking. Mark (a padlock over document lines): padlock marks are common; no close match found among AI docs tools (Context7). diff --git a/brand/build.py b/brand/build.py index 9fc45b2..3b92f91 100644 --- a/brand/build.py +++ b/brand/build.py @@ -60,7 +60,13 @@ def render(svg_rel, w, h=None): from PIL import Image h = h or w png = resvg_py.svg_to_bytes(svg_path=p(svg_rel), width=w, height=h) - return Image.open(io.BytesIO(bytes(png))).convert('RGBA') + img = Image.open(io.BytesIO(bytes(png))).convert('RGBA') + if img.size != (w, h): # a non-square master keeps its aspect ratio: centre it on the canvas + img.thumbnail((w, h), Image.LANCZOS) + canvas = Image.new('RGBA', (w, h), (0, 0, 0, 0)) + canvas.paste(img, ((w - img.width) // 2, (h - img.height) // 2)) + img = canvas + return img def save_png(img, rel): diff --git a/brand/favicon/favicon-16.png b/brand/favicon/favicon-16.png index 775a9905d1e4ff3ac322bf13764f12037a79153f..21726494dd5d932e92366a17ad2644f4641fc533 100644 GIT binary patch delta 76 zcmZ3>xS4T+3S-qoRX+#ABM}8Nd)7%zUueO5U}>X{_ygmfB=HMoszRv@I^h$U>;C>Y gbfRUB>B*Dl7#>9UE6li)p1}YFp00i_>zopr0Fy-?g8%>k delta 70 zcmdnYxR!B(3S-GcRX;0FZkMj)#w6wqmNQkD3tCwZIxNYTI8Y>N`QUoLevi+I$jNiw Z@G$Tcc*rSl4>M)}0#8>zmvv4FO#p3-7*GHJ diff --git a/brand/favicon/favicon.ico b/brand/favicon/favicon.ico index 9be3e3d8434a10a6ba84442662d895cee9c3e4ad..55453a716d005302f38475cb4c1315bd8a128117 100644 GIT binary patch delta 125 zcmeC;p1^I)z`)4B%pf2Dq7@i6GcYii0kMJtg#Q@GehI_|1`z%cW(I~c6OGjwt0qSK znHV04D45x^PGb5(3*G}u8+F7V822QJUocY@N@dUqpU7PI_s5|VEptpyo;=6!Ai`f^ R#+~$x$-5ZkH-4MK0sv;9B@O@p delta 119 zcmbQh-NkLpz`)4B%pf2Dq7@j{GB7Zh0kMJtg#Q@G24MpO2>%E(1H-Y2#_EhE6Qlj~ zJh@%Ek{gqlH(1V8VJ>K8J?O9`U*bTKsO5v}{rWvVCn6`$dBelNQ{W+|ygkf#@&QKq Jjen-F001#j9@PK< diff --git a/brand/favicon/grid-16.txt b/brand/favicon/grid-16.txt index 81b979b..a23df71 100644 --- a/brand/favicon/grid-16.txt +++ b/brand/favicon/grid-16.txt @@ -11,9 +11,9 @@ AAAAAACAACAAAAAA AAAAACAAAACAAAAA AAAAACAAAACAAAAA AAAAABBBBBBAAAAA +AAAAABAAAABAAAAA AAAAABBBBBBAAAAA -AAAAABBBBBBAAAAA -AAAAABBABBBAAAAA +AAAAABAAABBAAAAA AAAAABBBBBBAAAAA AAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAA diff --git a/brand/provenance.json b/brand/provenance.json index 1349e9b..b903435 100644 --- a/brand/provenance.json +++ b/brand/provenance.json @@ -3,7 +3,7 @@ "files": { "README.md": { "source": "the usage sheet for this brand home, written for the audit of 2026-09-28", - "sha256": "2d37af6176f75b3a1d09465b86c98cecbfe3dcf6dbf74e5a70da654e13390d06" + "sha256": "16ec0dd668209e524aa872750e84e3b5a4a4015066681156fa678fe7149b1efb" }, "app-icon/apple-touch-icon-180.png": { "source": "generated by build.py", @@ -34,12 +34,12 @@ "sha256": "b3365e3feb4b87fb388e42c80bedea9fdfb7cd8bbf4cd715763a12f11a469e7f" }, "build.py": { - "source": "copy of the shared brand-home generator /scratch/sylphx-apps/brand-kit/build.py, unmodified", - "sha256": "b570fd5af590caa0a73ccd0461ef7be445f05ea71d6560c9ee933442b8c072fb" + "source": "copy of the shared brand-home generator, unmodified", + "sha256": "406f811f5e85e5969fa6905cc8f8503788b7d70d0998426922e2ba97caa1bdcf" }, "favicon/favicon-16.png": { "source": "generated by build.py", - "sha256": "2a28e3f363ebf9256537766b76987e6130e8a94407a2c1dc27111a49004b2757" + "sha256": "aaf5e4a3665fd7e3578156604c4fe2f7704969a958f3593e003f1ab182edfdab" }, "favicon/favicon-32.png": { "source": "generated by build.py", @@ -51,7 +51,7 @@ }, "favicon/favicon.ico": { "source": "generated by build.py", - "sha256": "da819f7d2e32ac33a8b8fa5c0f930309e673c9d86aa118a9e19819c5b755f78b" + "sha256": "e020fcdb5849785fe457f6a7c98b94c42b2b556b074d1cc11c7ef2b5aa2d872e" }, "favicon/favicon.svg": { "source": "generated by build.py", @@ -59,7 +59,7 @@ }, "favicon/grid-16.txt": { "source": "generated by build.py", - "sha256": "2e114b685ee51deb6a7549c5901565842e1a23a075f1249f5919b47f9da57691" + "sha256": "1bbfd78ae12ec900a53e28309885563ccd64076005c02d73168c19176214b6d0" }, "favicon/grid-32.txt": { "source": "generated by build.py", diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico index 9be3e3d8434a10a6ba84442662d895cee9c3e4ad..55453a716d005302f38475cb4c1315bd8a128117 100644 GIT binary patch delta 125 zcmeC;p1^I)z`)4B%pf2Dq7@i6GcYii0kMJtg#Q@GehI_|1`z%cW(I~c6OGjwt0qSK znHV04D45x^PGb5(3*G}u8+F7V822QJUocY@N@dUqpU7PI_s5|VEptpyo;=6!Ai`f^ R#+~$x$-5ZkH-4MK0sv;9B@O@p delta 119 zcmbQh-NkLpz`)4B%pf2Dq7@j{GB7Zh0kMJtg#Q@G24MpO2>%E(1H-Y2#_EhE6Qlj~ zJh@%Ek{gqlH(1V8VJ>K8J?O9`U*bTKsO5v}{rWvVCn6`$dBelNQ{W+|ygkf#@&QKq Jjen-F001#j9@PK<