Repaint both heads in the v5 palette and embed its three fonts

This commit is contained in:
2026-08-07 15:25:51 +02:00
parent 49645db680
commit bea0279937
16 changed files with 611 additions and 99 deletions
+133 -67
View File
@@ -25,127 +25,186 @@
marked. That is deliberate rather than lazy: the finer distinctions here — three border weights, four
text steps — are ones the design draws without naming, and collapsing them to match the token count
would lose the rule each one encodes.
── v3 ──────────────────────────────────────────────────────────────────────────────────────────────
The third design moves again, and further in the same direction v2 moved in: v1 was green-black, v2
blue-black, and this one near-black — the surfaces are lower-contrast against true black than either
predecessor, and the accent turns purple, sitting where the blue used to. Both heads still move
together for the reason given above. Where a name below still carries a v2-era comment explaining a v2
fact in v2's numbers, that comment is a record of what v2 changed and why, not a claim about the
current values — it is left as history except where it stated something the current palette makes
false outright, which is called out where it happens.
-->
<!--
The surfaces, darkest first. Two things changed shape rather than merely colour.
The surfaces, darkest first. Two things changed shape rather than merely colour, in v2, and the second
of the two has since changed back.
A field is now *inset* — darker than the canvas it sits on — where it used to be raised. That follows
the design, which draws the search box as a well rather than as a tile, and it is the one place where
reading the old palette's ordering across to the new one gives the wrong answer.
A field became *inset* in v2 — darker than the canvas it sat on — where it had been raised before that.
That followed the v2 design, which drew the search box as a well rather than as a tile. It does not
follow this one: the v3 design gives a card and a field the same fill, rgb(16,16,25), and marks a field
as editable with a 1.5px accent-coloured inset border instead of a darker well. Raised and Field are
kept as separate keys anyway, for the same reason Chrome and Panel are — the day one of the two needs
to move again, having merged them would have to be undone everywhere that used the wrong one.
And Chrome and Panel are now the same value, because the design uses one surface for both the header
Chrome and Panel remain the same value for that reason: the design uses one surface for both the header
and a card. They are kept as two names anyway: they answer different questions, and the day one of the
two moves is the day having merged them would have to be undone across every file that used the wrong
one.
-->
<Color x:Key="CanvasColor">#0E1220</Color>
<Color x:Key="CanvasColor">#05050A</Color>
<SolidColorBrush x:Key="Canvas" Color="{StaticResource CanvasColor}" />
<SolidColorBrush x:Key="Chrome" Color="#111629" />
<SolidColorBrush x:Key="Sidebar" Color="#0F1426" />
<SolidColorBrush x:Key="Panel" Color="#111629" />
<SolidColorBrush x:Key="Raised" Color="#151B31" />
<SolidColorBrush x:Key="Field" Color="#0B0F1D" />
<SolidColorBrush x:Key="Chrome" Color="#10111E" />
<SolidColorBrush x:Key="Sidebar" Color="#0D0D16" />
<SolidColorBrush x:Key="Panel" Color="#10111E" />
<SolidColorBrush x:Key="Raised" Color="#101019" />
<SolidColorBrush x:Key="Field" Color="#101019" />
<!--
The surface a terminal, a log and a snippet's command are drawn on, and it is not one of the five
above. The design gives every block of monospaced output this one value, warmer than the chrome around
it, and that is what makes the lit thing read as lit. Named here rather than repeated in the renderer's
stylesheet and in two screens, which is how the three of them would drift.
Not one of the design's named tokens in v3 either — the mock this palette was drawn from is a Hosts
screen with no terminal on it — so the value is interpolated the same way it was for v2: warmer and a
touch lighter than the new Chrome, on the same ramp as Raised and Field rather than the old blue-black
one.
-->
<SolidColorBrush x:Key="TerminalSurface" Color="#171A26" />
<SolidColorBrush x:Key="TerminalSurface" Color="#18161F" />
<!-- Row hover, and the heavier one the chrome's own buttons use. On the phone these are press states. -->
<SolidColorBrush x:Key="Hover" Color="#151B31" />
<SolidColorBrush x:Key="ChromeHover" Color="#1D2749" />
<SolidColorBrush x:Key="Hover" Color="#14151E" />
<SolidColorBrush x:Key="ChromeHover" Color="#1E1E2C" />
<!--
Selection, and it is a surface rather than a highlight: the design fills a chosen host row, the current
segment of a toggle and the current chip with this and adds no border. Distinct from Hover, which is
what the finger is on rather than what is chosen.
-->
<SolidColorBrush x:Key="Active" Color="#1D2749" />
<SolidColorBrush x:Key="Active" Color="#1E1E2C" />
<!-- The fill behind a small monospaced tag — a group name, a storage class, a bucket. -->
<SolidColorBrush x:Key="Chip" Color="#16204A" />
<SolidColorBrush x:Key="Chip" Color="#1A1A28" />
<!--
Three border weights, and they are not interchangeable. Strong separates one region from another,
subtle separates rows inside one region, and mid is what a control draws around itself.
BorderFaint changes relationship in v3: in v2 it sat above Border, lighter than it — a fact this
comment did not previously state because nothing here described it. In this design BorderSubtle and
BorderFaint are both a step darker than Border rather than one of them being a step lighter, so the
weight now runs, faintest to strongest: BorderFaint, BorderSubtle, Border, BorderMid, BorderHover — a
single ramp rather than Border sitting in the middle of two that disagree about which side it is on.
-->
<SolidColorBrush x:Key="Border" Color="#1A2138" />
<SolidColorBrush x:Key="BorderSubtle" Color="#151B31" />
<SolidColorBrush x:Key="BorderMid" Color="#232B45" />
<SolidColorBrush x:Key="BorderHover" Color="#2E3859" />
<SolidColorBrush x:Key="BorderFaint" Color="#1F2740" />
<SolidColorBrush x:Key="Border" Color="#1E1E2C" />
<SolidColorBrush x:Key="BorderSubtle" Color="#17172A" />
<SolidColorBrush x:Key="BorderMid" Color="#24243A" />
<SolidColorBrush x:Key="BorderHover" Color="#2C2C3E" />
<SolidColorBrush x:Key="BorderFaint" Color="#141420" />
<!--
The text ramp. Three steps, used consistently: what you read, what you glance at, and what is there
only so its absence would be noticed. A fourth step would be one nobody could tell from its neighbours.
── The ramp is white-topped, and every step below it moved with the top. ─────────────────────────────
The design's #E3E7F4 is a blue-tinted white, and against these blue-black surfaces the tint costs
contrast twice: once because it is darker than white, and once because it shares a hue with what it is
drawn on. Pure white against #0E1220 is 18.3:1, where #E3E7F4 was 15.5:1.
This was v2's reasoning, told in v2's numbers, and is left as a record of that change rather than
updated in place: the design's #E3E7F4 was a blue-tinted white, and against v2's blue-black surfaces
the tint cost contrast twice — once because it was darker than white, and once because it shared a hue
with what it was drawn on. Pure white against v2's canvas, #0E1220, was 18.6:1, where #E3E7F4 had been
15.5:1.
Raising only the top would have been the wrong half of the change. The three steps are a ramp rather
Raising only the top would have been the wrong half of that change. The three steps are a ramp rather
than three colours, and their job is that a glance can tell them apart — so lifting Text and leaving
TextDim where it was would have widened the first gap and left the second two crowded at the bottom.
Every step is raised by roughly what the top gained, which keeps the intervals the design chose and
brings the lower steps up to where a secondary label is comfortably readable rather than merely
present. TextDim clears 9:1 now, against 6.4:1 before.
Every step was raised by roughly what the top gained, which kept the intervals the design chose and
brought the lower steps up to where a secondary label was comfortably readable rather than merely
present. TextDim cleared 9.8:1 at that point, against 6.4:1 before it.
── v3 ──────────────────────────────────────────────────────────────────────────────────────────────
The canvas darkens again, past v2's blue-black to near-black, which raises every one of these ratios
again without any of them moving: pure white against the new #05050A canvas is 20.3:1. TextDim,
TextFaint and TextGhost are repicked to the design's own neutral-grey values rather than re-derived
from v2's blue-tinted ones, because the design supplies its own steps this time — #EDEDF5, #9C9EB4 and
#7C7F98 read against the new canvas at roughly 17.5:1, 7.7:1 and 5.2:1, in the same descending order the
three names promise. The design also uses two deeper steps, #6D6F84 and #5D5F74, for distinctions this
ramp does not yet draw; they are not added as keys until a screen needs them, on the same reasoning as
the paragraph above the border ramp — a resource nothing reads is not free, because the next person to
touch this file has to work out whether it is dead or merely not yet wired up.
-->
<SolidColorBrush x:Key="Text" Color="#FFFFFF" />
<SolidColorBrush x:Key="TextDim" Color="#B4BCD4" />
<SolidColorBrush x:Key="TextFaint" Color="#7B849F" />
<SolidColorBrush x:Key="TextGhost" Color="#5A6280" />
<SolidColorBrush x:Key="TextDim" Color="#EDEDF5" />
<SolidColorBrush x:Key="TextFaint" Color="#9C9EB4" />
<SolidColorBrush x:Key="TextGhost" Color="#7C7F98" />
<!--
The accent, and it is two colours rather than one. The design fills with #5B8CFF — the button, the
floating action button, a progress bar, a toggle that is on — and writes with #8FA7FF: the current
The accent, and it is two colours rather than one. The design fills with #5D42DE — the button, the
floating action button, a progress bar, a toggle that is on — and writes with #A78BFA: the current
destination in the bottom bar, a link, the text inside a chip. They are not interchangeable in either
direction. The fill is too saturated to read as small text on a dark surface, and the text colour is
too pale to carry a button.
AccentInk is what goes *on* the fill. It equals Canvas today and is named separately anyway, because
"the darkest surface" and "the colour of text on a blue button" are two facts that happen to coincide,
and a light theme would separate them immediately.
AccentInk is what goes *on* the fill, and it no longer equals Canvas the way it did through v1 and v2:
the new accent is dark enough on its own that white carries better on it than the near-black the app
otherwise writes on. "The darkest surface" and "the colour of text on the accent" were two facts that
happened to coincide before; this design is the light theme the old comment predicted would separate
them, arriving by a different route.
AccentGradient and the glow beneath it are new in v3 — the design draws its primary buttons with a
top-to-bottom gradient rather than a flat fill, lighter violet into the accent proper, and lifts them
off the surface with a soft violet-tinted shadow rather than a border. Both are named here rather than
inlined in App.axaml for the same reason every other accent value is: one button styled from the wrong
purple is how a screenshot next to another screenshot finds the seam.
-->
<Color x:Key="AccentColor">#5B8CFF</Color>
<Color x:Key="AccentColor">#5D42DE</Color>
<SolidColorBrush x:Key="Accent" Color="{StaticResource AccentColor}" />
<SolidColorBrush x:Key="AccentText" Color="#8FA7FF" />
<SolidColorBrush x:Key="AccentInk" Color="#0E1220" />
<SolidColorBrush x:Key="AccentSoft" Color="#5B8CFF" Opacity="0.35" />
<SolidColorBrush x:Key="AccentWash" Color="#5B8CFF" Opacity="0.06" />
<SolidColorBrush x:Key="AccentText" Color="#A78BFA" />
<SolidColorBrush x:Key="AccentInk" Color="#FFFFFF" />
<SolidColorBrush x:Key="AccentSoft" Color="#5D42DE" Opacity="0.35" />
<SolidColorBrush x:Key="AccentWash" Color="#5D42DE" Opacity="0.06" />
<LinearGradientBrush x:Key="AccentGradient" StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Color="#8D51E7" Offset="0" />
<GradientStop Color="#5D42DE" Offset="1" />
</LinearGradientBrush>
<BoxShadows x:Key="AccentGlow">0 0 22 0 #598D51E7</BoxShadows>
<!--
◆ Green is no longer the accent, and that is the one semantic change in this file rather than a
recolour.
◆ Green is still not the accent, and purple replacing blue in v3 does not reopen that question.
It used to be both: Ellipse.dot.live filled with Accent, so "a shell is open on this host" and "this is
the thing the screen wants you to press" were the same colour by construction. The v2 design keeps a
green — #3DDC97, for the live dot, the sync light and the follow indicator — and makes the accent blue,
which finally separates the two. Live is that green, and nothing that is merely interactive may use it.
Blue was the accent through v1 and v2; purple is the accent now. Either way, Ellipse.dot.live keeps its
own colour rather than borrowing the accent's, which is the separation this comment originally recorded
when the accent first stopped being green: "a shell is open on this host" and "this is the thing the
screen wants you to press" read as two different colours by construction, not by coincidence of which
two hues the current design happens to pick.
Read the pair as: blue is what you can do, green is what is true.
Live is #34D399 in this design — the design's own green, a shade cooler than v2's #3DDC97 — for the
live dot, the sync light and the follow indicator. Nothing that is merely interactive may use it.
Read the pair as: purple is what you can do, green is what is true.
-->
<SolidColorBrush x:Key="Live" Color="#3DDC97" />
<SolidColorBrush x:Key="LiveWash" Color="#3DDC97" Opacity="0.12" />
<SolidColorBrush x:Key="Live" Color="#34D399" />
<SolidColorBrush x:Key="LiveWash" Color="#34D399" Opacity="0.12" />
<!--
The three colours that are allowed to disagree with the accent. Amber means a caveat worth reading, red
means refused or destructive, and blue-light is for the one thing that is neither — an informational log
line, a directory, a distinct scope.
Info is left at its v2 value, #7FB0FF, rather than moved: it was already meant to read as distinct from
the accent, and now that the accent has moved to purple a blue that used to sit close to it on the wheel
sits further from it instead — the separation this colour exists for gets stronger for free.
WarnText and DangerText are the prose weights: a wash is a very dark tint, and the signal colour that
works as a 6-pixel dot or a one-word label is not the one that reads as a sentence on top of it. The
design supplies both reds — #E86A6A labels the refusal and #FF8F8F writes it out — and one amber, so
the amber prose weight is interpolated.
works as a 6-pixel dot or a one-word label is not the one that reads as a sentence on top of it. Danger
keeps its v2 values outright — the design does not revisit red — so DangerSoft, DangerWash and
DangerText stay derived from the same #E86A6A they always were. Warn's fill and wash move to the
design's own #F59E0B, and WarnText to the design's own #F5B942 rather than the interpolated value v2
carried, because this design supplies a prose weight where v2's did not name one.
-->
<SolidColorBrush x:Key="Warn" Color="#FFB454" />
<SolidColorBrush x:Key="WarnSoft" Color="#FFB454" Opacity="0.35" />
<SolidColorBrush x:Key="WarnWash" Color="#FFB454" Opacity="0.12" />
<SolidColorBrush x:Key="WarnText" Color="#E4A868" />
<SolidColorBrush x:Key="Warn" Color="#F59E0B" />
<SolidColorBrush x:Key="WarnSoft" Color="#F59E0B" Opacity="0.35" />
<SolidColorBrush x:Key="WarnWash" Color="#F59E0B" Opacity="0.12" />
<SolidColorBrush x:Key="WarnText" Color="#F5B942" />
<SolidColorBrush x:Key="Danger" Color="#E86A6A" />
<SolidColorBrush x:Key="DangerSoft" Color="#E86A6A" Opacity="0.3" />
<SolidColorBrush x:Key="DangerWash" Color="#E86A6A" Opacity="0.12" />
@@ -153,17 +212,24 @@
<SolidColorBrush x:Key="Info" Color="#7FB0FF" />
<!--
The design asks for Instrument Sans and Fira Code, as the one before it asked for IBM Plex. Neither
ships with this application, and neither is on a stock Windows install or a stock Android one, so
requesting them by name would render as whatever the font fallback chose that day — which is worse than
choosing deliberately. Inter is embedded by both heads and is what they already draw with.
Montserrat, JetBrains Mono and Material Icons are embedded font files rather than names handed to the
font fallback system, and that is a change from every design before this one. v1 asked for IBM Plex,
v2 for Instrument Sans and Fira Code, and neither shipped with the application nor came stock on a
Windows or an Android install, so requesting either by name would have rendered as whatever fallback
the platform chose that day — worse than substituting deliberately. Inter stood in for both heads' sans
and the generic `monospace` stood in for the mono stack, and that substitution was the reversible
stand-in this comment used to describe.
The stack ends in the generic `monospace` rather than a Windows face, which is what makes it work on
both: Android has no Cascadia Mono or Consolas and resolves the generic name to its own mono face.
Named as a resource rather than repeated, because the substitution is the sort of thing that gets
reversed later and should be reversible in one place. See docs/design-import-gaps.md.
It no longer needs to be: the three faces this design names ship as TTFs under Assets/Fonts in this
project, embedded the same way WebAssets is, and resolved by the same avares:// URI scheme Inter
already used. MonoFont below is JetBrains Mono first, with the old system-mono stack kept after it as a
fallback for whatever glyph JetBrains Mono does not cover. IconFont is new: the design's icons are drawn
from Material Icons by codepoint rather than by name, and screens adopt that per glyph as they are
touched rather than all at once — this resource only makes the face available. Program.cs makes the
equivalent change for the sans stack, with Montserrat first and Inter kept as its fallback; see the
remarks on BuildAvaloniaApp.
-->
<FontFamily x:Key="MonoFont">ui-monospace,Cascadia Mono,Consolas,monospace</FontFamily>
<FontFamily x:Key="MonoFont">avares://DodoSSH.Client.Shell/Assets/Fonts#JetBrains Mono,ui-monospace,Cascadia Mono,Consolas,monospace</FontFamily>
<FontFamily x:Key="IconFont">avares://DodoSSH.Client.Shell/Assets/Fonts#Material Icons</FontFamily>
</ResourceDictionary>