Icons and marks
Fourteen, and the fourteenth is not in this catalogue on purpose. The step 9 idle control, 2026-08-12, found the brand mark standing on 28 coloured screens and appearing on no foundations page. It is a mark Tendd draws, so the count above is now thirteen icons plus one identity, and the identity is deliberately not measured against the 24 cell here: it is one letterform on a 100 unit square with a window cut out of it, it answers to DESIGN.md section The brand rather than to this page's optical rules, and it has a page of its own at brand-mark.html. Counted here so the gap is closed; drawn there because that is where its rules live.
A grid with rules, not a sheet of pictures. Tendd draws thirteen marks of its own and borrows fourteen. The thirteen are measured here, glyph by glyph, against the cell they are supposed to live in; the fourteen are brand marks drawn by other people and they answer to a different rule, which the last section states rather than assumes.
Anatomy of the cell
Every destination icon is one path set inside one square. Below is .ic-home on the module grid it was authored against, at about thirteen times the 22px it ships at. The petrol square is the safe area. The grey rectangle is the measured painted bounds, computed from the path data rather than eyeballed. The pale hairline running down the middle of every stroke is the path itself, and the distance between that hairline and the edge of the ink is what rule 3 is about.
The petrol line and the grey line are 0.15 of a module apart. At the 22px the tab bar actually renders, that is 0.14 of one pixel.
- One cell and one viewBox for the whole set. 24 by 24, on all nine, so the module is one user unit. The mask is painted at
mask-size: containinside a 22px square (.app .tabbar a .ic), which makes one module 0.9167px on screen. Everything below is stated in modules, because that is the only unit the drawing is authored in. - The safe area is two modules, so paint lives in 2..22. All nine hold it. The audit below gives the clearance on each side; the tightest number in the set is 0.15 of a module, and one glyph sits at it,
.ic-home. It was two until 2026-08-21:.ic-saveshared the number and retired with the Save tab, and the chart's frame, which ran the same 3.00 to 21.00, was replaced by an axis corner that clears at 1.15. - The boundary is checked on the paint, not on the curve. A stroke sits astride its path, so at 1.7 the ink reaches 0.85 past the line on either side. The roof of
.ic-homeruns from 3.00 to 21.00 on the centreline, which is one clean module inside the band, and the stroke eats 0.85 of that module. At stroke 2.0 it would paint exactly onto the safe line. At 2.2 it would break it. It is the tightest mark in the set and it has been the only one since 2026-08-21: the chart's frame ran the same 3.00 to 21.00 and the axis corner that replaced it runs 4.00 to 20.00, so its clearance went 0.65 to 1.15. Nothing in this set can be widened without re-measuring. - The set is aligned on a baseline, not on a centre. Horizontally every glyph is centred on its own ink at exactly x = 12.000, verified by rasterising, not by trusting the coordinates. Vertically the painted bottom is 20.85 on three of the four; the bell's clapper on
.ic-alertsis the one thing allowed below it, at 21.63. The painted tops vary over a whole module and are not held, which is correct: an icon row reads off its feet. - Stroke ends do not land on whole modules here, and the rule that says they should would not pay. Of 30 nodes across the four glyphs, 5 sit on a whole module, 16 on a half and 9 on neither. The reason to want whole modules is crispness, and crispness needs the module to map onto a device pixel: 24 units rendered into 22px makes a module 0.9167px, so no module boundary is a pixel boundary at any node. The honest rule for this set is the half-module grid it actually uses.
Optical balance
Equal bounding box is not equal weight. A disc inscribed in the 20 by 20 safe area covers 314.16 of its 400 square modules, which is 78.5 percent of the square that contains it; to carry the same area it has to grow to a diameter of 22.57 and overshoot the box by 12.8 percent. That is the arithmetic behind the rule, and it is why a set drawn to one rectangle makes its round forms look undersized. So this set is deliberately not one size.
What it does hold is the height. Recounted 2026-08-21 over the set as it now stands, which is four again but not the same four: Trends joined on the 18th, Save was retired on the 21st, and Trends was redrawn the same day. The painted heights run 16.70 to 18.50 modules, a spread of 11 percent. The widths run 16.10 to 19.70, a spread of 22 percent, and the split is by form rather than by importance: the two glyphs carrying a long horizontal, the roof and the chart frame, are the wide ones, and the head and shoulders is the narrow one. A cap height held while the width follows the drawing is what a stroke set is supposed to do.
x 2.15..21.85
y 2.65..20.85
clearance 0.15
x 3.55..20.45
y 3.15..21.63
clearance 0.37
x 3.15..20.85
y 3.65..20.35
clearance 1.15
x 3.95..20.05
y 3.55..20.85
clearance 1.15
x 5.00..19.00
y 3.00..20.00
clearance 1.00
x 9.00..16.00
y 5.00..19.00
clearance 3.00ships at 16px, not 22
x 8.00..15.00
y 5.00..19.00
clearance 3.00the same path, mirrored about x = 12
x 5.00..19.00
y 9.00..16.00
clearance 3.00the same path, quarter turn
x 5.00..19.00
y 5.00..19.00
clearance 3.00one path, two places: Clear and Close
rescaled from a
52 unit box
clearance 6.42not one of the four
Across the four DESTINATIONS, the painted bounds run 16.10 to 19.70 modules, with a median of 17.75 over the eight numbers (widths median 18.30, heights median 17.75). Nothing breaks the safe area. The last figure above is the unmatched merchant mark, drawn on a 52 unit box and rescaled here for comparison only; it is a third the area of the smallest destination icon, because it is a question mark inside a 52px square and not a destination in a 22px row.
The other five are measured but deliberately not averaged with the four. The shield is 14.00 by 17.00 with 1.00 of clearance; the three chevrons are 7.00 by 14.00, twice upright and once on its quarter turn, and the cross is 14.00 by 14.00 in both of the places it stands, those four all with 3.00. Folding them into one median would produce a number that describes nothing: a destination is a 22px square in a tab bar and has to hold its own against three neighbours, while a chevron ships at 16px, means a direction rather than a place, and is supposed to be lighter than what it points at. Same cell, same rules, different job, so they are compared inside their own group and not across it.
The left chevron added no number to this page, and that is the check that it belongs. It is the right one reflected about x = 12.000: m9 5 7 7-7 7 becomes m15 5-7 7 7 7, so x 9.00..16.00 becomes x 8.00..15.00 and every other number on the pair is one number written twice: 7.00 by 14.00, y 5.00..19.00, stroke 2, the same 24 cell, 3.00 of clearance. A mark whose measurements have to be argued for is a mark drawn from scratch; this one had already been drawn.
What the bounding box does not say
A box says how much room a glyph takes. It does not say how heavy it looks, and weight is what the eye compares when four marks sit in one row. So each glyph was also rasterised and its ink counted: the share of the 24 by 24 cell that is actually painted.
The finding was carried for eleven days and closed on 2026-08-21, and it took two moves to close it. It began at 106 percent of ink spread over a set of four, .ic-save against .ic-you. The first move was not a drawing at all: the Save tab was retired, its glyph went with it, and the spread fell to 81 percent without a line being redrawn - which is what said the problem was the SET rather than any one mark. The second was the founder choosing candidate B off four drawn options, each measured before it was offered: .ic-trends gave up its frame for an axis corner and went 23.82 to 14.71. The spread is now 61 percent, the heaviest mark is the house rather than the chart, and the boxes still differ by 22 percent, which is the width of a roof against the width of a head. The cause was never stroke weight. All four are drawn at 1.7 on the same 24 cell; what differs is PATH LENGTH, and the mark that closed a box carried roughly twice the perimeter of an open glyph. The candidate that simply deleted the box was drawn too and rejected on its own number: the chart fell to 10.60 and became the lightest thing in the bar, taking the spread to 99 percent. A mark needs enough structure to hold its own beside three neighbours.
It is written here and not repaired here, because a repair belongs in design/system/components/destination-icon.css and in kit.html beside it, never on a page that only describes them. The first of the two candidate repairs is the one that shipped: the frame of .ic-trends opened into a corner. The second is still on record and still available if the row ever needs to come tighter - lift .ic-you toward the safe line it clears by 3.15 modules - and it stays a named decision rather than a tidy-up, because it changes a shipped glyph on 29 pages. The four drawn candidates and their numbers are kept in design/kit/screens/icon-candidates.html.
The audit, and how it was measured
Every number on this page was computed from the path data in the repository, not read off a picture. The geometric extent is exact. Bezier extremes come from solving the derivative, arc extremes from the centre parameterisation of the arc; nothing is approximated by its control points. The painted box is the geometric box grown by half the stroke width on every side, which for the four destination glyphs is exact rather than conventional: all four declare stroke-linecap="round" and stroke-linejoin="round", and the painted region of such a path is precisely the path swept by a disc of radius half the stroke.
One glyph does not declare a round join, and it is the unmatched merchant mark, which sets only the cap. Its single corner, where the arc lands on the stem at 26.0, 26.2, is a right angle and takes the default miter. A miter at 90 degrees reaches 1.556 from the corner against 1.100 for a round join, putting its tip at 24.90, 25.10. That point is 4.633 from the centre of the arc, which paints a band from 4.50 to 6.70, so the miter tip falls inside ink the arc has already laid down and adds nothing to the painted region. The box printed for that mark is correct, and the reason it is correct is written down rather than assumed.
One number is an approximation and it is the ink. Coverage and the horizontal centre were taken by rasterising each glyph onto a 480 by 480 grid, one raster cell per 0.05 of a module, with curves flattened to 400 pieces each. The first pass ran at 240 and had to be thrown away: several cell centres landed exactly on a stroke boundary, floating point admitted one side and refused its mirror, and four symmetric glyphs came back asymmetric by a tenth of a module. At 480 no boundary in this set is a cell centre and all four come back symmetric, which is the check that the number is real.
| Glyph | viewBox | Stroke | Painted box | Size, modules | Tightest clearance | Ink | Safe area |
|---|---|---|---|---|---|---|---|
.ic-home | 0 0 24 24 | 1.7 | 2.15 2.65 21.85 20.85 | 19.70 x 18.20 | 0.15 | 21.12% | holds |
.ic-alerts | 0 0 24 24 | 1.7 | 3.55 3.15 20.45 21.63 | 16.90 x 18.48 | 0.37 | 15.62% | holds |
.ic-trends | 0 0 24 24 | 1.7 | 3.15 3.65 20.85 20.35 | 17.70 x 16.70 | 1.15 | 14.71% | holds |
.ic-you | 0 0 24 24 | 1.7 | 3.95 3.55 20.05 20.85 | 16.10 x 17.30 | 1.15 | 13.14% | holds |
.logo, unmatched | 0 0 52 52 | 2.2 | 19.30 13.90 32.70 36.00 | 6.18 x 10.20 | 6.42 | 2.84% | not a 24 cell |
.chart > svg | 0 0 300 100 | 2, in device px | 18 26 282 76, geometry only | stretches with its box | no safe area declared | - | not a 24 cell |
The four destination icons, and where they really live
They are not files and they are not inline SVG in a screen. Each one is a CSS mask built from an inline SVG data URL, declared in design/system/components/destination-icon.css, and the numbers above were parsed out of those data URLs. The extraction confirms the three things worth confirming: the viewBox really is 0 0 24 24 on all four, the stroke really is 1.7 on all four with round caps and round joins, and the encoded stroke='%23000' is dead weight, because a mask reads alpha and never hue.
The mask sits on a pseudo-element and not on the element, and that is a trap rather than a preference. Quoted from the component file: "The mask sits on a pseudo-element, not on .ic itself. A mask clips its own children, and .ic hosts the unread dot, so masking the element erased the one thing GC2 has to be able to show." GC2 is the global tab bar and the one thing is the unread dot, a 7px circle absolutely positioned inside .ic, overhanging its square by 3px at the top and 4px at the right. Masking .ic cut it off, and the screen still looked right, because a tab bar with no unread alerts is the normal case.
The tiles on this page are inline SVG and not masks, and they are not faked. The mask rules are scoped .app .ic-home::before, so on a stand page, which carries no .app, they resolve to nothing. Painting a solid square instead would say nothing at all. So every glyph above is the same path data drawn inline, in currentColor, byte for byte what the data URL contains.
| Class | Destination | The mark | Carries |
|---|---|---|---|
.ic-home | Home, the calm view | a roof over a doorway | nothing else |
.ic-alerts | Alerts | a bell with a clapper | the unread .dot, 7px, top right, outside the square |
.ic-trends | Trends | an axis corner with the product's own curve rising off it | nothing else |
.ic-you | You, settings and account | a head and shoulders | nothing else |
The set was authored, not extracted. The grey wireframes draw .ic as an empty 18px bordered box on purpose, so greyscale had nothing to hand over and these four were drawn at stage 07. They stand in 112 places across 28 grey pages.
The whole set, counted rather than remembered
This page used to show four marks and call itself the icon set. A sweep of every mask in design/system/ and every character doing an icon's job across all 57 grey pages and the 28 coloured ones found eight drawn marks and two characters. On 2026-08-12 both characters were drawn, so the table read ten drawn marks and one character. Two more arrived on 2026-08-14 with the rollout, and they are the kind of gap only a rollout finds: the set was complete for a seven screen sample and had no mark for a BANK and none for a source a person typed in by hand, which is both slots of the Connections card. Twelve drawn marks and one character now. The founder found the gap the way gaps like this are always found: by seeing a chevron on a component page and looking for it here.
Twelve rows over eleven glyphs, and the difference is not an error in either number. The block at the top of this page counts eleven marks on the 24 cell and this table counts twelve drawn: Close and Clear are the same cross, byte for byte, standing in two places. A row is a mark doing a job, a glyph is a path that had to be drawn, and a census that collapsed the two would either lose a place or invent a drawing.
A character is counted as a mark, and that is the point of the table. Punctuation standing in for an icon renders in the text font, takes the text baseline, and looks like a font fallback beside a drawn glyph. Counting the two kinds separately is what turns "the icons look inconsistent" into a list with a length. That list had three entries at step 5 and has one now.
Nothing on this page is a component, and that is a rule rather than a preference. The ladder runs foundations, then atoms, then molecules, then organisms, and each rung is built out of the one below it. A foundations page that shows a nav row or a form field to display a mark has borrowed from two rungs up, and the reader can no longer tell which part is the foundation. The marks are shown as marks: the glyph, the cell it was drawn against, and the numbers. Where each one stands is a column in the table, not a picture.
| Mark | What it means | How it is made | Where it stands | Status |
|---|---|---|---|---|
Home .ic-home | the calm view: a roof over a doorway | CSS mask, 24 cell, 22px box | the tab bar, 29 pages | drawn |
Alerts .ic-alerts | a bell, and the only mark that carries a second element, the unread dot | CSS mask, 24 cell, 22px box | the tab bar, 29 pages | drawn |
Trends .ic-trends | history and trends, the same money over time | CSS mask, 24 cell, 22px box | the tab bar, 29 pages | drawn |
You .ic-you | the account | CSS mask, 24 cell, 22px box | the tab bar, 29 pages | drawn |
Shield .trust::before | the trust line: where the figures came from and what we cannot do | CSS mask over a solid petrol fill, 24 cell | 11 grey pages, 9 coloured | drawn |
Chevron right .navrow .arrow | there is more this way, and it is a direction rather than a destination | CSS mask, 24 cell, 16px box | the nav rows of Settings, 8 places | drawn 2026-08-11, was › |
Chevron down select::picker-icon | the same thing turned a quarter: this control opens | the same path, the same 16px box | 4 selects, inside @supports | drawn 2026-08-11, was the browser's triangle |
Chevron left a.back | back, one step | CSS mask, 24 cell, 16px box. The right chevron's path, mirrored | the app bar's leading slot, 16 coloured screens, and 36 grey ones still on the character | drawn 2026-08-12, was ‹ |
Clear ::-webkit-search-cancel-button | empty this search and start again | CSS mask, 24 cell, 16px box | the preset search, 3 places | drawn 2026-08-11, was the browser's own glyph in the operating system's blue |
Close a.close | leave without finishing | the clear cross's own path, the same 24 cell and the same 16px box | 2 places, both on Upgrade | drawn 2026-08-12, was ×. The class split off a.back the same day |
Bank inline svg in .logo | a bank connection as a SOURCE, not as a merchant | 36 viewBox, 1.7 stroke, round caps | 2 places, Connections and its reconnect state | New, 2026-08-14. A pediment over three columns. Drawn rather than borrowed because the mark says "a bank", not "Chase": the card names the institution in words, and a real bank logo is somebody else's trademark. It takes the square's muted ink through currentColor, so it needs no colour rule and no dark pair |
Entered by hand inline svg in .logo | the private source: subscriptions a person typed in | 36 viewBox, 1.7 stroke, round joins | 2 places, the same two cards | New, 2026-08-14. A pen on a rule. It is the counterpart of the bank above and the two only make sense as a pair: one card says where the machine reads from, the other says where the person writes. Same cell, same stroke, same currentColor |
Down arrow .next::after | the step forward in a flow | content: "\2193" / "" | 2 places | the last character in the census: in CSS, with an empty alt, so it is at least invisible to a screen reader |
Why the back arrow was a character until 2026-08-12, and it was not laziness. a.back carried two different marks for two different jobs: 36 of them said ‹ Back and 2 said × Close, on one class. CSS cannot tell them apart, because nothing distinguishes them but the words inside, and no attribute, no child and no :has() reaches text. Drawing .back would have put a left chevron on the word Close, which is worse than a character on the right word, and that is why stage 07 and step 5 both left it alone.
The split landed with the renaming map at step 6, and both marks followed their names. app-bar.css declares .back and .close as siblings: one rule for the box, the 44px floor, the padding, the ink tier, the hover and the focus ring, and one drawn mark each. Nothing separates them but the mark, because nothing about them is different except where they take you. The markup moved in the same wave, and it is counted rather than assumed: 16 back controls and 2 Close controls across the coloured screens, with no character left in either. The grey originals are frozen and still write both, which is correct rather than a lag: a colour copy may differ from its grey by styling only, and a placeholder character is what the grey stage is for.
Neither path is new, and that is the whole reason this was cheap. The chevron is the one nav-row.css draws for "there is more this way", mirrored to point back; the cross is the one text-input.css draws on the search field's clear control, taken byte for byte. Same 24 unit cell, same 2 unit stroke, same round caps and joins, same 16px box, same mask over currentColor. That last one is what makes them free: the mark takes the control's own ink, so it darkens on hover with its label and needs no colour rule, no hover rule and no dark theme pair of its own. Three marks, one family, one place to change all of them.
The middle dot is not on this list. It appears 121 times, in "in 2 days · Aug 3", and it is a typographic separator rather than a mark: it means "and also", it sits on the text baseline on purpose, and drawing it would make a punctuation mark into an icon. Counted, considered, and left as text.
The two marks that are not on the grid
Two more marks were authored at stage 07, and neither one sits on the 24 cell, because neither one is a destination icon. Both replace a wireframe placeholder string: the grey page writes in square brackets what should be there, the colour page has to actually draw it, and this stage owns the drawing.
The unmatched merchant mark
It is drawn on a 52 unit box with a 2.2 stroke, because it is sized by its host and its host is the detail hero's 52px logo square. Rescaled onto the 24 cell for the comparison above, that stroke is 1.02 against the set's 1.70, and the painted mark is 6.18 by 10.20 modules: about a third of the area of the smallest destination icon. That is correct and not a miss. A destination icon has to be legible at 22px in a row of four; this one has a 52px square to itself and one job, which is to say quietly that we could not name the merchant.
Why it was drawn at all. On design/subscription-detail-unrecognized.html there is no merchant logo, because that is exactly what is missing: the charge repeats like a subscription and we could not match it to a service. The grey original writes [ ? ] in the hero, the same placeholder family as [logo], and a bracketed question mark on a coloured screen reads as an unfinished build rather than as a state. It is quiet on purpose. An unnamed charge is an ordinary gap in the data, not a failure, so it takes the recessed square and the muted tier rather than the clay of a genuine error, and it inherits its colour from .logo rather than naming one. It is drawn solid and not dashed: the first draft used a dashed outline, which is what "we have nothing to show" means in a wireframe, and DESIGN.md forbids a dashed border outright.
The trend plot
It has no painted bounds in modules, and the reason is a mechanic rather than an omission. The box stretches, the plot stretches with it under preserveAspectRatio="none", and every stroke inside carries vector-effect="non-scaling-stroke", so the ink is one device pixel wide at 360 and at 1280 alike. A half stroke measured in user units would therefore be a different number at every window width. What can be measured is the geometry, and it stopped being symmetric on purpose on 2026-08-18: the plot runs 0 to 300, edge to edge. The 6 per cent inset it used to carry is why the line and the month labels under it never read as one object - measured at a 1440 window, its ends stood 57px and 61px from the centres of “May” and “Jul”, on a page that claimed they lined up. The three gridlines at 22, 50 and 78 are unchanged, evenly 28 apart and 22 clear of each edge. The path itself changed shape on the same day, and that is a content change rather than a drawing one: it was a straight segment from 76 to 26 because the copy names only May and July, and it now runs M0 80 C 45 80, 100 28, 150 28 L300 28 - a rise into June and a plateau after it. The middle value is derived, not invented: the “What moved” list under the chart says ChatGPT Plus is new since June at $20.00 a month, and 172.90 + 20.00 is exactly 192.90, while Netflix's $2.50 is dated Jul 28 and has not been charged. So the three months are 172.90, 192.90, 192.90. Under it is the area, the same path closed to the foot of the frame, which makes the plot an object rather than a scratch and asserts nothing the line does not.
Why it was drawn rather than left as a placeholder. In grey the box carried the string [chart: monthly total, May to Jul], which reads as a build note the moment the screen is in colour. The plot is three months of one figure, the monthly total, and the accessible name on .chart says so in words, so the drawing adds shape and takes no information away. And it is not petrol. The accent is spent on the primary action, the current selection and the trust line; a data line would be a fourth claim on the one raised voice this system allows, so the plot takes the body tier and the gridlines take a container hairline. The sample above names no colour at all since 2026-08-18: it is the screen's own markup inside a .chart, so the system paints it and this page holds no second copy of a value to drift. Every stroke and fill moved out of the SVG attributes and into chart-placeholder.css, on the ground that a colour written as an SVG attribute is a screen carrying a style of its own in the one place the rollout ledger cannot see it: it greps for style= and for class names, and an SVG presentation attribute is neither.
It closed two more placeholders on 2026-08-12, and the second half of the job had been open since stage 07. Four coloured screens carry the box and stage 07 drew into two of them, leaving design/history-trends-loading.html printing [chart] and design/history-trends-empty.html printing [chart: waiting for a third month] as visible 12px text in the finished product. Both now take the frame alone: the same three gridlines at 22, 50 and 78, with no path, which is exactly what the Pro gate already carried. Nothing moved then. Both boxes measured 328 by 168 at 360 and 740 by 220 at 1280, in both themes, identical to the hundredth of a pixel before and after, and both pages are the same height they were: the change is entirely inside the frame.
The gridlines are the frame and the path is the data, and that split is what makes an empty state honest. A gridline is true before the numbers arrive and does not move when they land, so it belongs to a wait and to an absence as much as to a plot; a line through it would be an invention. And the sentence is where the reason lives: the empty screen says "Still gathering your history" in its heading, "we have less than three so far" in its paragraph and "Your first trend line appears once September closes" in its readout, and the box keeps saying it to a screen reader through the aria-label it already had. The bracketed string was never product copy: voice/docs/microcopy.md lists [chart] by name among the deferred assets that stand for a missing picture rather than for missing words.
Merchant marks, and why they are not on this grid
These fourteen are not part of the icon set and they are not measured against the 24 cell. They are other people's brand marks, drawn to other people's specifications, and a rule about Tendd's optical balance has no authority over them. Their box is viewBox="0 0 64 64", full bleed rather than inset, because the containing square is the geometry: the art fills it instead of floating in it, which is the exact opposite of a two module safe area. They are flat fills rather than strokes, so half a stroke width is not a quantity they have. And they are in full colour, which every glyph above deliberately is not.
The rule they do answer to: a real name and a real mark wherever the merchant is known. Never a cryptic bank code, never stock art, never an illustration standing in for a company. The founder's decision on 2026-08-10 was full colour real logos and zero photography anywhere in the product. A merchant mark is data, not chrome: it is the person's own Netflix, rendered as Netflix. The One Voice Rule survives it, because petrol stays the only colour Tendd itself speaks in.
Containment, and these are binding. The mark lives inside a fixed square with --radius-sm and never bleeds past it, so the row's geometry does not change per merchant. The mark is the only saturated thing in its row: amount, name and date stay ink and muted, and a row never tints its background, its border or its text to match the brand. Petrol never sits inside the square, so the one accent and the brand colour never touch. The grey wireframes write [logo] in 111 places across these 14 merchants, and filling that slot is the one thing a colour copy is allowed to add.
The fourteen, and the merchant each one stands for
| File | Merchant on screen | Fidelity |
|---|---|---|
netflix.svg | Netflix | close to right |
spotify.svg | Spotify Premium | close to right |
icloud.svg | iCloud+ | close to right |
notion.svg | Notion | close to right |
apple-music.svg | Apple Music | close to right |
adobe-cc.svg | Adobe Creative Cloud | close to right |
amazon-prime.svg | Amazon Prime | approximation, replace |
disney-plus.svg | Disney+ | monogram, no glyph exists |
hulu.svg | Hulu | monogram, no glyph exists |
chatgpt.svg | ChatGPT Plus | approximation, replace |
strava.svg | Strava | approximation, replace. Redrawn twice on 2026-08-17: it was the only mark in the fourteen that was not an approximation but a WRONG SHAPE, and the corrected pair was rejected too. It is ONE chevron now |
peloton.svg | Peloton App | monogram, no glyph exists |
nyt.svg | The New York Times | approximation, replace |
economist.svg | The Economist | monogram, no glyph exists |
One of the fourteen was not approximate, it was wrong, and the founder found it on a screenshot of the landing: "логотип почему-то один сломался". The Strava mark is two chevrons and the offset between them is the whole mark: the upper one's apex sits at x=10.463 and the lower one's at 15.387, so the lower sits down and to the RIGHT. The file carried the real coordinates for the lower chevron and a redraw for the upper one, with its apex moved to 13.828. That put the two on nearly the same vertical axis, and two chevrons stacked on one axis close into a symmetric rhombus with a notch in it, which is what shipped and what the eye read as "an A in a diamond". Both are the real coordinates now, and the group's transform centres the drawing's own bounds (x 3.463 to 20.537) rather than the viewBox, at a scale that puts it 36 tall in the 64 box: the same optical size as the Netflix N standing beside it.
And the corrected pair was rejected on sight as well, which is the second half of the lesson. With both chevrons on their real coordinates the mark is right at 150px and wrong at 36: the lower element is fourteen pixels of white that the eye resolves as a separate shape hanging under the upper one rather than as part of one mark. So the tile keeps the silhouette Strava is known by and drops the element that only works large. A mark is not correct in the abstract, it is correct at the size it is seen, and every one of these fourteen is seen at 36.
What it says about the other thirteen, which is why it is written here rather than only in the log. The fidelity column already declares these marks approximate, and an approximation reads as a slightly-off logo. This one did not: it read as a confidently drawn mark of something else, which is exactly the failure mode the note below warns about for the stock catalogue - "a brand query resolves to the nearest generic". The debt is not only sourcing, it is that nothing in this repository can tell a wrong mark from a rough one: every sweep here reads a rendered page and a rhombus renders as cleanly as a chevron. All fourteen were re-rendered at 150px and walked by eye the same day; this was the only one.
The fidelity column is a debt, and it is written here so it cannot quietly expire. The plan was to take every mark from the brand's own press page. What is committed is drawn to one specification instead, which buys uniformity and costs fidelity. A stock catalogue was tried first and covered 5 of the 14, and its failure mode is the dangerous kind: a brand query does not come back empty, it resolves to the nearest generic, so "peloton" returns a stationary bike and "economist" returns a businessman with a chart. Any of those would have shipped as a confident wrong logo. The four marked "no glyph exists" have only a wordmark in real life, and a wordmark cannot be read inside a 36px square, so a monogram may be the permanent answer for them. The full account is in design/visuals/README.md.
Fourteen files do not scale to 400+ presets and to whatever a bank returns. So .logo has three states: with an image, when we have the file or the provider returns a logo URL; the loading pass, .logo.is-skel; and the unmatched state, selected structurally as .logo:not(.is-skel):not(:has(img)), whose mark is measured above. The unmatched state is the one most merchants will actually get in production, so it is designed as a real state and not as a failure.
How to add one
Three kinds of mark, three homes, and one order that does not change: a mark enters the kit before it enters a screen. The value goes to the component file, the markup to kit.html, the line to docs/inventory.md, and the screen comes last. A mark that only exists inside one page is not in the system at all.
| Kind | Where it goes | Naming | Shown at |
|---|---|---|---|
| Destination icon | no file: the encoded SVG goes into design/system/components/destination-icon.css beside the other four |
.ic-<destination>, lowercase, one word |
22px |
| Merchant mark | design/visuals/logos/ |
<merchant>.svg, lowercase, hyphenated, no version and no year |
22 / 30 / 32 / 36 / 52, set by the host |
| Drawn mark | inline in the screen that needs it, with the rule that sizes it in its component file | named by the state it replaces, not by its shape | its host's size |
A fifth destination icon has to pass all six of these
| Check | The number it has to meet |
|---|---|
viewBox="0 0 24 24", no width and no height attribute | the mask is sized by CSS, and the cell has to match the four it joins |
stroke-width="1.7", round cap, round join, fill="none" | 1.7 exactly, so the painted overshoot stays 0.85 |
| painted bounds inside the safe area | 2.00 to 22.00, measured on the paint and not on the path |
| painted width and height in range | 16.10 to 19.70 modules, median 17.75 |
| painted bottom on the set's baseline | 20.85, unless it is a deliberate overhang like the bell's clapper |
| ink within the set | 12.97% to 26.73% of the cell, and closer to the middle is better than closer to either end |
Two things this sheet will not accept. A photograph, of anything, in a screen or on the landing: a person opening a money app to a smiling stranger on a sofa learns that the product is selling her a feeling instead of showing her a number. And an illustrated empty state: an empty state gets a sentence and a button, which is principle 1 applied exactly where products usually reach for a mascot.