The mark, rebuilt as true vector geometry — every curve a circle with a stated centre and radius.
+
+
+
+
Drawn 28 Aug 2026
+
Primary file 863 bytes
+
Curves 4 arcs, 1 line, 1 circle
+
Source public/brand/
+
+
+
+
+ 01
+
The set
+ 13 SVG · 2 PNG
+
+
+ One mark, cut for the grounds it actually has to sit on. The checkered stage shows where each file is transparent.
+
+
+
+
+
+
+ Primary markpsychsift-mark.svg
+
+
+
+
+
+ On brand bluepsychsift-mark-blue.svg
+
+
+
+
+
+ On a light tilepsychsift-mark-light.svg
+
+
+
+
+
+ Maskablepsychsift-mark-maskable.svg
+
+
+
+
+
+ Small-size cutpsychsift-favicon.svg
+
+
+
+
+
+
+
+
+
+
+ Glyph, no tilepsychsift-glyph-*.svg
+
+
+
+
+
+
+ 02
+
How it is built
+ glyph units, S height = 100
+
+
+ The gap down the middle is drawn first, and it is one straight line. Each stroke's facing edge is that line,
+ offset by half the gap, so the two edges are exactly parallel — one width the whole way down, and neither can
+ curve against the other. Each stroke is then built out from its own edge: an outer sweep on a circle centred on
+ its own radius, tangent to the top and left of its box; a flatter arc two-thirds of the way down that hooks the
+ tail into a long fine point; a throat under the head; and a single blend that carries the throat onto the cut.
+ Both blends bend the same way, away from the other stroke, so nothing on either facing edge turns over. At both
+ ends of each stroke the two edges simply run into each other, and that crossing is the point — a sharp cusp, not a
+ rounded cap.
+
+
+
+
+
+
+
+
+
+
Segment
+
Centre
+
r
+
Role
+
+
+
+
+
The cut
+
44.6629
+
118.806°
+
one straight line; both facing edges are it, offset ±2.1
+
+
+
Outer sweep
+
29.2009, 29.2009
+
29.2009
+
upper outer edge, head to two-thirds down
+
+
+
Tail hook
+
51.5937, 16.1418
+
55.1234
+
draws the tail to a point
+
+
+
Throat
+
44.8724, 20.0286
+
17.7232
+
the scoop under the head
+
+
+
Blend
+
17.4753, 33.9324
+
13.0
+
carries the throat onto the cut
+
+
+
Head cusp
+
41.3675, 2.6554
+
36°
+
outer sweep meets throat, sharp
+
+
+
Tail cusp
+
18.0434, 59.8793
+
81°
+
the cut meets the tail hook, sharp
+
+
+
Lower stroke
+
55.1029, 100.3813
+
×−1.07
+
the upper stroke, turned and 7% larger — exactly the same path
+
+
+
The point
+
60.4716, 17.3581
+
10.4586
+
43.95 along the cut, 14.90 off it
+
+
+
+
+
+
+
+
+
+ 03
+
Down to 16 px
+ rendered at true size
+
+
+ The 4.2-unit gap between the two strokes is the first thing to close up. The second row is a separate cut for
+ small sizes: each facing edge moves 1.5 units away from the other, widening the gap to 7.2. Only those two lines
+ move, so the outer silhouette is the one in the primary file.
+
+
Primary markpsychsift-mark.svg
+
+
+ 16
+
+
+ 20
+
+
+ 24
+
+
+ 32
+
+
+ 48
+
+
+ 64
+
+
+ 96
+
+
+
Small-size cutpsychsift-favicon.svg — use at 32 px and below
+
+
+ 16
+
+
+ 20
+
+
+ 24
+
+
+ 32
+
+
+ 48
+
+
+ 64
+
+
+ 96
+
+
+
+
+
+
+ 04
+
Lockups
+ Inter Display SemiBold, outlined
+
+
+ The wordmark is embedded as outlines, so the lockups render identically on a machine that has never seen Inter.
+
+
+
+ psychsift-lockup-horizontal.svg
+
+
+ psychsift-lockup-horizontal-tagline.svg
+
+
+ psychsift-lockup-horizontal-reverse.svg
+
+
+ psychsift-lockup-stacked.svg
+
+
+ psychsift-wordmark.svg
+
+
+
+
+
+
+ 05
+
Colours
+
+
+
+
+
+
Name
+
Hex
+
Where it is used
+
+
+
+
+
Deep Navy
+
#0D1B2A
+
The tile; and the S on light grounds
+
+
+
White
+
#FFFFFF
+
The S and the point, on navy
+
+
+
Brand Blue
+
#2563EB
+
The blue tile; the lockup tagline
+
+
+
Cool Gray
+
#F2F4F7
+
The light tile
+
+
+
+
+
+
+
+
+ 06
+
What changed from the original
+
+
+ The mark on the brand sheet is the design. Measured from a high-resolution view of it, arc by arc, at the 50%
+ level of its anti-aliased edges — the one place on a blurred edge the blur does not bias. Three earlier drafts got
+ it wrong in instructive ways, and every error came from trusting a measurement the source could not support.
+
+
+
+ 01
+
+ The terminals are sharp. At the size the original was first sampled, a sharp point blurs into a rounded
+ blob. The first draft fitted that blur and gave the mark rounded club-ends. Both tips are genuine cusps.
+
+
+
+ 02
+
+ The tail leaves the main sweep. The second draft put the whole outer edge on one circle. It is not:
+ two-thirds of the way down, the outer edge peels away and hooks out — by three full units at the tip. That
+ hook is what draws the tail into a long fine point. Nearest-point fitting had hidden it; comparing coverage
+ against the original's grey levels showed it at once.
+
+
+
+ 03
+
+ The middle line is one straight cut. Two drafts got this wrong the same way. The first gave each
+ stroke's facing edge its own circle, curving opposite ways, and the gap swelled from 5.4 units at its ends to
+ 8.6 in the middle. The second made both edges arcs of one circle, which fixed the width but left a 7.5-unit
+ blend reading as a knuckle on the spine. The strokes are now divided by a single straight line and each facing
+ edge is that line, offset — parallel by construction, so the gap cannot open and neither edge can curve
+ against the other.
+
+
+
+ 04
+
+ The two blue points are gone, and the glyph is now centred in its tile rather than pressed against the
+ right edge.
+
+
+
+ 05
+
+ The second stroke's outline stays 7% larger — kept, not corrected. Forcing the two to be identical
+ measurably worsens the match and opens the channel between them, which is the tension the mark depends on.
+
+
+
+
+
+
+
+ 07
+
Clear space and misuse
+ the four rules that matter
+
+
+ Keep a margin of at least one quarter of the mark's height clear on every side — of type, of image edges, of other
+ logos. The mark is already inset inside its own tile; this margin is measured from the tile.
+
+
+
+
+
+
+
+
+
+
Rule
+
Why
+
+
+
+
+
Never re-space the two strokes
+
+ the gap is a constructed 4.2 units; widening it breaks the one relationship the mark is built on
+
+
+
+
Never stretch it
+
+ every curve is a true circle; a non-uniform scale turns them into ellipses and the cut stops being
+ parallel
+
+
+
+
Never add a shadow or gradient
+
+ the mark is one flat colour on one flat ground, and it has to survive being printed in black
+
+
+
+
Never place the navy mark on a mid-tone
+
+ use psychsift-mark-light.svg on pale grounds and
+ psychsift-lockup-horizontal-reverse.svg on dark ones
+
+
+
+
Never re-trace a PNG
+
start from the SVG; the raster exports exist only for systems that refuse vectors
+
+
+
+
+
+
+
+
+
+
+
+ Do not stretch itthe circles become ellipses and the cut stops being parallel
+
+
+
+
+
+
+
+ Do not add a shadow or gradientthe mark is one flat colour on one flat ground
+
+
+
+
+
+
+
+ Do not re-space the strokesthe 4.2-unit gap is the relationship the mark is built on
+
+
+
+
+
+
+
+ Do not put it on a mid-toneuse the light file on pale grounds, the reverse on dark
+
+
+
+
+
+
+
+ 08
+
The files
+ 13 SVG · 2 PNG · public/brand/
+
+
+ Every file here is generated from the same geometry, so they cannot drift apart. If the mark ever changes, all
+ fifteen are rebuilt together.
+
+
+
+
+
+
File
+
Use it for
+
+
+
+
+
psychsift-mark.svg
+
the primary mark, navy tile — the default everywhere
+
+
+
psychsift-mark-blue.svg
+
on brand blue
+
+
+
psychsift-mark-light.svg
+
on a light tile, for pale backgrounds
+
+
+
psychsift-mark-maskable.svg
+
full-bleed square for PWA install and Android adaptive icons
+
+
+
psychsift-favicon.svg
+
browser tabs and anything under 32 px
+
+
+
psychsift-glyph-white.svg
+
the glyph alone, white, no tile
+
+
+
psychsift-glyph-navy.svg
+
the glyph alone, navy, no tile
+
+
+
psychsift-glyph-mono.svg
+
the glyph in currentColor, for inlining
+
+
+
psychsift-lockup-horizontal.svg
+
mark plus wordmark, light backgrounds
+
+
+
psychsift-lockup-horizontal-reverse.svg
+
the same, dark backgrounds
+
+
+
psychsift-lockup-horizontal-tagline.svg
+
with CLARITY. EVIDENCE. BETTER CARE.
+
+
+
psychsift-lockup-stacked.svg
+
mark above wordmark, for narrow spaces
+
+
+
psychsift-wordmark.svg
+
the wordmark alone
+
+
+
psychsift-mark-1024.png
+
raster export, where SVG is refused
+
+
+
psychsift-mark-maskable-1024.png
+
raster export of the maskable icon
+
+
+
+
+
+ The mark carries role="img" and a <title>, so a screen reader announces it as
+ "PsychSift" when it is inlined. Next to the word PsychSift, mark it aria-hidden instead, so the name
+ is not read twice.
+
+
+
+
+
+ 09
+
Still open
+ two for the owner; one now settled
+
+
+
+ 01
+
+ Two taglines. The brand sheet gives "CLARITY. EVIDENCE. BETTER CARE." in its header and "Clinical
+ clarity. Evidence. Better care." in its footer. The lockup uses the first. One of them should be retired.
+
+
+
+ 02
+
+ Inter or Geist. The sheet specifies Inter throughout; the running application sets its interface in
+ Geist. The wordmark here is outlined artwork so it is unaffected either way, but a wordmark set live in Geist
+ would not match it.
+
+
+
+ 03
+
+ The tail tips — settled. Both tails end in a clean angled cut at about 81°. A flatter tail hook would
+ draw them into finer points, closer to the blurry original; it was drawn, compared and rejected by the owner
+ on 2026-08-28. Fine points thin both strokes, they belong with a curved middle rather than a straight one, and
+ they are the first thing to disappear at 16 px. Do not revisit this without a reason the three of those do not
+ already answer.
+
+
+
+
+
+
+
diff --git a/docs/brand/psychsift-logo.md b/docs/brand/psychsift-logo.md
new file mode 100644
index 0000000000..8f0726b834
--- /dev/null
+++ b/docs/brand/psychsift-logo.md
@@ -0,0 +1,265 @@
+# The PsychSift logo
+
+The files in `public/brand/` are the master artwork. They are true vectors — every curve is a
+circular arc with an exact centre and radius, so the mark is identical at 16 px and at billboard
+size, and the primary file is 863 bytes.
+
+This page records **how the mark is built**, so it can be rebuilt or extended without guessing.
+
+## What the mark is
+
+A rounded navy tile carrying an **S** made of two counter-turning strokes, with a single white
+point settled to the upper right.
+
+The S is the initial of _Sift_ and reads as two halves of one judgement. The point is what has been
+sifted out and come to rest.
+
+An earlier version of these files carried two small blue points falling below the white one. They
+have been removed at the owner's direction, and the mark is now a single colour on its tile.
+
+## Provenance
+
+The mark was drawn from the original PsychSift brand sheet. The mark on that sheet is the
+authoritative design; the large blue raster that circulated alongside it is a low-fidelity
+enlargement and must not be used.
+
+The geometry here was measured from a high-resolution view of the original, arc by arc, at the
+**50% level of its anti-aliased edges** — the one place on a blurred edge that is not biased by the
+blur. Each arc was then fitted on its own, so its residual could be inspected rather than hidden in
+an aggregate: per-arc RMS runs from 0.05 to 0.14 units in 100. Outside the middle the mark covers 94% of
+the original by area (intersection over union); the middle departs from the original on purpose.
+
+Three earlier drafts got this wrong in instructive ways, and every error came from trusting a
+measurement that the source could not actually support.
+
+**The terminals are sharp.** At the size the original was first sampled, a sharp point blurs into a
+rounded blob. The first draft fitted that blur and gave the mark rounded club-ends. Both tips are
+genuine cusps: the outer edge runs into the inner edge and the crossing is the point.
+
+**The tail leaves the main sweep.** The second draft modelled each stroke as four arcs, with the
+whole outer edge on one circle. It is not: below about two-thirds of the way down, the outer edge
+peels away from that circle and hooks further out — by three full units at the very tip. That hook
+is what draws the tail into a long fine point, and without it the tail stops short and the stroke
+reads heavy. Fitting by nearest-point distance had hidden this, because every point on a slightly
+wrong outline still sits close to _some_ point of the right one. Comparing coverage against the
+original's grey levels showed it immediately.
+
+**The middle line is one straight cut.** Two drafts got this wrong in the same way. The first drew
+each stroke's facing edge from its own circle, curving opposite ways; two arcs bowing apart open a
+lens, and the gap swelled from 5.4 units at its ends to 8.6 in the middle. The second made both
+edges arcs of a single circle, which fixed the width but left each stroke reaching its edge through
+a tight 7.5-unit blend that reads as a knuckle on the spine of the S.
+
+The mark now does the simplest thing instead: the two strokes are divided by **one straight line**,
+and each stroke's facing edge is that line offset by half the gap. The two edges are therefore
+exactly parallel — the gap is one width down its whole length, and neither edge can curve against
+the other. Each stroke joins its edge with a single 13-unit blend under the head, and both blends
+bend the same way, away from the other stroke. Nothing on either facing edge turns over.
+
+This is a deliberate improvement on the original rather than a copy of it. The original's channel
+is 4.75 units wide with a standard deviation of 0.25 and bows by 1.25 units along its length; this
+one is 4.35 with a standard deviation of 0.28 and does not bow at all.
+
+Two things were corrected, and one apparent oddity was kept.
+
+**Corrected.** The glyph is centred in its tile; the original sat hard against the right edge. And
+every junction between arcs is tangent-continuous, so enlarging the mark reveals no kink.
+
+**Kept.** The second stroke's outer silhouette is drawn **7% larger** than the first's. Forcing the
+two to be identical measurably worsens the match and opens up the channel between them, which is the
+tension the mark depends on. A further 3.5° rotation improves the fit by another half a percent; it
+was left out deliberately, because an arbitrary tilt is not something a logo specification should
+carry.
+
+## Construction
+
+Work in **glyph units**: the S is exactly 100 units tall and 55.10 wide, with its top-left corner
+at the origin.
+
+### The middle line
+
+The two strokes are divided by **one straight line**:
+
+| | |
+| -------------------------- | ------------------------------ |
+| A point on it | (26.2380, 48.6900) |
+| Unit normal | (0.87626, 0.48184) |
+| Direction | 118.806° |
+| Upper stroke's facing edge | the line at normal·x = 44.6629 |
+| Lower stroke's facing edge | the line at normal·x = 48.8629 |
+
+The two edges are the same line offset ±2.1 units, so they are exactly parallel: the gap is
+**4.2 units wide down its whole length** and neither edge curves against the other. Everything else
+in the mark is built to meet those two lines tangentially.
+
+The cut sits where it does for a reason: at normal·x = 44.6629 the point reflection that makes the
+second stroke carries the **upper stroke's facing edge exactly onto the lower one**. The whole mark
+is therefore invariant under that single transform, and the second stroke can be — and is — written
+as a transform of the first rather than as its own path. If the gap changes, this position has to be
+re-solved from `(normal·origin − gap) / 2.07`; it is not a free number.
+
+This is the part to protect. Give the two facing edges separate curves and the gap opens into a
+lens; that is what the first two cuts of these files did.
+
+### The upper stroke
+
+Three arcs and one straight segment, meeting at two tangent points and two sharp cusps:
+
+| Segment | Centre | Radius | Role |
+| ----------- | ------------------ | -------- | ------------------------------------------------ |
+| Outer sweep | (29.2009, 29.2009) | 29.2009 | the outer edge, from the head to two-thirds down |
+| Tail hook | (51.5937, 16.1418) | 55.1234 | the hook that draws the tail to a point |
+| Throat | (44.8724, 20.0286) | 17.7232 | the scoop under the head |
+| Blend | (17.3065, 33.5945) | 13.0 | carries the throat onto the cut |
+| Facing edge | — | straight | the upper side of the middle line |
+
+- **Head cusp** at (41.3675, 2.6554) — outer sweep into throat, 36°.
+- **Tail cusp** at (18.0434, 59.8793) — the cut into the tail hook, 81°.
+- **Tangent points** at (3.9761, 43.9116) outer sweep into tail hook, (29.0679, 28.0493) throat into
+ blend, and (28.8667, 40.1963) blend onto the cut.
+
+The outer sweep is centred at (r, r) for its own radius, so it is tangent to both the top and the
+left edge of the stroke's bounding box. That relationship fell out of the fit rather than being
+imposed, and it is the quickest way to check a redraw.
+
+### The lower stroke
+
+The outer silhouette is the upper stroke's, point-reflected and enlarged 7%:
+
+```
+translate(55.1029 100.3813) scale(-1.07)
+```
+
+A negative uniform scale is a 180° rotation and a 7% enlargement in one step. That gives:
+
+| Segment | Centre | Radius | Role |
+| ----------- | ------------------ | -------- | --------------------------------- |
+| Outer sweep | (23.8579, 69.1363) | 31.2450 | the outer edge |
+| Tail hook | (−0.1024, 83.1096) | 58.9820 | the hook into the tail |
+| Throat | (7.0894, 78.9507) | 18.9638 | the scoop under the head |
+| Blend | (36.4044, 64.0737) | 13.9100 | carries the throat onto the cut |
+| Facing edge | — | straight | the lower side of the middle line |
+
+- **Head cusp** at (10.8397, 97.5400) — outer sweep into throat, 36°.
+- **Tail cusp** at (35.7966, 36.3105) — the cut into the tail hook, 81°.
+- **Tangent points** at (50.8485, 53.3959) outer sweep into tail hook, (24.0003, 70.3687) throat
+ into blend, and (24.2156, 57.3713) blend onto the cut.
+
+Every one of those numbers is the point reflection of the upper stroke's, including the facing edge
+and the blend, so the second stroke is written in the files as
+
+```
+
+```
+
+and the table above is a convenience, not a second source of truth. Keeping that exact — rather than
+approximately — true is what fixes the cut's position; see **The middle line** above.
+
+### The point
+
+A circle of radius **10.4586** centred at **(60.4716, 17.3581)**.
+
+Measured against the cut, its centre sits **43.95 units along** the cut from the cut's midpoint and
+**14.90 units off** it, on the lower stroke's side. Its nearest approach to the S is 13.65 units —
+a little over three times the width of the cut, which is what keeps it reading as a separate
+particle rather than part of the letter.
+
+An earlier draft of these files had it at (58.9234, 16.5279) with radius 10.2165. That was a
+measurement error, not a decision: 1.8 units in and 2.3% small, which crowded the S's shoulder and
+made the point look like a stray rather than a counterweight.
+
+With the point included, the glyph spans 0 to 70.93 horizontally and 0 to 100.38 vertically.
+
+### The tile
+
+A square with a corner radius of **3/16 of its side** (18.75%). The glyph is scaled to **80% of the
+tile height** and its ink bounding box — S and point together — is centred in the tile. At 512 px
+that leaves 112 px clear on the left and right and 51 px top and bottom.
+
+An earlier draft weighted the placement 46% of the way from the S's own centre toward the glyph's
+right edge, on the theory that the light point should not drag the heavy S leftward. It
+over-corrected badly: the mark sat 65 px from the left edge and 164 px from the right. Centring the
+bounding box is both simpler and visibly right.
+
+### Small sizes
+
+At 32 px and below the 4.2-unit cut closes up. `psychsift-favicon.svg` is a separate optical cut for
+that range: the gap is widened to **7.2 units** by moving each facing edge 1.5 units away from the
+other, and the blends are re-solved against the lines they now meet. Because only the facing edges
+move, the outer silhouette is identical to the one in the primary file. Use it for favicons, browser
+tabs, and anything rendered under 32 px.
+
+### The maskable icon
+
+`psychsift-mark-maskable.svg` is full-bleed, because Android crops adaptive icons to whatever shape
+the launcher uses. Everything that must survive that crop has to sit inside a centred circle of
+**66.7% of the icon's width**. The glyph is scaled to **62% of the height**, which puts its furthest
+ink 168 px from the centre of a 512 px icon against the 171 px the safe circle allows. Do not raise
+that 62%: an earlier draft used 58% with a different centring and still put ink 176 px out, outside
+the safe circle.
+
+## Colours
+
+| Name | Hex | Use |
+| ---------- | --------- | ------------------------------------ |
+| Deep Navy | `#0D1B2A` | the tile; and the S on light grounds |
+| White | `#FFFFFF` | the S and the point, on navy |
+| Brand Blue | `#2563EB` | the blue tile; the lockup tagline |
+| Cool Gray | `#F2F4F7` | the light tile |
+
+## The wordmark
+
+**Inter Display SemiBold**, tracked −0.02 em. The outlines are embedded as paths, so the lockups
+render identically without Inter installed. Inter is licensed under the SIL Open Font License 1.1.
+
+In the lockups the wordmark cap height is 42% of the mark's height, set one fifth of the mark's
+height to its right, with the cap-height centre aligned to the mark's centre. In the tagline
+version the tagline is tracked out to finish flush with the wordmark.
+
+Note that the running application currently sets its interface in **Geist**, not Inter. The
+wordmark is outlined artwork and is unaffected, but a wordmark set live in Geist would not match.
+
+## The files
+
+| File | Use |
+| ----------------------------------------- | ---------------------------------------------------- |
+| `psychsift-mark.svg` | primary mark, navy tile |
+| `psychsift-mark-blue.svg` | on brand blue |
+| `psychsift-mark-light.svg` | on a light tile, for pale backgrounds |
+| `psychsift-mark-maskable.svg` | full-bleed square for PWA and Android adaptive icons |
+| `psychsift-favicon.svg` | small-size optical cut, 32 px and below |
+| `psychsift-glyph-white.svg` | glyph alone, white, no tile |
+| `psychsift-glyph-navy.svg` | glyph alone, navy, no tile |
+| `psychsift-glyph-mono.svg` | glyph alone in `currentColor` |
+| `psychsift-lockup-horizontal.svg` | mark plus wordmark, for light backgrounds |
+| `psychsift-lockup-horizontal-reverse.svg` | the same for dark backgrounds |
+| `psychsift-lockup-horizontal-tagline.svg` | with "CLARITY. EVIDENCE. BETTER CARE." |
+| `psychsift-lockup-stacked.svg` | mark above wordmark, for narrow spaces |
+| `psychsift-wordmark.svg` | wordmark alone |
+| `psychsift-mark-1024.png` | raster export, where SVG is not accepted |
+| `psychsift-mark-maskable-1024.png` | raster export of the maskable icon |
+
+## Using it
+
+- **Clear space**: keep a margin of at least one quarter of the mark's height on every side.
+- **Never** re-space the two strokes, stretch the mark non-uniformly, add a shadow or gradient, or
+ place the full-colour mark on a mid-tone background — use the light or reverse file instead.
+- **Never** re-trace a PNG export. Start from the SVG.
+- The mark carries `role="img"` and a ``, so it is announced as "PsychSift" when inlined.
+ When it sits next to the word PsychSift, mark it `aria-hidden` instead so screen readers do not
+ say the name twice.
+
+## Settled
+
+**The tail tips.** Both tails end in a clean angled cut at about 81°. A flatter tail hook draws them
+into finer points, closer to the blurry original; that version was drawn, compared and rejected by
+the owner on 2026-08-28. Fine points thin both strokes, they belong with a curved middle line rather
+than a straight one, and they are the first thing to disappear at 16 px. Do not revisit this without
+a reason those three do not already answer.
+
+## Two things still open
+
+- The brand sheet gives two different taglines: "CLARITY. EVIDENCE. BETTER CARE." in the header and
+ "Clinical clarity. Evidence. Better care." in the footer. The lockup uses the first.
+- The sheet specifies Inter throughout; the application uses Geist. That difference should be
+ settled deliberately rather than left to drift.
diff --git a/docs/design-system.md b/docs/design-system.md
index e12da5922a..f9a9cea090 100644
--- a/docs/design-system.md
+++ b/docs/design-system.md
@@ -249,12 +249,21 @@ image"}` — never a possibly-empty variable alone.
noindexed (robots.ts + layout metadata), and exempt from token/type-scale rules — but
**promoting a mockup to production means bringing it onto the token system first** (see the
legacy-hex table above).
-- **Brand mark** is single-sourced in `src/lib/brand-mark.ts` (geometry + SVG builders).
- `BrandMark` (`clinical-dashboard/brand.tsx`) renders it token-themed; `app/icon.svg`,
+- **Brand mark** is the PsychSift S, single-sourced in `src/lib/brand-mark.ts` (geometry + SVG
+ builders). `BrandMark` (`clinical-dashboard/brand.tsx`) renders it token-themed; `app/icon.svg`,
`app/apple-icon`, the PWA maskable icons, and `app/opengraph-image` all derive from it. To
change the mark, edit `brand-mark.ts` then `npm run brand:update`; `brand:check` (in
`verify:cheap`) guards `app/icon.svg` from drift. `app/favicon.ico` is a multi-resolution
binary the toolchain can't emit — regenerate it offline from `icon.svg` when the mark changes.
+ Do not re-draw the paths by hand: they are the exact output of the construction recorded in
+ `docs/brand/psychsift-logo.md`, whose master artwork is in `public/brand/`, and the two strokes
+ are one path plus its point reflection — which is the only reason the cut between them stays
+ parallel. `app/icon.svg` deliberately uses the mark's small-size cut (a wider gap), because that
+ is the file browsers render at 16–32 px.
+- **The tile colour is not the brand's.** `BRAND_LIGHT.tile` / `BRAND_DARK.tile` are pinned to
+ `--clinical-accent` per theme by `tests/design-token-contract.test.ts`, so the mark rides the
+ application's accent rather than the brand sheet's Deep Navy. Putting the mark on navy means
+ moving that accent, which is an application-wide decision — not a brand-asset one.
## 11. What NOT to do
diff --git a/public/brand/psychsift-favicon.svg b/public/brand/psychsift-favicon.svg
new file mode 100644
index 0000000000..4c98f6d1d7
--- /dev/null
+++ b/public/brand/psychsift-favicon.svg
@@ -0,0 +1 @@
+
diff --git a/public/brand/psychsift-glyph-mono.svg b/public/brand/psychsift-glyph-mono.svg
new file mode 100644
index 0000000000..cbe702b612
--- /dev/null
+++ b/public/brand/psychsift-glyph-mono.svg
@@ -0,0 +1 @@
+
diff --git a/public/brand/psychsift-glyph-navy.svg b/public/brand/psychsift-glyph-navy.svg
new file mode 100644
index 0000000000..54a385c720
--- /dev/null
+++ b/public/brand/psychsift-glyph-navy.svg
@@ -0,0 +1 @@
+
diff --git a/public/brand/psychsift-glyph-white.svg b/public/brand/psychsift-glyph-white.svg
new file mode 100644
index 0000000000..bfaaa2d0da
--- /dev/null
+++ b/public/brand/psychsift-glyph-white.svg
@@ -0,0 +1 @@
+
diff --git a/public/brand/psychsift-lockup-horizontal-reverse.svg b/public/brand/psychsift-lockup-horizontal-reverse.svg
new file mode 100644
index 0000000000..cff09a14c3
--- /dev/null
+++ b/public/brand/psychsift-lockup-horizontal-reverse.svg
@@ -0,0 +1 @@
+
diff --git a/public/brand/psychsift-lockup-horizontal-tagline.svg b/public/brand/psychsift-lockup-horizontal-tagline.svg
new file mode 100644
index 0000000000..5eb53d6db7
--- /dev/null
+++ b/public/brand/psychsift-lockup-horizontal-tagline.svg
@@ -0,0 +1 @@
+
diff --git a/public/brand/psychsift-lockup-horizontal.svg b/public/brand/psychsift-lockup-horizontal.svg
new file mode 100644
index 0000000000..df84c62c14
--- /dev/null
+++ b/public/brand/psychsift-lockup-horizontal.svg
@@ -0,0 +1 @@
+
diff --git a/public/brand/psychsift-lockup-stacked.svg b/public/brand/psychsift-lockup-stacked.svg
new file mode 100644
index 0000000000..cc1faa59ba
--- /dev/null
+++ b/public/brand/psychsift-lockup-stacked.svg
@@ -0,0 +1 @@
+
diff --git a/public/brand/psychsift-mark-1024.png b/public/brand/psychsift-mark-1024.png
new file mode 100644
index 0000000000..3b873a7705
Binary files /dev/null and b/public/brand/psychsift-mark-1024.png differ
diff --git a/public/brand/psychsift-mark-blue.svg b/public/brand/psychsift-mark-blue.svg
new file mode 100644
index 0000000000..807a0af89b
--- /dev/null
+++ b/public/brand/psychsift-mark-blue.svg
@@ -0,0 +1 @@
+
diff --git a/public/brand/psychsift-mark-light.svg b/public/brand/psychsift-mark-light.svg
new file mode 100644
index 0000000000..3e55c874ea
--- /dev/null
+++ b/public/brand/psychsift-mark-light.svg
@@ -0,0 +1 @@
+
diff --git a/public/brand/psychsift-mark-maskable-1024.png b/public/brand/psychsift-mark-maskable-1024.png
new file mode 100644
index 0000000000..69633acd3d
Binary files /dev/null and b/public/brand/psychsift-mark-maskable-1024.png differ
diff --git a/public/brand/psychsift-mark-maskable.svg b/public/brand/psychsift-mark-maskable.svg
new file mode 100644
index 0000000000..a42d1a7b68
--- /dev/null
+++ b/public/brand/psychsift-mark-maskable.svg
@@ -0,0 +1 @@
+
diff --git a/public/brand/psychsift-mark.svg b/public/brand/psychsift-mark.svg
new file mode 100644
index 0000000000..b36ac67f6f
--- /dev/null
+++ b/public/brand/psychsift-mark.svg
@@ -0,0 +1 @@
+
diff --git a/public/brand/psychsift-wordmark.svg b/public/brand/psychsift-wordmark.svg
new file mode 100644
index 0000000000..7f6d5d2bd1
--- /dev/null
+++ b/public/brand/psychsift-wordmark.svg
@@ -0,0 +1 @@
+
diff --git a/src/app/favicon.ico b/src/app/favicon.ico
index 718d6fea48..ac23f32d15 100644
Binary files a/src/app/favicon.ico and b/src/app/favicon.ico differ
diff --git a/src/app/icon.svg b/src/app/icon.svg
index eb813f249c..07dcf1b4ff 100644
--- a/src/app/icon.svg
+++ b/src/app/icon.svg
@@ -1,13 +1,16 @@
-
);
}
diff --git a/src/lib/brand-image.tsx b/src/lib/brand-image.tsx
index cc47d5f78b..431d9b173a 100644
--- a/src/lib/brand-image.tsx
+++ b/src/lib/brand-image.tsx
@@ -8,9 +8,12 @@ import { BRAND_LIGHT, brandMarkSvg, type BrandColors } from "@/lib/brand-mark";
export const BRAND_ICON_FIELD = BRAND_LIGHT.tile;
/** Single-colour palette for manifest `monochrome` icons: platforms read only
- * the alpha channel and recolour the silhouette themselves, so the tile and
- * ink collapse into one white glyph on a transparent field. */
-export const BRAND_MONOCHROME: BrandColors = { tile: "#ffffff", ink: "#ffffff" };
+ * the alpha channel and recolour the silhouette themselves, so the tile drops
+ * out entirely and the glyph alone carries the shape. Filling the tile white
+ * as well — as this did until 2026-08-28 — hands the platform a solid rounded
+ * square with no mark in it, because a white glyph on a white tile is opaque
+ * everywhere the alpha channel can see. */
+export const BRAND_MONOCHROME: BrandColors = { tile: "transparent", ink: "#ffffff" };
/** data: URI of the flat brand-mark SVG for inside ImageResponse. Satori
* rasterises the SVG server-side, so no browser CSP or network is involved.
diff --git a/src/lib/brand-mark.ts b/src/lib/brand-mark.ts
index 7f58585df6..661e837764 100644
--- a/src/lib/brand-mark.ts
+++ b/src/lib/brand-mark.ts
@@ -1,31 +1,55 @@
-// Single source of truth for the Clinical KB brand mark: a refined ECG pulse on
-// a rounded clinical-teal tile. Every surface that draws the mark derives from
-// the geometry here so the favicon, the in-app , the browser-tab
-// icon (app/icon.svg), and the generated app-icon / maskable / OG image routes
-// can never drift apart.
+// Single source of truth for the brand mark: the PsychSift S — two
+// counter-turning strokes divided by one straight cut, with a settled point —
+// on a rounded tile. Every surface that draws the mark derives from the
+// geometry here so the favicon, the in-app , the browser-tab icon
+// (app/icon.svg), and the generated app-icon / maskable / OG image routes can
+// never drift apart.
+//
+// The construction is recorded in docs/brand/psychsift-logo.md and the master
+// artwork lives in public/brand/. Do not re-draw the paths below by hand: they
+// are the exact output of that construction, and the two strokes are one path
+// and its point reflection, which is what keeps the cut between them parallel.
//
// Pure data + strings (no JSX, no imports) so it is consumable everywhere: the
// React renders the exported geometry as JSX; scripts/generate-
// brand-assets.ts writes app/icon.svg from brandIconSvg(); the next/og image
// routes embed brandMarkSvg() as a data: URI.
-/** 48×48 coordinate system shared by every rendering. */
-export const BRAND_VIEWBOX = "0 0 48 48";
+/** 512×512 coordinate system shared by every rendering — the master artwork's own. */
+export const BRAND_VIEWBOX = "0 0 512 512";
+
+/** Rounded tile: a corner radius of 3/16 of the side. */
+export const BRAND_TILE = { x: 0, y: 0, size: 512, rx: 96 } as const;
+
+/** Places the 100-unit glyph inside the tile, centred on its ink bounding box. */
+export const BRAND_GLYPH_TRANSFORM = "translate(111.2867 51.2) scale(4.0804)";
+
+/** The upper stroke. Four arcs and the straight cut, meeting at two cusps. */
+export const BRAND_STROKE_PATH =
+ "M41.3675 2.6554 A17.7232 17.7232 0 0 0 29.0679 28.0493 A13 13 0 0 1 28.8667 40.1963 " +
+ "L18.0434 59.8793 A55.1234 55.1234 0 0 1 3.9761 43.9116 A29.2009 29.2009 0 0 1 41.3675 2.6554 Z";
+
+/** The small-size optical cut: the same silhouette with the 4.2-unit gap opened
+ * to 7.2 so it does not close up under 32 px. Used by the browser-tab icon. */
+export const BRAND_STROKE_PATH_SMALL =
+ "M41.3675 2.6554 A17.7232 17.7232 0 0 0 28.6276 27.115 A13 13 0 0 1 28.1033 38.5768 " +
+ "L16.8897 58.9696 A55.1234 55.1234 0 0 1 3.9761 43.9116 A29.2009 29.2009 0 0 1 41.3675 2.6554 Z";
+
+/** The lower stroke IS the upper one under this transform — a 180° rotation and
+ * a 7% enlargement in one step. The cut is positioned so this carries the upper
+ * stroke's facing edge exactly onto the lower one; see the brand doc. */
+export const BRAND_COUNTER_TRANSFORM = "translate(55.1029 100.3813) scale(-1.07)";
-/** Rounded tile. */
-export const BRAND_TILE = { x: 2, y: 2, size: 44, rx: 14 } as const;
-/** Subtle baked-in top highlight (theme-independent, very low opacity). */
-export const BRAND_HIGHLIGHT_PATH = "M2 16C2 8.3 8.3 2 16 2h16c7.7 0 14 6.3 14 14v1H2z";
-/** The refined ECG pulse: flat baseline, one clean QRS spike, flat baseline. */
-export const BRAND_PULSE_PATH = "M7 26h10.4l2.4-10 4 19 3-9H41";
-export const BRAND_PULSE_WIDTH = 3.3;
+/** The settled point. */
+export const BRAND_POINT = { cx: 60.4716, cy: 17.3581, r: 10.4586 } as const;
-/** Brand colours per theme. Light = Clinical Sky tile / white ink; dark inverts.
- * These mirror --clinical-accent and --clinical-accent-contrast per theme in
- * globals.css. They cannot read the tokens (the favicon and the generated PNG
- * icon routes render outside any stylesheet), so they must be re-derived by
- * hand whenever the accent moves — then `npm run brand:update` regenerates
- * app/icon.svg, which `npm run brand:check` verifies in verify:cheap. */
+/** Brand colours per theme. These mirror --clinical-accent and
+ * --clinical-accent-contrast per theme in globals.css. They cannot read the
+ * tokens (the favicon and the generated PNG icon routes render outside any
+ * stylesheet), so they must be re-derived by hand whenever the accent moves —
+ * then `npm run brand:update` regenerates app/icon.svg, which
+ * `npm run brand:check` verifies in verify:cheap, and the design-token
+ * contract test fails if they ever disagree with the token. */
export const BRAND_LIGHT = { tile: "#1d6fb8", ink: "#ffffff" } as const;
export const BRAND_DARK = { tile: "#74bdf0", ink: "#08203a" } as const;
@@ -34,12 +58,17 @@ export type BrandColors = { tile: string; ink: string };
/** Inner SVG markup (no wrapper) with explicit colours — shared by the
* flat SVG/PNG builders. The React renders the same geometry from
* the exported constants above. */
-export function brandMarkInner({ tile, ink }: BrandColors): string {
+export function brandMarkInner({ tile, ink }: BrandColors, small = false): string {
const t = BRAND_TILE;
+ const stroke = small ? BRAND_STROKE_PATH_SMALL : BRAND_STROKE_PATH;
+ const p = BRAND_POINT;
return (
`` +
- `` +
- ``
+ `` +
+ `` +
+ `` +
+ `` +
+ ``
);
}
@@ -50,22 +79,27 @@ export function brandMarkSvg(colors: BrandColors = BRAND_LIGHT): string {
}
/** Themed standalone SVG for the browser-tab icon (app/icon.svg): a
- * prefers-color-scheme swap that a raster app-icon cannot do. Written to
+ * prefers-color-scheme swap that a raster app-icon cannot do. Uses the
+ * small-size cut, because this is the file that renders at 16–32 px. Written to
* app/icon.svg by scripts/generate-brand-assets.ts (verified in verify:cheap). */
export function brandIconSvg(): string {
const t = BRAND_TILE;
+ const p = BRAND_POINT;
return `
-
-
+
+
+
+
+
`;
}