@godxjp/ui 23.4.12 → 24.1.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.
@@ -0,0 +1,10 @@
1
+ export declare const GODX_ARTWORK_ID_TOKEN = "__GXID__";
2
+ export interface BrandArtwork {
3
+ readonly viewBox: string;
4
+ /** Inner SVG markup — package-owned static artwork, never user input. */
5
+ readonly markup: string;
6
+ }
7
+ export declare const GODX_LOCKUP_LIGHT: BrandArtwork;
8
+ export declare const GODX_LOCKUP_DARK: BrandArtwork;
9
+ export declare const GODX_MARK_LIGHT: BrandArtwork;
10
+ export declare const GODX_MARK_DARK: BrandArtwork;
@@ -0,0 +1,24 @@
1
+ const GODX_ARTWORK_ID_TOKEN = "__GXID__";
2
+ const GODX_LOCKUP_LIGHT = Object.freeze({
3
+ viewBox: "30 30 871.285714 182.000000",
4
+ markup: '<defs>\n <radialGradient id="__GXID__-gx-lockup-l-iconBodyGradient" gradientUnits="userSpaceOnUse" cx="58" cy="39" r="183">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="0.46" stop-color="#3700A6"/><stop offset="1" stop-color="#0B0F3B"/>\n </radialGradient>\n <linearGradient id="__GXID__-gx-lockup-l-arrowTopGradient" gradientUnits="userSpaceOnUse" x1="138" y1="100" x2="245" y2="148">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="1" stop-color="#980BFF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-lockup-l-arrowFoldGradient" gradientUnits="userSpaceOnUse" x1="230" y1="125" x2="165" y2="214">\n <stop offset="0" stop-color="#6500E5"/><stop offset="0.40" stop-color="#A611FF"/><stop offset="1" stop-color="#FF3DFF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-lockup-l-xMainGradient" gradientUnits="userSpaceOnUse" x1="861" y1="48" x2="739" y2="202">\n <stop offset="0" stop-color="#0B0F3B"/><stop offset="0.28" stop-color="#310077"/><stop offset="0.62" stop-color="#990DFF"/><stop offset="1" stop-color="#7A00FF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-lockup-l-xUpperGradient" gradientUnits="userSpaceOnUse" x1="747" y1="52" x2="795" y2="119">\n <stop offset="0" stop-color="#0B0F3B"/><stop offset="0.70" stop-color="#21005D"/><stop offset="1" stop-color="#7A00FF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-lockup-l-xLowerGradient" gradientUnits="userSpaceOnUse" x1="810" y1="125" x2="863" y2="188">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="0.55" stop-color="#21005D"/><stop offset="1" stop-color="#0B0F3B"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-lockup-l-reverseIconGradient" gradientUnits="userSpaceOnUse" x1="52" y1="40" x2="182" y2="211">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="0.6" stop-color="#9A45FF"/><stop offset="1" stop-color="#D8BCFF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-lockup-l-reverseXMainGradient" gradientUnits="userSpaceOnUse" x1="861" y1="48" x2="739" y2="202">\n <stop offset="0" stop-color="#E8DAFF"/><stop offset="0.46" stop-color="#B264FF"/><stop offset="1" stop-color="#7A00FF"/>\n </linearGradient>\n <clipPath id="__GXID__-gx-lockup-l-arrowClip"><path d="M131 97 H244 Q247 97 249 100 L264 117 Q268 122 264 127 L194 201 Q190 205 185 205 H139 Q133 205 137 201 L176 160 Q179 157 176 154 L129 104 Q123 97 131 97 Z"/></clipPath>\n</defs>\n\n<g id="__GXID__-gx-lockup-l-godx-logo" transform="translate(5 7)">\n<path id="__GXID__-gx-lockup-l-icon-body" fill="url(#__GXID__-gx-lockup-l-iconBodyGradient)" fill-rule="evenodd" d="M118 23 H183 C186 23 187 24 189 26 L222 64 Q226 69 220 69 H122 C96 69 75 91 75 117 C75 142 97 161 122 161 H151 Q157 161 153 166 L120 203 Q118 205 115 205 C90 205 69 194 50 174 C32 155 25 138 25 117 C25 92 36 75 56 54 C77 32 95 23 118 23 Z"/>\n<path id="__GXID__-gx-lockup-l-icon-arrow" fill="url(#__GXID__-gx-lockup-l-arrowTopGradient)" fill-rule="evenodd" d="M131 97 H244 Q247 97 249 100 L264 117 Q268 122 264 127 L194 201 Q190 205 185 205 H139 Q133 205 137 201 L176 160 Q179 157 176 154 L129 104 Q123 97 131 97 Z"/>\n<path id="__GXID__-gx-lockup-l-arrow-fold" clip-path="url(#__GXID__-gx-lockup-l-arrowClip)" fill="url(#__GXID__-gx-lockup-l-arrowFoldGradient)" d="M177.5,157 C205,133 238,109 267,120 L285,230 L110,230 Z"/>\n<path id="__GXID__-gx-lockup-l-letter-g" fill="#0B0F3B" fill-rule="evenodd" d="M428 76 L399 98 C391 86 378 79 365 79 C342 79 325 98 325 121 C325 144 343 163 365 163 C382 163 395 153 402 137 H362 V107 H442 V121 C442 165 409 198 365 198 C322 198 287 164 287 121 C287 78 322 44 365 44 C390 44 413 57 428 76 Z"/>\n<path id="__GXID__-gx-lockup-l-letter-o" fill="#0B0F3B" fill-rule="evenodd" d="M566 142.5 C566 173.704 539.585 199 507 199 C474.415 199 448 173.704 448 142.5 C448 111.296 474.415 86 507 86 C539.585 86 566 111.296 566 142.5 Z M533.5 142.5 C533.5 156.583 521.636 168 507 168 C492.364 168 480.5 156.583 480.5 142.5 C480.5 128.417 492.364 117 507 117 C521.636 117 533.5 128.417 533.5 142.5 Z"/>\n<path id="__GXID__-gx-lockup-l-letter-d" fill="#0B0F3B" fill-rule="evenodd" d="M574 44 H650 C698 44 732 75 732 121 C732 162 698 198 650 198 H574 Z M616.5 78 H649 C673 78 690 96 690 121 C690 146 673 164 649 164 H616.5 Z"/>\n<path id="__GXID__-gx-lockup-l-x-upper" fill="url(#__GXID__-gx-lockup-l-xUpperGradient)" fill-rule="evenodd" d="M715 44 H762 Q764 44 765 46 L801 86 L774 117 L713 47 Q711 44 715 44 Z"/>\n<path id="__GXID__-gx-lockup-l-x-diagonal" fill="url(#__GXID__-gx-lockup-l-xMainGradient)" fill-rule="evenodd" d="M844 42 H895 Q897 42 895 44 L761 196 Q759 198 757 198 H711 Q708 198 710 196 L841 44 Q843 42 844 42 Z"/>\n<path id="__GXID__-gx-lockup-l-x-lower" fill="url(#__GXID__-gx-lockup-l-xLowerGradient)" fill-rule="evenodd" d="M831 121 L895 196 Q898 199 894 199 H847 Q844 199 842 197 L805 153 Q803 151 805 149 Z"/>\n</g>'
5
+ });
6
+ const GODX_LOCKUP_DARK = Object.freeze({
7
+ viewBox: "30 30 871.285714 182.000000",
8
+ markup: '<defs>\n <radialGradient id="__GXID__-gx-lockup-d-iconBodyGradient" gradientUnits="userSpaceOnUse" cx="58" cy="39" r="183">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="0.46" stop-color="#3700A6"/><stop offset="1" stop-color="#0B0F3B"/>\n </radialGradient>\n <linearGradient id="__GXID__-gx-lockup-d-arrowTopGradient" gradientUnits="userSpaceOnUse" x1="138" y1="100" x2="245" y2="148">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="1" stop-color="#980BFF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-lockup-d-arrowFoldGradient" gradientUnits="userSpaceOnUse" x1="230" y1="125" x2="165" y2="214">\n <stop offset="0" stop-color="#6500E5"/><stop offset="0.40" stop-color="#A611FF"/><stop offset="1" stop-color="#FF3DFF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-lockup-d-xMainGradient" gradientUnits="userSpaceOnUse" x1="861" y1="48" x2="739" y2="202">\n <stop offset="0" stop-color="#0B0F3B"/><stop offset="0.28" stop-color="#310077"/><stop offset="0.62" stop-color="#990DFF"/><stop offset="1" stop-color="#7A00FF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-lockup-d-xUpperGradient" gradientUnits="userSpaceOnUse" x1="747" y1="52" x2="795" y2="119">\n <stop offset="0" stop-color="#0B0F3B"/><stop offset="0.70" stop-color="#21005D"/><stop offset="1" stop-color="#7A00FF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-lockup-d-xLowerGradient" gradientUnits="userSpaceOnUse" x1="810" y1="125" x2="863" y2="188">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="0.55" stop-color="#21005D"/><stop offset="1" stop-color="#0B0F3B"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-lockup-d-reverseIconGradient" gradientUnits="userSpaceOnUse" x1="52" y1="40" x2="182" y2="211">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="0.6" stop-color="#9A45FF"/><stop offset="1" stop-color="#D8BCFF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-lockup-d-reverseXMainGradient" gradientUnits="userSpaceOnUse" x1="861" y1="48" x2="739" y2="202">\n <stop offset="0" stop-color="#E8DAFF"/><stop offset="0.46" stop-color="#B264FF"/><stop offset="1" stop-color="#7A00FF"/>\n </linearGradient>\n <clipPath id="__GXID__-gx-lockup-d-arrowClip"><path d="M131 97 H244 Q247 97 249 100 L264 117 Q268 122 264 127 L194 201 Q190 205 185 205 H139 Q133 205 137 201 L176 160 Q179 157 176 154 L129 104 Q123 97 131 97 Z"/></clipPath>\n</defs>\n\n<g id="__GXID__-gx-lockup-d-godx-logo" transform="translate(5 7)">\n<path id="__GXID__-gx-lockup-d-icon-body" fill="url(#__GXID__-gx-lockup-d-reverseIconGradient)" fill-rule="evenodd" d="M118 23 H183 C186 23 187 24 189 26 L222 64 Q226 69 220 69 H122 C96 69 75 91 75 117 C75 142 97 161 122 161 H151 Q157 161 153 166 L120 203 Q118 205 115 205 C90 205 69 194 50 174 C32 155 25 138 25 117 C25 92 36 75 56 54 C77 32 95 23 118 23 Z"/>\n<path id="__GXID__-gx-lockup-d-icon-arrow" fill="url(#__GXID__-gx-lockup-d-arrowTopGradient)" fill-rule="evenodd" d="M131 97 H244 Q247 97 249 100 L264 117 Q268 122 264 127 L194 201 Q190 205 185 205 H139 Q133 205 137 201 L176 160 Q179 157 176 154 L129 104 Q123 97 131 97 Z"/>\n<path id="__GXID__-gx-lockup-d-arrow-fold" clip-path="url(#__GXID__-gx-lockup-d-arrowClip)" fill="url(#__GXID__-gx-lockup-d-arrowFoldGradient)" d="M177.5,157 C205,133 238,109 267,120 L285,230 L110,230 Z"/>\n<path id="__GXID__-gx-lockup-d-letter-g" fill="#FFFFFF" fill-rule="evenodd" d="M428 76 L399 98 C391 86 378 79 365 79 C342 79 325 98 325 121 C325 144 343 163 365 163 C382 163 395 153 402 137 H362 V107 H442 V121 C442 165 409 198 365 198 C322 198 287 164 287 121 C287 78 322 44 365 44 C390 44 413 57 428 76 Z"/>\n<path id="__GXID__-gx-lockup-d-letter-o" fill="#FFFFFF" fill-rule="evenodd" d="M566 142.5 C566 173.704 539.585 199 507 199 C474.415 199 448 173.704 448 142.5 C448 111.296 474.415 86 507 86 C539.585 86 566 111.296 566 142.5 Z M533.5 142.5 C533.5 156.583 521.636 168 507 168 C492.364 168 480.5 156.583 480.5 142.5 C480.5 128.417 492.364 117 507 117 C521.636 117 533.5 128.417 533.5 142.5 Z"/>\n<path id="__GXID__-gx-lockup-d-letter-d" fill="#FFFFFF" fill-rule="evenodd" d="M574 44 H650 C698 44 732 75 732 121 C732 162 698 198 650 198 H574 Z M616.5 78 H649 C673 78 690 96 690 121 C690 146 673 164 649 164 H616.5 Z"/>\n<path id="__GXID__-gx-lockup-d-x-upper" fill="#FFFFFF" fill-rule="evenodd" d="M715 44 H762 Q764 44 765 46 L801 86 L774 117 L713 47 Q711 44 715 44 Z"/>\n<path id="__GXID__-gx-lockup-d-x-diagonal" fill="url(#__GXID__-gx-lockup-d-reverseXMainGradient)" fill-rule="evenodd" d="M844 42 H895 Q897 42 895 44 L761 196 Q759 198 757 198 H711 Q708 198 710 196 L841 44 Q843 42 844 42 Z"/>\n<path id="__GXID__-gx-lockup-d-x-lower" fill="#FFFFFF" fill-rule="evenodd" d="M831 121 L895 196 Q898 199 894 199 H847 Q844 199 842 197 L805 153 Q803 151 805 149 Z"/>\n</g>'
9
+ });
10
+ const GODX_MARK_LIGHT = Object.freeze({
11
+ viewBox: "30 30 241.000000 182.000000",
12
+ markup: '<defs>\n <radialGradient id="__GXID__-gx-mark-l-iconBodyGradient" gradientUnits="userSpaceOnUse" cx="58" cy="39" r="183">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="0.46" stop-color="#3700A6"/><stop offset="1" stop-color="#0B0F3B"/>\n </radialGradient>\n <linearGradient id="__GXID__-gx-mark-l-arrowTopGradient" gradientUnits="userSpaceOnUse" x1="138" y1="100" x2="245" y2="148">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="1" stop-color="#980BFF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-mark-l-arrowFoldGradient" gradientUnits="userSpaceOnUse" x1="230" y1="125" x2="165" y2="214">\n <stop offset="0" stop-color="#6500E5"/><stop offset="0.40" stop-color="#A611FF"/><stop offset="1" stop-color="#FF3DFF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-mark-l-xMainGradient" gradientUnits="userSpaceOnUse" x1="861" y1="48" x2="739" y2="202">\n <stop offset="0" stop-color="#0B0F3B"/><stop offset="0.28" stop-color="#310077"/><stop offset="0.62" stop-color="#990DFF"/><stop offset="1" stop-color="#7A00FF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-mark-l-xUpperGradient" gradientUnits="userSpaceOnUse" x1="747" y1="52" x2="795" y2="119">\n <stop offset="0" stop-color="#0B0F3B"/><stop offset="0.70" stop-color="#21005D"/><stop offset="1" stop-color="#7A00FF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-mark-l-xLowerGradient" gradientUnits="userSpaceOnUse" x1="810" y1="125" x2="863" y2="188">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="0.55" stop-color="#21005D"/><stop offset="1" stop-color="#0B0F3B"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-mark-l-reverseIconGradient" gradientUnits="userSpaceOnUse" x1="52" y1="40" x2="182" y2="211">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="0.6" stop-color="#9A45FF"/><stop offset="1" stop-color="#D8BCFF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-mark-l-reverseXMainGradient" gradientUnits="userSpaceOnUse" x1="861" y1="48" x2="739" y2="202">\n <stop offset="0" stop-color="#E8DAFF"/><stop offset="0.46" stop-color="#B264FF"/><stop offset="1" stop-color="#7A00FF"/>\n </linearGradient>\n <clipPath id="__GXID__-gx-mark-l-arrowClip"><path d="M131 97 H244 Q247 97 249 100 L264 117 Q268 122 264 127 L194 201 Q190 205 185 205 H139 Q133 205 137 201 L176 160 Q179 157 176 154 L129 104 Q123 97 131 97 Z"/></clipPath>\n</defs>\n\n<g id="__GXID__-gx-mark-l-godx-icon" transform="translate(5 7)">\n<path id="__GXID__-gx-mark-l-icon-body" fill="url(#__GXID__-gx-mark-l-iconBodyGradient)" fill-rule="evenodd" d="M118 23 H183 C186 23 187 24 189 26 L222 64 Q226 69 220 69 H122 C96 69 75 91 75 117 C75 142 97 161 122 161 H151 Q157 161 153 166 L120 203 Q118 205 115 205 C90 205 69 194 50 174 C32 155 25 138 25 117 C25 92 36 75 56 54 C77 32 95 23 118 23 Z"/>\n<path id="__GXID__-gx-mark-l-icon-arrow" fill="url(#__GXID__-gx-mark-l-arrowTopGradient)" fill-rule="evenodd" d="M131 97 H244 Q247 97 249 100 L264 117 Q268 122 264 127 L194 201 Q190 205 185 205 H139 Q133 205 137 201 L176 160 Q179 157 176 154 L129 104 Q123 97 131 97 Z"/>\n<path id="__GXID__-gx-mark-l-arrow-fold" clip-path="url(#__GXID__-gx-mark-l-arrowClip)" fill="url(#__GXID__-gx-mark-l-arrowFoldGradient)" d="M177.5,157 C205,133 238,109 267,120 L285,230 L110,230 Z"/>\n</g>'
13
+ });
14
+ const GODX_MARK_DARK = Object.freeze({
15
+ viewBox: "30 30 241.000000 182.000000",
16
+ markup: '<defs>\n <radialGradient id="__GXID__-gx-mark-d-iconBodyGradient" gradientUnits="userSpaceOnUse" cx="58" cy="39" r="183">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="0.46" stop-color="#3700A6"/><stop offset="1" stop-color="#0B0F3B"/>\n </radialGradient>\n <linearGradient id="__GXID__-gx-mark-d-arrowTopGradient" gradientUnits="userSpaceOnUse" x1="138" y1="100" x2="245" y2="148">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="1" stop-color="#980BFF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-mark-d-arrowFoldGradient" gradientUnits="userSpaceOnUse" x1="230" y1="125" x2="165" y2="214">\n <stop offset="0" stop-color="#6500E5"/><stop offset="0.40" stop-color="#A611FF"/><stop offset="1" stop-color="#FF3DFF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-mark-d-xMainGradient" gradientUnits="userSpaceOnUse" x1="861" y1="48" x2="739" y2="202">\n <stop offset="0" stop-color="#0B0F3B"/><stop offset="0.28" stop-color="#310077"/><stop offset="0.62" stop-color="#990DFF"/><stop offset="1" stop-color="#7A00FF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-mark-d-xUpperGradient" gradientUnits="userSpaceOnUse" x1="747" y1="52" x2="795" y2="119">\n <stop offset="0" stop-color="#0B0F3B"/><stop offset="0.70" stop-color="#21005D"/><stop offset="1" stop-color="#7A00FF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-mark-d-xLowerGradient" gradientUnits="userSpaceOnUse" x1="810" y1="125" x2="863" y2="188">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="0.55" stop-color="#21005D"/><stop offset="1" stop-color="#0B0F3B"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-mark-d-reverseIconGradient" gradientUnits="userSpaceOnUse" x1="52" y1="40" x2="182" y2="211">\n <stop offset="0" stop-color="#7A00FF"/><stop offset="0.6" stop-color="#9A45FF"/><stop offset="1" stop-color="#D8BCFF"/>\n </linearGradient>\n <linearGradient id="__GXID__-gx-mark-d-reverseXMainGradient" gradientUnits="userSpaceOnUse" x1="861" y1="48" x2="739" y2="202">\n <stop offset="0" stop-color="#E8DAFF"/><stop offset="0.46" stop-color="#B264FF"/><stop offset="1" stop-color="#7A00FF"/>\n </linearGradient>\n <clipPath id="__GXID__-gx-mark-d-arrowClip"><path d="M131 97 H244 Q247 97 249 100 L264 117 Q268 122 264 127 L194 201 Q190 205 185 205 H139 Q133 205 137 201 L176 160 Q179 157 176 154 L129 104 Q123 97 131 97 Z"/></clipPath>\n</defs>\n\n<g id="__GXID__-gx-mark-d-godx-icon" transform="translate(5 7)">\n<path id="__GXID__-gx-mark-d-icon-body" fill="url(#__GXID__-gx-mark-d-reverseIconGradient)" fill-rule="evenodd" d="M118 23 H183 C186 23 187 24 189 26 L222 64 Q226 69 220 69 H122 C96 69 75 91 75 117 C75 142 97 161 122 161 H151 Q157 161 153 166 L120 203 Q118 205 115 205 C90 205 69 194 50 174 C32 155 25 138 25 117 C25 92 36 75 56 54 C77 32 95 23 118 23 Z"/>\n<path id="__GXID__-gx-mark-d-icon-arrow" fill="url(#__GXID__-gx-mark-d-arrowTopGradient)" fill-rule="evenodd" d="M131 97 H244 Q247 97 249 100 L264 117 Q268 122 264 127 L194 201 Q190 205 185 205 H139 Q133 205 137 201 L176 160 Q179 157 176 154 L129 104 Q123 97 131 97 Z"/>\n<path id="__GXID__-gx-mark-d-arrow-fold" clip-path="url(#__GXID__-gx-mark-d-arrowClip)" fill="url(#__GXID__-gx-mark-d-arrowFoldGradient)" d="M177.5,157 C205,133 238,109 267,120 L285,230 L110,230 Z"/>\n</g>'
17
+ });
18
+ export {
19
+ GODX_ARTWORK_ID_TOKEN,
20
+ GODX_LOCKUP_DARK,
21
+ GODX_LOCKUP_LIGHT,
22
+ GODX_MARK_DARK,
23
+ GODX_MARK_LIGHT
24
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * THE FLAT GoDX MARK — the cut EMAIL paints. The web uses the master instead.
3
+ *
4
+ * Brand identity v2.3 requires the master SVG with its gradients and a light/dark variant
5
+ * (`brand/godx-artwork.generated.ts`), and forbids re-tinting it. An email cannot honour that:
6
+ * Outlook's Word renderer drops SVG gradients, `prefers-color-scheme` reaches only some clients,
7
+ * and a delivered message cannot swap a variant. So email paints the kit's own FLAT cut — a
8
+ * different brand ASSET, which is why the kit ships one — in a colour the template supplies.
9
+ *
10
+ * Brand identity v2.3. The coordinates are the kit's own normalised construction data
11
+ * (`02_Construction/GoDX/GoDX-geometry-v2_2.json`, origin already at 0,0), not a redraw and not a
12
+ * trace: a future artwork revision is a copy-paste of two `d` strings into this file.
13
+ *
14
+ * WHY IT IS STILL SHARED. `src/email` may only import its own siblings, so it receives a generated
15
+ * copy of these paths (`gen-email-tokens.mjs`). This file is the declaration both that generator
16
+ * and its `--check` guard compare against, so the email mark cannot drift from the package's own
17
+ * flat artwork — the drift that was caught once already.
18
+ *
19
+ * MONOCHROME. The kit also ships a gradient cut (#7A00FF → #3700A6 → #0B0F3B). It is deliberately
20
+ * not used: this mark is painted by a token (`--logo-godx-color` on the web, a colour argument in
21
+ * email) so it re-tints per theme and knocks out on dark surfaces. A baked gradient can do neither,
22
+ * and email clients are worse at gradients than they are at paths.
23
+ */
24
+ /** The artwork's own coordinate space, before fitting. */
25
+ export declare const GODX_MARK_SOURCE: Readonly<{
26
+ width: 241;
27
+ height: 182;
28
+ }>;
29
+ /** The square box every renderer draws into. */
30
+ export declare const GODX_MARK_VIEW_BOX = "0 0 32 32";
31
+ /**
32
+ * Fits 241×182 into the square box, centred vertically.
33
+ * scale = 32 / 241 = 0.132780; y = (32 − 182 × scale) / 2 = 3.917.
34
+ */
35
+ export declare const GODX_MARK_TRANSFORM = "translate(0 3.917) scale(0.132780)";
36
+ /** The "G" body. */
37
+ export declare const GODX_MARK_BODY_PATH = "M93.0 0.0H158.0C161.0 0.0 162.0 1.0 164.0 3.0L197.0 41.0Q201.0 46.0 195.0 46.0H97.0C71.0 46.0 50.0 68.0 50.0 94.0C50.0 119.0 72.0 138.0 97.0 138.0H126.0Q132.0 138.0 128.0 143.0L95.0 180.0Q93.0 182.0 90.0 182.0C65.0 182.0 44.0 171.0 25.0 151.0C7.0 132.0 0.0 115.0 0.0 94.0C0.0 69.0 11.0 52.0 31.0 31.0C52.0 9.0 70.0 0.0 93.0 0.0Z";
38
+ /** The arrow that crosses it. */
39
+ export declare const GODX_MARK_ARROW_PATH = "M106.0 74.0H219.0Q222.0 74.0 224.0 77.0L239.0 94.0Q243.0 99.0 239.0 104.0L169.0 178.0Q165.0 182.0 160.0 182.0H114.0Q108.0 182.0 112.0 178.0L151.0 137.0Q154.0 134.0 151.0 131.0L104.0 81.0Q98.0 74.0 106.0 74.0Z";
@@ -0,0 +1,12 @@
1
+ const GODX_MARK_SOURCE = Object.freeze({ width: 241, height: 182 });
2
+ const GODX_MARK_VIEW_BOX = "0 0 32 32";
3
+ const GODX_MARK_TRANSFORM = "translate(0 3.917) scale(0.132780)";
4
+ const GODX_MARK_BODY_PATH = "M93.0 0.0H158.0C161.0 0.0 162.0 1.0 164.0 3.0L197.0 41.0Q201.0 46.0 195.0 46.0H97.0C71.0 46.0 50.0 68.0 50.0 94.0C50.0 119.0 72.0 138.0 97.0 138.0H126.0Q132.0 138.0 128.0 143.0L95.0 180.0Q93.0 182.0 90.0 182.0C65.0 182.0 44.0 171.0 25.0 151.0C7.0 132.0 0.0 115.0 0.0 94.0C0.0 69.0 11.0 52.0 31.0 31.0C52.0 9.0 70.0 0.0 93.0 0.0Z";
5
+ const GODX_MARK_ARROW_PATH = "M106.0 74.0H219.0Q222.0 74.0 224.0 77.0L239.0 94.0Q243.0 99.0 239.0 104.0L169.0 178.0Q165.0 182.0 160.0 182.0H114.0Q108.0 182.0 112.0 178.0L151.0 137.0Q154.0 134.0 151.0 131.0L104.0 81.0Q98.0 74.0 106.0 74.0Z";
6
+ export {
7
+ GODX_MARK_ARROW_PATH,
8
+ GODX_MARK_BODY_PATH,
9
+ GODX_MARK_SOURCE,
10
+ GODX_MARK_TRANSFORM,
11
+ GODX_MARK_VIEW_BOX
12
+ };
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  export type LogoSize = "xs" | "sm" | "md" | "lg";
3
3
  export type LogoTone = "primary" | "success";
4
- export type LogoMark = "glyph" | "godx";
4
+ export type LogoMark = "glyph" | "godx" | "godx-lockup";
5
5
  export interface LogoProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> {
6
6
  /**
7
7
  * The brand glyph — a short mark (a letter/initials, default `"g"`) or a custom node such as an
@@ -1,5 +1,13 @@
1
+ "use client";
1
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
3
  import * as React from "react";
4
+ import {
5
+ GODX_ARTWORK_ID_TOKEN,
6
+ GODX_LOCKUP_DARK,
7
+ GODX_LOCKUP_LIGHT,
8
+ GODX_MARK_DARK,
9
+ GODX_MARK_LIGHT
10
+ } from "../../brand/godx-artwork.generated.js";
3
11
  import { Slot } from "../../lib/slot.js";
4
12
  import { cn } from "../../lib/utils.js";
5
13
  const X_HEIGHT_TOP = /^[acegmnopqrsuvwxyz]+$/;
@@ -38,7 +46,13 @@ function logoGlyphAdvance(glyph) {
38
46
  return counts;
39
47
  }
40
48
  function MarkArtwork({ mark, glyph }) {
41
- if (mark !== "godx") {
49
+ const instanceId = React.useId().replace(/[^a-zA-Z0-9_-]/g, "");
50
+ const artwork = mark === "godx-lockup" ? [GODX_LOCKUP_LIGHT, GODX_LOCKUP_DARK] : [GODX_MARK_LIGHT, GODX_MARK_DARK];
51
+ const html = React.useMemo(
52
+ () => artwork.map((a) => a.markup.replaceAll(GODX_ARTWORK_ID_TOKEN, instanceId)),
53
+ [artwork, instanceId]
54
+ );
55
+ if (mark === "glyph") {
42
56
  const advance = logoGlyphAdvance(glyph);
43
57
  return /* @__PURE__ */ jsx(
44
58
  "span",
@@ -56,25 +70,21 @@ function MarkArtwork({ mark, glyph }) {
56
70
  }
57
71
  );
58
72
  }
59
- return /* @__PURE__ */ jsx(
73
+ return /* @__PURE__ */ jsx(Fragment, { children: ["light", "dark"].map((scheme, index) => /* @__PURE__ */ jsx(
60
74
  "svg",
61
75
  {
62
76
  "data-slot": "logo-artwork",
63
- viewBox: "0 0 32 32",
64
- width: "32",
65
- height: "32",
77
+ "data-artwork": mark,
78
+ "data-scheme": scheme,
79
+ viewBox: artwork[index].viewBox,
66
80
  focusable: "false",
67
81
  "aria-hidden": "true",
68
- children: /* @__PURE__ */ jsx(
69
- "path",
70
- {
71
- fill: "currentColor",
72
- fillRule: "evenodd",
73
- d: "M8 7h16a7 7 0 0 1 7 7v4a7 7 0 0 1-7 7H8a7 7 0 0 1-7-7v-4a7 7 0 0 1 7-7Zm0 6a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1H8Z"
74
- }
75
- )
76
- }
77
- );
82
+ dangerouslySetInnerHTML: {
83
+ __html: html[index]
84
+ }
85
+ },
86
+ scheme
87
+ )) });
78
88
  }
79
89
  const Logo = React.forwardRef(
80
90
  ({
@@ -29,6 +29,23 @@ const DROPDOWN_MENU_PLACEMENT = {
29
29
  bottomStart: { side: "bottom", align: "start" },
30
30
  bottomEnd: { side: "bottom", align: "end" }
31
31
  };
32
+ class MenuCollectionBoundary extends React.Component {
33
+ state = { failed: false };
34
+ static getDerivedStateFromError() {
35
+ return { failed: true };
36
+ }
37
+ // Logged in EVERY build, not only development: "blank, with nothing in the console" is the half
38
+ // of gh#637 that cost the reporter a night of instrumenting `Object.prototype`.
39
+ componentDidCatch(error) {
40
+ console.error(
41
+ "[@godxjp/ui] DropdownMenu: the menu could not be built and was left empty; the rest of the application is unaffected. A menu's children must be collection nodes (DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup, DropdownMenuSub) or components returning one \u2014 an icon or a text label belongs INSIDE an item, not beside it. Underlying error:",
42
+ error
43
+ );
44
+ }
45
+ render() {
46
+ return this.state.failed ? null : this.props.children;
47
+ }
48
+ }
32
49
  function borrowedTag(children, asChild) {
33
50
  return asChild ? React.Children.only(children) : null;
34
51
  }
@@ -357,7 +374,7 @@ function DropdownMenuContent({
357
374
  )
358
375
  ),
359
376
  children: [
360
- /* @__PURE__ */ jsx(Menu, { shouldFocusWrap: loop, autoFocus: openedByHover.current ? false : void 0, children }),
377
+ /* @__PURE__ */ jsx(MenuCollectionBoundary, { children: /* @__PURE__ */ jsx(Menu, { shouldFocusWrap: loop, autoFocus: openedByHover.current ? false : void 0, children }) }),
361
378
  arrow ? /* @__PURE__ */ jsx(OverlayArrow, { children: /* @__PURE__ */ jsx(
362
379
  "svg",
363
380
  {
@@ -601,7 +618,7 @@ function DropdownMenuSubContent({
601
618
  }
602
619
  )
603
620
  ),
604
- children: /* @__PURE__ */ jsx(Menu, { shouldFocusWrap: loop, children })
621
+ children: /* @__PURE__ */ jsx(MenuCollectionBoundary, { children: /* @__PURE__ */ jsx(Menu, { shouldFocusWrap: loop, children }) })
605
622
  }
606
623
  );
607
624
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "23.4.12",
3
+ "version": "24.1.0",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -14,7 +14,8 @@ export interface EmailBrandMarkRect {
14
14
  readonly radius: number;
15
15
  }
16
16
  /** Intrinsic coordinate space of the mark artwork. */
17
- export declare const EMAIL_BRAND_MARK_VIEWBOX = "0 0 32 32";
17
+ /** The component's box, read from the shared artwork so the two cannot drift. */
18
+ export declare const EMAIL_BRAND_MARK_VIEWBOX: "0 0 32 32";
18
19
  /** The emerald capsule. */
19
20
  export declare const EMAIL_BRAND_MARK_CAPSULE: EmailBrandMarkRect;
20
21
  /** The internal light glyph bar inside the capsule. */
@@ -1,6 +1,6 @@
1
1
  import { EMAIL_COLORS } from "./color.js";
2
- import { EMAIL_GEOMETRY_SOURCE } from "./tokens.generated.js";
3
- const EMAIL_BRAND_MARK_VIEWBOX = "0 0 32 32";
2
+ import { EMAIL_GEOMETRY_SOURCE, EMAIL_MARK_SOURCE } from "./tokens.generated.js";
3
+ const EMAIL_BRAND_MARK_VIEWBOX = EMAIL_MARK_SOURCE.viewBox;
4
4
  const EMAIL_BRAND_MARK_CAPSULE = Object.freeze({
5
5
  x: 1,
6
6
  y: 7,
@@ -22,8 +22,8 @@ function roundedRectPath({ x, y, width, height, radius }) {
22
22
  const v = height - 2 * radius;
23
23
  return `M${x + radius} ${y}h${h}${arc(radius, radius, radius)}v${v}${arc(radius, -radius, radius)}H${x + radius}${arc(radius, -radius, -radius)}v-${v}${arc(radius, radius, -radius)}Z`;
24
24
  }
25
- const CAPSULE_PATH = roundedRectPath(EMAIL_BRAND_MARK_CAPSULE);
26
- const GLYPH_PATH = roundedRectPath(EMAIL_BRAND_MARK_GLYPH);
25
+ const CAPSULE_PATH = EMAIL_MARK_SOURCE.bodyPath;
26
+ const GLYPH_PATH = EMAIL_MARK_SOURCE.arrowPath;
27
27
  const DEFAULT_WIDTH = Number.parseFloat(EMAIL_GEOMETRY_SOURCE["--email-mark-width"]);
28
28
  const DEFAULT_HEIGHT = Number.parseFloat(EMAIL_GEOMETRY_SOURCE["--email-mark-height"]);
29
29
  function options(o = {}) {
@@ -39,7 +39,7 @@ const attr = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").repla
39
39
  function emailBrandMarkSvg(o = {}) {
40
40
  const { width, height, color, glyphColor, label } = options(o);
41
41
  const a11y = label ? ` role="img" aria-label="${attr(label)}"` : ` role="presentation" aria-hidden="true"`;
42
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${EMAIL_BRAND_MARK_VIEWBOX}" focusable="false"${a11y} style="display:block;border:0;outline:none;text-decoration:none"><path fill="${color}" d="${CAPSULE_PATH}"/><path fill="${glyphColor}" d="${GLYPH_PATH}"/></svg>`;
42
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${EMAIL_BRAND_MARK_VIEWBOX}" focusable="false"${a11y} style="display:block;border:0;outline:none;text-decoration:none"><g transform="${EMAIL_MARK_SOURCE.transform}" fill-rule="evenodd"><path fill="${color}" d="${CAPSULE_PATH}"/><path fill="${glyphColor}" d="${GLYPH_PATH}"/></g></svg>`;
43
43
  }
44
44
  function emailBrandMarkDataUri(o = {}) {
45
45
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(emailBrandMarkSvg(o))}`;
@@ -37,7 +37,7 @@ export declare const EMAIL_COLOR_SOURCE: {
37
37
  };
38
38
  readonly primary: {
39
39
  readonly cssVar: "--primary";
40
- readonly hsl: "204 100% 37%";
40
+ readonly hsl: "268.7 100% 50%";
41
41
  };
42
42
  readonly primaryForeground: {
43
43
  readonly cssVar: "--primary-foreground";
@@ -45,11 +45,11 @@ export declare const EMAIL_COLOR_SOURCE: {
45
45
  };
46
46
  readonly focus: {
47
47
  readonly cssVar: "--primary";
48
- readonly hsl: "204 100% 37%";
48
+ readonly hsl: "268.7 100% 50%";
49
49
  };
50
50
  readonly brand: {
51
51
  readonly cssVar: "--brand";
52
- readonly hsl: "160.5 100% 29.6%";
52
+ readonly hsl: "268.7 100% 50%";
53
53
  };
54
54
  readonly brandForeground: {
55
55
  readonly cssVar: "--brand-foreground";
@@ -96,7 +96,7 @@ export declare const EMAIL_COLOR_SOURCE_DARK: {
96
96
  };
97
97
  readonly primary: {
98
98
  readonly cssVar: "--primary";
99
- readonly hsl: "204 90% 60%";
99
+ readonly hsl: "268.7 100% 86.9%";
100
100
  };
101
101
  readonly primaryForeground: {
102
102
  readonly cssVar: "--primary-foreground";
@@ -104,11 +104,11 @@ export declare const EMAIL_COLOR_SOURCE_DARK: {
104
104
  };
105
105
  readonly focus: {
106
106
  readonly cssVar: "--primary";
107
- readonly hsl: "204 90% 60%";
107
+ readonly hsl: "268.7 100% 86.9%";
108
108
  };
109
109
  readonly brand: {
110
110
  readonly cssVar: "--brand";
111
- readonly hsl: "160.5 100% 36%";
111
+ readonly hsl: "268.7 100% 86.9%";
112
112
  };
113
113
  readonly brandForeground: {
114
114
  readonly cssVar: "--brand-foreground";
@@ -165,3 +165,13 @@ export declare const EMAIL_GEOMETRY_SOURCE: {
165
165
  readonly "--email-mobile-heading-font-size": "16px";
166
166
  readonly "--email-mobile-cta-width": "100%";
167
167
  };
168
+ /**
169
+ * The GoDX identity artwork — the same two paths `<Logo mark="godx" />` paints, from
170
+ * `src/brand/godx-mark.ts`. Regenerate with the rest of this file; `--check` fails on drift.
171
+ */
172
+ export declare const EMAIL_MARK_SOURCE: {
173
+ readonly viewBox: "0 0 32 32";
174
+ readonly transform: "translate(0 3.917) scale(0.132780)";
175
+ readonly bodyPath: "M93.0 0.0H158.0C161.0 0.0 162.0 1.0 164.0 3.0L197.0 41.0Q201.0 46.0 195.0 46.0H97.0C71.0 46.0 50.0 68.0 50.0 94.0C50.0 119.0 72.0 138.0 97.0 138.0H126.0Q132.0 138.0 128.0 143.0L95.0 180.0Q93.0 182.0 90.0 182.0C65.0 182.0 44.0 171.0 25.0 151.0C7.0 132.0 0.0 115.0 0.0 94.0C0.0 69.0 11.0 52.0 31.0 31.0C52.0 9.0 70.0 0.0 93.0 0.0Z";
176
+ readonly arrowPath: "M106.0 74.0H219.0Q222.0 74.0 224.0 77.0L239.0 94.0Q243.0 99.0 239.0 104.0L169.0 178.0Q165.0 182.0 160.0 182.0H114.0Q108.0 182.0 112.0 178.0L151.0 137.0Q154.0 134.0 151.0 131.0L104.0 81.0Q98.0 74.0 106.0 74.0Z";
177
+ };
@@ -29,7 +29,7 @@ const EMAIL_COLOR_SOURCE = {
29
29
  },
30
30
  "primary": {
31
31
  "cssVar": "--primary",
32
- "hsl": "204 100% 37%"
32
+ "hsl": "268.7 100% 50%"
33
33
  },
34
34
  "primaryForeground": {
35
35
  "cssVar": "--primary-foreground",
@@ -37,11 +37,11 @@ const EMAIL_COLOR_SOURCE = {
37
37
  },
38
38
  "focus": {
39
39
  "cssVar": "--primary",
40
- "hsl": "204 100% 37%"
40
+ "hsl": "268.7 100% 50%"
41
41
  },
42
42
  "brand": {
43
43
  "cssVar": "--brand",
44
- "hsl": "160.5 100% 29.6%"
44
+ "hsl": "268.7 100% 50%"
45
45
  },
46
46
  "brandForeground": {
47
47
  "cssVar": "--brand-foreground",
@@ -87,7 +87,7 @@ const EMAIL_COLOR_SOURCE_DARK = {
87
87
  },
88
88
  "primary": {
89
89
  "cssVar": "--primary",
90
- "hsl": "204 90% 60%"
90
+ "hsl": "268.7 100% 86.9%"
91
91
  },
92
92
  "primaryForeground": {
93
93
  "cssVar": "--primary-foreground",
@@ -95,11 +95,11 @@ const EMAIL_COLOR_SOURCE_DARK = {
95
95
  },
96
96
  "focus": {
97
97
  "cssVar": "--primary",
98
- "hsl": "204 90% 60%"
98
+ "hsl": "268.7 100% 86.9%"
99
99
  },
100
100
  "brand": {
101
101
  "cssVar": "--brand",
102
- "hsl": "160.5 100% 36%"
102
+ "hsl": "268.7 100% 86.9%"
103
103
  },
104
104
  "brandForeground": {
105
105
  "cssVar": "--brand-foreground",
@@ -155,8 +155,15 @@ const EMAIL_GEOMETRY_SOURCE = {
155
155
  "--email-mobile-heading-font-size": "16px",
156
156
  "--email-mobile-cta-width": "100%"
157
157
  };
158
+ const EMAIL_MARK_SOURCE = {
159
+ "viewBox": "0 0 32 32",
160
+ "transform": "translate(0 3.917) scale(0.132780)",
161
+ "bodyPath": "M93.0 0.0H158.0C161.0 0.0 162.0 1.0 164.0 3.0L197.0 41.0Q201.0 46.0 195.0 46.0H97.0C71.0 46.0 50.0 68.0 50.0 94.0C50.0 119.0 72.0 138.0 97.0 138.0H126.0Q132.0 138.0 128.0 143.0L95.0 180.0Q93.0 182.0 90.0 182.0C65.0 182.0 44.0 171.0 25.0 151.0C7.0 132.0 0.0 115.0 0.0 94.0C0.0 69.0 11.0 52.0 31.0 31.0C52.0 9.0 70.0 0.0 93.0 0.0Z",
162
+ "arrowPath": "M106.0 74.0H219.0Q222.0 74.0 224.0 77.0L239.0 94.0Q243.0 99.0 239.0 104.0L169.0 178.0Q165.0 182.0 160.0 182.0H114.0Q108.0 182.0 112.0 178.0L151.0 137.0Q154.0 134.0 151.0 131.0L104.0 81.0Q98.0 74.0 106.0 74.0Z"
163
+ };
158
164
  export {
159
165
  EMAIL_COLOR_SOURCE,
160
166
  EMAIL_COLOR_SOURCE_DARK,
161
- EMAIL_GEOMETRY_SOURCE
167
+ EMAIL_GEOMETRY_SOURCE,
168
+ EMAIL_MARK_SOURCE
162
169
  };
@@ -99,7 +99,6 @@
99
99
  height: var(--logo-godx-size, var(--logo-godx-size-md));
100
100
  border-radius: 0;
101
101
  background: transparent;
102
- color: hsl(var(--logo-godx-color, var(--brand)));
103
102
  }
104
103
 
105
104
  .ui-logo[data-mark="godx"][data-size="xs"] {
@@ -117,6 +116,31 @@
117
116
  height: var(--logo-godx-size, var(--logo-godx-size-lg));
118
117
  }
119
118
 
119
+ .ui-logo[data-mark="godx-lockup"] {
120
+ width: auto;
121
+ height: var(--logo-godx-size, var(--logo-godx-size-md));
122
+ border-radius: 0;
123
+ background: transparent;
124
+ }
125
+
126
+ .ui-logo[data-mark="godx-lockup"][data-size="xs"] {
127
+ height: var(--logo-godx-size, var(--logo-godx-size-xs));
128
+ }
129
+
130
+ .ui-logo[data-mark="godx-lockup"][data-size="sm"] {
131
+ height: var(--logo-godx-size, var(--logo-godx-size-sm));
132
+ }
133
+
134
+ .ui-logo[data-mark="godx-lockup"][data-size="lg"] {
135
+ height: var(--logo-godx-size, var(--logo-godx-size-lg));
136
+ }
137
+
138
+ .ui-logo[data-mark="godx-lockup"] [data-slot="logo-artwork"] {
139
+ display: block;
140
+ height: 100%;
141
+ width: auto;
142
+ }
143
+
120
144
  .ui-logo[data-mark="godx"] [data-slot="logo-artwork"] {
121
145
  display: block;
122
146
  width: 100%;
@@ -156,3 +180,26 @@
156
180
  color: hsl(var(--logo-wordmark-color, var(--logo-godx-color, var(--brand))));
157
181
  }
158
182
  }
183
+
184
+ [data-slot="logo-artwork"][data-scheme="dark"] {
185
+ display: none;
186
+ }
187
+
188
+ @media (prefers-color-scheme: dark) {
189
+ :root:not([data-theme="light"]) [data-slot="logo-artwork"][data-scheme="light"] {
190
+ display: none;
191
+ }
192
+ :root:not([data-theme="light"]) [data-slot="logo-artwork"][data-scheme="dark"] {
193
+ display: block;
194
+ }
195
+ }
196
+
197
+ .dark [data-slot="logo-artwork"][data-scheme="light"],
198
+ :root[data-theme="dark"] [data-slot="logo-artwork"][data-scheme="light"] {
199
+ display: none;
200
+ }
201
+
202
+ .dark [data-slot="logo-artwork"][data-scheme="dark"],
203
+ :root[data-theme="dark"] [data-slot="logo-artwork"][data-scheme="dark"] {
204
+ display: block;
205
+ }
@@ -2239,6 +2239,11 @@
2239
2239
  }
2240
2240
 
2241
2241
  @media (max-width: 768px) {
2242
+
2243
+ .ui-topbar-item {
2244
+ padding-inline: var(--topbar-item-padding-inline-compact);
2245
+ }
2246
+
2242
2247
  .tb-chip-sep,
2243
2248
  .tb-search .kbd,
2244
2249
 
@@ -2250,8 +2255,12 @@
2250
2255
  width: auto;
2251
2256
 
2252
2257
  flex: 0 0 auto;
2253
- min-inline-size: var(--control-height);
2254
- min-width: var(--control-height);
2258
+ min-inline-size: var(--control-height-sm);
2259
+ min-width: var(--control-height-sm);
2260
+ inline-size: var(--control-height-sm);
2261
+ block-size: var(--control-height-sm);
2262
+ justify-content: center;
2263
+ padding-inline: 0;
2255
2264
  }
2256
2265
 
2257
2266
  .tb-search span {
@@ -14,7 +14,9 @@
14
14
  --logo-success-foreground: initial;
15
15
  --logo-godx-color: initial;
16
16
 
17
- --logo-identity-foreground: 48 9% 9%;
17
+ --logo-identity-foreground: 60 33% 99%;
18
+
19
+ --logo-godx-ink-color: 235 68.6% 13.7%;
18
20
 
19
21
  --logo-glyph-cap-baseline-optical-offset: -0.01em;
20
22
  --logo-glyph-cap-descender-optical-offset: -0.09em;
@@ -45,3 +47,10 @@
45
47
  --logo-wordmark-font-family: initial;
46
48
  --logo-wordmark-color: initial;
47
49
  }
50
+
51
+ .dark,
52
+ :root[data-theme="dark"] {
53
+ --logo-identity-foreground: 48 9% 9%;
54
+
55
+ --logo-godx-ink-color: 140 23.1% 97.5%;
56
+ }
@@ -101,6 +101,8 @@
101
101
  --topbar-center-compact-display: none;
102
102
 
103
103
  --topbar-item-padding-inline: var(--space-3);
104
+
105
+ --topbar-item-padding-inline-compact: var(--space-2);
104
106
  --topbar-item-gap: var(--space-2);
105
107
  --topbar-item-min-width: var(--control-height);
106
108
 
@@ -9,7 +9,7 @@
9
9
  --popover: 60 33% 99%;
10
10
  --popover-foreground: 48 8% 13%;
11
11
 
12
- --primary: 204 100% 37%;
12
+ --primary: 268.7 100% 50%;
13
13
  --primary-foreground: 60 33% 99%;
14
14
  --secondary: 45 15% 95%;
15
15
  --secondary-foreground: 48 8% 13%;
@@ -34,15 +34,15 @@
34
34
  --attention: 25 99% 46%;
35
35
  --attention-foreground: 60 33% 99%;
36
36
 
37
- --brand: 160.5 100% 29.6%;
37
+ --brand: 268.7 100% 50%;
38
38
  --brand-foreground: 60 33% 99%;
39
39
 
40
40
  --text-tertiary: 44 5% 55%;
41
41
  --text-disabled: 40 6% 68%;
42
42
  --text-link: 204 100% 39%;
43
- --text-brand: 204 100% 39%;
43
+ --text-brand: 268.3 100% 41.6%;
44
44
 
45
- --text-primary: 204 100% 30%;
45
+ --text-primary: 268 100% 34.5%;
46
46
  --text-success: 152 84% 22%;
47
47
  --text-warning: 36 100% 28%;
48
48
  --text-info: 221 70% 38%;
@@ -321,7 +321,7 @@
321
321
  --card-foreground: 60 20% 96%;
322
322
  --popover: 48 8% 12%;
323
323
  --popover-foreground: 60 20% 96%;
324
- --primary: 204 90% 60%;
324
+ --primary: 268.7 100% 86.9%;
325
325
  --primary-foreground: 48 9% 9%;
326
326
  --secondary: 45 6% 18%;
327
327
  --secondary-foreground: 60 20% 96%;
@@ -346,15 +346,15 @@
346
346
  --attention: 25 95% 56%;
347
347
  --attention-foreground: 48 9% 9%;
348
348
 
349
- --brand: 160.5 100% 36%;
349
+ --brand: 268.7 100% 86.9%;
350
350
  --brand-foreground: 48 9% 9%;
351
351
 
352
352
  --text-tertiary: 44 6% 56%;
353
353
  --text-disabled: 45 6% 40%;
354
354
  --text-link: 204 90% 66%;
355
- --text-brand: 204 90% 66%;
355
+ --text-brand: 268.7 100% 86.9%;
356
356
 
357
- --text-primary: 204 90% 64%;
357
+ --text-primary: 262.7 100% 92.7%;
358
358
  --text-success: 146 55% 64%;
359
359
  --text-warning: 44 95% 62%;
360
360
  --text-info: 221 70% 72%;
@@ -122,10 +122,30 @@ pnpm test # FULL suite — CI only, never from an agent loop
122
122
  pnpm preview:build # integration test: examples + docs must build — at most once, pre-PR
123
123
  pnpm audit # godxjp-ui-audit — 0 errors for touched files
124
124
  pnpm check:mcp-sync # MCP registry ↔ library export drift guard
125
+ pnpm check:frame-axe # WCAG 2.2 AA over every frame — see FRAME-A11Y-CI.md
125
126
  ```
126
127
 
127
128
  `pnpm verify` and `pnpm verify:release` run these together (verify:release also builds) — **both include the full suite, so both belong to CI.** Locally, run them at most once immediately before opening a PR, never inside an edit loop and never while other agents are working on the same machine. It needs `pnpm exec playwright install chromium` once locally; see [FRAME-A11Y-CI.md](./FRAME-A11Y-CI.md) for how to run/scope it, read the evidence, and regenerate its baseline after an accessibility fix.
128
129
 
130
+ ### Reproducing one screen at one width — the frame routes
131
+
132
+ Three gates and every bug report start here, so the addressing is written down rather than guessed
133
+ (godx-jp/id#639 burned three attempts on `?frame=…`, which is not a thing):
134
+
135
+ ```
136
+ pnpm preview # :6008
137
+ http://localhost:6008/isolate/<id> # the demo ALONE, at the real viewport — measure here
138
+ http://localhost:6008/frame/<id> # the same demo inside the device-preset chrome
139
+ ```
140
+
141
+ `<id>` is the demo's path under `docs/`, minus `.tsx`, with `/` turned into `-`:
142
+ `docs/layout/topbar.tsx` → `layout-topbar`, `docs/data-entry/date-picker.tsx` → `data-entry-date-picker`.
143
+ A file may override it with a `slug` in its frontmatter. Both routes accept
144
+ `?dir=rtl&density=compact&theme=dark&locale=ja`; `/frame/**` additionally takes `?preset=`/`?w=`/`?h=`/`?zoom=`.
145
+
146
+ For an axe measurement at a given width, drive `/isolate/<id>` with Playwright at that viewport —
147
+ that is what `scripts/topbar-collision-visual.mjs` and the other `test:visual:*` gates do.
148
+
129
149
  All gates are **self-contained** — no internal/external tooling package required. The eslint, prettier, and vitest setup live in the package (`eslint.config.js`, `prettier.config.mjs`, `vitest.config.ts`, `src/test/`), so a fresh checkout can lint/type-check/test without anything beyond the declared devDependencies.
130
150
 
131
151
  The app side additionally runs **`npm run ui:audit`** (the design-system linter) and must report 0 errors for touched files.
@@ -0,0 +1,135 @@
1
+ # Per-frame accessibility CI — `check:frame-axe`
2
+
3
+ One ruler, run in the repository that owns the CSS.
4
+
5
+ ## Why it exists (gh#643)
6
+
7
+ `scripts/visual-audit.mjs` carries **eight** rules. A consumer's nightly runs `@axe-core/playwright`
8
+ with **68**. Six of our eight are design-language opinions with no axe equivalent —
9
+ `oversaturated-accent`, `sibling-card-gap`, `row-content-starved`, `emoji-rendered`,
10
+ `alert-controls-misplaced`, `css-layers-missing` — and a design system **should** own those. The
11
+ other sixty (names, roles, `aria-*`, contrast, focus order, target size) were checked **nowhere in
12
+ this repository**, and only in a consumer that cannot fix the CSS, because the CSS is here.
13
+
14
+ gh#639 is what that costs. A topbar shipped; every gate here said green; `target-size` failed in
15
+ `godx-jp/id`'s nightly at 320px. And our own `target-size-min` could not have caught it at any
16
+ threshold — it measures a **painted box**, and that failure was an **obscured** target (axe:
17
+ `partiallyObscured`, 8×28). Two rulers, and the disagreement only ever surfaces downstream.
18
+
19
+ ## What it does
20
+
21
+ ```bash
22
+ pnpm check:frame-axe # every frame, 3 viewports
23
+ pnpm check:frame-axe -- --update # rewrite the baseline
24
+ pnpm check:frame-axe -- --scope=showcase # the 30 whole-page frames only, 30s
25
+ pnpm check:frame-axe -- /isolate/layout-topbar # one route, while fixing
26
+ ```
27
+
28
+ | | |
29
+ | --------- | --------------------------------------------------------------------------------- |
30
+ | tool | `@axe-core/playwright` |
31
+ | tags | `wcag2a` · `wcag2aa` · `wcag21aa` · `wcag22aa` — **the consumer's set, verbatim** |
32
+ | routes | every `/isolate/<id>` in `window.__STORY_MANIFEST__` + every `/showcase/<id>` |
33
+ | viewports | 1440×900 · 375×667 · **320×568** |
34
+
35
+ 320 is not decoration: it is WCAG 2.2 SC 1.4.10's reflow width, it is the width the consumer's
36
+ nightly runs, and it is the width gh#639 failed at while 390 passed.
37
+
38
+ Showcases are included on purpose. They are the only frames here shaped like a real screen — a whole
39
+ page, a landmark tree, a focus order — which is precisely the class the component frames cannot
40
+ reach and the consumer has been carrying alone.
41
+
42
+ ## What it found on its first run
43
+
44
+ 54 rows, **159 violation nodes**, on code that passed every other gate in this repository:
45
+
46
+ | nodes | rows | rule | what it means |
47
+ | ----: | ---: | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
48
+ | 62 | 17 | `color-contrast` | text under 4.5:1 (SC 1.4.3) |
49
+ | 28 | 16 | `target-size` | under 24×24 with no spacing exception (SC 2.5.8) — on `Carousel`, `Attachments`, `FilterBar` and `Toolbar`, at **every** width |
50
+ | 21 | 6 | `aria-prohibited-attr` | an `aria-*` on a role that does not allow it |
51
+ | 18 | 6 | `button-name` | **a button a screen reader announces as nothing** |
52
+ | 18 | 6 | `aria-valid-attr-value` | an `aria-*` pointing at an id that is not there |
53
+ | 12 | 3 | `aria-conditional-attr` | an `aria-*` that is invalid in the state it is in |
54
+
55
+ 48 of those nodes are on `/isolate/**` — single components, this package's own output — and 111 on
56
+ `/showcase/**`. None of the six rules exists in `scripts/visual-audit.mjs`. The `target-size` rows
57
+ are the sharpest: our own `target-size-min` rule was running on those same frames and reporting them
58
+ clean, because it measures a painted box.
59
+
60
+ ## The baseline is a debt ledger, not an allowlist
61
+
62
+ The package did not start compliant, and a gate that fails the whole build on its first day gets
63
+ deleted rather than obeyed. `frame-axe-baseline.json` records what was already failing when the gate
64
+ landed, keyed one row per `(route, viewport, rule)`:
65
+
66
+ ```
67
+ "/isolate/layout-topbar @320 target-size": { "count": 2, "help": "…" }
68
+ ```
69
+
70
+ The gate fails on a key that is **not** in the baseline, and on a baselined key whose `count`
71
+ **grows**. A key that stops firing is reported so it can be dropped.
72
+
73
+ - **Delete rows as you fix them.** `--update` rewrites the file; the diff is the review.
74
+ - **Never add a row by hand to turn a red build green.** That is the one move this file exists to
75
+ make visible.
76
+
77
+ ## What this gate does NOT do — stated, not hidden
78
+
79
+ The gate deleted in #492 did three things this one does not, and each is a real gap:
80
+
81
+ 1. **No overlay scope.** It scans the frame as rendered. A menu, dialog, listbox or popover that is
82
+ closed at rest is never measured, so `aria-hidden-focus` and friends stay outside its field of
83
+ view. The old gate opened one overlay per frame from a `data-axe-open` attribute; that attribute
84
+ was removed from the demos along with the gate and would have to come back.
85
+ 2. **No chrome/component split.** The old gate held the preview toolbar to zero violations and
86
+ allowlisted the component scope separately. `/isolate/**` renders the demo alone, so there is
87
+ little chrome to separate — but `/showcase/**` is scanned whole.
88
+ 3. **No per-rule severity.** Every WCAG-tagged rule is treated alike.
89
+
90
+ ## Determinism
91
+
92
+ Two consecutive sweeps of identical code first disagreed on five `color-contrast` rows: a fade-in
93
+ caught mid-flight renders text at partial opacity and axe scores whatever it finds. A gate that
94
+ disagrees with itself gets ignored, so each page is pinned before the scan —
95
+ `reducedMotion: "reduce"`, an injected stylesheet zeroing every animation and transition duration,
96
+ and `document.fonts.ready` (web fonts change glyph geometry, which changes which boxes overlap,
97
+ which changes what `color-contrast` resolves a background to). Two sweeps after that: identical.
98
+
99
+ ## Cost
100
+
101
+ | | |
102
+ | ---------------------------------- | ----------------------------------------------------------------------------------- |
103
+ | full sweep | **3m20s** locally · **12m01s** on the self-hosted runner — 215 routes × 3 viewports |
104
+ | sequential | ~55 min — the three viewport passes run concurrently, which is the whole difference |
105
+ | showcase only (`--scope=showcase`) | 30s |
106
+
107
+ `--shard=i/n` is in the script for the day the sweep outgrows the lane. Using it adds check-run
108
+ names, which costs nothing here because none of them is in `REQUIRED_CI_CHECK_RUNS`.
109
+
110
+ ## Where it runs
111
+
112
+ Two lanes, split on a **measurement taken on the runner, not on a laptop**:
113
+
114
+ | lane | what | when | measured |
115
+ | ----------------------------------------------------------------- | ----------------------------------------- | -------------------------------------------------------------- | -------------------------------------- |
116
+ | `ci-browser.yml` · `Per-frame axe (showcases, WCAG 2.2 AA)` | `--scope=showcase` — 30 whole-page frames | every push to `main` | 30s locally |
117
+ | `ci-browser-full.yml` · `Per-frame axe (all frames, WCAG 2.2 AA)` | the full 215-route sweep | nightly, `workflow_dispatch`, or a `run-browser` label on a PR | **12m01s on the pool** (3m20s locally) |
118
+
119
+ The full sweep went into the merge lane first, on the local 3m20s. On the pool's runner the same
120
+ job took **12m01s** — three times the local wall clock and more than twice CONTRACT.md L4's
121
+ five-minute budget for that whole lane. So the merge lane keeps the showcases, which are the only
122
+ frames here shaped like the screens a consumer ships, and the wide matrix moved to the lane this
123
+ repository already reserves for wide matrices.
124
+
125
+ **Not** the PR lane: that file's own header lists axe among what it deliberately excludes, at a
126
+ measured 653–745s, and that decision is not reopened here.
127
+
128
+ The job is not in `REQUIRED_CI_CHECK_RUNS` (#492 removed it from the release proof map and this does
129
+ not put it back). It still protects a release through `assertCiProvenance`'s collateral rule — **any**
130
+ red check run on the SHA being published refuses the publish.
131
+
132
+ ## Related
133
+
134
+ - [FRAME-COVERAGE-STANDARD.md](./FRAME-COVERAGE-STANDARD.md) — which frames must exist at all.
135
+ - [DEVELOPMENT.md](./DEVELOPMENT.md) §5 — the `/isolate/<id>` · `/frame/<id>` addressing.
@@ -94,10 +94,18 @@ export default function Demo() {
94
94
  >
95
95
  {collapsed ? <PanelLeftOpen /> : <PanelLeftClose />}
96
96
  </Button>
97
- {/* Decorative mark — hidden below sm so the budget goes to the two real controls. */}
98
- <Avatar className="rounded-md">
99
- <AvatarFallback className="bg-primary text-primary-foreground font-bold">C</AvatarFallback>
100
- </Avatar>
97
+ {/* Decorative mark — hidden below sm so the budget goes to the two real controls. That was
98
+ only ever a COMMENT: nothing here hid anything, and at 320px the 32px mark was part of
99
+ why the switcher beside it was crushed to 0 visible px (gh#639). `hideBelow` is the
100
+ contract that makes the sentence true — a slot cannot shrink a control, so a bar that
101
+ does not fit has to drop one. */}
102
+ <Flex as="span" hideBelow="sm" gap="none">
103
+ <Avatar className="rounded-md">
104
+ <AvatarFallback className="bg-primary text-primary-foreground font-bold">
105
+ C
106
+ </AvatarFallback>
107
+ </Avatar>
108
+ </Flex>
101
109
  <DropdownMenu>
102
110
  <DropdownMenuTrigger asChild>
103
111
  {/* Button ships `shrink-0`, and only the LAST child of the start slot gets the built-in
@@ -150,10 +158,13 @@ export default function Demo() {
150
158
  // end · notifications + user menu, both consumer-composed.
151
159
  const end = (
152
160
  <>
153
- {}
154
- <Badge tone="warning" className="text-xs">
155
- ステージング
156
- </Badge>
161
+ {/* The environment marker is the end cluster's most optional item — 92px of a 320px bar, and
162
+ the single biggest reason the start cluster had nothing left. It goes first. */}
163
+ <Flex as="span" hideBelow="sm" gap="none">
164
+ <Badge tone="warning" className="text-xs">
165
+ ステージング
166
+ </Badge>
167
+ </Flex>
157
168
  <TopbarItem
158
169
  aria-label="通知"
159
170
  badge={unread ? 12 : undefined}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "23.4.12",
4
- "godxUiMcp": "23.4.12",
3
+ "version": "24.1.0",
4
+ "godxUiMcp": "24.1.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -52,7 +52,7 @@
52
52
  "./styles/*": "./dist/styles/*.css",
53
53
  "./theme/*": "./dist/theme/*",
54
54
  "./tokens": "./dist/tokens/base.css",
55
- "./contracts/measurement.json": "./dist/contracts/measurement.json",
55
+ "./contracts/measurement.json": "./dist/contracts/measurement.json",
56
56
  "./admin": {
57
57
  "types": "./dist/components/admin/index.d.ts",
58
58
  "import": "./dist/components/admin/index.js"
@@ -322,6 +322,7 @@
322
322
  "check:example-imports": "node scripts/check-example-imports.mjs",
323
323
  "check:frame-coverage": "node scripts/frame-coverage.mjs",
324
324
  "check:frame-coverage-report": "node scripts/frame-coverage.mjs --check",
325
+ "check:frame-axe": "node scripts/check-frame-axe.mjs",
325
326
  "gen:frame-coverage-ledger": "node scripts/gen-frame-coverage-ledger.mjs",
326
327
  "check:frame-coverage-ledger": "node scripts/gen-frame-coverage-ledger.mjs --check && node scripts/check-frame-coverage-ledger.mjs",
327
328
  "check:screen-reader-evidence": "node scripts/check-screen-reader-evidence.mjs",
@@ -469,6 +470,7 @@
469
470
  "vaul": "^1.1.2"
470
471
  },
471
472
  "devDependencies": {
473
+ "@axe-core/playwright": "^4.13.0",
472
474
  "@eslint/js": "^10.0.1",
473
475
  "@hookform/resolvers": "^5.9.1",
474
476
  "@radix-ui/react-accordion": "^1.2.20",