Files
DodoSSH/src/DodoSSH.Client.App/App.axaml
T
jaap-jan ccaf7a8e72
ci / build and test (pull_request) Failing after 2m33s
ci / desktop nightly (pull_request) Skipped
ci / api image (pull_request) Skipped
ci / android head (pull_request) Successful in 3m34s
Give the terminal back the width and the keyboard the session shell took
Seven things reported from a day's use of v5b's session shell, and they are one
commit because five of them are the same complaint from different angles: the
window spends too much of itself on chrome describing the session, and the parts
that are not chrome do not behave.

◆ THE HOST HEADER IS GONE, and that is a deliberate departure from the design.

Terminal.dc.html and SFTP.dc.html both draw a 60-pixel row above the pane: the
address on the left, a cross-surface button on the right. Both facts are worth
having and the strip they sat in was not — the tab already names the host, and a
full-width bar repeating it was the cheapest 60 pixels in the layout to give
back. The address is now the first line of the sidebar and the button is
stretched across the column under it, so nothing is lost and the pane is taller.

Which word that button carries and which command it runs used to be handed in
from the two usage sites in MainWindow.axaml, because the control was drawn
twice. One sidebar cannot do that, so SessionCrossSurfaceLabel and
OpenOtherSurfaceCommand resolve it in the shell — the same place SessionAddress
already decides which surface's fact to read. The two directions underneath are
untouched: SelectFilesHostAsync for a tab's host, OpenTerminalForFilesHostAsync
for a fresh terminal at whatever SFTP has open.

SessionHeader.axaml is deleted rather than left unused, and LayoutHarness stops
subtracting its 60 pixels from every session screen's budget — the same
treatment the retired window-wide tab strip got, and for the same reason: a
constant for chrome nobody draws is a suite quietly measuring the wrong
rectangle.

◆ AND THE SIDEBAR CLOSES, which the design has no state for at all.

300 pixels of an 1081-pixel minimum is a great deal to spend on a list that is
often two rows long. The column now folds to a 34-pixel rail carrying the
chevron that brings it back — a rail rather than nothing, because a panel that
vanishes leaving no trace is one people report as lost rather than as closed.
Both states live in the one control and swap on IsSessionSidebarOpen, so
MainWindow's own "Auto" column takes whichever width is showing without knowing
the state exists.

Written through to ClientSettings.SessionSidebarOpen rather than held for the
session. It is a decision about how much of the window a terminal gets, and one
that had to be made again on every launch would not really be on offer.

---- THE FOUR SMALLER ONES ----

A SNIP LANDED IN A TERMINAL NOBODY COULD TYPE AT, and looked selected when it
got there. Two causes with nothing in common. The click moved Win32 focus onto
the sidebar row, and term.focus() in the page cannot take it back — only the
host can, so the shell raises TerminalFocusRequested and the window answers with
the same posted focus every other path here uses. The highlight was bash: xterm
wraps a paste in bracketed-paste markers, readline marks what arrives inside
them as an active region, and it stays in reverse video until the next
keystroke. Right for a clipboard paste, wrong for a snippet picked off a
sidebar. Single-line snips are typed rather than pasted now, which needs no
markers; multi-line still pastes, because "runs three commands unasked" is the
worse of the two failures and the markers are the whole of what prevents it.

A BLACK BAR UNDER THE TERMINAL, on Windows. xterm.css paints its scrolling
viewport #000 — its own comment explains why, and it is a macOS scrollbar
concern. Everywhere else that black is covered by the rows, except along the
bottom: the fit addon floors the row count, so the remainder below the last
whole row is bare viewport, up to a line tall, against this page's #171a26. The
light square at its right-hand end is where WebView2's classic scrollbar corner
lands. The viewport is repainted in the page's own background, and the scrollbar
with it — thin and in these colours rather than a grey Windows channel down the
side of a near-black terminal, and kept rather than hidden, because a surface
that scrolls with no sign that it does is worse than a quiet bar.

THE PINS ROW DREW A TOFU BOX. U+E946 is not in the embedded Material Icons face
at all — that file is the 2019 build and its cmap skips E944 and E946 — so the
rail's Pins row and the hosts screen's own pin badge have both been drawing a
missing-glyph rectangle since v5b picked the codepoint. push_pin in that vintage
is U+F10D, verified against the file rather than against a codepoints table for
a later release of the font. Every other icon codepoint in the repository was
audited the same way; this was the only miss.

THE KBD CHIP CUT THE CHORD IN HALF. 34 pixels is the design's width for a chip
reading ⌘K, and this build substitutes CTRL K — six characters and a space,
wider than 34 at 10.5 mono. MinWidth and padding instead, so the design's
footprint survives for the day this face has a ⌘ to draw.

---- AND THE POPOVER UNDER THE USER CHIP ----

Reported as not matching the design, and it was not: Button.poprow set a corner
radius and a padding and never touched the Background, so every row wore the
Fluent theme's own #33FFFFFF button fill. Six raised pills stacked in a menu the
design draws as six lines of text — and the hover rule underneath was already
correct and simply invisible against a fill that never went away. Set on the
ContentPresenter as well as on the Button, the same as Button.flat, because the
theme binds its brush there and a Background set only on the control loses to
it. The panel itself gets this window's own radius-12 card treatment through a
FlyoutPresenter class rather than by widening the shared context-menu rule, and
Vaults and Preferences stop being drawn one step dimmer than Settings and
Logout, which read as two disabled entries in a menu of five live ones.

---- WHAT PROVES IT ----

Three tests in the layout suite, two of them checked against the defect they
describe: the popover row's resting fill (fails with #33ffffff without the
style), and the kbd chip against the natural width of its own text, measured on
a detached copy because a TextBlock's DesiredSize is already clipped to what it
was given and reports 34 inside a 34-pixel chip either way. SessionSidebarTests
is new — the sidebar has never been laid out by a test, and it now holds a
string of unbounded length beside a button that has to stay clickable. In the
shell suite: the cross-surface row in both directions, the closed state
surviving to disk, and the focus request being made when a snip lands and not
made when it does not.
2026-08-10 16:49:44 +02:00

1409 lines
79 KiB
XML

<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="DodoSSH.Client.App.DodoSshApp"
RequestedThemeVariant="Dark">
<!--
Dark by default rather than following the system. A terminal is a dark surface either way, and
a light chrome around a dark terminal is the worst of both: the eye adapts to the bright frame
and then cannot read the text.
-->
<Application.Resources>
<!--
The palette and the font stack, shared with the Android head. They moved into DodoSSH.Client.Shell
when there were two heads that had to agree on them; Theme/Palette.axaml carries the reasoning behind
the names, the five near-black surfaces and the font substitution.
-->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://DodoSSH.Client.Shell/Theme/Palette.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
<Application.Styles>
<FluentTheme />
<!--
Application-wide rather than on MainWindow, which is where the first three lived until the vault column
became its own control. A Window's styles reach its whole tree, so the move changes nothing about how
the application renders — but a control laid out on its own, as the layout harness lays the column out,
would otherwise lose them. That matters more than it looks: "hint" carries TextWrapping, and text that
wraps in the application and does not wrap in the harness makes every measured height too small, which
is the one way this kind of test lies quietly.
-->
<!--
◆ THE BASE SIZE, and it is set here rather than on the window.
Every explicit FontSize below is a step off a base nothing was stating: a bare TextBlock took
TextElement's own default of 12, which meant the one number the whole scale is measured from lived in
Avalonia rather than in this file. Raising the scale meant naming it.
Declared on Window *and* on UserControl, which is not redundancy. FontSize inherits, so the window
alone would reach everything the application draws — but the layout harness hosts a UserControl in a
plain `new Window()` it constructs itself, and that window has no style of this application's on it.
Without the second selector the harness would measure every screen a point smaller than it ships,
which is the failure mode App.axaml's own note at the top of this block was written about: a test that
lays out text smaller than the application does reports heights that are all slightly too small, and
it does it silently.
A selector on TextBlock would have been the obvious way and is wrong. Style setters beat inherited
values in Avalonia, so `Selector="TextBlock"` would win over the size a Button sets on itself and
reach the TextBlock inside that button's template — collapsing every deliberate step below back to one
number.
-->
<Style Selector="Window">
<Setter Property="FontSize" Value="13" />
</Style>
<Style Selector="UserControl">
<Setter Property="FontSize" Value="13" />
</Style>
<Style Selector="TextBlock.hint">
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
<Style Selector="TextBlock.heading">
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="FontSize" Value="19" />
<Setter Property="FontWeight" Value="SemiBold" />
</Style>
<Style Selector="Border.card">
<Setter Property="Background" Value="{StaticResource Chrome}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="12" />
<Setter Property="Padding" Value="24" />
<Setter Property="MaxWidth" Value="520" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
<!--
Monospace, which in this design is not a decoration for code. Host names, fingerprints, addresses and
counts are all things people compare character by character, and a proportional font is what makes two
similar fingerprints look alike.
-->
<Style Selector="TextBlock.mono">
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
</Style>
<Style Selector="SelectableTextBlock.mono">
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
</Style>
<!--
The small tracked-out capitals the design uses for every section heading. Letter spacing rather than
bold: at this size weight closes the counters up and the word turns into a smudge, whereas spacing is
what makes six characters read as a label instead of as an abbreviation.
-->
<Style Selector="TextBlock.label">
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="11" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="LetterSpacing" Value="1.2" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<!--
A chip: a bordered scrap of text stating one fact. The design uses them for tags, roles, scopes and
shortcut hints, which have nothing in common except that each is a noun the eye should be able to skip.
CornerRadius 6 rather than v2's 4: v3 draws every small chip — a tag, a pin badge — at 5-6, distinct
from the 10-12 the cards and fields moved to, so a chip keeps reading as a chip beside either.
-->
<Style Selector="Border.chip">
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="6" />
<Setter Property="Padding" Value="6,2" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style Selector="Border.chip > TextBlock">
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="11" />
<Setter Property="FontWeight" Value="Medium" />
<Setter Property="LetterSpacing" Value="0.5" />
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<Style Selector="Border.chip.accent">
<Setter Property="BorderBrush" Value="{StaticResource AccentSoft}" />
</Style>
<!--
AccentText rather than Accent, which is the v2 split: the fill colour is too saturated to read as
small text on a dark surface, and a chip is small text. See the remark on the pair in Palette.axaml.
-->
<Style Selector="Border.chip.accent > TextBlock">
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
</Style>
<!--
A chip that is also a button: the tag picker in the host editor. It borrows Border.chip's geometry
rather than inheriting it, because a Button is not a Border and Avalonia's selectors are structural —
the numbers are repeated here and the two have to move together, which is why they sit adjacent.
Worn is filled, unworn is outlined, and the difference is the accent rather than a tick: a row of
these has to read as "the tags this host has" at a glance, and a checkbox column reads as a form.
-->
<Style Selector="Button.chiptoggle">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="6" />
<Setter Property="Padding" Value="6,2" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontWeight" Value="Medium" />
</Style>
<Style Selector="Button.chiptoggle.worn">
<Setter Property="BorderBrush" Value="{StaticResource AccentSoft}" />
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
</Style>
<Style Selector="Button.chiptoggle.worn /template/ ContentPresenter">
<Setter Property="Background" Value="{StaticResource AccentWash}" />
</Style>
<Style Selector="Button.chiptoggle:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{StaticResource Hover}" />
</Style>
<Style Selector="Border.chip.warn">
<Setter Property="BorderBrush" Value="{StaticResource WarnSoft}" />
</Style>
<Style Selector="Border.chip.warn > TextBlock">
<Setter Property="Foreground" Value="{StaticResource Warn}" />
</Style>
<!--
The buttons. Fluent's own chrome is a rounded grey slab, and one of those beside a hairline-bordered
chip reads as belonging to a different application — so the three shapes this design uses are declared
here rather than restyled per view.
Each sets its brushes on the content presenter, not on the button. That is where the Fluent theme puts
its own, so a Background set on the button is a TemplateBinding the theme's :pointerover rule overrides
— which shows up as a button that is styled until the pointer arrives and then is not.
-->
<Style Selector="Button.ghost /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<Style Selector="Button.ghost:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource ChromeHover}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderHover}" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<Style Selector="Button.ghost:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource BorderFaint}" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<!--
The primary action, and since v3 filled with a gradient rather than a flat colour: the design draws
every accent button top-to-bottom from a lighter violet into the accent proper, and lifts it off the
surface with a soft violet glow instead of a border. Both live in Palette.axaml as AccentGradient and
AccentGlow, named there rather than here for the reason every other accent value is — see the remark
on the pair in that file.
BoxShadow is set here rather than on the Button itself: this rule already targets the template's
ContentPresenter for Background for the Fluent-override reason explained above, and ContentPresenter
carries its own BoxShadow property in Avalonia 11, so the glow and the fill live on the same element
and move together.
-->
<Style Selector="Button.accent /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource AccentGradient}" />
<Setter Property="BoxShadow" Value="{StaticResource AccentGlow}" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Foreground" Value="{StaticResource AccentInk}" />
</Style>
<Style Selector="Button.accent:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource AccentGradient}" />
<Setter Property="Opacity" Value="0.85" />
<Setter Property="Foreground" Value="{StaticResource AccentInk}" />
</Style>
<!--
A disabled accent button keeps its shape and loses its fill. Fluent's disabled state greys the
foreground and leaves the background, which on a purple slab is unreadable rather than merely dim.
BoxShadow is cleared here too, and that is new alongside the gradient: the base rule now sets a glow
that reads as "this is lit and wants pressing", which is the opposite of what a disabled control
should say. Left unset it would still apply — the base rule's Setter is still in effect wherever a
more specific one does not override it — so the glow has to be named here, as "none", to go away.
◆ "none" RATHER THAN THE EMPTY STRING. The remark this replaced claimed an empty string parses to "no
shadows", and it does not on the Avalonia this project builds against — BoxShadowsTypeConverter throws
an InvalidCastException on it instead, which failed silently at style-sheet load time in nothing that
exercises this rule's own screen and loudly in every headless layout test that lays out any window at
all, because Application.Styles is global. "none" is the literal this converter does accept for an
empty BoxShadows.
-->
<Style Selector="Button.accent:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource BorderFaint}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BoxShadow" Value="none" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Button.danger /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource DangerSoft}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Foreground" Value="{StaticResource Danger}" />
</Style>
<Style Selector="Button.danger:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource DangerWash}" />
<Setter Property="BorderBrush" Value="{StaticResource Danger}" />
<Setter Property="Foreground" Value="{StaticResource Danger}" />
</Style>
<Style Selector="Button.danger:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource BorderFaint}" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<!--
Every button in this window is small, mono and tracked out; only the colours differ.
◆ VerticalContentAlignment IS NOT VerticalAlignment, and this style used to set only the second. The
first places the caption inside the button; the second places the button inside its parent. Avalonia's
default for content alignment is Stretch, so on any of these given a fixed Height — the hosts toolbar's
three at 40, and every dialog's row of them — the ContentPresenter stretched the caption's TextBlock to
the full content box and a TextBlock draws its line at the TOP of its bounds. Measured on the hosts
toolbar: a 40-pixel button with 9 pixels above the ink and 20 below it. Nothing was the wrong height,
which is why this read as one — the box was right and the label sat in the top third of it.
Center rather than a hand-tuned Padding, because the gap is the difference between the line box and the
content box and so moves with the font size: these carry 11.5 by default and the primary action
overrides it to 13.5. Every other button class here already sets it — navseg, sesstab, headerghost,
sidebarrow, fieldrow, paneicon — so this is the three shapes that were missed rather than a new idiom.
HorizontalContentAlignment is deliberately left alone. It is Stretch too, and it is invisible on a
button sized to its own caption; the ones that are stretched wide state their own (the flyouts' rows
ask for Left), and centring those from here would move text nobody complained about.
-->
<Style Selector="Button.ghost, Button.accent, Button.danger">
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="11.5" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="LetterSpacing" Value="0.8" />
<Setter Property="Padding" Value="10,5" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
<!--
Buttons that are not shaped like buttons: a group heading, a nav-rail destination, a terminal tab, a
window control. All four are clickable regions that read as part of the chrome, so they carry no
border and no fill until the pointer is over them — and all four are buttons rather than borders with
a gesture, because a button is focusable, keyboard-activatable and announced as a control.
-->
<Style Selector="Button.flat">
<Setter Property="MinHeight" Value="0" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Style>
<Style Selector="Button.flat /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="0" />
</Style>
<Style Selector="Button.flat:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource ChromeHover}" />
</Style>
<Style Selector="Button.flat:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource ChromeHover}" />
</Style>
<!--
The titlebar's search pill. A plain <c>Border</c> rather than a styled <c>Button</c> template, because
the pill sits inside a <c>Button.flat</c> that already owns the click — see <c>TitleBar.axaml</c> — and
what changes under the pointer is the pill's own border, not a fill behind it. Declared on the parent
button's <c>:pointerover</c> rather than the border's own, because a <c>Border</c> has no pointer state
of its own to key a selector on.
-->
<Style Selector="Border.searchpill">
<Setter Property="Background" Value="{StaticResource SearchPill}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="Button.search:pointerover Border.searchpill">
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
</Style>
<!--
Button.grouphead was here: the padding on the fold-away group heading that used to sit between the host
cards. The headings went when the grid became cards and the group cards above it became the thing that
names a group — see HostsScreen.axaml — and a style with nothing wearing it is a shape somebody will
later reintroduce by reaching for it.
-->
<!-- A category in the vault's rail: the same accent strip a selected row carries, at full row height. -->
<Style Selector="Button.cat">
<Setter Property="Padding" Value="0,6,14,6" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="11" />
<Setter Property="FontWeight" Value="Medium" />
<Setter Property="LetterSpacing" Value="0.5" />
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<Style Selector="Button.cat /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<Style Selector="Button.cat.active /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource AccentWash}" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<!--
The nav rail. v5b redraws it against TitleBar.dc.html's sibling NavRail.dc.html rather than against
the v2 mock this replaces — see the comment block at the top of NavRail.axaml for what moved and why.
The active destination is now a filled accent row rather than a wash with a bar down its edge; the
idiom is otherwise the same one the switcher below and a selected list row already use.
TextGhost rather than TextFaint for the resting label: the mock computes rgb(124,127,152) for an
inactive row, which is TextGhost's own #7C7F98 exactly, not TextFaint's paler #9C9EB4.
-->
<Style Selector="Button.nav">
<Setter Property="Height" Value="35" />
<Setter Property="Padding" Value="11,8" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="FontSize" Value="10.5" />
<Setter Property="LetterSpacing" Value="0.1" />
<Setter Property="FontWeight" Value="Medium" />
<Setter Property="Foreground" Value="{StaticResource TextGhost}" />
</Style>
<Style Selector="Button.nav /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource TextGhost}" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="8" />
</Style>
<Style Selector="Button.nav:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<Style Selector="Button.nav.active">
<Setter Property="FontWeight" Value="SemiBold" />
</Style>
<Style Selector="Button.nav.active /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="Background" Value="{StaticResource Accent}" />
</Style>
<!--
The two parts of a sidebar row: a 19-pixel Material glyph and its word, ten pixels apart. v2's third
column — a count read off the vault — left with the row it decorated; see the remark on
<c>Button.nav</c> above for where that number went and why it is not replaced here.
-->
<Style Selector="TextBlock.navicon">
<Setter Property="FontFamily" Value="{StaticResource IconFont}" />
<Setter Property="FontSize" Value="19" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style Selector="TextBlock.navlabel">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
</Style>
<!--
The segmented SSH / SFTP / S3 switcher at the rail's head. A track the width of the rail's own content
column, 2 pixels of padding holding three equal segments — so the three are Buttons with
HorizontalAlignment="Stretch" inside a Grid of three equal columns, the same "buttons carry no state"
reasoning <c>NavRail.axaml</c>'s own remarks give for every row beneath them.
-->
<Style Selector="Border.navtrack">
<Setter Property="Height" Value="31" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Background" Value="{StaticResource Track}" />
<Setter Property="Padding" Value="2" />
</Style>
<Style Selector="Button.navseg">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Height" Value="27" />
<Setter Property="CornerRadius" Value="7" />
<Setter Property="FontSize" Value="10.5" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="LetterSpacing" Value="0.1" />
<Setter Property="Foreground" Value="{StaticResource TextGhost}" />
</Style>
<Style Selector="Button.navseg /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource TextGhost}" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="7" />
</Style>
<Style Selector="Button.navseg.active /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="Background" Value="{StaticResource Accent}" />
</Style>
<!--
The rail's foot: the user chip that opens the popover. Flat until the pointer arrives, the same idiom
every other row in this rail follows, at its own height and radius because it is a chip rather than a
destination — see <c>NavRail.axaml</c> for what it opens and why a <c>Flyout</c> is safe here.
-->
<Style Selector="Button.navuser">
<Setter Property="Height" Value="34" />
<Setter Property="Padding" Value="13,7" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="CornerRadius" Value="12" />
</Style>
<Style Selector="Button.navuser /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="12" />
</Style>
<Style Selector="Button.navuser:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<!--
The user popover itself: the panel the rail's chip opens, in this window's own idiom rather than the
theme's. The shared MenuFlyoutPresenter rule further down this file gives every popup Chrome and a
4-pixel radius, which is right for a context menu and wrong for this one — the design draws the account
menu as a rounded card, the same radius-12 shape as every other floating surface here. Reached with
FlyoutPresenterClasses from NavRail.axaml rather than by widening that rule, so a right-click menu two
screens away does not quietly become a card as well.
-->
<Style Selector="FlyoutPresenter.poppanel">
<Setter Property="Background" Value="{StaticResource Raised}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="12" />
<Setter Property="Padding" Value="8" />
</Style>
<!--
A row inside the user popover: a vault switch, "New vault", Settings, Preferences, Vaults, Logout. All
six share one shape — flat, a track fill under the pointer, 8 pixels of rounding — because the popover
draws them as one list and a row that looked different from its neighbours would read as a separator
that is not one.
◆ FLAT MEANS SAYING SO, which this rule did not. It set a corner radius and a padding and left the
Background alone, so every row wore the Fluent theme's own button fill and its border: six raised pills
stacked in a menu, where the design draws six lines of text that light up under the pointer. The hover
rule below was already right and was simply invisible against a fill that was there all along. Set on
the ContentPresenter as well as on the Button, the same as Button.flat does and for the same reason —
the theme's template binds its own brush there, and a Background set only on the control loses to it.
-->
<Style Selector="Button.poprow">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Padding" Value="11,4" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="MinHeight" Value="20" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Style>
<Style Selector="Button.poprow /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="8" />
</Style>
<Style Selector="Button.poprow:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<Style Selector="Button.poprow:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<!--
The check square beside a shown vault in the popover — magenta rather than the accent, because the
accent already means "press this" everywhere else in the window and a vault switch is a fact, not an
action waiting to be taken. See Palette.axaml's remark on Magenta.
-->
<Style Selector="Border.vaultcheck">
<Setter Property="Width" Value="12" />
<Setter Property="Height" Value="12" />
<Setter Property="CornerRadius" Value="3" />
<Setter Property="Background" Value="{StaticResource Magenta}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<!--
── v5b: SESSION TAB ROW ─────────────────────────────────────────────────────────────────────────────
The tab strip's replacement, per <c>Terminal.dc.html</c>/<c>SFTP.dc.html</c>: rounded only at the top,
fused visually to the bordered container it sits above rather than a free-floating pill — see
<c>SessionTabRow.axaml</c>. It went from a 42-pixel strip spanning the window to a 38-pixel row inside
each of the two screens that carry one, which is why the layout budget in <c>LayoutHarness</c> no
longer subtracts a tab strip's height from every screen: only these two now pay it, out of their own
26-pixel padded column rather than out of the window's own chrome.
◆ ORDER IS THE BEHAVIOUR HERE, as it was for the pill this replaces. The base tab comes first, then its
hover, then <c>.active</c> — each later rule is an exception to what came before it, because Avalonia
has no specificity and settles two rules matching one element by declaration order alone.
-->
<Style Selector="Button.sesstab">
<Setter Property="Padding" Value="16,10,12,10" />
<Setter Property="Height" Value="38" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="VerticalAlignment" Value="Bottom" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="CornerRadius" Value="9,9,0,0" />
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="13.5" />
<Setter Property="FontWeight" Value="Medium" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Button.sesstab /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="9,9,0,0" />
</Style>
<Style Selector="Button.sesstab:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<!--
The tab whose pane the container below is showing: DeepChrome body — the same value the container's
own host header and status bar are painted in, which is what fuses the two into one shape rather than
a tab floating above a separate box — and a 2px top border in one of two colours. Which colour is set
by the row's own <c>Classes="sftp"</c>, from the usage site in <c>MainWindow.axaml</c>: unmarked is the
terminal row's <c>TerminalTabAccent</c>, and <c>.sftp</c> is the SFTP row's <c>Magenta</c> — see the
remark on both keys in <c>Palette.axaml</c>. Marked on <c>TerminalTabViewModel.IsSelected</c> rather
than <c>IsShowing</c>, unlike the strip this replaces: the SFTP row's own active tab is not "the pane
the terminal surface is showing" at all, so the narrower flag would leave it permanently dark. See
<c>MainWindowViewModel.SelectFilesHostCommand</c>.
-->
<Style Selector="Button.sesstab.active">
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style Selector="Button.sesstab.active /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource DeepChrome}" />
<Setter Property="BorderThickness" Value="0,2,0,0" />
<Setter Property="BorderBrush" Value="{StaticResource TerminalTabAccent}" />
</Style>
<Style Selector=".sftp Button.sesstab.active /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="BorderBrush" Value="{StaticResource Magenta}" />
</Style>
<!--
The button that opens a connection, reusing the strip's own shape rather than a new one: no active
state, because it is never the thing showing, and no outline, because it is not one of the things
being chosen between.
-->
<Style Selector="Button.sesstab.plus">
<Setter Property="Padding" Value="0" />
<Setter Property="Width" Value="28" />
<Setter Property="Height" Value="28" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Button.sesstab.plus /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="8" />
</Style>
<Style Selector="Button.sesstab.plus:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<!--
── v5b: THE "OPEN SFTP" / "OPEN TERMINAL" GHOST BUTTON ─────────────────────────────────────────────
A ghost button distinct from <c>Button.ghost</c> above: this one's resting border is <c>BorderHover</c>
rather than <c>BorderMid</c> — the mock's own inset ring for this one control — and the design gives it
no filled hover, only the border turning to the accent.
Named for the host header it was drawn for, which v5c-4 retired; the button itself moved intact to the
head of the session sidebar and is stretched across that column there. See <c>SessionSidebar.axaml</c>.
-->
<Style Selector="Button.headerghost">
<Setter Property="Height" Value="32" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="Padding" Value="16,0" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="12.5" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<Style Selector="Button.headerghost /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource BorderHover}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="9" />
</Style>
<Style Selector="Button.headerghost:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<!--
── v5b: THE SESSION SIDEBAR'S ROWS ──────────────────────────────────────────────────────────────────
QUICK ACCESS's pins and, on the terminal surface, SNIPS — both h33, radius 9, a glyph and a mono
label, flat until the pointer finds them. See <c>SessionSidebar.axaml</c>.
-->
<Style Selector="Button.sidebarrow">
<Setter Property="Height" Value="33" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="Padding" Value="12,8" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="CornerRadius" Value="9" />
</Style>
<Style Selector="Button.sidebarrow /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="9" />
</Style>
<Style Selector="Button.sidebarrow:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<!--
── v5c-4: THE SIDEBAR'S OWN CLOSE AND REOPEN ────────────────────────────────────────────────────────
One class for both, because they are one control in two states — a 26-pixel square carrying a chevron,
at the head of the column when it is open and at the head of the rail when it is not. Square rather
than the 33-tall rows below it: it is chrome belonging to the panel, not an entry in the list the panel
is holding, and matching the rows' shape would have offered it as one.
-->
<Style Selector="Button.sidebargrip">
<Setter Property="Width" Value="26" />
<Setter Property="Height" Value="26" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="CornerRadius" Value="8" />
</Style>
<Style Selector="Button.sidebargrip /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="8" />
</Style>
<Style Selector="Button.sidebargrip:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<!--
The "+ Pin folder" / "+ Add Snip" row at the foot of each section: 30 tall rather than 33, and its own
quieter foreground — the mock draws these as the same greyed-out "add" idiom in both sections.
-->
<Style Selector="Button.sidebaradd">
<Setter Property="Height" Value="30" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="Padding" Value="12,8" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="CornerRadius" Value="9" />
<Setter Property="FontSize" Value="11.5" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Button.sidebaradd /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="9" />
</Style>
<Style Selector="Button.sidebaradd:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<!--
A pair of buttons standing in for a two-way choice, inside a pane rather than down a rail. Not the
.cat style, which stretches to fill a 176-pixel rail row and would be wrong at this width — and which
the category rail's own test counts, so borrowing it would have made this a fourth category.
-->
<Style Selector="Button.choice">
<Setter Property="Padding" Value="10,5" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="10.5" />
<Setter Property="LetterSpacing" Value="0.5" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Button.choice /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Background" Value="{StaticResource Raised}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<Style Selector="Button.choice:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<Style Selector="Button.choice.active /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource AccentWash}" />
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<!-- The close box on a tab, and the window controls. Square, quiet, and red only where it means it. -->
<Style Selector="Button.close /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Button.close:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource DangerWash}" />
<Setter Property="Foreground" Value="{StaticResource Danger}" />
</Style>
<!--
The one inside a tab, as opposed to the ones in the titlebar. Rounded and small, because a square
full-height red panel inside a tab reads as a divider between two tabs rather than as part of one —
which is what it looked like while it was a sibling of the tab instead of a child.
-->
<Style Selector="Button.close.inline /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="3" />
</Style>
<!--
Text input. Fluent draws a filled box with a thick focus underline; this design draws a hairline field
that changes border colour, and the two do not sit together in one row.
-->
<Style Selector="TextBox">
<Setter Property="Background" Value="{StaticResource Field}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="SelectionBrush" Value="{StaticResource AccentSoft}" />
<Setter Property="CaretBrush" Value="{StaticResource Accent}" />
<Setter Property="MinHeight" Value="30" />
<Setter Property="Padding" Value="8,4" />
<Setter Property="FontSize" Value="13" />
</Style>
<Style Selector="TextBox /template/ Border#PART_BorderElement">
<Setter Property="Background" Value="{StaticResource Field}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="TextBox:pointerover /template/ Border#PART_BorderElement">
<Setter Property="Background" Value="{StaticResource Field}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
</Style>
<Style Selector="TextBox:focus /template/ Border#PART_BorderElement">
<Setter Property="Background" Value="{StaticResource Field}" />
<Setter Property="BorderBrush" Value="{StaticResource AccentSoft}" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="TextBox /template/ TextBlock#PART_Watermark">
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<!--
◆ THE ADDRESS FIELD, the one input the host drawer draws with an accent border rather than the
ordinary grey hairline — the mock's own choice, kept, because it is the one field a session cannot be
opened without. Taller than the drawer's other fields too, at 42 rather than the TextBox default.
No drawn caret: the mock blinks one inside the field's own text, but this is a real TextBox with a real
one already, so painting a second would either fight the true caret for the same pixel or lie about
where typing lands. See HostDrawer.axaml's ADDRESS section, on both the pane and the editor.
Declared after the base TextBox rules — including :pointerover and :focus — for the reason this file
states three times already for Button.tab, Border.rowmark and the tiles list: Avalonia has no
specificity, and a rule declared before an exception it overrides does nothing at all. Placed here
rather than beside them so both still read as one un-interrupted block of "what every TextBox looks
like" with the one exception after it, not threaded through the middle.
-->
<Style Selector="TextBox.address">
<Setter Property="Height" Value="42" />
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
</Style>
<Style Selector="TextBox.address /template/ Border#PART_BorderElement">
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
<Setter Property="BorderThickness" Value="1.5" />
</Style>
<!--
Lists. The rows in this design are drawn by their own templates and marked as selected with an accent
strip down the left edge, so the theme's rounded blue selection block has to go — it would sit behind
every row as a second, disagreeing highlight.
-->
<Style Selector="ListBox">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="0" />
</Style>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="0" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="CornerRadius" Value="0" />
</Style>
<Style Selector="ListBoxItem /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="Padding" Value="0" />
</Style>
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Hover}" />
</Style>
<Style Selector="ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource AccentWash}" />
</Style>
<Style Selector="ListBoxItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource AccentWash}" />
</Style>
<!--
◆ A LIST OF TILES, which is the hosts screen's grid of group and host cards.
The three rules above paint the *item* — a full-bleed rectangle behind whatever the template draws —
and that is right for a row and wrong for a card: a square wash behind a 10-pixel-rounded tile shows
as four grey corners, and it does it on hover as well as on selection, which is most of the time the
pointer is anywhere near the grid. So a tiles list clears all three and the tile paints its own
states.
Declared after them and not before. Avalonia has no specificity: two rules matching one element are
settled by declaration order, so an exception stated above the rule it excepts does nothing at all.
That trap is recorded twice more in this file, for Button.tab and for Border.rowmark.
-->
<Style Selector="ListBox.tiles > ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="ListBox.tiles > ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="ListBox.tiles > ListBoxItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
</Style>
<!--
── v5b: THE SFTP PANES' OWN FILE ROWS ───────────────────────────────────────────────────────────────
A third list shape, on the same reasoning A LIST OF TILES gives above it: the design draws a file row
with 7-pixel rounded corners and a single fill — Track, not Hover or AccentWash — for both hover and
selected, and neither the accent wash nor a square-cornered highlight peeking out from behind a rounded
row is the mock's own choice. See TransfersScreen.axaml's local and remote listings.
No selected+unhovered accent strip either, unlike Border.rowmark's own rows: the design has nothing to
distinguish "selected" from "hovered" beyond which one is true at the moment, and :selected alone
already answers that once the pointer has moved on — a strip drawn on top would be a second, redundant
mark for the one state this list bothers to keep after the pointer leaves.
Declared after ListBox's own base rules, for the reason recorded three times over already in this file:
Avalonia has no specificity, and an exception declared above the rule it excepts does nothing at all.
-->
<Style Selector="ListBox.filerows > ListBoxItem /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="7" />
</Style>
<Style Selector="ListBox.filerows > ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<Style Selector="ListBox.filerows > ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<Style Selector="ListBox.filerows > ListBoxItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<!--
The card itself. A fixed width and a free height, which is the pair that makes a WrapPanel of these
into a grid: equal columns, and a card that grew a third line of tags is taller than its neighbours
rather than narrower.
◆ 224 IS DERIVED, and the arithmetic is written out because getting it wrong is invisible. The grid's
column at the window's minimum is 1016 less the rail's 190 and the drawer's 320, which is 506. The
scrolling stack inside it takes 16 of margin on each side, and the vertical scrollbar takes its own —
call the usable width 474. A WrapPanel fits floor(474 / (Width + 10)) per row, so two columns needs
Width no more than 227.
The first number here was 248, from the same reasoning with the two margins left out. It laid out
cleanly and the layout harness passed it, because the harness asks whether a control is inside the
window and not how many of them fit on a line — so the grid quietly became one column wide at exactly
the size this application guarantees, which is the shape the cards exist to avoid. The second was 232,
derived the same way against the drawer's own 304; v5 widened the drawer to 320 for the ADDRESS field's
breathing room, which narrowed the budget this number is drawn from and had to move it down in step.
-->
<Style Selector="Border.tile">
<Setter Property="Background" Value="{StaticResource Raised}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="12" />
<Setter Property="Padding" Value="12,10" />
<Setter Property="Width" Value="224" />
<Setter Property="Margin" Value="0,0,10,10" />
</Style>
<Style Selector="ListBoxItem:pointerover Border.tile">
<Setter Property="BorderBrush" Value="{StaticResource BorderHover}" />
</Style>
<!--
Selected is the accent outline *and* the filled surface, which is the same pair the nav rail and the
tab strip use for "you are here". A tile marked by its border alone is legible on a card you are
looking at and invisible in peripheral vision, which is where a selected card usually is once the
drawer beside it has opened.
-->
<Style Selector="ListBoxItem:selected Border.tile">
<Setter Property="Background" Value="{StaticResource Active}" />
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
</Style>
<!--
◆ A CARD IN THE CHOSEN SET, which is not the same thing as the selected one.
The list holds one selection — the card the drawer and CONNECT are about — and the set is what Ctrl,
Shift and a band dragged across the grid build on top of it. Six ticked cards with one of them also
selected is the ordinary shape, so the two marks have to be legible together and legible apart.
The fill and the tick on the card are the mark; the border is deliberately left to the rule above. A
chosen card that also drew an accent outline would be indistinguishable from the selected one, and the
question "which of these is the drawer showing" would have no answer. Declared after that rule anyway,
because it sets the same Background and Avalonia settles two matching rules by declaration order —
the trap this file records for Button.tab and Border.rowmark.
-->
<Style Selector="Border.tile.chosen">
<Setter Property="Background" Value="{StaticResource Active}" />
</Style>
<!-- The square a tile carries on its left: a group's mark, or a host's prompt. -->
<Style Selector="Border.tileicon">
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<!--
◆ THE PENCIL ON A HOST CARD, which is the only way the drawer opens from the grid.
It exists because selecting a host stopped opening it. A card that took 304 pixels off the grid the
moment it was touched charged the price of the pane for the act of choosing — most of which is arrowing
past machines you did not want — so opening is now a thing you ask for and this is the asking. See
VaultViewModel.IsHostPaneOpen.
Hidden until the pointer is on the card, and shown on the selected one whether or not the pointer is
there, because the selected card is the one whose pane you are most likely to want and a control that
vanished from under the card you just clicked would have to be hunted for. Both are declared after the
base rule: Avalonia has no specificity and settles two matching rules by declaration order, which this
file records three times over.
IsVisible rather than Opacity. A button at zero opacity is still a button — it takes the click, and the
card underneath does not — so half the grid would swallow presses aimed at selecting a host. The space
is held open by the Panel around it in the item template rather than by this, so the card does not
reflow as the pointer crosses it; see HostsScreen.axaml.
-->
<Style Selector="Button.rowedit">
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
<Setter Property="CornerRadius" Value="6" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="Button.rowedit /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="6" />
</Style>
<Style Selector="Button.rowedit:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource ChromeHover}" />
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
</Style>
<Style Selector="ListBoxItem:pointerover Button.rowedit">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="ListBoxItem:selected Button.rowedit">
<Setter Property="IsVisible" Value="True" />
</Style>
<!--
◆ THE DRAWER'S FURNITURE: a card, its heading, and the rows inside it.
The hosts drawer is a column of grouped fields rather than a run of labelled controls — address here,
the general facts here, the connection and its credentials here — which is what the design draws and
what a 320-pixel column needs to stay readable: three cards is three things to find, where fourteen
stacked controls is fourteen. v5 widened the column from 304 to 320 for the ADDRESS field's own
breathing room, and moved every affected number in this file and its neighbours with it.
Raised on Sidebar, which is the one pairing that works in this column. Panel and Chrome are the same
value as each other and near enough Sidebar to vanish against it. Field no longer reads as a well
beneath either: v3 gives a field and a card the same fill, where a field used to sit visibly below
one — see the remark on Field in Palette.axaml. What still marks a field as a field here is its
border and its focus state, not a darker surface underneath it.
-->
<Style Selector="Border.section">
<Setter Property="Background" Value="{StaticResource Raised}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="12" />
<Setter Property="Padding" Value="12" />
</Style>
<!--
A card's heading, and deliberately not TextBlock.label. That style is the small tracked-out capitals
this design uses for a *section of a screen* — GROUPS, HOSTS, the vault's categories — and three of
them inside a 320-pixel column would claim the same weight as the screen headings beside it. These are
captions on a card, so they are sentence case at the body size.
Still what the group editor's two cards use — GROUP, and what its hosts inherit — because that editor
is not the mock this file's ADDRESS/GENERAL/CONNECTION/QUICK ACCESS labels were drawn from and nothing
asked it to match a screen it is not on.
-->
<Style Selector="TextBlock.sectionhead">
<Setter Property="FontSize" Value="13" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<!--
◆ THE OTHER CARD HEADING, v5's own, and the two are not interchangeable.
TextBlock.sectionhead above is sentence case at the body size — a caption on a card, the same weight
the design's own prose is drawn at. This is the v5 mock's own idiom for the same job, on the one screen
it was drawn for: ADDRESS, GENERAL, CONNECTION and QUICK ACCESS in the host drawer, tracked-out capitals
at a third the size. Both exist because the group editor's cards were not restyled to the v5 mock — see
the remark above — so the drawer now carries a heading in each of the two idioms depending on which
panel is showing, and a single class could not have been both.
-->
<Style Selector="TextBlock.sectionlabel">
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="10" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="LetterSpacing" Value="1.2" />
<Setter Property="Foreground" Value="{StaticResource TextGhost}" />
</Style>
<!--
◆ A ROW IN THE DETAIL PANE, AND IT IS A BUTTON THAT LOOKS LIKE A FIELD.
The design draws every fact about a host as a filled box, which is the shape of an input — so a reader
arrives expecting to type in one. They cannot: saving here is a whole-host operation that validates the
key-or-credential exclusion and writes one encrypted payload, so a box that committed on every
keystroke would be a save per character and a half-typed hostname on the wire.
Rather than draw an input that refuses the pointer, each row *is* the door to the editor. Pressing any
of them opens the host editor, which is the same three cards with real boxes in them — so the thing
that looked editable turns out to be editable, one step further along, and nothing on screen is a
control that does nothing. The alternative was a flat read-only pane with one EDIT button, which is
what this replaced.
It borrows the TextBox's own geometry rather than sharing it, for the reason Button.chiptoggle borrows
Border.chip's: a Button is not a TextBox and Avalonia's selectors are structural. The numbers are
repeated and the two have to move together, which is why they are adjacent in this file.
-->
<Style Selector="Button.fieldrow">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Padding" Value="8,5" />
<Setter Property="FontSize" Value="13" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<Style Selector="Button.fieldrow /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Field}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<Style Selector="Button.fieldrow:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Hover}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderHover}" />
</Style>
<!--
The detail pane's own ADDRESS row, matched to TextBox.address above: taller, and bordered in accent
rather than the ordinary hairline even under the pointer — declared after the :pointerover rule for the
reason that class's own remark gives, so it wins in both states rather than only the resting one.
-->
<Style Selector="Button.fieldrow.address">
<Setter Property="MinHeight" Value="42" />
</Style>
<Style Selector="Button.fieldrow.address /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
<Setter Property="BorderThickness" Value="1.5" />
</Style>
<!--
The glyph at the head of such a row. A fixed width so that four rows in a card line their text up, and
the faintest step in the ramp because it is a mark to skip rather than a fact to read: what the row
says is to its right.
-->
<Style Selector="TextBlock.fieldglyph">
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="11" />
<Setter Property="Width" Value="16" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<!--
The two buttons in the drawer's header: the overflow menu and the arrow that puts the drawer away.
Chrome rather than controls — they are the frame around whichever panel is showing, which is why they
are flat and quiet until the pointer finds them.
-->
<Style Selector="Button.paneicon">
<Setter Property="Width" Value="26" />
<Setter Property="Height" Value="26" />
<Setter Property="CornerRadius" Value="6" />
<Setter Property="FontSize" Value="13" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
<Style Selector="Button.paneicon /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="6" />
</Style>
<Style Selector="Button.paneicon:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource ChromeHover}" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<!--
── v5b: THE SFTP PANE HEADERS' QUIET GLYPHS ─────────────────────────────────────────────────────────
Two variants of the icon-only button above, for the two places wave C's SFTP restyle draws one that is
not the drawer's own chrome: a pane's UP/REFRESH/DELETE affordances — see TransfersScreen.axaml, whose
remote pane keeps DELETE's own destructive colour rather than borrowing the plain one — and a pane's
drive picker, which is a chip rather than a square icon and so borrows Border.chip's own geometry
instead, for the reason Button.chiptoggle borrows it too.
-->
<Style Selector="Button.paneicon.danger">
<Setter Property="Foreground" Value="{StaticResource Danger}" />
</Style>
<Style Selector="Button.paneicon.danger:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource DangerWash}" />
<Setter Property="Foreground" Value="{StaticResource Danger}" />
</Style>
<Style Selector="Button.panechip">
<Setter Property="Padding" Value="8,4" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="11" />
<Setter Property="FontWeight" Value="Medium" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Button.panechip /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="6" />
</Style>
<Style Selector="Button.panechip:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<!--
The status dot, in one place rather than as a converter in code.
Two states and not the design's three. Green means connected — a terminal is open on this host, or a
connection to the server is held with nothing waiting to be sent. Grey means it is not. The design also
has an amber dot, which would have to mean "reachable but not connected", and nothing in this
application ever checks whether a host is reachable; a colour for a state nobody computes would be
decoration that reads as information.
A style rather than a value converter so that the two colours are the palette's, once. A converter
would have had to name the hex value in C#, which is how a green ends up one shade off in the fourth
place it appears — and it has already moved once, from v2's #3DDC97 to this design's #34D399, without
touching this file.
Live rather than Accent, and that is a v2 change with meaning rather than a recolour, one that survives
the accent moving again from blue to purple in v3: the green is the colour of a fact regardless of what
hue the accent currently is — see the remark on Live in Palette.axaml. Filling this with Accent would
say a connected host is a thing to press.
-->
<Style Selector="Ellipse.dot">
<Setter Property="Width" Value="6" />
<Setter Property="Height" Value="6" />
<Setter Property="Fill" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Ellipse.dot.live">
<Setter Property="Fill" Value="{StaticResource Live}" />
</Style>
<!--
Amber, and it does not contradict the rule above it. Green is what is true and this is not yet true;
purple is what you can press and a dot is not pressable. What is left is the caveat colour, which is
exactly what a connection still being made is. The same amber the connecting card's track uses, from
the same brush, so that the tab and the card the tab opens agree.
-->
<Style Selector="Ellipse.dot.connecting">
<Setter Property="Fill" Value="{StaticResource Warn}" />
</Style>
<!--
The accent strip a selected row carries, drawn by the row template rather than by the item, because the
item's presenter is the thing the theme keeps repainting.
-->
<Style Selector="Border.rowmark">
<Setter Property="Width" Value="2" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="ListBoxItem:selected Border.rowmark">
<Setter Property="Background" Value="{StaticResource Accent}" />
</Style>
<!--
Quick connect's own rows. In this file rather than QuickConnect.axaml, the same reason every other
list style is here: that view is the one caller, but what a selected row looks like is a palette
answer and belongs beside every other one.
Classes="tiles" on that ListBox clears the theme's own full-bleed selection wash first — see the
remark above "A LIST OF TILES" — for the same reason: a wash behind a 9-pixel-rounded row shows as
square corners peeking out from behind it. This is the one list in the application whose selected row
fills solid rather than tinting or carrying a strip, which is the mock's own choice and reads as
right here specifically: this palette is choosing a machine to connect to *now*, not one it is idly
browsing, and nothing else in the window asks to look this insistent.
-->
<Style Selector="Border.qcrow">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="ListBoxItem:pointerover Border.qcrow">
<Setter Property="Background" Value="{StaticResource Hover}" />
</Style>
<Style Selector="ListBoxItem:selected Border.qcrow, ListBoxItem:selected:pointerover Border.qcrow">
<Setter Property="Background" Value="{StaticResource Accent}" />
</Style>
<Style Selector="TextBlock.qcrow-text">
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<Style Selector="TextBlock.qcrow-subtext">
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="ListBoxItem:selected TextBlock.qcrow-text, ListBoxItem:selected TextBlock.qcrow-subtext">
<!--
AccentInk rather than a literal white: it is v5's name for "the colour text sits in on top of the
accent", which is what a filled row is. Naming it that way rather than #FFFFFF is what keeps this
rule true if the accent itself ever moves to a hue where white stops being the readable choice.
-->
<Setter Property="Foreground" Value="{StaticResource AccentInk}" />
</Style>
<!--
A context menu, in this window's palette rather than the theme's. The Fluent default is a lighter grey
than anything else here, which on a near-black chrome reads as a dialog from another application.
FlyoutPresenter is in the same rule rather than one of its own: the vault menu on the tab strip is a
plain Flyout, and a popup that came out of this window in a different grey from the context menu two
screens away would read as two applications rather than one.
-->
<Style Selector="MenuFlyoutPresenter, ContextMenu, FlyoutPresenter">
<Setter Property="Background" Value="{StaticResource Chrome}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style Selector="MenuItem">
<Setter Property="FontSize" Value="13" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<!--
The same strip on the vault's category rail, which is buttons rather than list items — so the class
the rail sets stands in for the :selected pseudo-class.
It has to come after Border.rowmark, and that is not tidiness. Avalonia has no CSS specificity: two
class selectors matching one element carry the same priority, and the later declaration wins. Declared
above, "Background: Transparent" from Border.rowmark overrode this and the active category had no
strip at all — which looks exactly like a rail that was never given one.
-->
<Style Selector="Button.cat Border.catmark">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Button.cat.active Border.catmark">
<Setter Property="Background" Value="{StaticResource Accent}" />
</Style>
<Style Selector="ComboBox">
<Setter Property="Background" Value="{StaticResource Field}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="MinHeight" Value="30" />
<Setter Property="FontSize" Value="13" />
</Style>
<Style Selector="NumericUpDown">
<Setter Property="Background" Value="{StaticResource Field}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="MinHeight" Value="30" />
<Setter Property="FontSize" Value="13" />
</Style>
<!--
The foreground goes on the content presenter as well as on the control, for the same reason the
buttons' brushes do: the Fluent theme sets its own there, and a Foreground set only on the CheckBox is
a TemplateBinding the theme's own value wins over — which showed up as a label in the theme's blue-grey
beside a hint in this palette's.
-->
<Style Selector="CheckBox">
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
<Setter Property="FontSize" Value="13" />
<Setter Property="MinHeight" Value="0" />
</Style>
<Style Selector="CheckBox /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<Style Selector="CheckBox:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<!--
── v5c ──────────────────────────────────────────────────────────────────────────────────────────────
Settings mode's own furniture: a page title, a tracked section label, a card group and the rows inside
it. Named apart from Border.section and TextBlock.sectionlabel above rather than reusing them — those
are the hosts drawer's 320-pixel column, at that column's own padding and heading size, and the
settings pages are a 1100-pixel one at the numbers Settings-General.dc.html and its three siblings all
state identically: 12-radius cards on Raised with a 1px Border inset, 20/24 row padding, a 1px
BorderSubtle divider between rows and none under the last one.
-->
<Style Selector="TextBlock.settingstitle">
<Setter Property="FontSize" Value="33" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="LetterSpacing" Value="-0.5" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<Style Selector="TextBlock.settingssection">
<Setter Property="FontSize" Value="10.5" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="LetterSpacing" Value="1.2" />
<Setter Property="Foreground" Value="{StaticResource TextGhost}" />
<Setter Property="Margin" Value="0,32,0,12" />
</Style>
<Style Selector="Border.settingscard">
<Setter Property="Background" Value="{StaticResource Raised}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="12" />
</Style>
<Style Selector="Border.settingsrow">
<Setter Property="BorderBrush" Value="{StaticResource BorderSubtle}" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="Padding" Value="24,20" />
</Style>
<Style Selector="Border.settingsrow.last">
<Setter Property="BorderThickness" Value="0" />
</Style>
<Style Selector="TextBlock.settingsrowtitle">
<Setter Property="FontSize" Value="14.5" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="LetterSpacing" Value="-0.2" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<Style Selector="TextBlock.settingsrowcaption">
<Setter Property="FontSize" Value="12.5" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="LineHeight" Value="19.4" />
<Setter Property="MaxWidth" Value="640" />
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
</Application.Styles>
</Application>