Match the settings pill border to the prototype
All checks were successful
review/design-claude B1/B2/N1/N4 passed Sonnet re-review at 2ea93010; B3 pill border one-frame re-check by firstmate: dash rhythm and 1dp stroke match, delta additive (6 files, pill only)

This commit is contained in:
justinzeus 2026-11-05 16:28:09 +01:00
parent 2ea9301069
commit 74033ee1da
6 changed files with 58 additions and 8 deletions

View file

@ -77,6 +77,11 @@ resolves consistently in native captures; the approved JSON weights stay unchang
Nav, picker names, labels and pill weights use that same mapping. Nav label colour Nav, picker names, labels and pill weights use that same mapping. Nav label colour
fades for 200 ms ease-out; the blob retains its critically damped spring. fades for 200 ms ease-out; the blob retains its critically damped spring.
Settings pills have a separate 1 dp stroke (2 px on the stage), not a focus ring.
The stroke is inset within the fixed 20 dp / 40 px height; horizontal border-box
padding includes it. The 6 px dash / 4 px gap follows the browser's 3:2 stroke-width
ratios. Resting borders are white at 22% opacity; focused borders use ink at 40%.
The small gear under the final person opens Settings with Requests focused. The small gear under the final person opens Settings with Requests focused.
Settings groups hand off to existing upstream pages; the Requests entry explicitly Settings groups hand off to existing upstream pages; the Requests entry explicitly
has no request data yet. **Advanced settings → Wolfskin screens** is the persistent has no request data yet. **Advanced settings → Wolfskin screens** is the persistent

View file

@ -215,6 +215,32 @@
"dp": 20.0, "dp": 20.0,
"source": "locked index.html:71-92, 256-263, 303-328; spec 6.1 and nav-shell measured bounds" "source": "locked index.html:71-92, 256-263, 303-328; spec 6.1 and nav-shell measured bounds"
}, },
"settingsPillStroke": {
"$type": "dimension",
"px": 2,
"dp": 1.0,
"source": "index.html:122; pill border 2 px inside its 40 px border-box"
},
"settingsPillDashRatio": {
"$type": "number",
"$value": 3,
"source": "st-091 frozen browser top border: 6 px dashes at 2 px stroke width"
},
"settingsPillDashGapRatio": {
"$type": "number",
"$value": 2,
"source": "st-091 frozen browser top border: 4 px gaps at 2 px stroke width"
},
"settingsPillBorder": {
"$type": "color",
"$value": "#ffffff38",
"source": "index.html:122; white at 22 percent opacity, rounded to 8-bit alpha"
},
"settingsPillFocusedBorder": {
"$type": "color",
"$value": "#0b0b1066",
"source": "index.html:328; focused pill ink at 40 percent opacity"
},
"settingsPillPadding": { "settingsPillPadding": {
"$type": "dimension", "$type": "dimension",
"px": 16, "px": 16,

View file

@ -24,8 +24,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
@ -108,10 +110,19 @@ fun SettingsRow(group: SettingsGroup, focused: Boolean, openRequests: Int?, modi
@Composable @Composable
private fun SettingsPill(label: String, focused: Boolean) { private fun SettingsPill(label: String, focused: Boolean) {
val foreground = if (focused) C.onFocus else C.dim val foreground = if (focused) C.onFocus else C.dim
Box(Modifier.height(S.settingsPillHeight).drawBehind { val border = if (focused) S.settingsPillFocusedBorder else S.settingsPillBorder
val stroke = WolfskinTokens.Focus.ringButtonRing.toPx() Box(Modifier.height(S.settingsPillHeight).drawWithCache {
drawRoundRect(foreground.copy(alpha = .4f), cornerRadius = CornerRadius(WolfskinTokens.Radius.pill.toPx()), style = Stroke(stroke, pathEffect = PathEffect.dashPathEffect(floatArrayOf(stroke * 2, stroke * 2)))) val stroke = S.settingsPillStroke.toPx()
}.padding(horizontal = S.settingsPillPadding), contentAlignment = Alignment.Center) { val inset = Offset(stroke / 2, stroke / 2)
val bounds = Size(size.width - stroke, size.height - stroke)
val outline = Stroke(stroke, pathEffect = PathEffect.dashPathEffect(
floatArrayOf(stroke * S.settingsPillDashRatio, stroke * S.settingsPillDashGapRatio),
))
val radius = CornerRadius(WolfskinTokens.Radius.pill.toPx())
onDrawBehind {
drawRoundRect(border, topLeft = inset, size = bounds, cornerRadius = radius, style = outline)
}
}.padding(horizontal = S.settingsPillPadding + S.settingsPillStroke), contentAlignment = Alignment.Center) {
Text(label, color = foreground, style = WolfskinTokens.Type.caption.copy(fontWeight = WolfskinTokens.Type.label.fontWeight)) Text(label, color = foreground, style = WolfskinTokens.Type.caption.copy(fontWeight = WolfskinTokens.Type.label.fontWeight))
} }
} }

View file

@ -266,6 +266,11 @@ object WolfskinTokens {
val settingsRowGap = 12.0.dp val settingsRowGap = 12.0.dp
val settingsIcon = 18.0.dp val settingsIcon = 18.0.dp
val settingsPillHeight = 20.0.dp val settingsPillHeight = 20.0.dp
val settingsPillStroke = 1.0.dp
const val settingsPillDashRatio = 3.0f
const val settingsPillDashGapRatio = 2.0f
val settingsPillBorder = ComposeColor(0x38FFFFFF)
val settingsPillFocusedBorder = ComposeColor(0x660B0B10)
val settingsPillPadding = 8.0.dp val settingsPillPadding = 8.0.dp
val settingsPillGap = 6.0.dp val settingsPillGap = 6.0.dp
val settingsChevron = 15.0.dp val settingsChevron = 15.0.dp

View file

@ -163,6 +163,9 @@
--shell-settingsRowGap: 24.0px; --shell-settingsRowGap: 24.0px;
--shell-settingsIcon: 36.0px; --shell-settingsIcon: 36.0px;
--shell-settingsPillHeight: 40.0px; --shell-settingsPillHeight: 40.0px;
--shell-settingsPillStroke: 2.0px;
--shell-settingsPillBorder: #ffffff38;
--shell-settingsPillFocusedBorder: #0b0b1066;
--shell-settingsPillPadding: 16.0px; --shell-settingsPillPadding: 16.0px;
--shell-settingsPillGap: 12.0px; --shell-settingsPillGap: 12.0px;
--shell-settingsChevron: 30.0px; --shell-settingsChevron: 30.0px;

View file

@ -1,5 +1,5 @@
1438f7ca4a2b6f951c9e643ab0afacbf2f7fc98e57905295d518c6225e054de4 generate_tokens.py 1438f7ca4a2b6f951c9e643ab0afacbf2f7fc98e57905295d518c6225e054de4 generate_tokens.py
6d1d19656b40e6a442cb548bb238bb12244adb6b3b717ed9e564af61119559c8 shell-tokens.json 408b7c3f4db2028deda97ac0570a57a4da4877917fd0980b2bfa5afe8f8fa790 shell-tokens.json
58131daf2bf889dcc2cf2a55f4a179fd6ad9bafa2da798b9a49a65ae3d58ae71 src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt 1037c46c9d804f9349e927257de13ba32cbd1c05e208ec0bc1d345f4b465f1eb src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt
9143548adf883d934ee86d5d4dd140a9a2d8116c6c8a905a41d2bf7d828bc371 tokens.css c133085ef60612df9753ef3681446d9ccb2eb29b1ee1955b86254714c67525e3 tokens.css
5b939f1f3eb90b428008fda75720f6e3a4e844c486668931db3d52bffb987b53 tokens.json 5b939f1f3eb90b428008fda75720f6e3a4e844c486668931db3d52bffb987b53 tokens.json