Mike0021 commited on
Commit
32a3530
·
verified ·
1 Parent(s): 1662832

Pi Web fork: Settings switches centred in the pixels (no knob shadow, whole-pixel rows)

Browse files
Files changed (1) hide show
  1. piweb/hf-design.patch +294 -275
piweb/hf-design.patch CHANGED
@@ -5888,7 +5888,7 @@ index 79c8282..97731f1 100644
5888
  lang: "en",
5889
  icons: [
5890
  diff --git a/app/settings.css b/app/settings.css
5891
- index 0bf21eb..ef0584d 100644
5892
  --- a/app/settings.css
5893
  +++ b/app/settings.css
5894
  @@ -35,14 +35,15 @@
@@ -6399,7 +6399,7 @@ index 0bf21eb..ef0584d 100644
6399
  outline-offset: 2px;
6400
  }
6401
 
6402
- @@ -1248,33 +1273,39 @@
6403
  margin-left: auto;
6404
  }
6405
 
@@ -6407,7 +6407,9 @@ index 0bf21eb..ef0584d 100644
6407
  + track's height less 2px all round, so it sits centred in both states, and it
6408
  + travels the track's width less its height. Off, a muted knob on a quiet
6409
  + track; on, the ink-filled track with the page's colour for the knob (the
6410
- + accent is neutral, so a white knob would vanish on it in dark). */
 
 
6411
  .config-switch {
6412
  + --switch-h: 18px;
6413
  + --switch-w: 32px;
@@ -6451,7 +6453,7 @@ index 0bf21eb..ef0584d 100644
6451
  .config-switch::before {
6452
  content: "";
6453
  position: absolute;
6454
- @@ -1283,7 +1314,7 @@
6455
 
6456
  .config-switch:disabled {
6457
  cursor: default;
@@ -6460,7 +6462,7 @@ index 0bf21eb..ef0584d 100644
6460
  }
6461
 
6462
  .config-switch.is-loading:disabled {
6463
- @@ -1294,42 +1325,39 @@
6464
  position: absolute;
6465
  top: 2px;
6466
  left: 2px;
@@ -6471,7 +6473,6 @@ index 0bf21eb..ef0584d 100644
6471
  border-radius: 50%;
6472
  background: var(--text-muted);
6473
  - transition: transform 0.15s, background 0.15s;
6474
- + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
6475
  + transition: transform 0.2s cubic-bezier(0.34, 1.4, 0.64, 1), background-color 0.15s ease;
6476
  }
6477
 
@@ -6518,7 +6519,7 @@ index 0bf21eb..ef0584d 100644
6518
  }
6519
 
6520
  .config-list-action-button {
6521
- @@ -1342,7 +1370,7 @@
6522
  min-height: 30px;
6523
  padding: 0 8px;
6524
  border: 0;
@@ -6527,7 +6528,7 @@ index 0bf21eb..ef0584d 100644
6527
  background: transparent;
6528
  color: var(--text-dim);
6529
  cursor: pointer;
6530
- @@ -1358,7 +1386,7 @@
6531
 
6532
  .config-list-action-button[aria-current="page"] {
6533
  background: var(--bg-selected);
@@ -6536,7 +6537,7 @@ index 0bf21eb..ef0584d 100644
6536
  }
6537
 
6538
  .config-list-action-button:disabled {
6539
- @@ -1382,6 +1410,14 @@
6540
  opacity: 0.7;
6541
  }
6542
 
@@ -6551,7 +6552,7 @@ index 0bf21eb..ef0584d 100644
6551
  .settings-dialog-backdrop {
6552
  position: fixed;
6553
  inset: 0;
6554
- @@ -1389,7 +1425,7 @@
6555
  display: flex;
6556
  align-items: center;
6557
  justify-content: center;
@@ -6560,7 +6561,7 @@ index 0bf21eb..ef0584d 100644
6560
  }
6561
 
6562
  /* Focus moves to the dialog itself when Settings opens (tabIndex -1); it is not a control, so no ring. */
6563
- @@ -1398,109 +1434,114 @@
6564
  }
6565
 
6566
  .settings-dialog-surface {
@@ -6741,7 +6742,7 @@ index 0bf21eb..ef0584d 100644
6741
  }
6742
 
6743
  .settings-section-icon {
6744
- @@ -1511,14 +1552,66 @@
6745
  transform: scale(1.25);
6746
  }
6747
 
@@ -6812,7 +6813,7 @@ index 0bf21eb..ef0584d 100644
6812
  }
6813
 
6814
  .settings-dialog-main {
6815
- @@ -1537,336 +1630,513 @@
6816
  min-height: 0;
6817
  }
6818
 
@@ -6868,11 +6869,14 @@ index 0bf21eb..ef0584d 100644
6868
  + color: var(--text-strong);
6869
  + font-size: 14px;
6870
  + font-weight: 600;
6871
- + line-height: 1.4;
6872
  }
6873
 
6874
  -.settings-general-description {
6875
  - margin: 0 0 12px;
 
 
 
6876
  +.config-group-description {
6877
  + max-width: 520px;
6878
  + margin: 4px 0 0;
@@ -6880,7 +6884,7 @@ index 0bf21eb..ef0584d 100644
6880
  - font-size: 11px;
6881
  - line-height: 1.5;
6882
  + font-size: 13px;
6883
- + line-height: 1.45;
6884
  }
6885
 
6886
  -/* A note on the options above it, not an introduction to them. */
@@ -6919,76 +6923,65 @@ index 0bf21eb..ef0584d 100644
6919
  - cursor: pointer;
6920
  - font-size: 12px;
6921
  - font-weight: 400;
6922
- -}
6923
- -
 
6924
  -.settings-theme-option:has(input:checked) {
6925
  - background: var(--bg-selected);
6926
  - color: var(--accent);
6927
  - font-weight: 600;
6928
- + padding: 12px 0;
6929
- }
6930
-
6931
- -.settings-theme-option:not(:has(input:checked)):hover {
6932
- - background: var(--bg-hover);
6933
- -}
6934
- -
6935
- -.settings-theme-option:has(input:focus-visible) {
6936
- - outline: 2px solid var(--accent);
6937
- - outline-offset: 1px;
6938
  +.config-row + .config-row {
 
6939
  + border-top: 1px solid color-mix(in srgb, var(--text) 7%, transparent);
6940
  }
6941
 
6942
- -.settings-theme-option-label {
 
6943
  +.config-row-text {
6944
  + display: flex;
6945
  + flex: 1 1 auto;
6946
  + flex-direction: column;
6947
  + gap: 4px;
6948
- min-width: 0;
6949
- - overflow-wrap: anywhere;
6950
- -}
6951
- -
6952
- -.settings-font-rows {
6953
- - display: grid;
6954
- - grid-template-columns: max-content minmax(0, 1fr) auto 28px;
6955
- - gap: 12px 8px;
6956
  }
6957
 
6958
- -.settings-font-rows > .settings-font-row {
6959
- - display: grid;
6960
- - grid-column: 1 / -1;
6961
- - grid-template-columns: max-content minmax(0, 1fr) auto 28px;
6962
- - grid-template-columns: subgrid;
6963
- - row-gap: 0;
6964
  +.config-row-label {
6965
  + color: var(--text);
6966
  + font-size: 13px;
6967
  + font-weight: 400;
6968
- + line-height: 1.4;
6969
  }
6970
 
6971
- -.settings-font-rows > .settings-font-row > input[type="range"] {
6972
- - grid-column: 1 / -1;
 
6973
  +label.config-row-label {
6974
  + cursor: pointer;
6975
  }
6976
 
6977
- -.settings-chat-range-header.settings-font-header {
6978
- - grid-column: 1 / -1;
6979
  - grid-template-columns: max-content minmax(0, 1fr) auto 28px;
6980
- - grid-template-columns: subgrid;
6981
  +.config-row-description {
6982
  + max-width: 420px;
6983
  + margin: 0;
6984
  + color: var(--text-dim);
6985
  + font-size: 13px;
6986
- + line-height: 1.45;
6987
  }
6988
 
6989
- -.settings-chat-range-header.settings-font-header label {
6990
- - padding-right: 4px;
6991
- -}
 
 
 
6992
  +.config-row-control {
6993
  + display: flex;
6994
  + flex-shrink: 0;
@@ -6996,35 +6989,20 @@ index 0bf21eb..ef0584d 100644
6996
  + justify-content: flex-end;
6997
  + gap: 8px;
6998
  + min-width: 0;
6999
- +}
7000
 
7001
- -/* Drawn in the font it names: the live preview. */
7002
- -.settings-chat-range-option .settings-font-input {
7003
- - height: 26px;
7004
- - margin: 0;
7005
- - padding: 0 8px;
7006
- - border: 1px solid var(--border);
7007
- - border-radius: 5px;
7008
- - background: var(--bg);
7009
- - color: var(--text);
7010
- - font-family: var(--font-ui);
7011
- - font-weight: var(--font-ui-weight);
7012
- - font-size: 12px;
7013
  +.config-row.is-stacked {
7014
  + flex-direction: column;
7015
  + align-items: stretch;
7016
  + gap: 10px;
7017
- }
7018
-
7019
- -.settings-chat-range-option .settings-font-input.is-mono {
7020
- - font-family: var(--font-mono);
7021
- - font-weight: var(--font-mono-weight);
7022
  +.config-row.is-stacked > .config-row-control {
7023
  + justify-content: flex-start;
7024
- }
7025
-
7026
- -.settings-font-input::placeholder {
7027
- - color: var(--text-dim);
7028
  +/* Sliders and the font fields share one column: field or slider, value, reset. */
7029
  +.settings-range-row > .config-row-control,
7030
  +.settings-font-row > .config-row-control {
@@ -7033,125 +7011,106 @@ index 0bf21eb..ef0584d 100644
7033
  + align-items: center;
7034
  + gap: 6px 4px;
7035
  + width: 300px;
7036
- }
7037
-
7038
- -.settings-font-input:focus-visible {
7039
- - outline: 2px solid var(--accent);
7040
- - outline-offset: 1px;
7041
  +.settings-range-row > .config-row-control > input[type="range"] {
7042
  + grid-column: 1;
7043
- }
7044
-
7045
- -@media (max-width: 640px) {
7046
- - /* Phones get 16px form text (no focus zoom), so the name field needs the height. */
7047
- - .settings-chat-range-option .settings-font-input {
7048
- - height: 32px;
7049
- - }
7050
  +.settings-font-row > .config-row-control > .settings-font-input {
7051
  + grid-column: 1 / 3;
7052
- }
7053
-
7054
- -.settings-chat-options {
7055
- - display: flex;
7056
- - flex-direction: column;
7057
- - gap: 12px;
7058
- - width: 100%;
7059
- - max-width: 420px;
7060
  +.settings-font-row > .config-row-control > input[type="range"] {
7061
  + grid-column: 1;
7062
- }
7063
-
7064
- -.settings-chat-option {
7065
- - width: 100%;
7066
- - color: var(--text);
7067
- +.settings-font-row {
7068
- + align-items: flex-start;
7069
  +}
7070
  +
 
 
 
 
 
 
 
 
7071
  +.settings-font-row > .config-row-text {
7072
  + padding-top: 7px;
7073
- +}
7074
- +
 
 
7075
  +.settings-range-value {
7076
  + color: var(--text-muted);
7077
  + font-family: var(--font-ui);
7078
- font-size: 12px;
7079
  + font-variant-numeric: tabular-nums;
7080
  + text-align: right;
7081
- }
7082
-
7083
- -.settings-chat-switch-option {
7084
- - display: flex;
7085
- - align-items: center;
7086
- - justify-content: space-between;
7087
- - gap: 16px;
7088
- - min-height: 28px;
7089
  +.config-row-control input[type="range"] {
7090
  + display: block;
7091
  + width: 100%;
7092
  + min-width: 0;
7093
  + margin: 0;
7094
  + accent-color: var(--text-strong);
7095
- }
7096
-
7097
- -.settings-chat-range-option {
7098
  +.settings-chat-reset {
7099
  + width: 28px;
7100
  + height: 28px;
7101
- padding: 0;
7102
  }
7103
 
7104
- -.settings-chat-range-header {
7105
- - display: grid;
7106
- - grid-template-columns: minmax(0, 1fr) auto 28px;
7107
- - align-items: center;
7108
- - gap: 8px;
7109
- +/* Drawn in the font it names: the live preview. */
7110
  +.settings-font-input {
7111
  + width: 100%;
7112
  + min-width: 0;
7113
  + height: var(--size-control);
7114
- + margin: 0;
 
 
 
 
7115
  + padding: 0 10px;
7116
  + border: 0;
7117
  + border-radius: var(--radius-md);
7118
  + background: var(--bg-surface);
7119
- + color: var(--text);
7120
- + font-family: var(--font-ui);
7121
- + font-weight: var(--font-ui-weight);
 
7122
  + font-size: 13px;
7123
  }
7124
 
7125
- -.settings-chat-range-header output {
7126
- - color: var(--text-muted);
7127
  +.settings-font-input.is-mono {
7128
  font-family: var(--font-mono);
7129
- - font-size: 11px;
7130
- + font-weight: var(--font-mono-weight);
 
7131
  }
7132
 
7133
- -.settings-send-mode-options {
7134
- - display: flex;
7135
- +.settings-font-input::placeholder {
7136
- + color: var(--text-dim);
7137
- +}
7138
- +
7139
- +.settings-font-input:focus-visible {
7140
  + outline: 1px solid var(--focus-line);
7141
  + outline-offset: -1px;
7142
  + box-shadow: var(--focus-glow);
7143
- +}
7144
- +
 
 
 
 
 
7145
  +/* ---------------------------------------------------------------- a segmented choice
7146
  + Claude's: 32px, options on a 5% track 1px in, the chosen one on the dialog's
7147
  + own surface with a 1px 10% ring inside (a fill a step up in dark). */
7148
  +.config-segmented {
7149
  + display: inline-flex;
7150
- flex-shrink: 0;
7151
- - gap: 3px;
7152
- - padding: 3px;
7153
- - border: 1px solid var(--border);
7154
- - border-radius: 6px;
7155
  + gap: 0;
7156
  + max-width: 100%;
7157
  + padding: 1px;
@@ -7159,43 +7118,46 @@ index 0bf21eb..ef0584d 100644
7159
  + background: color-mix(in srgb, var(--text) 5%, transparent);
7160
  }
7161
 
7162
- -.settings-send-mode-option {
7163
- +.config-segmented-option {
7164
- position: relative;
7165
  - display: flex;
 
 
 
 
 
 
7166
  + display: inline-flex;
7167
- flex: 1 0 auto;
7168
- align-items: center;
7169
- justify-content: center;
7170
- - min-height: 26px;
7171
- - padding: 0 10px;
7172
  + min-width: 0;
7173
  + height: 30px;
7174
  + padding: 0 12px;
7175
- border: 0;
7176
- - border-radius: 4px;
7177
  + border-radius: calc(var(--radius-md) - 1px);
7178
- background: transparent;
7179
- color: var(--text-muted);
7180
- cursor: pointer;
7181
- - font-size: 12px;
7182
  + font: inherit;
7183
  + font-size: 13px;
7184
- font-weight: 400;
7185
  + white-space: nowrap;
7186
  + transition: background-color 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;
7187
  }
7188
 
7189
- -.settings-send-mode-option:has(input:checked) {
7190
- - background: var(--bg-selected);
7191
- - color: var(--accent);
7192
- - font-weight: 600;
7193
  +.config-segmented-option:hover {
7194
- + color: var(--text);
 
7195
  }
7196
 
7197
- -.settings-send-mode-option:not(:has(input:checked)):hover {
7198
- - background: var(--bg-hover);
 
 
 
 
7199
  +.config-segmented-option:has(input:checked),
7200
  +.config-segmented-option[aria-checked="true"] {
7201
  + background: var(--bg-elevated);
@@ -7204,33 +7166,40 @@ index 0bf21eb..ef0584d 100644
7204
  + font-weight: 500;
7205
  }
7206
 
7207
- -.settings-send-mode-option:has(input:focus-visible) {
7208
- - outline: 2px solid var(--accent);
7209
- - outline-offset: 1px;
7210
  +html.dark .config-segmented-option:has(input:checked),
7211
  +html.dark .config-segmented-option[aria-checked="true"] {
7212
  + background: color-mix(in srgb, var(--text) 12%, transparent);
7213
  }
7214
 
7215
- -.settings-send-mode-label {
7216
- - white-space: nowrap;
 
 
 
7217
  +.config-segmented-option:has(input:focus-visible) {
7218
  + outline: 1px solid var(--focus-line);
7219
  + outline-offset: -1px;
7220
  + box-shadow: var(--focus-glow);
7221
  }
7222
 
7223
- -.settings-chat-reset {
7224
- - width: 28px;
7225
- - height: 28px;
7226
- - padding: 0;
7227
  +.config-segmented-label {
7228
  + overflow: hidden;
7229
  + text-overflow: ellipsis;
7230
  }
7231
 
7232
- -.settings-chat-range-option input {
7233
- - display: block;
 
 
 
 
 
7234
  +/* ---------------------------------------------------------------- theme cards
7235
  + Each card draws a small window in its theme's own tokens ([data-theme] scopes
7236
  + them), so the palette shows instead of being named; System halves Light and
@@ -7239,143 +7208,149 @@ index 0bf21eb..ef0584d 100644
7239
  + display: grid;
7240
  + grid-template-columns: repeat(6, minmax(0, 1fr));
7241
  + gap: 12px;
7242
- width: 100%;
7243
- - margin-top: 2px;
7244
- - accent-color: var(--accent);
7245
  }
7246
 
7247
- -.settings-language-options {
7248
  +.settings-theme-option {
7249
- + position: relative;
7250
  display: flex;
7251
- flex-direction: column;
7252
- - gap: 3px;
7253
- - width: 100%;
7254
- - max-width: 420px;
7255
- -}
7256
- -
7257
- -.settings-language-option {
7258
- - display: flex;
7259
  - align-items: center;
7260
- - gap: 10px;
7261
- - height: 44px;
7262
  - padding: 0 10px;
7263
  - border: 0;
7264
- - border-radius: 5px;
7265
  - background: transparent;
7266
- - color: var(--text);
7267
  + align-items: stretch;
7268
  + gap: 7px;
7269
  + min-width: 0;
7270
- + color: var(--text-muted);
7271
  cursor: pointer;
7272
  font-size: 12px;
7273
- - text-align: left;
7274
  }
7275
 
7276
- -.settings-language-option[aria-checked="true"] {
7277
  - background: var(--bg-selected);
 
 
7278
  +.settings-theme-preview {
7279
  + display: flex;
7280
- + aspect-ratio: 8 / 5;
7281
  + overflow: hidden;
7282
  + border-radius: var(--radius-md);
7283
  + box-shadow: 0 0 0 1px var(--line);
7284
  + transition: box-shadow 0.12s ease;
7285
  }
7286
 
7287
- -.settings-language-option:not([aria-checked="true"]):hover,
7288
- -.settings-language-option:focus-visible {
7289
  - background: var(--bg-hover);
7290
- - outline: none;
7291
  +.settings-theme-option:hover .settings-theme-preview {
7292
  + box-shadow: 0 0 0 1px color-mix(in srgb, var(--text) 30%, transparent);
7293
  }
7294
 
7295
- -.settings-language-radio {
7296
- - display: grid;
7297
- - flex-shrink: 0;
7298
- - place-items: center;
7299
- - width: 16px;
7300
- - height: 16px;
7301
- - border: 1px solid var(--border);
7302
- - border-radius: 50%;
7303
  +.settings-theme-option:has(input:checked) {
7304
  + color: var(--text-strong);
7305
  + font-weight: 500;
7306
  }
7307
 
7308
- -.settings-language-option[aria-checked="true"] .settings-language-radio {
7309
- - border-color: var(--accent);
7310
  +.settings-theme-option:has(input:checked) .settings-theme-preview {
7311
  + box-shadow: 0 0 0 1.5px var(--text-strong);
7312
  }
7313
 
7314
- -.settings-language-radio-dot {
7315
- - width: 8px;
7316
- - height: 8px;
7317
- - border-radius: 50%;
7318
- - background: var(--accent);
7319
  +.settings-theme-option-label {
7320
  + min-width: 0;
 
7321
  + overflow-wrap: anywhere;
7322
  + text-align: center;
7323
  }
7324
 
7325
- -.settings-language-label {
 
 
 
 
7326
  +.settings-theme-preview-half {
7327
  + display: flex;
7328
- flex: 1;
7329
  + min-width: 0;
7330
  + background: var(--bg);
7331
  }
7332
 
7333
- -.settings-language-code {
7334
- - color: var(--text-dim);
7335
- - font-family: var(--font-mono);
7336
- - font-size: 10px;
7337
  +.settings-theme-preview-sidebar {
7338
- + display: flex;
7339
  + flex: 0 0 30%;
7340
- + flex-direction: column;
7341
- + gap: 3px;
 
 
7342
  + padding: 7px 5px;
7343
  + background: var(--bg-panel);
7344
- +}
7345
- +
 
 
 
 
 
 
 
 
 
 
 
 
 
7346
  +.settings-theme-preview-half + .settings-theme-preview-half .settings-theme-preview-sidebar {
7347
  + display: none;
7348
- +}
7349
- +
 
 
7350
  +.settings-theme-preview i {
7351
  + display: block;
7352
  + height: 3px;
7353
  + border-radius: 2px;
7354
  + background: color-mix(in srgb, var(--text) 22%, transparent);
7355
- +}
7356
- +
 
 
 
 
7357
  +.settings-theme-preview-sidebar i:first-child {
7358
  + background: color-mix(in srgb, var(--text) 40%, transparent);
7359
  }
7360
 
7361
- -.settings-shell-option {
 
 
 
 
 
 
 
7362
  +.settings-theme-preview-sidebar i:nth-child(2) {
7363
  + width: 80%;
7364
- +}
7365
- +
 
 
7366
  +.settings-theme-preview-chat {
7367
- display: flex;
7368
- - align-items: center;
7369
- - justify-content: space-between;
7370
- - gap: 16px;
7371
- - width: 100%;
7372
- - max-width: 420px;
7373
- - min-height: 44px;
7374
- - padding: 0 10px;
7375
- - border-radius: 5px;
7376
- - background: var(--bg-panel);
7377
- - color: var(--text);
7378
- - font-size: 12px;
7379
  + flex: 1;
7380
  + flex-direction: column;
7381
  + gap: 3px;
@@ -7383,10 +7358,11 @@ index 0bf21eb..ef0584d 100644
7383
  + padding: 7px 7px 6px;
7384
  }
7385
 
7386
- -.settings-general-error {
7387
- - margin: 8px 0 0;
7388
- - color: #ef4444;
7389
- - font-size: 11px;
 
7390
  +.settings-theme-preview-chat i.is-bubble {
7391
  + align-self: flex-end;
7392
  + width: 55%;
@@ -7394,20 +7370,40 @@ index 0bf21eb..ef0584d 100644
7394
  + margin-bottom: 2px;
7395
  + border-radius: 3px;
7396
  + background: var(--user-bg);
7397
- +}
7398
- +
 
 
7399
  +.settings-theme-preview-chat i.is-short {
7400
  + width: 60%;
7401
- +}
7402
- +
 
 
 
 
7403
  +.settings-theme-preview-chat i.is-composer {
7404
  + height: 9px;
7405
  + margin-top: auto;
7406
  + border-radius: 3px;
7407
  + background: var(--bg-surface);
7408
  + box-shadow: inset 0 0 0 0.5px color-mix(in srgb, var(--text) 14%, transparent);
7409
- +}
7410
- +
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7411
  +/* ---------------------------------------------------------------- phones
7412
  + The column and the page take turns, each across the dialog: the sections as
7413
  + a list with chevrons, then the page with a back button before its title.
@@ -7421,7 +7417,11 @@ index 0bf21eb..ef0584d 100644
7421
  + height: calc(100dvh - 12px);
7422
  + max-height: none;
7423
  + }
7424
- +
 
 
 
 
7425
  + .settings-nav-column {
7426
  + flex: 1 1 auto;
7427
  + border-right: 0;
@@ -7499,6 +7499,10 @@ index 0bf21eb..ef0584d 100644
7499
  + padding: 12px 0;
7500
  + }
7501
  +
 
 
 
 
7502
  + .config-row-control {
7503
  + justify-content: flex-start;
7504
  + }
@@ -7541,7 +7545,7 @@ index 0bf21eb..ef0584d 100644
7541
  }
7542
 
7543
  @media (max-width: 640px) {
7544
- @@ -1887,13 +2157,13 @@
7545
  width: 100%;
7546
  height: 190px;
7547
  border-right: 0;
@@ -7557,7 +7561,7 @@ index 0bf21eb..ef0584d 100644
7557
  }
7558
 
7559
  .agents-feature-actions {
7560
- @@ -1905,29 +2175,6 @@
7561
  padding: 14px;
7562
  }
7563
 
@@ -7587,7 +7591,7 @@ index 0bf21eb..ef0584d 100644
7587
  }
7588
 
7589
  @keyframes saved-pop {
7590
- @@ -2066,7 +2313,7 @@
7591
  max-width: 100%;
7592
  padding: 0 5px;
7593
  border: 1px solid var(--border);
@@ -7596,7 +7600,7 @@ index 0bf21eb..ef0584d 100644
7596
  background: var(--bg-panel);
7597
  color: var(--text);
7598
  font-family: var(--font-mono);
7599
- @@ -2085,7 +2332,7 @@
7600
  padding: 6px 8px;
7601
  overflow: auto;
7602
  border: 1px solid var(--border);
@@ -7605,7 +7609,7 @@ index 0bf21eb..ef0584d 100644
7605
  background: var(--bg-panel);
7606
  color: var(--text-muted);
7607
  font-family: var(--font-mono);
7608
- @@ -2172,9 +2419,8 @@
7609
  min-width: 0;
7610
  height: 32px;
7611
  padding: 0 10px;
@@ -7617,7 +7621,7 @@ index 0bf21eb..ef0584d 100644
7617
  color: var(--text);
7618
  font-family: var(--font-mono);
7619
  font-size: 12px;
7620
- @@ -2266,10 +2512,9 @@ select.mcp-add-input {
7621
  max-width: 100%;
7622
  max-height: 100%;
7623
  overflow: hidden;
@@ -7631,7 +7635,7 @@ index 0bf21eb..ef0584d 100644
7631
  }
7632
 
7633
  /* Focus moves to the dialog itself when it opens (tabIndex -1); it is not a control, so no ring. */
7634
- @@ -2282,7 +2527,7 @@ select.mcp-add-input {
7635
  flex: 1 1 auto;
7636
  gap: 12px;
7637
  min-height: 0;
@@ -7640,7 +7644,7 @@ index 0bf21eb..ef0584d 100644
7640
  overflow: auto;
7641
  overscroll-behavior: contain;
7642
  }
7643
- @@ -2315,7 +2560,7 @@ select.mcp-add-input {
7644
  margin-top: 10px;
7645
  padding: 8px 10px;
7646
  border: 1px solid var(--border);
@@ -7649,7 +7653,7 @@ index 0bf21eb..ef0584d 100644
7649
  background: var(--bg);
7650
  color: var(--text);
7651
  font-family: var(--font-mono);
7652
- @@ -2333,7 +2578,7 @@ select.mcp-add-input {
7653
  .project-trust-error {
7654
  flex-shrink: 0;
7655
  max-height: 30vh;
@@ -7658,7 +7662,7 @@ index 0bf21eb..ef0584d 100644
7658
  overflow: auto;
7659
  color: #ef4444;
7660
  font-size: 12px;
7661
- @@ -2353,19 +2598,20 @@ select.mcp-add-input {
7662
  flex-shrink: 0;
7663
  justify-content: flex-end;
7664
  gap: 8px;
@@ -7686,7 +7690,7 @@ index 0bf21eb..ef0584d 100644
7686
  }
7687
 
7688
  .project-trust-button:disabled {
7689
- @@ -2373,10 +2619,8 @@ select.mcp-add-input {
7690
  }
7691
 
7692
  .project-trust-button.is-primary {
@@ -7699,7 +7703,7 @@ index 0bf21eb..ef0584d 100644
7699
  }
7700
 
7701
  .project-trust-button.is-primary:disabled {
7702
- @@ -2450,7 +2694,7 @@ select.mcp-add-input {
7703
  min-width: 0;
7704
  padding: 8px 10px;
7705
  border: 1px solid var(--border);
@@ -7708,7 +7712,7 @@ index 0bf21eb..ef0584d 100644
7708
  background: var(--bg);
7709
  }
7710
 
7711
- @@ -2472,7 +2716,7 @@ select.mcp-add-input {
7712
  .project-trust-mcp-tag {
7713
  flex-shrink: 0;
7714
  padding: 1px 5px;
@@ -7717,7 +7721,7 @@ index 0bf21eb..ef0584d 100644
7717
  background: rgba(120, 120, 120, 0.12);
7718
  color: var(--text-dim);
7719
  font-size: 10px;
7720
- @@ -2493,7 +2737,7 @@ select.mcp-add-input {
7721
  display: inline-block;
7722
  margin: 2px 4px 0 0;
7723
  padding: 0 4px;
@@ -24779,10 +24783,10 @@ index 9f12e18..3f4126f 100644
24779
  </div>
24780
  );
24781
  diff --git a/components/SettingsUi.test.mjs b/components/SettingsUi.test.mjs
24782
- index 30e33f5..e31f4a5 100644
24783
  --- a/components/SettingsUi.test.mjs
24784
  +++ b/components/SettingsUi.test.mjs
24785
- @@ -29,3 +29,27 @@ test("the MCP panel's reasons are visible text, never only a tooltip", () => {
24786
  // The panel's own title is the only one: no reason hides in a tooltip.
24787
  assert.deepEqual(mcp.match(/\btitle=\{[^}]*\}/g), ['title={t("settings.mcp")}']);
24788
  });
@@ -24810,6 +24814,21 @@ index 30e33f5..e31f4a5 100644
24810
  + }
24811
  + assert.deepEqual([px(track, "--switch-h"), px(track, "--switch-w")], [18, 32]);
24812
  +});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24813
  diff --git a/components/SettingsUi.tsx b/components/SettingsUi.tsx
24814
  index 1f1ae33..27ed896 100644
24815
  --- a/components/SettingsUi.tsx
@@ -30075,7 +30094,7 @@ index 42e1816..3dbf55f 100644
30075
  +- The viewers render in shadow roots. Their colours and type come from CSS variables in `app/globals.css` (`.code-diff, .code-file`); the background is set on the host (`diffs-container`) because the library's theme sets it there (`:host`), which beats an inherited value. A text selection inside a viewer reaches the document through `Selection.getComposedRanges` (the file viewer's "mention selected lines"); an extension dialog measures the viewer's scrolling `[data-code]` to widen for code. More than 400 lines with a scroll box (the file viewer's panel, a tool's 400px box) render only the rows in view (`useScrollRootVirtualizer`, set up in a passive effect: the panel mounts in the same commit as the code).
30076
  +- Library quirks handled here: a token's bold/italic is applied in the shadow root (the library sets them through `light-dark()`, which only takes colours); the expand controls get a tab stop, a name and Enter/Space; its "modified" accent is overridden to gray (it is blue); its lookbehind literal `/(?<=\n)/` is rewritten at build time (`lib/pierre-diffs-split-loader.cjs`, as for #753), and Safari before 16.4 highlights with Shiki's Oniguruma (WASM) engine since the JavaScript engine compiles grammars to lookbehind RegExps.
30077
  diff --git a/docs/agents/settings-ui.md b/docs/agents/settings-ui.md
30078
- index b23f1c7..b2f46ee 100644
30079
  --- a/docs/agents/settings-ui.md
30080
  +++ b/docs/agents/settings-ui.md
30081
  @@ -1,10 +1,19 @@
@@ -30086,7 +30105,7 @@ index b23f1c7..b2f46ee 100644
30086
  +- **Phones** (≤640px): the column and the page take turns across the dialog (`data-view` on the surface, `nav` or `page`). Settings opens on the page of its section, with a back button before the title; the column lists the sections at 44px with chevrons and carries the close button. Whichever side shows takes focus (the back button, or the open section's row), since the button that was pressed is hidden.
30087
  +- **Embedded sections** have no fill of their own: the list (`.config-sidebar`) sits on the page's surface with a hairline (`--line`) to its detail, and its rows' text under the page title (the list's left padding is the side less a row's 8px); a section's own top line (the Models banner, Sub-agents' switch) is a line under the page title with a hairline under it; a `ConfigNotice` is a rounded callout on a 5% tint; the footer has a hairline above. Inner fills (`.enabled-models-list`, examples) are `--bg-surface`, as fields are, never `--bg-panel`, which reads as a hole in the dialog.
30088
  +- **Rows** (`ConfigGroup`, `ConfigRow`, `ConfigSegmented` in `SettingsUi.tsx`): a group is its title (13px, 600), an optional line about it, then rows divided by hairlines; a row is the label (13px) and its description (12px, `--text-dim`, at most 400px) on the left and the control on the right, centred on the row (52px at least). `htmlFor` makes the label the control's, so a click on "Expand thinking blocks…" switches it (`ConfigSwitch` takes an `id`). Sliders and the font fields share one 300px control column (field or slider, value, reset). On a phone rows stack, the control under its label at the row's width, except a lone switch, which stays at the right of its label.
30089
- +- **Switches** (`ConfigSwitch`) are Claude's design system's Switch: the knob is the track's height less 4px, 2px from every edge in both states, travelling the width less the height (18 × 32, small 14 × 24). Off, a `--text-muted` knob on a 16% tint of the text; on, the ink (`--accent`) with an `--accent-contrast` knob (the accent is neutral, so a white knob would vanish on it in dark). `components/SettingsUi.test.mjs` checks the geometry and `design/tests/piweb.py` measures it on the page in both states.
30090
  +- **A segmented choice** (`ConfigSegmented`; the send key, the file browser, the language): native radios hidden in their options on one quiet track (6% tint), the chosen option lifted on `--bg-elevated` with a soft shadow in light, a 14% tint in dark. The language keeps its `role="radio"` buttons on the same track (`.settings-language-options`).
30091
  +
30092
  ## General appearance
 
5888
  lang: "en",
5889
  icons: [
5890
  diff --git a/app/settings.css b/app/settings.css
5891
+ index 0bf21eb..f8d9a86 100644
5892
  --- a/app/settings.css
5893
  +++ b/app/settings.css
5894
  @@ -35,14 +35,15 @@
 
6399
  outline-offset: 2px;
6400
  }
6401
 
6402
+ @@ -1248,33 +1273,41 @@
6403
  margin-left: auto;
6404
  }
6405
 
 
6407
  + track's height less 2px all round, so it sits centred in both states, and it
6408
  + travels the track's width less its height. Off, a muted knob on a quiet
6409
  + track; on, the ink-filled track with the page's colour for the knob (the
6410
+ + accent is neutral, so a white knob would vanish on it in dark). The knob
6411
+ + casts no shadow: a shadow 1px below merged with the dark knob in light and
6412
+ + made it read low, though its box was centred. */
6413
  .config-switch {
6414
  + --switch-h: 18px;
6415
  + --switch-w: 32px;
 
6453
  .config-switch::before {
6454
  content: "";
6455
  position: absolute;
6456
+ @@ -1283,7 +1316,7 @@
6457
 
6458
  .config-switch:disabled {
6459
  cursor: default;
 
6462
  }
6463
 
6464
  .config-switch.is-loading:disabled {
6465
+ @@ -1294,42 +1327,38 @@
6466
  position: absolute;
6467
  top: 2px;
6468
  left: 2px;
 
6473
  border-radius: 50%;
6474
  background: var(--text-muted);
6475
  - transition: transform 0.15s, background 0.15s;
 
6476
  + transition: transform 0.2s cubic-bezier(0.34, 1.4, 0.64, 1), background-color 0.15s ease;
6477
  }
6478
 
 
6519
  }
6520
 
6521
  .config-list-action-button {
6522
+ @@ -1342,7 +1371,7 @@
6523
  min-height: 30px;
6524
  padding: 0 8px;
6525
  border: 0;
 
6528
  background: transparent;
6529
  color: var(--text-dim);
6530
  cursor: pointer;
6531
+ @@ -1358,7 +1387,7 @@
6532
 
6533
  .config-list-action-button[aria-current="page"] {
6534
  background: var(--bg-selected);
 
6537
  }
6538
 
6539
  .config-list-action-button:disabled {
6540
+ @@ -1382,6 +1411,14 @@
6541
  opacity: 0.7;
6542
  }
6543
 
 
6552
  .settings-dialog-backdrop {
6553
  position: fixed;
6554
  inset: 0;
6555
+ @@ -1389,7 +1426,7 @@
6556
  display: flex;
6557
  align-items: center;
6558
  justify-content: center;
 
6561
  }
6562
 
6563
  /* Focus moves to the dialog itself when Settings opens (tabIndex -1); it is not a control, so no ring. */
6564
+ @@ -1398,109 +1435,114 @@
6565
  }
6566
 
6567
  .settings-dialog-surface {
 
6742
  }
6743
 
6744
  .settings-section-icon {
6745
+ @@ -1511,14 +1553,66 @@
6746
  transform: scale(1.25);
6747
  }
6748
 
 
6813
  }
6814
 
6815
  .settings-dialog-main {
6816
+ @@ -1537,136 +1631,197 @@
6817
  min-height: 0;
6818
  }
6819
 
 
6869
  + color: var(--text-strong);
6870
  + font-size: 14px;
6871
  + font-weight: 600;
6872
+ + line-height: 20px;
6873
  }
6874
 
6875
  -.settings-general-description {
6876
  - margin: 0 0 12px;
6877
+ +/* Line heights here are whole pixels (not 1.4 × 13px = 18.2px), and so are the
6878
+ + theme cards: the rows below them, and their switches, then sit on whole
6879
+ + pixels, so the knob's and the track's edges render alike. */
6880
  +.config-group-description {
6881
  + max-width: 520px;
6882
  + margin: 4px 0 0;
 
6884
  - font-size: 11px;
6885
  - line-height: 1.5;
6886
  + font-size: 13px;
6887
+ + line-height: 18px;
6888
  }
6889
 
6890
  -/* A note on the options above it, not an introduction to them. */
 
6923
  - cursor: pointer;
6924
  - font-size: 12px;
6925
  - font-weight: 400;
6926
+ + padding: 12px 0;
6927
+ }
6928
+
6929
  -.settings-theme-option:has(input:checked) {
6930
  - background: var(--bg-selected);
6931
  - color: var(--accent);
6932
  - font-weight: 600;
6933
+ +/* The divider takes its pixel from the padding, so the content box stays even
6934
+ + and what is centred in it lands on a whole pixel. */
 
 
 
 
 
 
 
 
6935
  +.config-row + .config-row {
6936
+ + padding-top: 11px;
6937
  + border-top: 1px solid color-mix(in srgb, var(--text) 7%, transparent);
6938
  }
6939
 
6940
+ -.settings-theme-option:not(:has(input:checked)):hover {
6941
+ - background: var(--bg-hover);
6942
  +.config-row-text {
6943
  + display: flex;
6944
  + flex: 1 1 auto;
6945
  + flex-direction: column;
6946
  + gap: 4px;
6947
+ + min-width: 0;
 
 
 
 
 
 
 
6948
  }
6949
 
6950
+ -.settings-theme-option:has(input:focus-visible) {
6951
+ - outline: 2px solid var(--accent);
6952
+ - outline-offset: 1px;
 
 
 
6953
  +.config-row-label {
6954
  + color: var(--text);
6955
  + font-size: 13px;
6956
  + font-weight: 400;
6957
+ + line-height: 18px;
6958
  }
6959
 
6960
+ -.settings-theme-option-label {
6961
+ - min-width: 0;
6962
+ - overflow-wrap: anywhere;
6963
  +label.config-row-label {
6964
  + cursor: pointer;
6965
  }
6966
 
6967
+ -.settings-font-rows {
6968
+ - display: grid;
6969
  - grid-template-columns: max-content minmax(0, 1fr) auto 28px;
6970
+ - gap: 12px 8px;
6971
  +.config-row-description {
6972
  + max-width: 420px;
6973
  + margin: 0;
6974
  + color: var(--text-dim);
6975
  + font-size: 13px;
6976
+ + line-height: 18px;
6977
  }
6978
 
6979
+ -.settings-font-rows > .settings-font-row {
6980
+ - display: grid;
6981
+ - grid-column: 1 / -1;
6982
+ - grid-template-columns: max-content minmax(0, 1fr) auto 28px;
6983
+ - grid-template-columns: subgrid;
6984
+ - row-gap: 0;
6985
  +.config-row-control {
6986
  + display: flex;
6987
  + flex-shrink: 0;
 
6989
  + justify-content: flex-end;
6990
  + gap: 8px;
6991
  + min-width: 0;
6992
+ }
6993
 
6994
+ -.settings-font-rows > .settings-font-row > input[type="range"] {
6995
+ - grid-column: 1 / -1;
 
 
 
 
 
 
 
 
 
 
6996
  +.config-row.is-stacked {
6997
  + flex-direction: column;
6998
  + align-items: stretch;
6999
  + gap: 10px;
7000
+ +}
7001
+ +
 
 
 
7002
  +.config-row.is-stacked > .config-row-control {
7003
  + justify-content: flex-start;
7004
+ +}
7005
+ +
 
 
7006
  +/* Sliders and the font fields share one column: field or slider, value, reset. */
7007
  +.settings-range-row > .config-row-control,
7008
  +.settings-font-row > .config-row-control {
 
7011
  + align-items: center;
7012
  + gap: 6px 4px;
7013
  + width: 300px;
7014
+ +}
7015
+ +
 
 
 
7016
  +.settings-range-row > .config-row-control > input[type="range"] {
7017
  + grid-column: 1;
7018
+ +}
7019
+ +
 
 
 
 
 
7020
  +.settings-font-row > .config-row-control > .settings-font-input {
7021
  + grid-column: 1 / 3;
7022
+ +}
7023
+ +
 
 
 
 
 
 
7024
  +.settings-font-row > .config-row-control > input[type="range"] {
7025
  + grid-column: 1;
 
 
 
 
 
 
 
7026
  +}
7027
  +
7028
+ +.settings-font-row {
7029
+ + align-items: flex-start;
7030
+ }
7031
+
7032
+ -.settings-chat-range-header.settings-font-header {
7033
+ - grid-column: 1 / -1;
7034
+ - grid-template-columns: max-content minmax(0, 1fr) auto 28px;
7035
+ - grid-template-columns: subgrid;
7036
  +.settings-font-row > .config-row-text {
7037
  + padding-top: 7px;
7038
+ }
7039
+
7040
+ -.settings-chat-range-header.settings-font-header label {
7041
+ - padding-right: 4px;
7042
  +.settings-range-value {
7043
  + color: var(--text-muted);
7044
  + font-family: var(--font-ui);
7045
+ + font-size: 12px;
7046
  + font-variant-numeric: tabular-nums;
7047
  + text-align: right;
7048
+ +}
7049
+ +
 
 
 
 
 
 
7050
  +.config-row-control input[type="range"] {
7051
  + display: block;
7052
  + width: 100%;
7053
  + min-width: 0;
7054
  + margin: 0;
7055
  + accent-color: var(--text-strong);
7056
+ +}
7057
+ +
 
7058
  +.settings-chat-reset {
7059
  + width: 28px;
7060
  + height: 28px;
7061
+ + padding: 0;
7062
  }
7063
 
7064
+ /* Drawn in the font it names: the live preview. */
7065
+ -.settings-chat-range-option .settings-font-input {
7066
+ - height: 26px;
 
 
 
7067
  +.settings-font-input {
7068
  + width: 100%;
7069
  + min-width: 0;
7070
  + height: var(--size-control);
7071
+ margin: 0;
7072
+ - padding: 0 8px;
7073
+ - border: 1px solid var(--border);
7074
+ - border-radius: 5px;
7075
+ - background: var(--bg);
7076
  + padding: 0 10px;
7077
  + border: 0;
7078
  + border-radius: var(--radius-md);
7079
  + background: var(--bg-surface);
7080
+ color: var(--text);
7081
+ font-family: var(--font-ui);
7082
+ font-weight: var(--font-ui-weight);
7083
+ - font-size: 12px;
7084
  + font-size: 13px;
7085
  }
7086
 
7087
+ -.settings-chat-range-option .settings-font-input.is-mono {
 
7088
  +.settings-font-input.is-mono {
7089
  font-family: var(--font-mono);
7090
+ font-weight: var(--font-mono-weight);
7091
+ }
7092
+ @@ -1676,197 +1831,324 @@
7093
  }
7094
 
7095
+ .settings-font-input:focus-visible {
7096
+ - outline: 2px solid var(--accent);
7097
+ - outline-offset: 1px;
 
 
 
 
7098
  + outline: 1px solid var(--focus-line);
7099
  + outline-offset: -1px;
7100
  + box-shadow: var(--focus-glow);
7101
+ }
7102
+
7103
+ -@media (max-width: 640px) {
7104
+ - /* Phones get 16px form text (no focus zoom), so the name field needs the height. */
7105
+ - .settings-chat-range-option .settings-font-input {
7106
+ - height: 32px;
7107
+ - }
7108
  +/* ---------------------------------------------------------------- a segmented choice
7109
  + Claude's: 32px, options on a 5% track 1px in, the chosen one on the dialog's
7110
  + own surface with a 1px 10% ring inside (a fill a step up in dark). */
7111
  +.config-segmented {
7112
  + display: inline-flex;
7113
+ + flex-shrink: 0;
 
 
 
 
7114
  + gap: 0;
7115
  + max-width: 100%;
7116
  + padding: 1px;
 
7118
  + background: color-mix(in srgb, var(--text) 5%, transparent);
7119
  }
7120
 
7121
+ -.settings-chat-options {
 
 
7122
  - display: flex;
7123
+ - flex-direction: column;
7124
+ - gap: 12px;
7125
+ - width: 100%;
7126
+ - max-width: 420px;
7127
+ +.config-segmented-option {
7128
+ + position: relative;
7129
  + display: inline-flex;
7130
+ + flex: 1 0 auto;
7131
+ + align-items: center;
7132
+ + justify-content: center;
 
 
7133
  + min-width: 0;
7134
  + height: 30px;
7135
  + padding: 0 12px;
7136
+ + border: 0;
 
7137
  + border-radius: calc(var(--radius-md) - 1px);
7138
+ + background: transparent;
7139
+ + color: var(--text-muted);
7140
+ + cursor: pointer;
 
7141
  + font: inherit;
7142
  + font-size: 13px;
7143
+ + font-weight: 400;
7144
  + white-space: nowrap;
7145
  + transition: background-color 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;
7146
  }
7147
 
7148
+ -.settings-chat-option {
7149
+ - width: 100%;
 
 
7150
  +.config-segmented-option:hover {
7151
+ color: var(--text);
7152
+ - font-size: 12px;
7153
  }
7154
 
7155
+ -.settings-chat-switch-option {
7156
+ - display: flex;
7157
+ - align-items: center;
7158
+ - justify-content: space-between;
7159
+ - gap: 16px;
7160
+ - min-height: 28px;
7161
  +.config-segmented-option:has(input:checked),
7162
  +.config-segmented-option[aria-checked="true"] {
7163
  + background: var(--bg-elevated);
 
7166
  + font-weight: 500;
7167
  }
7168
 
7169
+ -.settings-chat-range-option {
7170
+ - padding: 0;
 
7171
  +html.dark .config-segmented-option:has(input:checked),
7172
  +html.dark .config-segmented-option[aria-checked="true"] {
7173
  + background: color-mix(in srgb, var(--text) 12%, transparent);
7174
  }
7175
 
7176
+ -.settings-chat-range-header {
7177
+ - display: grid;
7178
+ - grid-template-columns: minmax(0, 1fr) auto 28px;
7179
+ - align-items: center;
7180
+ - gap: 8px;
7181
  +.config-segmented-option:has(input:focus-visible) {
7182
  + outline: 1px solid var(--focus-line);
7183
  + outline-offset: -1px;
7184
  + box-shadow: var(--focus-glow);
7185
  }
7186
 
7187
+ -.settings-chat-range-header output {
7188
+ - color: var(--text-muted);
7189
+ - font-family: var(--font-mono);
7190
+ - font-size: 11px;
7191
  +.config-segmented-label {
7192
  + overflow: hidden;
7193
  + text-overflow: ellipsis;
7194
  }
7195
 
7196
+ -.settings-send-mode-options {
7197
+ - display: flex;
7198
+ - flex-shrink: 0;
7199
+ - gap: 3px;
7200
+ - padding: 3px;
7201
+ - border: 1px solid var(--border);
7202
+ - border-radius: 6px;
7203
  +/* ---------------------------------------------------------------- theme cards
7204
  + Each card draws a small window in its theme's own tokens ([data-theme] scopes
7205
  + them), so the palette shows instead of being named; System halves Light and
 
7208
  + display: grid;
7209
  + grid-template-columns: repeat(6, minmax(0, 1fr));
7210
  + gap: 12px;
7211
+ + width: 100%;
 
 
7212
  }
7213
 
7214
+ -.settings-send-mode-option {
7215
  +.settings-theme-option {
7216
+ position: relative;
7217
  display: flex;
7218
+ - flex: 1 0 auto;
 
 
 
 
 
 
 
7219
  - align-items: center;
7220
+ - justify-content: center;
7221
+ - min-height: 26px;
7222
  - padding: 0 10px;
7223
  - border: 0;
7224
+ - border-radius: 4px;
7225
  - background: transparent;
7226
+ + flex-direction: column;
7227
  + align-items: stretch;
7228
  + gap: 7px;
7229
  + min-width: 0;
7230
+ color: var(--text-muted);
7231
  cursor: pointer;
7232
  font-size: 12px;
7233
+ - font-weight: 400;
7234
  }
7235
 
7236
+ -.settings-send-mode-option:has(input:checked) {
7237
  - background: var(--bg-selected);
7238
+ - color: var(--accent);
7239
+ - font-weight: 600;
7240
  +.settings-theme-preview {
7241
  + display: flex;
7242
+ + height: 76px;
7243
  + overflow: hidden;
7244
  + border-radius: var(--radius-md);
7245
  + box-shadow: 0 0 0 1px var(--line);
7246
  + transition: box-shadow 0.12s ease;
7247
  }
7248
 
7249
+ -.settings-send-mode-option:not(:has(input:checked)):hover {
 
7250
  - background: var(--bg-hover);
 
7251
  +.settings-theme-option:hover .settings-theme-preview {
7252
  + box-shadow: 0 0 0 1px color-mix(in srgb, var(--text) 30%, transparent);
7253
  }
7254
 
7255
+ -.settings-send-mode-option:has(input:focus-visible) {
7256
+ - outline: 2px solid var(--accent);
7257
+ - outline-offset: 1px;
 
 
 
 
 
7258
  +.settings-theme-option:has(input:checked) {
7259
  + color: var(--text-strong);
7260
  + font-weight: 500;
7261
  }
7262
 
7263
+ -.settings-send-mode-label {
7264
+ - white-space: nowrap;
7265
  +.settings-theme-option:has(input:checked) .settings-theme-preview {
7266
  + box-shadow: 0 0 0 1.5px var(--text-strong);
7267
  }
7268
 
7269
+ -.settings-chat-reset {
7270
+ - width: 28px;
7271
+ - height: 28px;
7272
+ - padding: 0;
 
7273
  +.settings-theme-option-label {
7274
  + min-width: 0;
7275
+ + line-height: 16px;
7276
  + overflow-wrap: anywhere;
7277
  + text-align: center;
7278
  }
7279
 
7280
+ -.settings-chat-range-option input {
7281
+ - display: block;
7282
+ - width: 100%;
7283
+ - margin-top: 2px;
7284
+ - accent-color: var(--accent);
7285
  +.settings-theme-preview-half {
7286
  + display: flex;
7287
+ + flex: 1;
7288
  + min-width: 0;
7289
  + background: var(--bg);
7290
  }
7291
 
7292
+ -.settings-language-options {
 
 
 
7293
  +.settings-theme-preview-sidebar {
7294
+ display: flex;
7295
  + flex: 0 0 30%;
7296
+ flex-direction: column;
7297
+ gap: 3px;
7298
+ - width: 100%;
7299
+ - max-width: 420px;
7300
  + padding: 7px 5px;
7301
  + background: var(--bg-panel);
7302
+ }
7303
+
7304
+ -.settings-language-option {
7305
+ - display: flex;
7306
+ - align-items: center;
7307
+ - gap: 10px;
7308
+ - height: 44px;
7309
+ - padding: 0 10px;
7310
+ - border: 0;
7311
+ - border-radius: 5px;
7312
+ - background: transparent;
7313
+ - color: var(--text);
7314
+ - cursor: pointer;
7315
+ - font-size: 12px;
7316
+ - text-align: left;
7317
  +.settings-theme-preview-half + .settings-theme-preview-half .settings-theme-preview-sidebar {
7318
  + display: none;
7319
+ }
7320
+
7321
+ -.settings-language-option[aria-checked="true"] {
7322
+ - background: var(--bg-selected);
7323
  +.settings-theme-preview i {
7324
  + display: block;
7325
  + height: 3px;
7326
  + border-radius: 2px;
7327
  + background: color-mix(in srgb, var(--text) 22%, transparent);
7328
+ }
7329
+
7330
+ -.settings-language-option:not([aria-checked="true"]):hover,
7331
+ -.settings-language-option:focus-visible {
7332
+ - background: var(--bg-hover);
7333
+ - outline: none;
7334
  +.settings-theme-preview-sidebar i:first-child {
7335
  + background: color-mix(in srgb, var(--text) 40%, transparent);
7336
  }
7337
 
7338
+ -.settings-language-radio {
7339
+ - display: grid;
7340
+ - flex-shrink: 0;
7341
+ - place-items: center;
7342
+ - width: 16px;
7343
+ - height: 16px;
7344
+ - border: 1px solid var(--border);
7345
+ - border-radius: 50%;
7346
  +.settings-theme-preview-sidebar i:nth-child(2) {
7347
  + width: 80%;
7348
+ }
7349
+
7350
+ -.settings-language-option[aria-checked="true"] .settings-language-radio {
7351
+ - border-color: var(--accent);
7352
  +.settings-theme-preview-chat {
7353
+ + display: flex;
 
 
 
 
 
 
 
 
 
 
 
7354
  + flex: 1;
7355
  + flex-direction: column;
7356
  + gap: 3px;
 
7358
  + padding: 7px 7px 6px;
7359
  }
7360
 
7361
+ -.settings-language-radio-dot {
7362
+ - width: 8px;
7363
+ - height: 8px;
7364
+ - border-radius: 50%;
7365
+ - background: var(--accent);
7366
  +.settings-theme-preview-chat i.is-bubble {
7367
  + align-self: flex-end;
7368
  + width: 55%;
 
7370
  + margin-bottom: 2px;
7371
  + border-radius: 3px;
7372
  + background: var(--user-bg);
7373
+ }
7374
+
7375
+ -.settings-language-label {
7376
+ - flex: 1;
7377
  +.settings-theme-preview-chat i.is-short {
7378
  + width: 60%;
7379
+ }
7380
+
7381
+ -.settings-language-code {
7382
+ - color: var(--text-dim);
7383
+ - font-family: var(--font-mono);
7384
+ - font-size: 10px;
7385
  +.settings-theme-preview-chat i.is-composer {
7386
  + height: 9px;
7387
  + margin-top: auto;
7388
  + border-radius: 3px;
7389
  + background: var(--bg-surface);
7390
  + box-shadow: inset 0 0 0 0.5px color-mix(in srgb, var(--text) 14%, transparent);
7391
+ }
7392
+
7393
+ -.settings-shell-option {
7394
+ - display: flex;
7395
+ - align-items: center;
7396
+ - justify-content: space-between;
7397
+ - gap: 16px;
7398
+ - width: 100%;
7399
+ - max-width: 420px;
7400
+ - min-height: 44px;
7401
+ - padding: 0 10px;
7402
+ - border-radius: 5px;
7403
+ - background: var(--bg-panel);
7404
+ - color: var(--text);
7405
+ - font-size: 12px;
7406
+ -}
7407
  +/* ---------------------------------------------------------------- phones
7408
  + The column and the page take turns, each across the dialog: the sections as
7409
  + a list with chevrons, then the page with a back button before its title.
 
7417
  + height: calc(100dvh - 12px);
7418
  + max-height: none;
7419
  + }
7420
+
7421
+ -.settings-general-error {
7422
+ - margin: 8px 0 0;
7423
+ - color: #ef4444;
7424
+ - font-size: 11px;
7425
  + .settings-nav-column {
7426
  + flex: 1 1 auto;
7427
  + border-right: 0;
 
7499
  + padding: 12px 0;
7500
  + }
7501
  +
7502
+ + .config-row + .config-row {
7503
+ + padding-top: 11px;
7504
+ + }
7505
+ +
7506
  + .config-row-control {
7507
  + justify-content: flex-start;
7508
  + }
 
7545
  }
7546
 
7547
  @media (max-width: 640px) {
7548
+ @@ -1887,13 +2169,13 @@
7549
  width: 100%;
7550
  height: 190px;
7551
  border-right: 0;
 
7561
  }
7562
 
7563
  .agents-feature-actions {
7564
+ @@ -1905,29 +2187,6 @@
7565
  padding: 14px;
7566
  }
7567
 
 
7591
  }
7592
 
7593
  @keyframes saved-pop {
7594
+ @@ -2066,7 +2325,7 @@
7595
  max-width: 100%;
7596
  padding: 0 5px;
7597
  border: 1px solid var(--border);
 
7600
  background: var(--bg-panel);
7601
  color: var(--text);
7602
  font-family: var(--font-mono);
7603
+ @@ -2085,7 +2344,7 @@
7604
  padding: 6px 8px;
7605
  overflow: auto;
7606
  border: 1px solid var(--border);
 
7609
  background: var(--bg-panel);
7610
  color: var(--text-muted);
7611
  font-family: var(--font-mono);
7612
+ @@ -2172,9 +2431,8 @@
7613
  min-width: 0;
7614
  height: 32px;
7615
  padding: 0 10px;
 
7621
  color: var(--text);
7622
  font-family: var(--font-mono);
7623
  font-size: 12px;
7624
+ @@ -2266,10 +2524,9 @@ select.mcp-add-input {
7625
  max-width: 100%;
7626
  max-height: 100%;
7627
  overflow: hidden;
 
7635
  }
7636
 
7637
  /* Focus moves to the dialog itself when it opens (tabIndex -1); it is not a control, so no ring. */
7638
+ @@ -2282,7 +2539,7 @@ select.mcp-add-input {
7639
  flex: 1 1 auto;
7640
  gap: 12px;
7641
  min-height: 0;
 
7644
  overflow: auto;
7645
  overscroll-behavior: contain;
7646
  }
7647
+ @@ -2315,7 +2572,7 @@ select.mcp-add-input {
7648
  margin-top: 10px;
7649
  padding: 8px 10px;
7650
  border: 1px solid var(--border);
 
7653
  background: var(--bg);
7654
  color: var(--text);
7655
  font-family: var(--font-mono);
7656
+ @@ -2333,7 +2590,7 @@ select.mcp-add-input {
7657
  .project-trust-error {
7658
  flex-shrink: 0;
7659
  max-height: 30vh;
 
7662
  overflow: auto;
7663
  color: #ef4444;
7664
  font-size: 12px;
7665
+ @@ -2353,19 +2610,20 @@ select.mcp-add-input {
7666
  flex-shrink: 0;
7667
  justify-content: flex-end;
7668
  gap: 8px;
 
7690
  }
7691
 
7692
  .project-trust-button:disabled {
7693
+ @@ -2373,10 +2631,8 @@ select.mcp-add-input {
7694
  }
7695
 
7696
  .project-trust-button.is-primary {
 
7703
  }
7704
 
7705
  .project-trust-button.is-primary:disabled {
7706
+ @@ -2450,7 +2706,7 @@ select.mcp-add-input {
7707
  min-width: 0;
7708
  padding: 8px 10px;
7709
  border: 1px solid var(--border);
 
7712
  background: var(--bg);
7713
  }
7714
 
7715
+ @@ -2472,7 +2728,7 @@ select.mcp-add-input {
7716
  .project-trust-mcp-tag {
7717
  flex-shrink: 0;
7718
  padding: 1px 5px;
 
7721
  background: rgba(120, 120, 120, 0.12);
7722
  color: var(--text-dim);
7723
  font-size: 10px;
7724
+ @@ -2493,7 +2749,7 @@ select.mcp-add-input {
7725
  display: inline-block;
7726
  margin: 2px 4px 0 0;
7727
  padding: 0 4px;
 
24783
  </div>
24784
  );
24785
  diff --git a/components/SettingsUi.test.mjs b/components/SettingsUi.test.mjs
24786
+ index 30e33f5..1796163 100644
24787
  --- a/components/SettingsUi.test.mjs
24788
  +++ b/components/SettingsUi.test.mjs
24789
+ @@ -29,3 +29,42 @@ test("the MCP panel's reasons are visible text, never only a tooltip", () => {
24790
  // The panel's own title is the only one: no reason hides in a tooltip.
24791
  assert.deepEqual(mcp.match(/\btitle=\{[^}]*\}/g), ['title={t("settings.mcp")}']);
24792
  });
 
24814
  + }
24815
  + assert.deepEqual([px(track, "--switch-h"), px(track, "--switch-w")], [18, 32]);
24816
  +});
24817
+ +
24818
+ +test("a switch's knob looks centred, not only measures so: no shadow, and rows on whole pixels", async () => {
24819
+ + const css = await readFile(new URL("../app/settings.css", import.meta.url), "utf8");
24820
+ + const body = (selector) => css.match(new RegExp(`(?:^|\\n)${selector.replace(/[.[\]"=+]/g, "\\$&")} \\{([^}]*)\\}`))?.[1] ?? "";
24821
+ + // A shadow 1px below merged with the dark knob in light and made it read low (design/toggle-centering-feedback.jpg).
24822
+ + assert.doesNotMatch(body(".config-switch-knob"), /box-shadow/);
24823
+ + // Fractional line heights (1.4 × 13px) put switches at y = 745.9; every row metric above them is whole pixels.
24824
+ + for (const selector of [".config-group-title", ".config-group-description", ".config-row-label", ".config-row-description", ".settings-theme-option-label"]) {
24825
+ + assert.match(body(selector), /line-height: \d+px;/, selector);
24826
+ + }
24827
+ + assert.match(body(".settings-theme-preview"), /height: \d+px;/);
24828
+ + assert.doesNotMatch(body(".settings-theme-preview"), /aspect-ratio/);
24829
+ + // A row's divider takes its pixel from the padding, so its content box stays even.
24830
+ + assert.match(body(".config-row + .config-row"), /padding-top: 11px;/);
24831
+ +});
24832
  diff --git a/components/SettingsUi.tsx b/components/SettingsUi.tsx
24833
  index 1f1ae33..27ed896 100644
24834
  --- a/components/SettingsUi.tsx
 
30094
  +- The viewers render in shadow roots. Their colours and type come from CSS variables in `app/globals.css` (`.code-diff, .code-file`); the background is set on the host (`diffs-container`) because the library's theme sets it there (`:host`), which beats an inherited value. A text selection inside a viewer reaches the document through `Selection.getComposedRanges` (the file viewer's "mention selected lines"); an extension dialog measures the viewer's scrolling `[data-code]` to widen for code. More than 400 lines with a scroll box (the file viewer's panel, a tool's 400px box) render only the rows in view (`useScrollRootVirtualizer`, set up in a passive effect: the panel mounts in the same commit as the code).
30095
  +- Library quirks handled here: a token's bold/italic is applied in the shadow root (the library sets them through `light-dark()`, which only takes colours); the expand controls get a tab stop, a name and Enter/Space; its "modified" accent is overridden to gray (it is blue); its lookbehind literal `/(?<=\n)/` is rewritten at build time (`lib/pierre-diffs-split-loader.cjs`, as for #753), and Safari before 16.4 highlights with Shiki's Oniguruma (WASM) engine since the JavaScript engine compiles grammars to lookbehind RegExps.
30096
  diff --git a/docs/agents/settings-ui.md b/docs/agents/settings-ui.md
30097
+ index b23f1c7..31dbd68 100644
30098
  --- a/docs/agents/settings-ui.md
30099
  +++ b/docs/agents/settings-ui.md
30100
  @@ -1,10 +1,19 @@
 
30105
  +- **Phones** (≤640px): the column and the page take turns across the dialog (`data-view` on the surface, `nav` or `page`). Settings opens on the page of its section, with a back button before the title; the column lists the sections at 44px with chevrons and carries the close button. Whichever side shows takes focus (the back button, or the open section's row), since the button that was pressed is hidden.
30106
  +- **Embedded sections** have no fill of their own: the list (`.config-sidebar`) sits on the page's surface with a hairline (`--line`) to its detail, and its rows' text under the page title (the list's left padding is the side less a row's 8px); a section's own top line (the Models banner, Sub-agents' switch) is a line under the page title with a hairline under it; a `ConfigNotice` is a rounded callout on a 5% tint; the footer has a hairline above. Inner fills (`.enabled-models-list`, examples) are `--bg-surface`, as fields are, never `--bg-panel`, which reads as a hole in the dialog.
30107
  +- **Rows** (`ConfigGroup`, `ConfigRow`, `ConfigSegmented` in `SettingsUi.tsx`): a group is its title (13px, 600), an optional line about it, then rows divided by hairlines; a row is the label (13px) and its description (12px, `--text-dim`, at most 400px) on the left and the control on the right, centred on the row (52px at least). `htmlFor` makes the label the control's, so a click on "Expand thinking blocks…" switches it (`ConfigSwitch` takes an `id`). Sliders and the font fields share one 300px control column (field or slider, value, reset). On a phone rows stack, the control under its label at the row's width, except a lone switch, which stays at the right of its label.
30108
+ +- **Switches** (`ConfigSwitch`) are Claude's design system's Switch: the knob is the track's height less 4px, 2px from every edge in both states, travelling the width less the height (18 × 32, small 14 × 24). Off, a `--text-muted` knob on a 16% tint of the text; on, the ink (`--accent`) with an `--accent-contrast` knob (the accent is neutral, so a white knob would vanish on it in dark). The knob casts **no shadow** (one 1px below merged with the dark knob in light and made it read low), and every row metric above a switch is a whole pixel (line heights in px, theme cards a fixed 76px tall, a row's divider taking its pixel from the top padding), so switches land on whole pixels and their edges render alike. Boxes cannot show either fault: `components/SettingsUi.test.mjs` checks the geometry and those rules, and `design/tests/switch_pixels.py` reads the knob's edges from the rendered pixels, off and on, light and dark, at 1x–3x.
30109
  +- **A segmented choice** (`ConfigSegmented`; the send key, the file browser, the language): native radios hidden in their options on one quiet track (6% tint), the chosen option lifted on `--bg-elevated` with a soft shadow in light, a 14% tint in dark. The language keeps its `role="radio"` buttons on the same track (`.settings-language-options`).
30110
  +
30111
  ## General appearance