| author | |
| committer | |
| log | 52f9046260981270292e1fd9c77c2c3fd942be54 |
| tree | 6bd12d08325491c5dedccadd5ee55d2af3ee38f7 |
| parent | c32ade41390be10783246fbbeed45de7e3add79b |
| signature | Signed by PGP key B5690EEEBB952194 |
9 files changed, 301 insertions(+), 99 deletions(-)
apps/mobile/generated-uniwind-themes.css+58-58| ... | @@ -337,7 +337,7 @@ | ... | @@ -337,7 +337,7 @@ |
| 337 | --color-composer-surface: rgba(250, 243, 251, 0.94); | 337 | --color-composer-surface: rgba(250, 243, 251, 0.94); |
| 338 | --color-composer-border: rgba(238, 225, 237, 0.54); | 338 | --color-composer-border: rgba(238, 225, 237, 0.54); |
| 339 | --color-foreground: #501854; | 339 | --color-foreground: #501854; |
| 340 | --color-foreground-secondary: #ac1668; | 340 | --color-foreground-secondary: #ab1668; |
| 341 | --color-foreground-muted: #8d1255; | 341 | --color-foreground-muted: #8d1255; |
| 342 | --color-foreground-tertiary: #ac1668; | 342 | --color-foreground-tertiary: #ac1668; |
| 343 | --color-border: #eee1ed; | 343 | --color-border: #eee1ed; |
| ... | @@ -351,7 +351,7 @@ | ... | @@ -351,7 +351,7 @@ |
| 351 | --color-inline-skill-foreground: #454554; | 351 | --color-inline-skill-foreground: #454554; |
| 352 | --color-primary: #db2777; | 352 | --color-primary: #db2777; |
| 353 | --color-primary-foreground: #ffffff; | 353 | --color-primary-foreground: #ffffff; |
| 354 | --color-primary-text: #d32572; | 354 | --color-primary-text: #aa1e5d; |
| 355 | --color-primary-shadow: #000000; | 355 | --color-primary-shadow: #000000; |
| 356 | --color-secondary: #f1c4e6; | 356 | --color-secondary: #f1c4e6; |
| 357 | --color-secondary-foreground: #77347c; | 357 | --color-secondary-foreground: #77347c; |
| ... | @@ -371,7 +371,7 @@ | ... | @@ -371,7 +371,7 @@ |
| 371 | --color-input: #faf3fb; | 371 | --color-input: #faf3fb; |
| 372 | --color-input-border: #e7c1dc; | 372 | --color-input-border: #e7c1dc; |
| 373 | --color-sidebar-search: #f8f8f7; | 373 | --color-sidebar-search: #f8f8f7; |
| 374 | --color-placeholder: #8b5f90; | 374 | --color-placeholder: #714e76; |
| 375 | --color-icon: #501854; | 375 | --color-icon: #501854; |
| 376 | --color-icon-muted: #ac1668; | 376 | --color-icon-muted: #ac1668; |
| 377 | --color-icon-subtle: #ac1668; | 377 | --color-icon-subtle: #ac1668; |
| ... | @@ -510,7 +510,7 @@ | ... | @@ -510,7 +510,7 @@ |
| 510 | --color-inline-skill-foreground: #f8f1f5; | 510 | --color-inline-skill-foreground: #f8f1f5; |
| 511 | --color-primary: #a3004c; | 511 | --color-primary: #a3004c; |
| 512 | --color-primary-foreground: #fbd0e8; | 512 | --color-primary-foreground: #fbd0e8; |
| 513 | --color-primary-text: #cc729c; | 513 | --color-primary-text: #d07ea4; |
| 514 | --color-primary-shadow: #000000; | 514 | --color-primary-shadow: #000000; |
| 515 | --color-secondary: #362d3d; | 515 | --color-secondary: #362d3d; |
| 516 | --color-secondary-foreground: #d4c7e1; | 516 | --color-secondary-foreground: #d4c7e1; |
| ... | @@ -530,7 +530,7 @@ | ... | @@ -530,7 +530,7 @@ |
| 530 | --color-input: #29232d; | 530 | --color-input: #29232d; |
| 531 | --color-input-border: #302029; | 531 | --color-input-border: #302029; |
| 532 | --color-sidebar-search: #261922; | 532 | --color-sidebar-search: #261922; |
| 533 | --color-placeholder: #968d9f; | 533 | --color-placeholder: #9d94a5; |
| 534 | --color-icon: #f9f8fb; | 534 | --color-icon: #f9f8fb; |
| 535 | --color-icon-muted: #d4c7e1; | 535 | --color-icon-muted: #d4c7e1; |
| 536 | --color-icon-subtle: #e7d0dd; | 536 | --color-icon-subtle: #e7d0dd; |
| ... | @@ -642,7 +642,7 @@ | ... | @@ -642,7 +642,7 @@ |
| 642 | --color-sheet: rgba(243, 247, 244, 0.98); | 642 | --color-sheet: rgba(243, 247, 244, 0.98); |
| 643 | --color-sheet-solid: #f3f7f4; | 643 | --color-sheet-solid: #f3f7f4; |
| 644 | --color-card: #f3f7f4; | 644 | --color-card: #f3f7f4; |
| 645 | --color-grouped-card: #f3f7f4; | 645 | --color-grouped-card: #ecefed; |
| 646 | --color-card-alt: #ecefed; | 646 | --color-card-alt: #ecefed; |
| 647 | --color-card-translucent: rgba(243, 247, 244, 0.8); | 647 | --color-card-translucent: rgba(243, 247, 244, 0.8); |
| 648 | --color-thread-canvas: #f3f7f4; | 648 | --color-thread-canvas: #f3f7f4; |
| ... | @@ -652,11 +652,11 @@ | ... | @@ -652,11 +652,11 @@ |
| 652 | --color-thread-hover: #cae0d5; | 652 | --color-thread-hover: #cae0d5; |
| 653 | --color-row-hover: #d5e6dd; | 653 | --color-row-hover: #d5e6dd; |
| 654 | --color-composer-panel: rgba(243, 247, 244, 0.88); | 654 | --color-composer-panel: rgba(243, 247, 244, 0.88); |
| 655 | --color-composer-surface: rgba(243, 247, 244, 0.94); | 655 | --color-composer-surface: rgba(236, 239, 237, 0.94); |
| 656 | --color-composer-border: rgba(203, 213, 209, 0.54); | 656 | --color-composer-border: rgba(203, 213, 209, 0.8); |
| 657 | --color-foreground: #241523; | 657 | --color-foreground: #241523; |
| 658 | --color-foreground-secondary: #726a71; | 658 | --color-foreground-secondary: #6f686f; |
| 659 | --color-foreground-muted: #6e696f; | 659 | --color-foreground-muted: #6d686e; |
| 660 | --color-foreground-tertiary: #746c73; | 660 | --color-foreground-tertiary: #746c73; |
| 661 | --color-border: #cbd5d1; | 661 | --color-border: #cbd5d1; |
| 662 | --color-focus: #1b7d50; | 662 | --color-focus: #1b7d50; |
| ... | @@ -669,7 +669,7 @@ | ... | @@ -669,7 +669,7 @@ |
| 669 | --color-inline-skill-foreground: #241523; | 669 | --color-inline-skill-foreground: #241523; |
| 670 | --color-primary: #8f6410; | 670 | --color-primary: #8f6410; |
| 671 | --color-primary-foreground: #fffaff; | 671 | --color-primary-foreground: #fffaff; |
| 672 | --color-primary-text: #8f6410; | 672 | --color-primary-text: #8d6210; |
| 673 | --color-primary-shadow: #000000; | 673 | --color-primary-shadow: #000000; |
| 674 | --color-secondary: #e2ede7; | 674 | --color-secondary: #e2ede7; |
| 675 | --color-secondary-foreground: #241523; | 675 | --color-secondary-foreground: #241523; |
| ... | @@ -689,7 +689,7 @@ | ... | @@ -689,7 +689,7 @@ |
| 689 | --color-input: #f3f7f4; | 689 | --color-input: #f3f7f4; |
| 690 | --color-input-border: #becbc5; | 690 | --color-input-border: #becbc5; |
| 691 | --color-sidebar-search: #d3dcd8; | 691 | --color-sidebar-search: #d3dcd8; |
| 692 | --color-placeholder: #716971; | 692 | --color-placeholder: #6f686f; |
| 693 | --color-icon: #241523; | 693 | --color-icon: #241523; |
| 694 | --color-icon-muted: #746c73; | 694 | --color-icon-muted: #746c73; |
| 695 | --color-icon-subtle: #746c73; | 695 | --color-icon-subtle: #746c73; |
| ... | @@ -697,7 +697,7 @@ | ... | @@ -697,7 +697,7 @@ |
| 697 | --color-header-foreground: #241523; | 697 | --color-header-foreground: #241523; |
| 698 | --color-header-border: #d5e6dd; | 698 | --color-header-border: #d5e6dd; |
| 699 | --color-glass-surface: rgba(231, 233, 232, 0.74); | 699 | --color-glass-surface: rgba(231, 233, 232, 0.74); |
| 700 | --color-glass-fallback: rgba(243, 247, 244, 0.94); | 700 | --color-glass-fallback: rgba(236, 239, 237, 0.94); |
| 701 | --color-glass-tint: rgba(231, 233, 232, 0.22); | 701 | --color-glass-tint: rgba(231, 233, 232, 0.22); |
| 702 | --color-status-bar: #f3f7f4; | 702 | --color-status-bar: #f3f7f4; |
| 703 | --color-md-body: #241523; | 703 | --color-md-body: #241523; |
| ... | @@ -801,7 +801,7 @@ | ... | @@ -801,7 +801,7 @@ |
| 801 | --color-sheet: rgba(27, 40, 33, 0.98); | 801 | --color-sheet: rgba(27, 40, 33, 0.98); |
| 802 | --color-sheet-solid: #1b2821; | 802 | --color-sheet-solid: #1b2821; |
| 803 | --color-card: #1b2821; | 803 | --color-card: #1b2821; |
| 804 | --color-grouped-card: #1b2821; | 804 | --color-grouped-card: #36413c; |
| 805 | --color-card-alt: #36413c; | 805 | --color-card-alt: #36413c; |
| 806 | --color-card-translucent: rgba(27, 40, 33, 0.8); | 806 | --color-card-translucent: rgba(27, 40, 33, 0.8); |
| 807 | --color-thread-canvas: #1b2821; | 807 | --color-thread-canvas: #1b2821; |
| ... | @@ -811,11 +811,11 @@ | ... | @@ -811,11 +811,11 @@ |
| 811 | --color-thread-hover: #2a4938; | 811 | --color-thread-hover: #2a4938; |
| 812 | --color-row-hover: #325c46; | 812 | --color-row-hover: #325c46; |
| 813 | --color-composer-panel: rgba(27, 40, 33, 0.92); | 813 | --color-composer-panel: rgba(27, 40, 33, 0.92); |
| 814 | --color-composer-surface: rgba(27, 40, 33, 0.9); | 814 | --color-composer-surface: rgba(54, 65, 60, 0.9); |
| 815 | --color-composer-border: rgba(65, 95, 79, 0.46); | 815 | --color-composer-border: rgba(65, 95, 79, 0.8); |
| 816 | --color-foreground: #fffaff; | 816 | --color-foreground: #fffaff; |
| 817 | --color-foreground-secondary: #a6aaaa; | 817 | --color-foreground-secondary: #afb1b1; |
| 818 | --color-foreground-muted: #a3aba8; | 818 | --color-foreground-muted: #acb2b0; |
| 819 | --color-foreground-tertiary: #919595; | 819 | --color-foreground-tertiary: #919595; |
| 820 | --color-border: #415f4f; | 820 | --color-border: #415f4f; |
| 821 | --color-focus: #69d69a; | 821 | --color-focus: #69d69a; |
| ... | @@ -848,7 +848,7 @@ | ... | @@ -848,7 +848,7 @@ |
| 848 | --color-input: #1b2821; | 848 | --color-input: #1b2821; |
| 849 | --color-input-border: #4f725f; | 849 | --color-input-border: #4f725f; |
| 850 | --color-sidebar-search: #45554d; | 850 | --color-sidebar-search: #45554d; |
| 851 | --color-placeholder: #a9abab; | 851 | --color-placeholder: #afb1b1; |
| 852 | --color-icon: #fffaff; | 852 | --color-icon: #fffaff; |
| 853 | --color-icon-muted: #919595; | 853 | --color-icon-muted: #919595; |
| 854 | --color-icon-subtle: #919595; | 854 | --color-icon-subtle: #919595; |
| ... | @@ -856,7 +856,7 @@ | ... | @@ -856,7 +856,7 @@ |
| 856 | --color-header-foreground: #fffaff; | 856 | --color-header-foreground: #fffaff; |
| 857 | --color-header-border: #36654c; | 857 | --color-header-border: #36654c; |
| 858 | --color-glass-surface: rgba(68, 77, 73, 0.74); | 858 | --color-glass-surface: rgba(68, 77, 73, 0.74); |
| 859 | --color-glass-fallback: rgba(27, 40, 33, 0.9); | 859 | --color-glass-fallback: rgba(54, 65, 60, 0.9); |
| 860 | --color-glass-tint: rgba(68, 77, 73, 0.22); | 860 | --color-glass-tint: rgba(68, 77, 73, 0.22); |
| 861 | --color-status-bar: #1b2821; | 861 | --color-status-bar: #1b2821; |
| 862 | --color-md-body: #fffaff; | 862 | --color-md-body: #fffaff; |
| ... | @@ -960,7 +960,7 @@ | ... | @@ -960,7 +960,7 @@ |
| 960 | --color-sheet: rgba(245, 247, 248, 0.98); | 960 | --color-sheet: rgba(245, 247, 248, 0.98); |
| 961 | --color-sheet-solid: #f5f7f8; | 961 | --color-sheet-solid: #f5f7f8; |
| 962 | --color-card: #f5f7f8; | 962 | --color-card: #f5f7f8; |
| 963 | --color-grouped-card: #f5f7f8; | 963 | --color-grouped-card: #edeff1; |
| 964 | --color-card-alt: #edeff1; | 964 | --color-card-alt: #edeff1; |
| 965 | --color-card-translucent: rgba(245, 247, 248, 0.8); | 965 | --color-card-translucent: rgba(245, 247, 248, 0.8); |
| 966 | --color-thread-canvas: #f5f7f8; | 966 | --color-thread-canvas: #f5f7f8; |
| ... | @@ -970,10 +970,10 @@ | ... | @@ -970,10 +970,10 @@ |
| 970 | --color-thread-hover: #cdddea; | 970 | --color-thread-hover: #cdddea; |
| 971 | --color-row-hover: #d8e4ee; | 971 | --color-row-hover: #d8e4ee; |
| 972 | --color-composer-panel: rgba(245, 247, 248, 0.88); | 972 | --color-composer-panel: rgba(245, 247, 248, 0.88); |
| 973 | --color-composer-surface: rgba(245, 247, 248, 0.94); | 973 | --color-composer-surface: rgba(237, 239, 241, 0.94); |
| 974 | --color-composer-border: rgba(205, 212, 220, 0.54); | 974 | --color-composer-border: rgba(205, 212, 220, 0.8); |
| 975 | --color-foreground: #241523; | 975 | --color-foreground: #241523; |
| 976 | --color-foreground-secondary: #726a73; | 976 | --color-foreground-secondary: #706870; |
| 977 | --color-foreground-muted: #6f6873; | 977 | --color-foreground-muted: #6f6873; |
| 978 | --color-foreground-tertiary: #746c75; | 978 | --color-foreground-tertiary: #746c75; |
| 979 | --color-border: #cdd4dc; | 979 | --color-border: #cdd4dc; |
| ... | @@ -1007,7 +1007,7 @@ | ... | @@ -1007,7 +1007,7 @@ |
| 1007 | --color-input: #f5f7f8; | 1007 | --color-input: #f5f7f8; |
| 1008 | --color-input-border: #c0c9d4; | 1008 | --color-input-border: #c0c9d4; |
| 1009 | --color-sidebar-search: #d5dbe2; | 1009 | --color-sidebar-search: #d5dbe2; |
| 1010 | --color-placeholder: #716972; | 1010 | --color-placeholder: #706870; |
| 1011 | --color-icon: #241523; | 1011 | --color-icon: #241523; |
| 1012 | --color-icon-muted: #746c75; | 1012 | --color-icon-muted: #746c75; |
| 1013 | --color-icon-subtle: #746c75; | 1013 | --color-icon-subtle: #746c75; |
| ... | @@ -1015,7 +1015,7 @@ | ... | @@ -1015,7 +1015,7 @@ |
| 1015 | --color-header-foreground: #241523; | 1015 | --color-header-foreground: #241523; |
| 1016 | --color-header-border: #d8e4ee; | 1016 | --color-header-border: #d8e4ee; |
| 1017 | --color-glass-surface: rgba(232, 233, 235, 0.74); | 1017 | --color-glass-surface: rgba(232, 233, 235, 0.74); |
| 1018 | --color-glass-fallback: rgba(245, 247, 248, 0.94); | 1018 | --color-glass-fallback: rgba(237, 239, 241, 0.94); |
| 1019 | --color-glass-tint: rgba(232, 233, 235, 0.22); | 1019 | --color-glass-tint: rgba(232, 233, 235, 0.22); |
| 1020 | --color-status-bar: #f5f7f8; | 1020 | --color-status-bar: #f5f7f8; |
| 1021 | --color-md-body: #241523; | 1021 | --color-md-body: #241523; |
| ... | @@ -1119,7 +1119,7 @@ | ... | @@ -1119,7 +1119,7 @@ |
| 1119 | --color-sheet: rgba(23, 33, 43, 0.98); | 1119 | --color-sheet: rgba(23, 33, 43, 0.98); |
| 1120 | --color-sheet-solid: #17212b; | 1120 | --color-sheet-solid: #17212b; |
| 1121 | --color-card: #17212b; | 1121 | --color-card: #17212b; |
| 1122 | --color-grouped-card: #17212b; | 1122 | --color-grouped-card: #333b45; |
| 1123 | --color-card-alt: #333b45; | 1123 | --color-card-alt: #333b45; |
| 1124 | --color-card-translucent: rgba(23, 33, 43, 0.8); | 1124 | --color-card-translucent: rgba(23, 33, 43, 0.8); |
| 1125 | --color-thread-canvas: #17212b; | 1125 | --color-thread-canvas: #17212b; |
| ... | @@ -1129,11 +1129,11 @@ | ... | @@ -1129,11 +1129,11 @@ |
| 1129 | --color-thread-hover: #283e50; | 1129 | --color-thread-hover: #283e50; |
| 1130 | --color-row-hover: #324e66; | 1130 | --color-row-hover: #324e66; |
| 1131 | --color-composer-panel: rgba(23, 33, 43, 0.92); | 1131 | --color-composer-panel: rgba(23, 33, 43, 0.92); |
| 1132 | --color-composer-surface: rgba(23, 33, 43, 0.9); | 1132 | --color-composer-surface: rgba(51, 59, 69, 0.9); |
| 1133 | --color-composer-border: rgba(64, 85, 103, 0.46); | 1133 | --color-composer-border: rgba(64, 85, 103, 0.8); |
| 1134 | --color-foreground: #fffaff; | 1134 | --color-foreground: #fffaff; |
| 1135 | --color-foreground-secondary: #a2a3aa; | 1135 | --color-foreground-secondary: #a5a7ad; |
| 1136 | --color-foreground-muted: #9ea4ad; | 1136 | --color-foreground-muted: #a2a7b0; |
| 1137 | --color-foreground-tertiary: #8d8f97; | 1137 | --color-foreground-tertiary: #8d8f97; |
| 1138 | --color-border: #405567; | 1138 | --color-border: #405567; |
| 1139 | --color-focus: #70b9ee; | 1139 | --color-focus: #70b9ee; |
| ... | @@ -1166,7 +1166,7 @@ | ... | @@ -1166,7 +1166,7 @@ |
| 1166 | --color-input: #17212b; | 1166 | --color-input: #17212b; |
| 1167 | --color-input-border: #4f677b; | 1167 | --color-input-border: #4f677b; |
| 1168 | --color-sidebar-search: #424e5a; | 1168 | --color-sidebar-search: #424e5a; |
| 1169 | --color-placeholder: #a4a4ac; | 1169 | --color-placeholder: #a6a6ad; |
| 1170 | --color-icon: #fffaff; | 1170 | --color-icon: #fffaff; |
| 1171 | --color-icon-muted: #8d8f97; | 1171 | --color-icon-muted: #8d8f97; |
| 1172 | --color-icon-subtle: #8d8f97; | 1172 | --color-icon-subtle: #8d8f97; |
| ... | @@ -1174,7 +1174,7 @@ | ... | @@ -1174,7 +1174,7 @@ |
| 1174 | --color-header-foreground: #fffaff; | 1174 | --color-header-foreground: #fffaff; |
| 1175 | --color-header-border: #36566f; | 1175 | --color-header-border: #36566f; |
| 1176 | --color-glass-surface: rgba(65, 72, 81, 0.74); | 1176 | --color-glass-surface: rgba(65, 72, 81, 0.74); |
| 1177 | --color-glass-fallback: rgba(23, 33, 43, 0.9); | 1177 | --color-glass-fallback: rgba(51, 59, 69, 0.9); |
| 1178 | --color-glass-tint: rgba(65, 72, 81, 0.22); | 1178 | --color-glass-tint: rgba(65, 72, 81, 0.22); |
| 1179 | --color-status-bar: #17212b; | 1179 | --color-status-bar: #17212b; |
| 1180 | --color-md-body: #fffaff; | 1180 | --color-md-body: #fffaff; |
| ... | @@ -1278,7 +1278,7 @@ | ... | @@ -1278,7 +1278,7 @@ |
| 1278 | --color-sheet: rgba(249, 247, 245, 0.98); | 1278 | --color-sheet: rgba(249, 247, 245, 0.98); |
| 1279 | --color-sheet-solid: #f9f7f5; | 1279 | --color-sheet-solid: #f9f7f5; |
| 1280 | --color-card: #f9f7f5; | 1280 | --color-card: #f9f7f5; |
| 1281 | --color-grouped-card: #f9f7f5; | 1281 | --color-grouped-card: #f1efee; |
| 1282 | --color-card-alt: #f1efee; | 1282 | --color-card-alt: #f1efee; |
| 1283 | --color-card-translucent: rgba(249, 247, 245, 0.8); | 1283 | --color-card-translucent: rgba(249, 247, 245, 0.8); |
| 1284 | --color-thread-canvas: #f9f7f5; | 1284 | --color-thread-canvas: #f9f7f5; |
| ... | @@ -1288,11 +1288,11 @@ | ... | @@ -1288,11 +1288,11 @@ |
| 1288 | --color-thread-hover: #ead8cf; | 1288 | --color-thread-hover: #ead8cf; |
| 1289 | --color-row-hover: #eee0d9; | 1289 | --color-row-hover: #eee0d9; |
| 1290 | --color-composer-panel: rgba(249, 247, 245, 0.88); | 1290 | --color-composer-panel: rgba(249, 247, 245, 0.88); |
| 1291 | --color-composer-surface: rgba(249, 247, 245, 0.94); | 1291 | --color-composer-surface: rgba(241, 239, 238, 0.94); |
| 1292 | --color-composer-border: rgba(221, 210, 206, 0.54); | 1292 | --color-composer-border: rgba(221, 210, 206, 0.8); |
| 1293 | --color-foreground: #241523; | 1293 | --color-foreground: #241523; |
| 1294 | --color-foreground-secondary: #746a72; | 1294 | --color-foreground-secondary: #726870; |
| 1295 | --color-foreground-muted: #74686f; | 1295 | --color-foreground-muted: #73686e; |
| 1296 | --color-foreground-tertiary: #766c74; | 1296 | --color-foreground-tertiary: #766c74; |
| 1297 | --color-border: #ddd2ce; | 1297 | --color-border: #ddd2ce; |
| 1298 | --color-focus: #ae552a; | 1298 | --color-focus: #ae552a; |
| ... | @@ -1325,7 +1325,7 @@ | ... | @@ -1325,7 +1325,7 @@ |
| 1325 | --color-input: #f9f7f5; | 1325 | --color-input: #f9f7f5; |
| 1326 | --color-input-border: #d4c6c1; | 1326 | --color-input-border: #d4c6c1; |
| 1327 | --color-sidebar-search: #e2d9d6; | 1327 | --color-sidebar-search: #e2d9d6; |
| 1328 | --color-placeholder: #736971; | 1328 | --color-placeholder: #726870; |
| 1329 | --color-icon: #241523; | 1329 | --color-icon: #241523; |
| 1330 | --color-icon-muted: #766c74; | 1330 | --color-icon-muted: #766c74; |
| 1331 | --color-icon-subtle: #766c74; | 1331 | --color-icon-subtle: #766c74; |
| ... | @@ -1333,7 +1333,7 @@ | ... | @@ -1333,7 +1333,7 @@ |
| 1333 | --color-header-foreground: #241523; | 1333 | --color-header-foreground: #241523; |
| 1334 | --color-header-border: #eee0d9; | 1334 | --color-header-border: #eee0d9; |
| 1335 | --color-glass-surface: rgba(236, 233, 233, 0.74); | 1335 | --color-glass-surface: rgba(236, 233, 233, 0.74); |
| 1336 | --color-glass-fallback: rgba(249, 247, 245, 0.94); | 1336 | --color-glass-fallback: rgba(241, 239, 238, 0.94); |
| 1337 | --color-glass-tint: rgba(236, 233, 233, 0.22); | 1337 | --color-glass-tint: rgba(236, 233, 233, 0.22); |
| 1338 | --color-status-bar: #f9f7f5; | 1338 | --color-status-bar: #f9f7f5; |
| 1339 | --color-md-body: #241523; | 1339 | --color-md-body: #241523; |
| ... | @@ -1437,7 +1437,7 @@ | ... | @@ -1437,7 +1437,7 @@ |
| 1437 | --color-sheet: rgba(41, 30, 26, 0.98); | 1437 | --color-sheet: rgba(41, 30, 26, 0.98); |
| 1438 | --color-sheet-solid: #291e1a; | 1438 | --color-sheet-solid: #291e1a; |
| 1439 | --color-card: #291e1a; | 1439 | --color-card: #291e1a; |
| 1440 | --color-grouped-card: #291e1a; | 1440 | --color-grouped-card: #433835; |
| 1441 | --color-card-alt: #433835; | 1441 | --color-card-alt: #433835; |
| 1442 | --color-card-translucent: rgba(41, 30, 26, 0.8); | 1442 | --color-card-translucent: rgba(41, 30, 26, 0.8); |
| 1443 | --color-thread-canvas: #291e1a; | 1443 | --color-thread-canvas: #291e1a; |
| ... | @@ -1447,11 +1447,11 @@ | ... | @@ -1447,11 +1447,11 @@ |
| 1447 | --color-thread-hover: #4f3528; | 1447 | --color-thread-hover: #4f3528; |
| 1448 | --color-row-hover: #644330; | 1448 | --color-row-hover: #644330; |
| 1449 | --color-composer-panel: rgba(41, 30, 26, 0.92); | 1449 | --color-composer-panel: rgba(41, 30, 26, 0.92); |
| 1450 | --color-composer-surface: rgba(41, 30, 26, 0.9); | 1450 | --color-composer-surface: rgba(67, 56, 53, 0.9); |
| 1451 | --color-composer-border: rgba(102, 76, 63, 0.46); | 1451 | --color-composer-border: rgba(102, 76, 63, 0.8); |
| 1452 | --color-foreground: #fffaff; | 1452 | --color-foreground: #fffaff; |
| 1453 | --color-foreground-secondary: #a8a2a2; | 1453 | --color-foreground-secondary: #aca5a6; |
| 1454 | --color-foreground-muted: #aca19f; | 1454 | --color-foreground-muted: #afa5a2; |
| 1455 | --color-foreground-tertiary: #968e8f; | 1455 | --color-foreground-tertiary: #968e8f; |
| 1456 | --color-border: #664c3f; | 1456 | --color-border: #664c3f; |
| 1457 | --color-focus: #f09a64; | 1457 | --color-focus: #f09a64; |
| ... | @@ -1484,7 +1484,7 @@ | ... | @@ -1484,7 +1484,7 @@ |
| 1484 | --color-input: #291e1a; | 1484 | --color-input: #291e1a; |
| 1485 | --color-input-border: #7a5d4d; | 1485 | --color-input-border: #7a5d4d; |
| 1486 | --color-sidebar-search: #584943; | 1486 | --color-sidebar-search: #584943; |
| 1487 | --color-placeholder: #aba3a5; | 1487 | --color-placeholder: #aca5a6; |
| 1488 | --color-icon: #fffaff; | 1488 | --color-icon: #fffaff; |
| 1489 | --color-icon-muted: #968e8f; | 1489 | --color-icon-muted: #968e8f; |
| 1490 | --color-icon-subtle: #968e8f; | 1490 | --color-icon-subtle: #968e8f; |
| ... | @@ -1492,7 +1492,7 @@ | ... | @@ -1492,7 +1492,7 @@ |
| 1492 | --color-header-foreground: #fffaff; | 1492 | --color-header-foreground: #fffaff; |
| 1493 | --color-header-border: #6e4934; | 1493 | --color-header-border: #6e4934; |
| 1494 | --color-glass-surface: rgba(79, 69, 67, 0.74); | 1494 | --color-glass-surface: rgba(79, 69, 67, 0.74); |
| 1495 | --color-glass-fallback: rgba(41, 30, 26, 0.9); | 1495 | --color-glass-fallback: rgba(67, 56, 53, 0.9); |
| 1496 | --color-glass-tint: rgba(79, 69, 67, 0.22); | 1496 | --color-glass-tint: rgba(79, 69, 67, 0.22); |
| 1497 | --color-status-bar: #291e1a; | 1497 | --color-status-bar: #291e1a; |
| 1498 | --color-md-body: #fffaff; | 1498 | --color-md-body: #fffaff; |
| ... | @@ -1596,7 +1596,7 @@ | ... | @@ -1596,7 +1596,7 @@ |
| 1596 | --color-sheet: rgba(248, 247, 249, 0.98); | 1596 | --color-sheet: rgba(248, 247, 249, 0.98); |
| 1597 | --color-sheet-solid: #f8f7f9; | 1597 | --color-sheet-solid: #f8f7f9; |
| 1598 | --color-card: #f8f7f9; | 1598 | --color-card: #f8f7f9; |
| 1599 | --color-grouped-card: #f8f7f9; | 1599 | --color-grouped-card: #f0eff2; |
| 1600 | --color-card-alt: #f0eff2; | 1600 | --color-card-alt: #f0eff2; |
| 1601 | --color-card-translucent: rgba(248, 247, 249, 0.8); | 1601 | --color-card-translucent: rgba(248, 247, 249, 0.8); |
| 1602 | --color-thread-canvas: #f8f7f9; | 1602 | --color-thread-canvas: #f8f7f9; |
| ... | @@ -1606,11 +1606,11 @@ | ... | @@ -1606,11 +1606,11 @@ |
| 1606 | --color-thread-hover: #ded8ed; | 1606 | --color-thread-hover: #ded8ed; |
| 1607 | --color-row-hover: #e5e0f0; | 1607 | --color-row-hover: #e5e0f0; |
| 1608 | --color-composer-panel: rgba(248, 247, 249, 0.88); | 1608 | --color-composer-panel: rgba(248, 247, 249, 0.88); |
| 1609 | --color-composer-surface: rgba(248, 247, 249, 0.94); | 1609 | --color-composer-surface: rgba(240, 239, 242, 0.94); |
| 1610 | --color-composer-border: rgba(214, 209, 222, 0.54); | 1610 | --color-composer-border: rgba(214, 209, 222, 0.8); |
| 1611 | --color-foreground: #241523; | 1611 | --color-foreground: #241523; |
| 1612 | --color-foreground-secondary: #746a74; | 1612 | --color-foreground-secondary: #716871; |
| 1613 | --color-foreground-muted: #726874; | 1613 | --color-foreground-muted: #716773; |
| 1614 | --color-foreground-tertiary: #766c76; | 1614 | --color-foreground-tertiary: #766c76; |
| 1615 | --color-border: #d6d1de; | 1615 | --color-border: #d6d1de; |
| 1616 | --color-focus: #7253b9; | 1616 | --color-focus: #7253b9; |
| ... | @@ -1643,7 +1643,7 @@ | ... | @@ -1643,7 +1643,7 @@ |
| 1643 | --color-input: #f8f7f9; | 1643 | --color-input: #f8f7f9; |
| 1644 | --color-input-border: #ccc5d6; | 1644 | --color-input-border: #ccc5d6; |
| 1645 | --color-sidebar-search: #ddd9e3; | 1645 | --color-sidebar-search: #ddd9e3; |
| 1646 | --color-placeholder: #736973; | 1646 | --color-placeholder: #716871; |
| 1647 | --color-icon: #241523; | 1647 | --color-icon: #241523; |
| 1648 | --color-icon-muted: #766c76; | 1648 | --color-icon-muted: #766c76; |
| 1649 | --color-icon-subtle: #766c76; | 1649 | --color-icon-subtle: #766c76; |
| ... | @@ -1651,7 +1651,7 @@ | ... | @@ -1651,7 +1651,7 @@ |
| 1651 | --color-header-foreground: #241523; | 1651 | --color-header-foreground: #241523; |
| 1652 | --color-header-border: #e5e0f0; | 1652 | --color-header-border: #e5e0f0; |
| 1653 | --color-glass-surface: rgba(235, 233, 237, 0.74); | 1653 | --color-glass-surface: rgba(235, 233, 237, 0.74); |
| 1654 | --color-glass-fallback: rgba(248, 247, 249, 0.94); | 1654 | --color-glass-fallback: rgba(240, 239, 242, 0.94); |
| 1655 | --color-glass-tint: rgba(235, 233, 237, 0.22); | 1655 | --color-glass-tint: rgba(235, 233, 237, 0.22); |
| 1656 | --color-status-bar: #f8f7f9; | 1656 | --color-status-bar: #f8f7f9; |
| 1657 | --color-md-body: #241523; | 1657 | --color-md-body: #241523; |
| ... | @@ -1755,7 +1755,7 @@ | ... | @@ -1755,7 +1755,7 @@ |
| 1755 | --color-sheet: rgba(29, 25, 41, 0.98); | 1755 | --color-sheet: rgba(29, 25, 41, 0.98); |
| 1756 | --color-sheet-solid: #1d1929; | 1756 | --color-sheet-solid: #1d1929; |
| 1757 | --color-card: #1d1929; | 1757 | --color-card: #1d1929; |
| 1758 | --color-grouped-card: #1d1929; | 1758 | --color-grouped-card: #383443; |
| 1759 | --color-card-alt: #383443; | 1759 | --color-card-alt: #383443; |
| 1760 | --color-card-translucent: rgba(29, 25, 41, 0.8); | 1760 | --color-card-translucent: rgba(29, 25, 41, 0.8); |
| 1761 | --color-thread-canvas: #1d1929; | 1761 | --color-thread-canvas: #1d1929; |
| ... | @@ -1765,8 +1765,8 @@ | ... | @@ -1765,8 +1765,8 @@ |
| 1765 | --color-thread-hover: #352c4f; | 1765 | --color-thread-hover: #352c4f; |
| 1766 | --color-row-hover: #433765; | 1766 | --color-row-hover: #433765; |
| 1767 | --color-composer-panel: rgba(29, 25, 41, 0.92); | 1767 | --color-composer-panel: rgba(29, 25, 41, 0.92); |
| 1768 | --color-composer-surface: rgba(29, 25, 41, 0.9); | 1768 | --color-composer-surface: rgba(56, 52, 67, 0.9); |
| 1769 | --color-composer-border: rgba(77, 67, 102, 0.46); | 1769 | --color-composer-border: rgba(77, 67, 102, 0.8); |
| 1770 | --color-foreground: #fffaff; | 1770 | --color-foreground: #fffaff; |
| 1771 | --color-foreground-secondary: #a09da6; | 1771 | --color-foreground-secondary: #a09da6; |
| 1772 | --color-foreground-muted: #a19cab; | 1772 | --color-foreground-muted: #a19cab; |
| ... | @@ -1810,7 +1810,7 @@ | ... | @@ -1810,7 +1810,7 @@ |
| 1810 | --color-header-foreground: #fffaff; | 1810 | --color-header-foreground: #fffaff; |
| 1811 | --color-header-border: #4a3c70; | 1811 | --color-header-border: #4a3c70; |
| 1812 | --color-glass-surface: rgba(69, 66, 80, 0.74); | 1812 | --color-glass-surface: rgba(69, 66, 80, 0.74); |
| 1813 | --color-glass-fallback: rgba(29, 25, 41, 0.9); | 1813 | --color-glass-fallback: rgba(56, 52, 67, 0.9); |
| 1814 | --color-glass-tint: rgba(69, 66, 80, 0.22); | 1814 | --color-glass-tint: rgba(69, 66, 80, 0.22); |
| 1815 | --color-status-bar: #1d1929; | 1815 | --color-status-bar: #1d1929; |
| 1816 | --color-md-body: #fffaff; | 1816 | --color-md-body: #fffaff; |
apps/mobile/src/features/pull-requests/MaterialFloatingToolbar.android.tsx+1-1| ... | @@ -15,7 +15,7 @@ export function MaterialFloatingToolbar(props: { readonly children: ReactElement | ... | @@ -15,7 +15,7 @@ export function MaterialFloatingToolbar(props: { readonly children: ReactElement |
| 15 | > | 15 | > |
| 16 | <HorizontalFloatingToolbar | 16 | <HorizontalFloatingToolbar |
| 17 | colors={{ | 17 | colors={{ |
| 18 | toolbarContainerColor: colors["--color-card-alt"], | 18 | toolbarContainerColor: colors["--color-grouped-card"], |
| 19 | toolbarContentColor: colors["--color-foreground"], | 19 | toolbarContentColor: colors["--color-foreground"], |
| 20 | }} | 20 | }} |
| 21 | > | 21 | > |
apps/mobile/src/features/pull-requests/PullRequestDescription.tsx+7-6| ... | @@ -6,7 +6,7 @@ import { | ... | @@ -6,7 +6,7 @@ import { |
| 6 | } from "./PullRequestMedia"; | 6 | } from "./PullRequestMedia"; |
| 7 | import { PullRequestSectionHeading } from "./PullRequestSectionHeading"; | 7 | import { PullRequestSectionHeading } from "./PullRequestSectionHeading"; |
| 8 | import { useContext, useMemo, useState } from "react"; | 8 | import { useContext, useMemo, useState } from "react"; |
| 9 | import { Pressable, ScrollView, StyleSheet, View } from "react-native"; | 9 | import { Platform, Pressable, ScrollView, StyleSheet, View } from "react-native"; |
| 10 | import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg"; | 10 | import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg"; |
| 11 | import { pullRequestImageSource } from "./pullRequestMediaSource"; | 11 | import { pullRequestImageSource } from "./pullRequestMediaSource"; |
| 12 | import { parseMarkdownDocument } from "../../lib/markdownDocument"; | 12 | import { parseMarkdownDocument } from "../../lib/markdownDocument"; |
| ... | @@ -30,6 +30,7 @@ export function PullRequestDescription(props: { | ... | @@ -30,6 +30,7 @@ export function PullRequestDescription(props: { |
| 30 | const [bodyHeight, setBodyHeight] = useState(0); | 30 | const [bodyHeight, setBodyHeight] = useState(0); |
| 31 | const mediaContext = useContext(PullRequestMediaContext); | 31 | const mediaContext = useContext(PullRequestMediaContext); |
| 32 | const { themeVariables: colors } = useAppearancePreferences(); | 32 | const { themeVariables: colors } = useAppearancePreferences(); |
| 33 | const fadeColor = colors[Platform.OS === "android" ? "--color-sheet-solid" : "--color-screen"]; | ||
| 33 | const content = useMemo(() => { | 34 | const content = useMemo(() => { |
| 34 | const document = parseMarkdownDocument(props.markdown); | 35 | const document = parseMarkdownDocument(props.markdown); |
| 35 | const content = pullRequestDescriptionContent(document, splitPullRequestBody(props.markdown)); | 36 | const content = pullRequestDescriptionContent(document, splitPullRequestBody(props.markdown)); |
| ... | @@ -87,10 +88,10 @@ export function PullRequestDescription(props: { | ... | @@ -87,10 +88,10 @@ export function PullRequestDescription(props: { |
| 87 | y2="80" | 88 | y2="80" |
| 88 | gradientUnits="userSpaceOnUse" | 89 | gradientUnits="userSpaceOnUse" |
| 89 | > | 90 | > |
| 90 | <Stop offset="0" stopColor={colors["--color-screen"]} stopOpacity={0} /> | 91 | <Stop offset="0" stopColor={fadeColor} stopOpacity={0} /> |
| 91 | <Stop offset="0.5" stopColor={colors["--color-screen"]} stopOpacity={0.94} /> | 92 | <Stop offset="0.5" stopColor={fadeColor} stopOpacity={0.94} /> |
| 92 | <Stop offset="0.72" stopColor={colors["--color-screen"]} stopOpacity={1} /> | 93 | <Stop offset="0.72" stopColor={fadeColor} stopOpacity={1} /> |
| 93 | <Stop offset="1" stopColor={colors["--color-screen"]} stopOpacity={1} /> | 94 | <Stop offset="1" stopColor={fadeColor} stopOpacity={1} /> |
| 94 | </LinearGradient> | 95 | </LinearGradient> |
| 95 | </Defs> | 96 | </Defs> |
| 96 | <Rect width="100%" height="100%" fill="url(#description-fade)" /> | 97 | <Rect width="100%" height="100%" fill="url(#description-fade)" /> |
| ... | @@ -102,7 +103,7 @@ export function PullRequestDescription(props: { | ... | @@ -102,7 +103,7 @@ export function PullRequestDescription(props: { |
| 102 | left: 0, | 103 | left: 0, |
| 103 | right: 0, | 104 | right: 0, |
| 104 | height: 44, | 105 | height: 44, |
| 105 | backgroundColor: colors["--color-screen"], | 106 | backgroundColor: fadeColor, |
| 106 | }} | 107 | }} |
| 107 | /> | 108 | /> |
| 108 | </View> | 109 | </View> |
apps/mobile/src/features/workspace/AndroidWorkspaceNavigation.android.tsx+6-10| ... | @@ -12,7 +12,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; | ... | @@ -12,7 +12,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 12 | import { useKeyboardState } from "react-native-keyboard-controller"; | 12 | import { useKeyboardState } from "react-native-keyboard-controller"; |
| 13 | 13 | ||
| 14 | import { useAndroidControlSizing } from "../../components/useAndroidControlSizing"; | 14 | import { useAndroidControlSizing } from "../../components/useAndroidControlSizing"; |
| 15 | import { flattenThemeColor, themeColorWithAlpha } from "../../lib/mobileTheme"; | 15 | import { androidWorkspaceNavigationColors } from "./android-workspace-navigation-colors"; |
| 16 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | 16 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; |
| 17 | import { useWorkspaceTabs } from "./WorkspaceTabsContext"; | 17 | import { useWorkspaceTabs } from "./WorkspaceTabsContext"; |
| 18 | import type { WorkspaceTab } from "./workspace-tabs-state"; | 18 | import type { WorkspaceTab } from "./workspace-tabs-state"; |
| ... | @@ -49,12 +49,8 @@ export function AndroidWorkspaceNavigation(props: { | ... | @@ -49,12 +49,8 @@ export function AndroidWorkspaceNavigation(props: { |
| 49 | const { themeAppearance, themeVariables: colors } = useAppearancePreferences(); | 49 | const { themeAppearance, themeVariables: colors } = useAppearancePreferences(); |
| 50 | const { scale } = useAndroidControlSizing(); | 50 | const { scale } = useAndroidControlSizing(); |
| 51 | const rail = Boolean(props.rail); | 51 | const rail = Boolean(props.rail); |
| 52 | const containerColor = rail | 52 | const { containerColor, indicatorColor, selectedColor, unselectedColor } = |
| 53 | ? colors["--color-header"] | 53 | androidWorkspaceNavigationColors(colors, rail); |
| 54 | : flattenThemeColor( | ||
| 55 | themeColorWithAlpha(colors["--color-header-foreground"], 0.05), | ||
| 56 | flattenThemeColor(colors["--color-header"], colors["--color-screen"]), | ||
| 57 | ); | ||
| 58 | if (!rail && keyboardVisible) return null; | 54 | if (!rail && keyboardVisible) return null; |
| 59 | 55 | ||
| 60 | return ( | 56 | return ( |
| ... | @@ -77,9 +73,9 @@ export function AndroidWorkspaceNavigation(props: { | ... | @@ -77,9 +73,9 @@ export function AndroidWorkspaceNavigation(props: { |
| 77 | dark={themeAppearance === "dark"} | 73 | dark={themeAppearance === "dark"} |
| 78 | textScale={scale} | 74 | textScale={scale} |
| 79 | containerColor={nativeColor(containerColor)} | 75 | containerColor={nativeColor(containerColor)} |
| 80 | selectedColor={nativeColor(colors["--color-secondary-foreground"])} | 76 | selectedColor={nativeColor(selectedColor)} |
| 81 | indicatorColor={nativeColor(colors["--color-secondary"])} | 77 | indicatorColor={nativeColor(indicatorColor)} |
| 82 | unselectedColor={nativeColor(colors["--color-header-foreground"])} | 78 | unselectedColor={nativeColor(unselectedColor)} |
| 83 | onTabSelected={(event) => { | 79 | onTabSelected={(event) => { |
| 84 | Keyboard.dismiss(); | 80 | Keyboard.dismiss(); |
| 85 | const tab = event.nativeEvent.tab; | 81 | const tab = event.nativeEvent.tab; |
apps/mobile/src/features/workspace/android-workspace-navigation-colors.test.ts created+110| ... | @@ -0,0 +1,110 @@ | ||
| 1 | import { describe, expect, it } from "vite-plus/test"; | ||
| 2 | import { Hct, SchemeTonalSpot, argbFromHex, hexFromArgb } from "@material/material-color-utilities"; | ||
| 3 | |||
| 4 | import { MOBILE_THEME_IDS } from "../../lib/mobileTheme"; | ||
| 5 | import { getMobileThemeRuntimeVariables } from "../../lib/mobileThemeVariables"; | ||
| 6 | import { materialYouPaletteToMobileThemeVariables } from "../../lib/materialYouTheme.android"; | ||
| 7 | import type { MaterialYouPalette } from "../../lib/materialYouPalette"; | ||
| 8 | import { androidWorkspaceNavigationColors } from "./android-workspace-navigation-colors"; | ||
| 9 | |||
| 10 | function luminance(hex: string) { | ||
| 11 | const channels = hex | ||
| 12 | .slice(1, 7) | ||
| 13 | .match(/.{2}/g)! | ||
| 14 | .map((channel) => Number.parseInt(channel, 16) / 255) | ||
| 15 | .map((channel) => (channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4)); | ||
| 16 | return 0.2126 * channels[0]! + 0.7152 * channels[1]! + 0.0722 * channels[2]!; | ||
| 17 | } | ||
| 18 | |||
| 19 | function contrast(first: string, second: string) { | ||
| 20 | const firstLuminance = luminance(first); | ||
| 21 | const secondLuminance = luminance(second); | ||
| 22 | return ( | ||
| 23 | (Math.max(firstLuminance, secondLuminance) + 0.05) / | ||
| 24 | (Math.min(firstLuminance, secondLuminance) + 0.05) | ||
| 25 | ); | ||
| 26 | } | ||
| 27 | |||
| 28 | function expectVisibleSelection(colors: ReturnType<typeof androidWorkspaceNavigationColors>) { | ||
| 29 | expect(contrast(colors.indicatorColor, colors.containerColor)).toBeGreaterThanOrEqual(1.5); | ||
| 30 | // Native labels sit below the indicator, while their icons sit within it. | ||
| 31 | for (const surface of [colors.containerColor, colors.indicatorColor]) { | ||
| 32 | expect(contrast(colors.selectedColor, surface)).toBeGreaterThanOrEqual(4.5); | ||
| 33 | } | ||
| 34 | expect(contrast(colors.unselectedColor, colors.containerColor)).toBeGreaterThanOrEqual(4.5); | ||
| 35 | } | ||
| 36 | |||
| 37 | describe("Android workspace navigation colors", () => { | ||
| 38 | it.each(MOBILE_THEME_IDS)( | ||
| 39 | "keeps %s selection visible in both navigation positions", | ||
| 40 | (themeId) => { | ||
| 41 | for (const appearance of ["light", "dark"] as const) { | ||
| 42 | const variables = getMobileThemeRuntimeVariables(themeId, appearance, "android"); | ||
| 43 | const snapshot = { ...variables }; | ||
| 44 | for (const rail of [false, true]) { | ||
| 45 | expectVisibleSelection(androidWorkspaceNavigationColors(variables, rail)); | ||
| 46 | } | ||
| 47 | expect(variables).toEqual(snapshot); | ||
| 48 | } | ||
| 49 | }, | ||
| 50 | ); | ||
| 51 | |||
| 52 | it.each(["#6750a4", "#006a6a", "#9c4146"])( | ||
| 53 | "keeps Material You selection visible for system seed %s", | ||
| 54 | (seed) => { | ||
| 55 | for (const appearance of ["light", "dark"] as const) { | ||
| 56 | const scheme = new SchemeTonalSpot( | ||
| 57 | Hct.fromInt(argbFromHex(seed)), | ||
| 58 | appearance === "dark", | ||
| 59 | 0, | ||
| 60 | ); | ||
| 61 | const roles = [ | ||
| 62 | "primary", | ||
| 63 | "onPrimary", | ||
| 64 | "primaryContainer", | ||
| 65 | "onPrimaryContainer", | ||
| 66 | "inversePrimary", | ||
| 67 | "secondaryContainer", | ||
| 68 | "onSecondaryContainer", | ||
| 69 | "tertiary", | ||
| 70 | "tertiaryContainer", | ||
| 71 | "onTertiaryContainer", | ||
| 72 | "surface", | ||
| 73 | "onSurface", | ||
| 74 | "onSurfaceVariant", | ||
| 75 | "surfaceContainer", | ||
| 76 | "surfaceContainerHigh", | ||
| 77 | "surfaceContainerHighest", | ||
| 78 | "surfaceContainerLow", | ||
| 79 | "surfaceContainerLowest", | ||
| 80 | "errorContainer", | ||
| 81 | "onErrorContainer", | ||
| 82 | "outline", | ||
| 83 | "outlineVariant", | ||
| 84 | "scrim", | ||
| 85 | ] as const satisfies ReadonlyArray<keyof MaterialYouPalette>; | ||
| 86 | const palette = Object.fromEntries( | ||
| 87 | roles.map((role) => [role, `${hexFromArgb(scheme[role])}ff`]), | ||
| 88 | ) as Record<(typeof roles)[number], string>; | ||
| 89 | const variables = materialYouPaletteToMobileThemeVariables( | ||
| 90 | palette, | ||
| 91 | appearance, | ||
| 92 | getMobileThemeRuntimeVariables("material-you", appearance, "android"), | ||
| 93 | ); | ||
| 94 | for (const rail of [false, true]) { | ||
| 95 | expectVisibleSelection(androidWorkspaceNavigationColors(variables, rail)); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | }, | ||
| 99 | ); | ||
| 100 | |||
| 101 | it("preserves an already visible tonal fill", () => { | ||
| 102 | const variables = getMobileThemeRuntimeVariables("t3-chat", "light", "android"); | ||
| 103 | const colors = androidWorkspaceNavigationColors( | ||
| 104 | { ...variables, "--color-secondary": "#e091cf" }, | ||
| 105 | true, | ||
| 106 | ); | ||
| 107 | expect(colors.indicatorColor).toBe("#e091cf"); | ||
| 108 | expectVisibleSelection(colors); | ||
| 109 | }); | ||
| 110 | }); | ||
apps/mobile/src/features/workspace/android-workspace-navigation-colors.ts created+28| ... | @@ -0,0 +1,28 @@ | ||
| 1 | import { | ||
| 2 | flattenThemeColor, | ||
| 3 | themeColorWithAlpha, | ||
| 4 | themeColorWithContrast, | ||
| 5 | type MobileThemeVariables, | ||
| 6 | } from "../../lib/mobileTheme"; | ||
| 7 | |||
| 8 | /** Both navigation positions keep a visible tonal selection against their actual surface. */ | ||
| 9 | export function androidWorkspaceNavigationColors(colors: MobileThemeVariables, rail: boolean) { | ||
| 10 | const header = flattenThemeColor(colors["--color-header"], colors["--color-screen"]); | ||
| 11 | const containerColor = rail | ||
| 12 | ? header | ||
| 13 | : flattenThemeColor(themeColorWithAlpha(colors["--color-header-foreground"], 0.05), header); | ||
| 14 | const indicatorColor = themeColorWithContrast( | ||
| 15 | flattenThemeColor(colors["--color-secondary"], containerColor), | ||
| 16 | containerColor, | ||
| 17 | 1.5, | ||
| 18 | ); | ||
| 19 | return { | ||
| 20 | containerColor, | ||
| 21 | indicatorColor, | ||
| 22 | selectedColor: themeColorWithContrast( | ||
| 23 | flattenThemeColor(colors["--color-secondary-foreground"], indicatorColor), | ||
| 24 | [indicatorColor, containerColor], | ||
| 25 | ), | ||
| 26 | unselectedColor: flattenThemeColor(colors["--color-header-foreground"], containerColor), | ||
| 27 | }; | ||
| 28 | } | ||
apps/mobile/src/lib/mobileTheme.test-support.ts+6-2| ... | @@ -9,14 +9,18 @@ import { | ... | @@ -9,14 +9,18 @@ import { |
| 9 | export function readDefaultMobileThemeVariables( | 9 | export function readDefaultMobileThemeVariables( |
| 10 | appearance: MobileThemeAppearance, | 10 | appearance: MobileThemeAppearance, |
| 11 | ): MobileThemeVariables { | 11 | ): MobileThemeVariables { |
| 12 | return readGeneratedMobileThemeVariables(appearance); | ||
| 13 | } | ||
| 14 | |||
| 15 | export function readGeneratedMobileThemeVariables(themeName: string): MobileThemeVariables { | ||
| 12 | const stylesheet = NodeFS.readFileSync( | 16 | const stylesheet = NodeFS.readFileSync( |
| 13 | new URL("../../generated-uniwind-themes.css", import.meta.url), | 17 | new URL("../../generated-uniwind-themes.css", import.meta.url), |
| 14 | "utf8", | 18 | "utf8", |
| 15 | ); | 19 | ); |
| 16 | const variant = new RegExp(`@variant ${appearance} \\{([\\s\\S]*?)\\n \\}`, "u").exec( | 20 | const variant = new RegExp(`@variant ${themeName} \\{([\\s\\S]*?)\\n \\}`, "u").exec( |
| 17 | stylesheet, | 21 | stylesheet, |
| 18 | )?.[1]; | 22 | )?.[1]; |
| 19 | if (variant === undefined) throw new Error(`Missing generated default ${appearance} theme.`); | 23 | if (variant === undefined) throw new Error(`Missing generated ${themeName} theme.`); |
| 20 | 24 | ||
| 21 | return Object.fromEntries( | 25 | return Object.fromEntries( |
| 22 | Array.from(variant.matchAll(/(--color-[a-z0-9-]+):\s*([^;]+);/gu), ([, name, value]) => [ | 26 | Array.from(variant.matchAll(/(--color-[a-z0-9-]+):\s*([^;]+);/gu), ([, name, value]) => [ |
apps/mobile/src/lib/mobileTheme.test.ts+48-7| ... | @@ -8,7 +8,10 @@ import { | ... | @@ -8,7 +8,10 @@ import { |
| 8 | MOBILE_THEME_IDS, | 8 | MOBILE_THEME_IDS, |
| 9 | getThemeColorsForAppearance, | 9 | getThemeColorsForAppearance, |
| 10 | } from "@t3tools/shared/themePalettes"; | 10 | } from "@t3tools/shared/themePalettes"; |
| 11 | import { readDefaultMobileThemeVariables } from "./mobileTheme.test-support"; | 11 | import { |
| 12 | readDefaultMobileThemeVariables, | ||
| 13 | readGeneratedMobileThemeVariables, | ||
| 14 | } from "./mobileTheme.test-support"; | ||
| 12 | import { getMobileThemeRuntimeVariables } from "./mobileThemeVariables"; | 15 | import { getMobileThemeRuntimeVariables } from "./mobileThemeVariables"; |
| 13 | 16 | ||
| 14 | import { | 17 | import { |
| ... | @@ -96,12 +99,7 @@ describe("mobile themes", () => { | ... | @@ -96,12 +99,7 @@ describe("mobile themes", () => { |
| 96 | ); | 99 | ); |
| 97 | expect(variables["--color-card"]).toBe(themeColorToNativeColor(colors.surface)); | 100 | expect(variables["--color-card"]).toBe(themeColorToNativeColor(colors.surface)); |
| 98 | expect(variables["--color-composer-surface"]).toBe( | 101 | expect(variables["--color-composer-surface"]).toBe( |
| 99 | themeColorWithAlpha( | 102 | themeColorWithAlpha(variables["--color-grouped-card"], appearance === "dark" ? 0.9 : 0.94), |
| 100 | themeId === DEFAULT_MOBILE_THEME_ID | ||
| 101 | ? variables["--color-grouped-card"] | ||
| 102 | : themeColorToNativeColor(colors.surface), | ||
| 103 | appearance === "dark" ? 0.9 : 0.94, | ||
| 104 | ), | ||
| 105 | ); | 103 | ); |
| 106 | expect(variables["--color-thread-selected"]).toBe( | 104 | expect(variables["--color-thread-selected"]).toBe( |
| 107 | themeColorToNativeColor(colors.sidebarRowActive), | 105 | themeColorToNativeColor(colors.sidebarRowActive), |
| ... | @@ -182,6 +180,49 @@ describe("mobile themes", () => { | ... | @@ -182,6 +180,49 @@ describe("mobile themes", () => { |
| 182 | expect(variables["--color-screen"]).toMatch(/^#/); | 180 | expect(variables["--color-screen"]).toMatch(/^#/); |
| 183 | }); | 181 | }); |
| 184 | 182 | ||
| 183 | it.each(MOBILE_THEME_IDS)("separates %s filled groups from their sheets", (themeId) => { | ||
| 184 | for (const appearance of ["light", "dark"] as const) { | ||
| 185 | const generated = readGeneratedMobileThemeVariables( | ||
| 186 | themeId === DEFAULT_MOBILE_THEME_ID ? appearance : `${themeId}-${appearance}`, | ||
| 187 | ); | ||
| 188 | expect(generated).toEqual(getMobileThemeVariables(themeId, appearance)); | ||
| 189 | for (const platform of ["ios", "android"]) { | ||
| 190 | const variables = getMobileThemeRuntimeVariables(themeId, appearance, platform); | ||
| 191 | const sheet = variables["--color-sheet-solid"]; | ||
| 192 | const group = variables["--color-grouped-card"]; | ||
| 193 | expect( | ||
| 194 | contrastRatio(group, sheet), | ||
| 195 | `${platform} ${appearance}: grouped surface on sheet`, | ||
| 196 | ).toBeGreaterThanOrEqual(1.025); | ||
| 197 | for (const role of [ | ||
| 198 | "--color-foreground", | ||
| 199 | "--color-foreground-secondary", | ||
| 200 | "--color-foreground-muted", | ||
| 201 | "--color-placeholder", | ||
| 202 | "--color-primary-text", | ||
| 203 | ] as const) { | ||
| 204 | expect( | ||
| 205 | contrastRatio(variables[role], group), | ||
| 206 | `${platform} ${appearance}: ${role} on grouped surface`, | ||
| 207 | ).toBeGreaterThanOrEqual(4.5); | ||
| 208 | } | ||
| 209 | if (platform === "android") { | ||
| 210 | const header = flattenThemeColor(variables["--color-header"], sheet); | ||
| 211 | for (const role of ["--color-header-foreground", "--color-foreground-muted"] as const) { | ||
| 212 | expect( | ||
| 213 | contrastRatio(variables[role], header), | ||
| 214 | `${appearance}: ${role} on Android header`, | ||
| 215 | ).toBeGreaterThanOrEqual(4.5); | ||
| 216 | } | ||
| 217 | } | ||
| 218 | expect( | ||
| 219 | contrastRatio(variables["--color-foreground-muted"], variables["--color-secondary"]), | ||
| 220 | `${platform} ${appearance}: muted label on secondary surface`, | ||
| 221 | ).toBeGreaterThanOrEqual(4.5); | ||
| 222 | } | ||
| 223 | } | ||
| 224 | }); | ||
| 225 | |||
| 185 | it.each(["light", "dark"] as const)( | 226 | it.each(["light", "dark"] as const)( |
| 186 | "separates default settings groups from their %s background", | 227 | "separates default settings groups from their %s background", |
| 187 | (appearance) => { | 228 | (appearance) => { |
apps/mobile/src/lib/mobileTheme.ts+37-15| ... | @@ -191,15 +191,23 @@ function contrastRatio( | ... | @@ -191,15 +191,23 @@ function contrastRatio( |
| 191 | ); | 191 | ); |
| 192 | } | 192 | } |
| 193 | 193 | ||
| 194 | /** Preserve the color's hue while giving text 4.5:1 contrast on every supplied surface. */ | 194 | /** Keep the theme tint while adjusting lightness to contrast with every supplied surface. */ |
| 195 | function readableTextColor(accent: string, surface: string | ReadonlyArray<string>): string { | 195 | export function themeColorWithContrast( |
| 196 | accent: string, | ||
| 197 | surface: string | ReadonlyArray<string>, | ||
| 198 | minimum = 4.5, | ||
| 199 | ): string { | ||
| 196 | const accentChannels = rgbChannels(accent); | 200 | const accentChannels = rgbChannels(accent); |
| 197 | const surfaceChannels = (typeof surface === "string" ? [surface] : surface) | 201 | const surfaceChannels = (typeof surface === "string" ? [surface] : surface) |
| 198 | .map(rgbChannels) | 202 | .map(rgbChannels) |
| 199 | .filter((channels) => channels !== null); | 203 | .filter((channels) => channels !== null); |
| 200 | const minimumContrast = (channels: readonly [number, number, number]) => | 204 | const minimumContrast = (channels: readonly [number, number, number]) => |
| 201 | Math.min(...surfaceChannels.map((surface) => contrastRatio(channels, surface))); | 205 | Math.min(...surfaceChannels.map((surface) => contrastRatio(channels, surface))); |
| 202 | if (!accentChannels || surfaceChannels.length === 0 || minimumContrast(accentChannels) >= 4.5) { | 206 | if ( |
| 207 | !accentChannels || | ||
| 208 | surfaceChannels.length === 0 || | ||
| 209 | minimumContrast(accentChannels) >= minimum | ||
| 210 | ) { | ||
| 203 | return accent; | 211 | return accent; |
| 204 | } | 212 | } |
| 205 | 213 | ||
| ... | @@ -216,7 +224,7 @@ function readableTextColor(accent: string, surface: string | ReadonlyArray<strin | ... | @@ -216,7 +224,7 @@ function readableTextColor(accent: string, surface: string | ReadonlyArray<strin |
| 216 | Math.round(accentChannels[1] + (target[1] - accentChannels[1]) * amount), | 224 | Math.round(accentChannels[1] + (target[1] - accentChannels[1]) * amount), |
| 217 | Math.round(accentChannels[2] + (target[2] - accentChannels[2]) * amount), | 225 | Math.round(accentChannels[2] + (target[2] - accentChannels[2]) * amount), |
| 218 | ]; | 226 | ]; |
| 219 | if (minimumContrast(candidate) >= 4.5) { | 227 | if (minimumContrast(candidate) >= minimum) { |
| 220 | readable = candidate; | 228 | readable = candidate; |
| 221 | upperAmount = amount; | 229 | upperAmount = amount; |
| 222 | } else { | 230 | } else { |
| ... | @@ -242,7 +250,15 @@ export function createMobileThemeVariables( | ... | @@ -242,7 +250,15 @@ export function createMobileThemeVariables( |
| 242 | ) { | 250 | ) { |
| 243 | const c = nativeColors(colors); | 251 | const c = nativeColors(colors); |
| 244 | const groupedCard = themeColorToNativeColor(groupedCardColor); | 252 | const groupedCard = themeColorToNativeColor(groupedCardColor); |
| 245 | const textSurfaces = [c.canvas, c.surface, c.surfaceRaised, c.chrome, groupedCard]; | 253 | const textSurfaces = [ |
| 254 | c.canvas, | ||
| 255 | c.surface, | ||
| 256 | c.surfaceRaised, | ||
| 257 | c.chrome, | ||
| 258 | c.sidebar, | ||
| 259 | c.secondary, | ||
| 260 | groupedCard, | ||
| 261 | ]; | ||
| 246 | return { | 262 | return { |
| 247 | "--color-screen": c.canvas, | 263 | "--color-screen": c.canvas, |
| 248 | "--color-sheet": withAlpha(c.chrome, 0.98), | 264 | "--color-sheet": withAlpha(c.chrome, 0.98), |
| ... | @@ -254,7 +270,7 @@ export function createMobileThemeVariables( | ... | @@ -254,7 +270,7 @@ export function createMobileThemeVariables( |
| 254 | "--color-thread-canvas": c.canvas, | 270 | "--color-thread-canvas": c.canvas, |
| 255 | "--color-thread-selected": c.sidebarRowActive, | 271 | "--color-thread-selected": c.sidebarRowActive, |
| 256 | "--color-thread-selected-foreground": c.sidebarForeground, | 272 | "--color-thread-selected-foreground": c.sidebarForeground, |
| 257 | "--color-thread-selected-foreground-muted": readableTextColor( | 273 | "--color-thread-selected-foreground-muted": themeColorWithContrast( |
| 258 | c.sidebarMutedForeground, | 274 | c.sidebarMutedForeground, |
| 259 | c.sidebarRowActive, | 275 | c.sidebarRowActive, |
| 260 | ), | 276 | ), |
| ... | @@ -270,8 +286,8 @@ export function createMobileThemeVariables( | ... | @@ -270,8 +286,8 @@ export function createMobileThemeVariables( |
| 270 | groupedCard === c.surface ? (appearance === "dark" ? 0.46 : 0.54) : 0.8, | 286 | groupedCard === c.surface ? (appearance === "dark" ? 0.46 : 0.54) : 0.8, |
| 271 | ), | 287 | ), |
| 272 | "--color-foreground": c.text, | 288 | "--color-foreground": c.text, |
| 273 | "--color-foreground-secondary": readableTextColor(c.textMuted, textSurfaces), | 289 | "--color-foreground-secondary": themeColorWithContrast(c.textMuted, textSurfaces), |
| 274 | "--color-foreground-muted": readableTextColor(c.mutedForeground, textSurfaces), | 290 | "--color-foreground-muted": themeColorWithContrast(c.mutedForeground, textSurfaces), |
| 275 | "--color-foreground-tertiary": c.secondaryLabel, | 291 | "--color-foreground-tertiary": c.secondaryLabel, |
| 276 | "--color-border": c.border, | 292 | "--color-border": c.border, |
| 277 | "--color-focus": c.focus, | 293 | "--color-focus": c.focus, |
| ... | @@ -284,7 +300,7 @@ export function createMobileThemeVariables( | ... | @@ -284,7 +300,7 @@ export function createMobileThemeVariables( |
| 284 | "--color-inline-skill-foreground": c.accentSurfaceForeground, | 300 | "--color-inline-skill-foreground": c.accentSurfaceForeground, |
| 285 | "--color-primary": c.messageAction, | 301 | "--color-primary": c.messageAction, |
| 286 | "--color-primary-foreground": c.messageActionForeground, | 302 | "--color-primary-foreground": c.messageActionForeground, |
| 287 | "--color-primary-text": readableTextColor(c.messageAction, textSurfaces), | 303 | "--color-primary-text": themeColorWithContrast(c.messageAction, textSurfaces), |
| 288 | "--color-primary-shadow": "#000000", | 304 | "--color-primary-shadow": "#000000", |
| 289 | "--color-secondary": c.secondary, | 305 | "--color-secondary": c.secondary, |
| 290 | "--color-secondary-foreground": c.secondaryForeground, | 306 | "--color-secondary-foreground": c.secondaryForeground, |
| ... | @@ -304,7 +320,7 @@ export function createMobileThemeVariables( | ... | @@ -304,7 +320,7 @@ export function createMobileThemeVariables( |
| 304 | "--color-input": c.surface, | 320 | "--color-input": c.surface, |
| 305 | "--color-input-border": c.input, | 321 | "--color-input-border": c.input, |
| 306 | "--color-sidebar-search": c.sidebarControlSurface, | 322 | "--color-sidebar-search": c.sidebarControlSurface, |
| 307 | "--color-placeholder": readableTextColor(c.placeholder, textSurfaces), | 323 | "--color-placeholder": themeColorWithContrast(c.placeholder, textSurfaces), |
| 308 | "--color-icon": c.text, | 324 | "--color-icon": c.text, |
| 309 | "--color-icon-muted": c.iconMuted, | 325 | "--color-icon-muted": c.iconMuted, |
| 310 | "--color-icon-subtle": c.secondaryLabel, | 326 | "--color-icon-subtle": c.secondaryLabel, |
| ... | @@ -317,7 +333,7 @@ export function createMobileThemeVariables( | ... | @@ -317,7 +333,7 @@ export function createMobileThemeVariables( |
| 317 | "--color-status-bar": c.canvas, | 333 | "--color-status-bar": c.canvas, |
| 318 | "--color-md-body": c.text, | 334 | "--color-md-body": c.text, |
| 319 | "--color-md-strong": c.text, | 335 | "--color-md-strong": c.text, |
| 320 | "--color-md-link": readableTextColor(c.messageAction, c.canvas), | 336 | "--color-md-link": themeColorWithContrast(c.messageAction, c.canvas), |
| 321 | "--color-md-blockquote-border": c.border, | 337 | "--color-md-blockquote-border": c.border, |
| 322 | "--color-md-blockquote-bg": c.muted, | 338 | "--color-md-blockquote-bg": c.muted, |
| 323 | "--color-md-code-bg": c.codeBackground, | 339 | "--color-md-code-bg": c.codeBackground, |
| ... | @@ -330,11 +346,14 @@ export function createMobileThemeVariables( | ... | @@ -330,11 +346,14 @@ export function createMobileThemeVariables( |
| 330 | "--color-user-bubble": c.messageSurface, | 346 | "--color-user-bubble": c.messageSurface, |
| 331 | "--color-user-bubble-foreground": c.messageForeground, | 347 | "--color-user-bubble-foreground": c.messageForeground, |
| 332 | "--color-user-bubble-foreground-muted": withAlpha(c.messageForeground, 0.78), | 348 | "--color-user-bubble-foreground-muted": withAlpha(c.messageForeground, 0.78), |
| 333 | "--color-user-bubble-skill-foreground": readableTextColor(c.messageAction, c.messageSurface), | 349 | "--color-user-bubble-skill-foreground": themeColorWithContrast( |
| 350 | c.messageAction, | ||
| 351 | c.messageSurface, | ||
| 352 | ), | ||
| 334 | "--color-backdrop": withAlpha("#000000", appearance === "dark" ? 0.48 : 0.22), | 353 | "--color-backdrop": withAlpha("#000000", appearance === "dark" ? 0.48 : 0.22), |
| 335 | "--color-drawer": c.sidebar, | 354 | "--color-drawer": c.sidebar, |
| 336 | "--color-drawer-foreground": c.sidebarForeground, | 355 | "--color-drawer-foreground": c.sidebarForeground, |
| 337 | "--color-drawer-foreground-muted": readableTextColor(c.sidebarMutedForeground, [ | 356 | "--color-drawer-foreground-muted": themeColorWithContrast(c.sidebarMutedForeground, [ |
| 338 | c.sidebar, | 357 | c.sidebar, |
| 339 | c.sidebarRowHover, | 358 | c.sidebarRowHover, |
| 340 | ]), | 359 | ]), |
| ... | @@ -368,13 +387,16 @@ export function getMobileThemeVariables( | ... | @@ -368,13 +387,16 @@ export function getMobileThemeVariables( |
| 368 | ): MobileThemeVariables { | 387 | ): MobileThemeVariables { |
| 369 | const colors = getMobileThemeColors(themeId, appearance); | 388 | const colors = getMobileThemeColors(themeId, appearance); |
| 370 | // Mobile settings groups and fallback materials use tonal fills where desktop | 389 | // Mobile settings groups and fallback materials use tonal fills where desktop |
| 371 | // uses outlined cards. Regular cards retain their shared desktop surface. | 390 | // uses outlined cards. Raise flat desktop surfaces above the sheet so grouped |
| 391 | // content keeps a visible boundary. Regular cards retain their desktop surface. | ||
| 372 | const groupedCard = | 392 | const groupedCard = |
| 373 | themeId === DEFAULT_MOBILE_THEME_ID | 393 | themeId === DEFAULT_MOBILE_THEME_ID |
| 374 | ? appearance === "light" | 394 | ? appearance === "light" |
| 375 | ? colors.toolbarControlHover | 395 | ? colors.toolbarControlHover |
| 376 | : colors.sidebarRowActive | 396 | : colors.sidebarRowActive |
| 377 | : colors.surface; | 397 | : colors.surface === colors.chrome |
| 398 | ? colors.surfaceRaised | ||
| 399 | : colors.surface; | ||
| 378 | const mobileColors = | 400 | const mobileColors = |
| 379 | themeId === DEFAULT_MOBILE_THEME_ID | 401 | themeId === DEFAULT_MOBILE_THEME_ID |
| 380 | ? { | 402 | ? { |