@fungi.computer/caps 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/styles.css CHANGED
@@ -2422,26 +2422,73 @@
2422
2422
  color: var(--color-base-content);
2423
2423
  font-size: 0.875rem;
2424
2424
  }
2425
- .caps-composer > .join {
2426
- border: var(--border) solid
2427
- color-mix(in oklab, var(--color-base-content) 12%, var(--color-base-300));
2428
- border-radius: var(--radius-box);
2429
- background: var(--color-base-200);
2430
- box-shadow: var(
2431
- --stipe-shadow-inset,
2432
- inset 0 calc(var(--depth) * 1px) calc(var(--depth) * 2px)
2433
- color-mix(
2434
- in oklab,
2435
- var(--color-base-content) calc(var(--depth) * 12%),
2436
- transparent
2437
- ),
2438
- 0 calc(var(--depth) * 1px) 0
2439
- color-mix(
2440
- in oklab,
2441
- var(--color-base-100) calc(var(--depth) * 60%),
2442
- transparent
2443
- )
2444
- );
2425
+ /* The composer is a surface, not a box inside a panel: the host region is
2426
+ the field, and the text sits directly on the host's face. A slim row along
2427
+ the bottom edge carries the toolbar and, at its end, the action key. */
2428
+ .caps-composer {
2429
+ display: flex;
2430
+ min-width: 0;
2431
+ flex-direction: column;
2432
+ gap: var(--spacing-xs);
2433
+ }
2434
+ .caps-composer-attachments {
2435
+ display: flex;
2436
+ flex-wrap: wrap;
2437
+ gap: var(--spacing-xs);
2438
+ }
2439
+ .caps-composer-input {
2440
+ position: relative;
2441
+ min-width: 0;
2442
+ }
2443
+ .caps-composer-input :is(textarea, input),
2444
+ .caps-composer-input :is(textarea, input):focus,
2445
+ .caps-composer-input :is(textarea, input):focus-visible {
2446
+ width: 100%;
2447
+ border: 0;
2448
+ border-radius: 0;
2449
+ background: transparent;
2450
+ box-shadow: none;
2451
+ outline: none;
2452
+ padding-inline: 0;
2453
+ }
2454
+ .caps-composer-bar {
2455
+ display: flex;
2456
+ min-width: 0;
2457
+ align-items: center;
2458
+ gap: var(--spacing-xs);
2459
+ }
2460
+ .caps-composer-toolbar {
2461
+ display: flex;
2462
+ min-width: 0;
2463
+ flex: 1;
2464
+ align-items: center;
2465
+ gap: var(--spacing-xs);
2466
+ }
2467
+ /* A StatusTray seated as the composer's toolbar: one wrapping row of keys,
2468
+ selects and chips on the theme's field-size grid, the same in every App. */
2469
+ .caps-composer-toolbar > .caps-status-tray {
2470
+ flex: 1;
2471
+ min-width: 0;
2472
+ --caps-status-tray-row-wrap: wrap;
2473
+ --caps-status-tray-chips-display: contents;
2474
+ --caps-status-tray-key-height: calc(var(--size-field) * 8);
2475
+ --caps-status-tray-chip-height: var(--caps-status-tray-key-height);
2476
+ --caps-status-tray-width: 28rem;
2477
+ --caps-keycap-width: auto;
2478
+ --caps-keycap-height: var(--caps-status-tray-key-height);
2479
+ }
2480
+ .caps-composer-toolbar
2481
+ > .caps-status-tray
2482
+ .caps-status-tray-row
2483
+ :is(.caps-keycap-button, .caps-status-tray-chip, .caps-select) {
2484
+ height: var(--caps-status-tray-key-height);
2485
+ min-height: var(--caps-status-tray-key-height);
2486
+ }
2487
+ /* A chip label is a flex row, so an inline label box adds no text strut
2488
+ and chip text shares the keys' baseline. */
2489
+ .caps-composer-toolbar > .caps-status-tray .caps-status-tray-label {
2490
+ display: flex;
2491
+ align-items: center;
2445
2492
  }
2446
2493
  .caps-alert[data-tone="error"] h3 {
2447
2494
  color: var(--color-error);
@@ -2933,34 +2980,23 @@
2933
2980
  }
2934
2981
  .caps-resize-handle {
2935
2982
  display: grid;
2936
- width: 64px;
2937
- height: 22px;
2983
+ width: 44px;
2984
+ height: 24px;
2985
+ grid-template-columns: minmax(0, 1fr);
2938
2986
  place-items: center;
2939
- border: var(--border) solid var(--stipe-linework);
2987
+ border: 0;
2940
2988
  border-radius: var(--radius-field);
2941
- background: var(--color-base-200);
2989
+ background: transparent;
2942
2990
  cursor: row-resize;
2943
2991
  touch-action: none;
2944
2992
  user-select: none;
2945
2993
  }
2946
2994
  .caps-resize-handle[aria-orientation="vertical"] {
2947
- width: 22px;
2948
- height: 64px;
2995
+ width: 24px;
2996
+ height: 44px;
2949
2997
  cursor: col-resize;
2950
2998
  }
2951
- .caps-resize-handle[data-resize-variant="notch"] {
2952
- border-radius: var(--radius-box) var(--radius-box) var(--radius-field)
2953
- var(--radius-field);
2954
- box-shadow:
2955
- inset 0 1px 0 oklch(100% 0 0 / calc(var(--depth) * 0.55)),
2956
- 0 calc(var(--depth) * 1px) calc(var(--depth) * 2px)
2957
- color-mix(
2958
- in oklab,
2959
- var(--color-base-content) calc(var(--depth) * 12%),
2960
- transparent
2961
- );
2962
- }
2963
- .caps-resize-handle:is(:hover, :focus-visible) {
2999
+ .caps-resize-handle:focus-visible {
2964
3000
  outline: 2px solid var(--color-info);
2965
3001
  outline-offset: 2px;
2966
3002
  }
@@ -2969,49 +3005,46 @@
2969
3005
  }
2970
3006
  .caps-resize-grip {
2971
3007
  position: relative;
2972
- width: 38px;
2973
- height: 8px;
3008
+ width: 28px;
3009
+ height: 6px;
2974
3010
  border-radius: var(--radius-button);
2975
- background-color: var(--color-neutral);
2976
- background-image: linear-gradient(
2977
- to bottom,
2978
- oklch(100% 0 0 / calc(var(--depth) * 0.08)),
2979
- transparent 50%,
2980
- oklch(0% 0 0 / calc(var(--depth) * 0.015))
2981
- );
2982
- box-shadow: var(
2983
- --stipe-shadow-raised,
2984
- inset 0 1px 0 oklch(100% 0 0 / calc(var(--depth) * 0.6)),
2985
- inset 0 -2px 0 color-mix(in oklab, var(--color-neutral) 75%, black),
2986
- inset 0 0 0 1px
3011
+ background-color: var(--color-base-200);
3012
+ box-shadow:
3013
+ 0 0 0 1px
3014
+ color-mix(in oklab, var(--stipe-linework) 80%, var(--color-base-200)),
3015
+ inset 0 1px 0 oklch(100% 0 0 / calc(var(--depth) * 0.18)),
3016
+ 0 1px 0
2987
3017
  color-mix(
2988
3018
  in oklab,
2989
- var(--color-neutral),
2990
- black calc(var(--depth) * 14%)
2991
- )
2992
- );
3019
+ var(--color-base-200),
3020
+ var(--color-base-content) 14%
3021
+ );
3022
+ }
3023
+ .caps-resize-handle:hover .caps-resize-grip {
3024
+ background-color: var(--color-base-300);
2993
3025
  }
2994
3026
  .caps-resize-grip::after {
2995
3027
  content: "";
2996
3028
  position: absolute;
2997
- inset-inline: 7px;
3029
+ left: 50%;
2998
3030
  top: 50%;
3031
+ width: 2px;
2999
3032
  height: 2px;
3000
- transform: translateY(-50%);
3001
- border-radius: var(--radius-button);
3002
- background: linear-gradient(
3003
- to bottom,
3004
- oklch(100% 0 0 / calc(var(--depth) * 0.45)) 50%,
3005
- oklch(0% 0 0 / calc(var(--depth) * 0.28)) 50%
3006
- );
3033
+ transform: translate(-50%, -50%);
3034
+ border-radius: var(--radius-selector);
3035
+ background: color-mix(in oklab, var(--color-base-content) 45%, transparent);
3007
3036
  box-shadow:
3008
- 0 -3px 0 -0.5px oklch(100% 0 0 / calc(var(--depth) * 0.45)),
3009
- 0 -2px 0 -0.5px oklch(0% 0 0 / calc(var(--depth) * 0.28)),
3010
- 0 3px 0 -0.5px oklch(100% 0 0 / calc(var(--depth) * 0.45)),
3011
- 0 4px 0 -0.5px oklch(0% 0 0 / calc(var(--depth) * 0.28));
3037
+ -5px 0 0 color-mix(in oklab, var(--color-base-content) 45%, transparent),
3038
+ 5px 0 0 color-mix(in oklab, var(--color-base-content) 45%, transparent);
3012
3039
  }
3013
3040
  .caps-resize-handle[aria-orientation="vertical"] .caps-resize-grip {
3014
- transform: rotate(90deg);
3041
+ width: 6px;
3042
+ height: 28px;
3043
+ }
3044
+ .caps-resize-handle[aria-orientation="vertical"] .caps-resize-grip::after {
3045
+ box-shadow:
3046
+ 0 -5px 0 color-mix(in oklab, var(--color-base-content) 45%, transparent),
3047
+ 0 5px 0 color-mix(in oklab, var(--color-base-content) 45%, transparent);
3015
3048
  }
3016
3049
  @media (prefers-reduced-motion: reduce) {
3017
3050
  .caps-scroll-thumb-surface {
@@ -3019,6 +3052,10 @@
3019
3052
  }
3020
3053
  }
3021
3054
  @media (forced-colors: active) {
3055
+ .caps-resize-grip {
3056
+ border: 1px solid ButtonText;
3057
+ box-shadow: none;
3058
+ }
3022
3059
  .caps-scroll-track {
3023
3060
  background: Canvas;
3024
3061
  box-shadow: none;
@@ -4221,6 +4258,14 @@
4221
4258
  text-align: center;
4222
4259
  }
4223
4260
  /* SendAction: the arrow and the working glyph share one cell. */
4261
+ /* The send key: one square keycap whose glyph changes, never a second key. */
4262
+ .caps-send-action {
4263
+ --caps-keycap-width: auto;
4264
+ --caps-keycap-justify: center;
4265
+ --caps-keycap-padding: 0;
4266
+ --caps-keycap-min-width: var(--caps-keycap-height, 2.5rem);
4267
+ flex: none;
4268
+ }
4224
4269
  .caps-send-glyph {
4225
4270
  display: inline-grid;
4226
4271
  place-items: center;
@@ -4610,6 +4655,232 @@
4610
4655
  padding-bottom: var(--spacing-sm);
4611
4656
  }
4612
4657
 
4658
+ /* The label gutter belongs to an opted-in form. No default Field or Form
4659
+ changes geometry, including the signed-off permissions panel. */
4660
+ :is(.caps-form, .caps-form-section, .caps-fieldset)[data-layout="columns"] {
4661
+ --caps-form-label-col: 9rem;
4662
+ }
4663
+ .caps-form[data-layout="columns"]
4664
+ :is(.caps-form-body, .caps-form-section-fields) {
4665
+ gap: var(--spacing-sm);
4666
+ }
4667
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4668
+ :is(
4669
+ .caps-field[data-orientation="vertical"],
4670
+ .caps-field[data-orientation="horizontal"] > .caps-field-row
4671
+ ) {
4672
+ display: grid;
4673
+ grid-template-columns: var(--caps-form-label-col) minmax(0, 1fr);
4674
+ column-gap: var(--spacing-md);
4675
+ row-gap: var(--spacing-2xs);
4676
+ align-items: center;
4677
+ justify-content: stretch;
4678
+ --caps-field-control-width: 100%;
4679
+ }
4680
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4681
+ :is(
4682
+ .caps-field[data-orientation="vertical"],
4683
+ .caps-field[data-orientation="horizontal"] > .caps-field-row
4684
+ )
4685
+ > .caps-field-copy {
4686
+ display: contents;
4687
+ }
4688
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4689
+ :is(
4690
+ .caps-field[data-orientation="vertical"],
4691
+ .caps-field[data-orientation="horizontal"] > .caps-field-row
4692
+ )
4693
+ > .caps-field-copy
4694
+ > label {
4695
+ grid-column: 1;
4696
+ grid-row: 1;
4697
+ min-width: 0;
4698
+ overflow: hidden;
4699
+ text-overflow: ellipsis;
4700
+ white-space: nowrap;
4701
+ }
4702
+ .caps-fieldset[data-layout="columns"] > .caps-fieldset-legend {
4703
+ min-width: 0;
4704
+ overflow: hidden;
4705
+ text-overflow: ellipsis;
4706
+ white-space: nowrap;
4707
+ }
4708
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4709
+ :is(
4710
+ .caps-field[data-orientation="vertical"],
4711
+ .caps-field[data-orientation="horizontal"] > .caps-field-row
4712
+ )
4713
+ > .caps-field-copy
4714
+ > .caps-field-description {
4715
+ grid-column: 2;
4716
+ grid-row: 2;
4717
+ }
4718
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4719
+ :is(
4720
+ .caps-field[data-orientation="vertical"],
4721
+ .caps-field[data-orientation="horizontal"] > .caps-field-row
4722
+ )
4723
+ > :not(.caps-field-copy, .caps-field-lead, .caps-field-error-reveal) {
4724
+ grid-column: 2;
4725
+ grid-row: 1;
4726
+ width: 100%;
4727
+ max-width: 30rem;
4728
+ }
4729
+ /* Grid controls fill the value column; a switch or checkbox is a fixed-size
4730
+ key, never a stretched track spanning the next settings row. */
4731
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4732
+ :is(
4733
+ .caps-field[data-orientation="vertical"],
4734
+ .caps-field[data-orientation="horizontal"] > .caps-field-row
4735
+ )
4736
+ > .caps-switch {
4737
+ width: 40px;
4738
+ justify-self: start;
4739
+ }
4740
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4741
+ :is(
4742
+ .caps-field[data-orientation="vertical"],
4743
+ .caps-field[data-orientation="horizontal"] > .caps-field-row
4744
+ )
4745
+ > .caps-checkbox {
4746
+ width: 24px;
4747
+ justify-self: start;
4748
+ }
4749
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4750
+ .caps-field[data-orientation="vertical"]
4751
+ > .caps-field-lead {
4752
+ grid-column: 1;
4753
+ grid-row: 1;
4754
+ margin: 0;
4755
+ }
4756
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4757
+ .caps-field[data-orientation="vertical"]
4758
+ > .caps-field-error-reveal {
4759
+ grid-column: 2;
4760
+ grid-row: 2;
4761
+ }
4762
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4763
+ .caps-field[data-orientation="horizontal"]
4764
+ > .caps-field-row
4765
+ > .caps-field-lead {
4766
+ grid-column: 1;
4767
+ grid-row: 1;
4768
+ }
4769
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4770
+ .caps-field[data-orientation="horizontal"]
4771
+ > .caps-field-error-reveal {
4772
+ padding-inline-start: calc(var(--caps-form-label-col) + var(--spacing-md));
4773
+ }
4774
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4775
+ .caps-field:has(> .caps-field-error-reveal[data-open="true"])
4776
+ .caps-field-description,
4777
+ .caps-fieldset[data-layout="columns"]:has(
4778
+ > .caps-field-error-reveal[data-open="true"]
4779
+ )
4780
+ > .caps-fieldset-description {
4781
+ display: none;
4782
+ }
4783
+ .caps-fieldset[data-layout="columns"] {
4784
+ display: grid;
4785
+ grid-template-columns: var(--caps-form-label-col) minmax(0, 1fr);
4786
+ column-gap: var(--spacing-md);
4787
+ row-gap: var(--spacing-2xs);
4788
+ align-items: start;
4789
+ }
4790
+ .caps-fieldset[data-layout="columns"] > .caps-fieldset-legend {
4791
+ grid-column: 1;
4792
+ grid-row: 1;
4793
+ margin: 0;
4794
+ }
4795
+ .caps-fieldset[data-layout="columns"] > .caps-fieldset-fields {
4796
+ grid-column: 2;
4797
+ grid-row: 1;
4798
+ flex-direction: row;
4799
+ flex-wrap: wrap;
4800
+ margin: 0;
4801
+ }
4802
+ .caps-fieldset[data-layout="columns"]
4803
+ > :is(.caps-fieldset-description, .caps-field-error-reveal) {
4804
+ grid-column: 2;
4805
+ grid-row: 2;
4806
+ }
4807
+ .caps-form[data-layout="columns"] .caps-form-footer {
4808
+ padding-inline-start: calc(var(--caps-form-label-col) + var(--spacing-md));
4809
+ max-width: calc(var(--caps-form-label-col) + var(--spacing-md) + 30rem);
4810
+ }
4811
+ /* A short add form can share its one field row with the actions. */
4812
+ @container (min-width: 36rem) {
4813
+ .caps-form[data-layout="columns"]
4814
+ .caps-form-body:has(> .caps-field:first-child:nth-last-child(2)) {
4815
+ display: grid;
4816
+ grid-template-columns: minmax(0, 1fr) auto;
4817
+ align-items: start;
4818
+ }
4819
+ .caps-form[data-layout="columns"]
4820
+ .caps-form-body:has(> .caps-field:first-child:nth-last-child(2))
4821
+ > .caps-form-footer {
4822
+ padding-inline-start: 0;
4823
+ max-width: none;
4824
+ }
4825
+ }
4826
+ /* Decide once per form: the 30rem example still has space for a 9rem label
4827
+ and its control, while genuinely narrow phone forms stack every field. */
4828
+ @container (max-width: 20rem) {
4829
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4830
+ :is(
4831
+ .caps-field[data-orientation="vertical"],
4832
+ .caps-field[data-orientation="horizontal"] > .caps-field-row
4833
+ ),
4834
+ .caps-fieldset[data-layout="columns"] {
4835
+ grid-template-columns: minmax(0, 1fr);
4836
+ }
4837
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4838
+ :is(
4839
+ .caps-field[data-orientation="vertical"],
4840
+ .caps-field[data-orientation="horizontal"] > .caps-field-row
4841
+ )
4842
+ > :not(.caps-field-copy, .caps-field-lead, .caps-field-error-reveal),
4843
+ .caps-fieldset[data-layout="columns"] > .caps-fieldset-fields {
4844
+ grid-column: 1;
4845
+ grid-row: 2;
4846
+ max-width: none;
4847
+ }
4848
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4849
+ :is(
4850
+ .caps-field[data-orientation="vertical"],
4851
+ .caps-field[data-orientation="horizontal"] > .caps-field-row
4852
+ )
4853
+ > .caps-field-copy
4854
+ > .caps-field-description,
4855
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4856
+ .caps-field[data-orientation="vertical"]
4857
+ > .caps-field-error-reveal,
4858
+ .caps-fieldset[data-layout="columns"]
4859
+ > :is(.caps-fieldset-description, .caps-field-error-reveal) {
4860
+ grid-column: 1;
4861
+ grid-row: 3;
4862
+ }
4863
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4864
+ :is(
4865
+ .caps-field[data-orientation="vertical"],
4866
+ .caps-field[data-orientation="horizontal"] > .caps-field-row
4867
+ )
4868
+ > .caps-field-copy
4869
+ > label,
4870
+ .caps-fieldset[data-layout="columns"] > .caps-fieldset-legend {
4871
+ overflow: visible;
4872
+ text-overflow: clip;
4873
+ white-space: normal;
4874
+ }
4875
+ :is(.caps-form, .caps-form-section)[data-layout="columns"]
4876
+ .caps-field[data-orientation="horizontal"]
4877
+ > .caps-field-error-reveal,
4878
+ .caps-form[data-layout="columns"] .caps-form-footer {
4879
+ padding-inline-start: 0;
4880
+ max-width: none;
4881
+ }
4882
+ }
4883
+
4613
4884
  /* Choice cards: a RadioGroup option with room for a description and a
4614
4885
  trailing readout. Selection is ink, not accent. */
4615
4886
  .caps-choice-card {
@@ -5333,8 +5604,8 @@
5333
5604
  isolation: var(--caps-button-outline-isolation, auto);
5334
5605
  }
5335
5606
  .caps-button.btn-outline:not(:disabled):not([aria-disabled="true"]) {
5336
- background-color: var(--caps-button-outline-face, revert-layer);
5337
- color: var(--caps-button-outline-ink, revert-layer);
5607
+ background-color: var(--caps-key-face);
5608
+ color: var(--caps-button-outline-ink, var(--color-base-content));
5338
5609
  }
5339
5610
  .caps-button.btn-outline:is(:disabled, [aria-disabled="true"]) {
5340
5611
  background-color: var(
@@ -5345,11 +5616,11 @@
5345
5616
  .caps-button.btn-outline:not(:disabled):not([aria-disabled="true"]):hover {
5346
5617
  background-color: var(
5347
5618
  --caps-button-outline-hover-face,
5348
- var(--caps-button-outline-face, revert-layer)
5619
+ var(--caps-keycap-hover-face, var(--caps-key-face))
5349
5620
  );
5350
5621
  color: var(
5351
5622
  --caps-button-outline-hover-ink,
5352
- var(--caps-button-outline-ink, revert-layer)
5623
+ var(--caps-button-outline-ink, var(--color-base-content))
5353
5624
  );
5354
5625
  }
5355
5626
  /* A cut outline uses an inset face; an ordinary outline needs no extra face. */
@@ -5365,24 +5636,48 @@
5365
5636
  background: var(--color-base-content);
5366
5637
  }
5367
5638
 
5368
- /* An outline button is a hairline frame with a medium label: no drop in
5369
- any state, unless a theme's finish asks for one. */
5639
+ /* The Caps key material: a flat face on a 1px lip shaded toward the ink,
5640
+ a faint top edge, and a press that settles the face onto its lip. Every
5641
+ Button reads it; KeycapButton reads its face and lip and raises its own
5642
+ finish on them. Everything scales with --depth, so flat themes keep a
5643
+ flat linework key, and a theme's --caps-button-drop/-press still win. */
5644
+ .caps-keycap-button,
5645
+ .caps-button-filled,
5370
5646
  .caps-button.btn-outline {
5371
- --btn-border: color-mix(
5372
- in oklab,
5373
- var(--color-base-content) 24%,
5374
- transparent
5375
- );
5647
+ --caps-key-lip: calc(var(--depth) * 1px);
5648
+ --caps-key-rest-shadow:
5649
+ inset 0 1px 0
5650
+ oklch(100% 0 0 / calc(var(--depth) * var(--caps-key-top-edge, 0.14))),
5651
+ 0 var(--caps-key-lip) 0
5652
+ color-mix(
5653
+ in oklab,
5654
+ var(--caps-key-face),
5655
+ var(--caps-key-lip-ink, var(--color-base-content)) 14%
5656
+ );
5657
+ --caps-key-pressed-shadow:
5658
+ inset 0 1px 0 oklch(0% 0 0 / calc(var(--depth) * 0.08)), 0 0 0 transparent;
5659
+ }
5660
+
5661
+ /* An outline button is the neutral key: a base-200 face on linework. */
5662
+ .caps-button.btn-outline {
5663
+ --btn-border: var(--stipe-linework);
5664
+ --caps-key-face: var(--caps-button-outline-face, var(--color-base-200));
5376
5665
 
5377
5666
  font-weight: var(--caps-button-weight, 500);
5378
- box-shadow: var(--caps-button-outline-drop, var(--caps-button-drop, none));
5667
+ box-shadow: var(
5668
+ --caps-button-outline-drop,
5669
+ var(--caps-button-drop, var(--caps-key-rest-shadow))
5670
+ );
5671
+ transition-property: background-color, border-color, box-shadow, translate;
5672
+ transition-duration: var(--stipe-motion-fast);
5673
+ transition-timing-function: ease-out;
5379
5674
  }
5380
5675
 
5381
5676
  .caps-button.btn-outline:not(:disabled):not([aria-disabled="true"]):active {
5382
- translate: 0 var(--caps-button-press, var(--stipe-press-depth));
5677
+ translate: 0 var(--caps-button-press, var(--caps-key-lip));
5383
5678
  box-shadow: var(
5384
5679
  --caps-button-outline-drop,
5385
- var(--caps-button-drop-active, none)
5680
+ var(--caps-button-drop-active, var(--caps-key-pressed-shadow))
5386
5681
  );
5387
5682
  }
5388
5683
 
@@ -5394,25 +5689,28 @@
5394
5689
  --radius-field: var(--radius-button);
5395
5690
  }
5396
5691
 
5397
- /* A filled button is a flat key: one solid fill, a hairline edge a step
5398
- darker than the fill, and at most a faint 1px top highlight. Flat themes
5399
- edge it in linework instead; a theme sets --caps-button-linework: 0% to
5400
- edge it in its own fill. Press feel comes from the theme's
5401
- --stipe-press-depth and --stipe-press-scale. */
5692
+ /* A filled button is the same key in its role colour: one solid fill with
5693
+ no lighter ring, an edge a hair darker than the fill, a quiet lip shaded
5694
+ darker still, and only a very faint top edge. Flat themes edge it in
5695
+ linework instead; a theme sets --caps-button-linework: 0% to edge it in
5696
+ its own fill. */
5402
5697
  .caps-button-filled {
5403
5698
  --btn-bg: var(--btn-color);
5699
+ --caps-key-face: var(--btn-color);
5700
+ --caps-key-lip-ink: black;
5701
+ --caps-key-top-edge: 0.06;
5404
5702
  --caps-button-filled-edge: var(
5405
5703
  --caps-button-border-color,
5406
5704
  color-mix(
5407
5705
  in oklab,
5408
- color-mix(in oklab, var(--btn-bg), black calc(var(--depth) * 8%)),
5706
+ color-mix(in oklab, var(--btn-bg), black calc(var(--depth) * 6%)),
5409
5707
  var(--stipe-linework)
5410
5708
  calc((1 - var(--depth)) * var(--caps-button-linework, 100%))
5411
5709
  )
5412
5710
  );
5413
5711
  --caps-button-filled-drop: var(
5414
5712
  --caps-button-drop,
5415
- inset 0 1px 0 oklch(100% 0 0 / calc(var(--depth) * 0.18))
5713
+ var(--caps-key-rest-shadow)
5416
5714
  );
5417
5715
 
5418
5716
  background-color: var(--btn-bg);
@@ -5470,12 +5768,9 @@
5470
5768
  ) {
5471
5769
  --btn-bg: color-mix(in oklab, var(--btn-color) 92%, black);
5472
5770
 
5473
- translate: 0 var(--caps-button-press, var(--stipe-press-depth));
5771
+ translate: 0 var(--caps-button-press, var(--caps-key-lip));
5474
5772
  scale: var(--stipe-press-scale);
5475
- box-shadow: var(
5476
- --caps-button-drop-active,
5477
- inset 0 1px 0 oklch(0% 0 0 / calc(var(--depth) * 0.08))
5478
- );
5773
+ box-shadow: var(--caps-button-drop-active, var(--caps-key-pressed-shadow));
5479
5774
  transition-duration: var(--stipe-motion-squish);
5480
5775
  }
5481
5776
 
@@ -6686,8 +6981,9 @@
6686
6981
  scale: none;
6687
6982
  }
6688
6983
 
6984
+ /* The press is a state, not motion: a key still settles onto its lip. */
6689
6985
  .caps-button-filled:not(:disabled):not([aria-disabled="true"]):active {
6690
- translate: var(--caps-button-press-reduced, none);
6986
+ translate: var(--caps-button-press-reduced, 0 var(--caps-key-lip));
6691
6987
  }
6692
6988
 
6693
6989
  .caps-gooey-motion,
@@ -7990,7 +8286,8 @@
7990
8286
 
7991
8287
  @media (prefers-reduced-motion: reduce) {
7992
8288
  .caps-keycap,
7993
- .caps-keycap-button {
8289
+ .caps-keycap-button,
8290
+ .caps-button.btn-outline {
7994
8291
  transition: none;
7995
8292
  }
7996
8293
  }
@@ -8071,11 +8368,23 @@
8071
8368
  .caps-game-meter .progress {
8072
8369
  height: 0.625rem;
8073
8370
  }
8074
- /* A key: a flat face on a 1px lip, with a faint top highlight. The lip and
8075
- press travel scale with --depth, so flat themes keep a flat linework key.
8076
- A host or theme picks the face through --caps-keycap-face/-ink/-edge. */
8371
+ /* A key: today's flat key on a 1px lip, a touch richer. The face carries a
8372
+ faint ±3% grade, the top edge is 0.18, a small soft drop sits under the
8373
+ lip, and the linework edge is softened 20% toward the face. It reads its
8374
+ face and lip from the Caps key material; everything scales with --depth,
8375
+ so flat themes keep a flat linework key. A host or theme picks the face
8376
+ through --caps-keycap-face/-ink/-edge. */
8077
8377
  .caps-keycap-button {
8078
- --caps-keycap-lip: calc(var(--depth) * 1px);
8378
+ --caps-key-face: var(--caps-keycap-face, var(--color-base-200));
8379
+ --caps-keycap-rest-shadow:
8380
+ inset 0 1px 0 oklch(100% 0 0 / calc(var(--depth) * 0.18)),
8381
+ 0 var(--caps-key-lip) 0
8382
+ color-mix(in oklab, var(--caps-key-face), var(--color-base-content) 14%),
8383
+ 0 var(--caps-key-lip) calc(var(--depth) * 2px)
8384
+ oklch(0% 0 0 / calc(var(--depth) * 0.15));
8385
+ --caps-keycap-pressed-shadow:
8386
+ inset 0 1px 0 oklch(0% 0 0 / calc(var(--depth) * 0.08)),
8387
+ 0 0 0 transparent, 0 0 0 transparent;
8079
8388
  display: flex;
8080
8389
  width: var(--caps-keycap-width, 100%);
8081
8390
  min-width: var(--caps-keycap-min-width, 0);
@@ -8086,7 +8395,11 @@
8086
8395
  padding: var(--caps-keycap-padding, var(--spacing-xs) var(--spacing-sm));
8087
8396
  border: var(
8088
8397
  --caps-keycap-context-border,
8089
- var(--border) solid var(--caps-keycap-edge, var(--stipe-linework))
8398
+ var(--border) solid
8399
+ var(
8400
+ --caps-keycap-edge,
8401
+ color-mix(in oklab, var(--stipe-linework) 80%, var(--caps-key-face))
8402
+ )
8090
8403
  );
8091
8404
  font-size: var(--caps-keycap-font-size, inherit);
8092
8405
  font-weight: var(
@@ -8099,20 +8412,26 @@
8099
8412
  var(--caps-keycap-face, var(--color-base-200))
8100
8413
  );
8101
8414
  background-color: var(--caps-keycap-painted-face);
8102
- --caps-keycap-default-image: none;
8415
+ --caps-keycap-default-image: linear-gradient(
8416
+ to bottom,
8417
+ color-mix(
8418
+ in oklab,
8419
+ var(--caps-keycap-painted-face),
8420
+ white calc(var(--depth) * 3%)
8421
+ ),
8422
+ color-mix(
8423
+ in oklab,
8424
+ var(--caps-keycap-painted-face),
8425
+ black calc(var(--depth) * 3%)
8426
+ )
8427
+ );
8103
8428
  background-image: var(
8104
8429
  --caps-keycap-context-image,
8105
8430
  var(--caps-keycap-default-image)
8106
8431
  );
8107
8432
  box-shadow: var(
8108
8433
  --caps-keycap-context-shadow,
8109
- inset 0 1px 0 oklch(100% 0 0 / calc(var(--depth) * 0.14)),
8110
- 0 var(--caps-keycap-lip) 0
8111
- color-mix(
8112
- in oklab,
8113
- var(--caps-keycap-face, var(--color-base-200)),
8114
- var(--color-base-content) 14%
8115
- )
8434
+ var(--caps-keycap-rest-shadow)
8116
8435
  );
8117
8436
  color: var(--caps-keycap-ink, var(--color-base-content));
8118
8437
  text-align: left;
@@ -8123,11 +8442,10 @@
8123
8442
  transition-timing-function: ease-out;
8124
8443
  }
8125
8444
  .caps-keycap-button:active {
8126
- translate: 0 var(--caps-keycap-context-travel, var(--caps-keycap-lip));
8445
+ translate: 0 var(--caps-keycap-context-travel, var(--caps-key-lip));
8127
8446
  box-shadow: var(
8128
8447
  --caps-keycap-active-shadow,
8129
- inset 0 1px 0 oklch(0% 0 0 / calc(var(--depth) * 0.08)),
8130
- 0 0 0 transparent
8448
+ var(--caps-keycap-pressed-shadow)
8131
8449
  );
8132
8450
  }
8133
8451
  .caps-keycap-button:focus-visible {
@@ -8164,6 +8482,25 @@
8164
8482
  --caps-keycap-ink: var(--color-neutral-content);
8165
8483
  --caps-keycap-edge: var(--color-neutral);
8166
8484
  }
8485
+ /* The send key is a filled key in its role colour, like a filled Button:
8486
+ a solid face and a hairline edge one step darker, no light rim. With
8487
+ nothing to send it rests as a calm neutral key. */
8488
+ .caps-keycap-button.caps-send-action[data-tone="primary"] {
8489
+ --caps-keycap-face: var(--color-primary);
8490
+ --caps-keycap-ink: var(--color-primary-content);
8491
+ --caps-keycap-edge: color-mix(in oklab, var(--color-primary) 82%, black);
8492
+ --caps-keycap-hover-face: color-mix(
8493
+ in oklab,
8494
+ var(--color-primary) 92%,
8495
+ black
8496
+ );
8497
+ }
8498
+ .caps-keycap-button.caps-send-action[data-send-state="empty"] {
8499
+ --caps-keycap-face: var(--color-base-200);
8500
+ --caps-keycap-ink: var(--color-base-content);
8501
+ --caps-keycap-edge: var(--stipe-linework);
8502
+ --caps-keycap-hover-face: var(--color-base-200);
8503
+ }
8167
8504
  /* Destructive keys stay quiet until the pointer or focus is on them. */
8168
8505
  .caps-keycap-button[data-tone="destructive"]:is(:hover, :focus-visible) {
8169
8506
  --caps-keycap-ink: var(--color-error);