@qw-ui/tokens 0.3.0-next.40 → 0.3.0-next.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -474,6 +474,13 @@ const T2_OKLCH_STEPS = [
474
474
  800,
475
475
  900
476
476
  ];
477
+ /** RFC0012 approved local standard endpoint; Figma synchronization is pending. */
478
+ const T2_BLACK_CONTRAST_ENDPOINT = {
479
+ step: "contrast-endpoint",
480
+ value: "#000000",
481
+ source: "CSS Color sRGB black mathematical endpoint; specs/rfcs/0012-brand-solid-text-contrast.md",
482
+ rationale: "The zero sRGB endpoint is a standard constant for on-brand AA text, independent of Seed and existing color scales."
483
+ };
477
484
  const FIGMA_T2_SOLID_SCALE = {
478
485
  brand: {
479
486
  50: "#F2F6FF",
@@ -621,8 +628,9 @@ const FIGMA_T2_SOLID_SCALE = {
621
628
  }
622
629
  };
623
630
  /**
624
- * Figma「Color Token System」T3 语义角色表(60 个,light/dark 均引用 T2 路径)。
625
- * 与 Figma 节点 22258:49986 逐条对齐;`effect.shadow.*.color` 为复合阴影的颜色
631
+ * Figma「Color Token System」既有角色及用户批准的本地补充角色。
632
+ * 既有角色与 Figma 节点 22258:49986 对齐;新角色等待 Figma 同步,不冒充已回流。
633
+ * light/dark 均引用 T2;`effect.shadow.*.color` 为复合阴影的颜色
626
634
  * 分量(对应 Figma 变量 `--qw-sem-effect-shadow-*-color`)。
627
635
  */
628
636
  const FIGMA_T3_COLOR_ROLES = {
@@ -666,6 +674,10 @@ const FIGMA_T3_COLOR_ROLES = {
666
674
  light: "color.gray.50",
667
675
  dark: "color.black.900"
668
676
  },
677
+ "content.danger": {
678
+ light: "color.red.600",
679
+ dark: "color.red.300"
680
+ },
669
681
  "content.primary": {
670
682
  light: "color.black.base",
671
683
  dark: "color.white.base"
@@ -706,6 +718,10 @@ const FIGMA_T3_COLOR_ROLES = {
706
718
  light: "color.white.base",
707
719
  dark: "color.white.base"
708
720
  },
721
+ "content.on-brand": {
722
+ light: "color.white.base",
723
+ dark: "color.black.contrast-endpoint"
724
+ },
709
725
  "content.on-warning-subtle": {
710
726
  light: "color.yellow.800",
711
727
  dark: "color.yellow.200"
@@ -718,6 +734,10 @@ const FIGMA_T3_COLOR_ROLES = {
718
734
  light: "color.gray.300",
719
735
  dark: "color.gray.600"
720
736
  },
737
+ "border.control": {
738
+ light: "color.gray.600",
739
+ dark: "color.gray.base"
740
+ },
721
741
  "border.focus": {
722
742
  light: "color.brand.base",
723
743
  dark: "color.brand.500"
@@ -779,8 +799,8 @@ const FIGMA_T3_COLOR_ROLES = {
779
799
  dark: "color.blue.a20"
780
800
  },
781
801
  "control-action.danger.default": {
782
- light: "color.red.400",
783
- dark: "color.red.400"
802
+ light: "color.red.600",
803
+ dark: "color.red.600"
784
804
  },
785
805
  "control-action.danger.subtle": {
786
806
  light: "color.red.a10",
@@ -850,6 +870,10 @@ const FIGMA_T3_COLOR_ROLES = {
850
870
  light: "color.black.a50",
851
871
  dark: "color.black.a50"
852
872
  },
873
+ "overlay.control-on-media": {
874
+ light: "color.black.a50",
875
+ dark: "color.white.a40"
876
+ },
853
877
  shadow: {
854
878
  light: "color.black.a10",
855
879
  dark: "color.black.a50"
@@ -1309,6 +1333,7 @@ function evaluateDerivation(d, seed) {
1309
1333
  case "fontStack": return formatFontFamily(seedValueAt(seed, d.seedRef));
1310
1334
  case "cubicBezier": return formatCubicBezier(seedValueAt(seed, d.seedRef));
1311
1335
  case "literal": return String(d.value);
1336
+ case "cssEnvironment": return `env(${d.name}, ${d.fallback})`;
1312
1337
  case "shadowLayer": {
1313
1338
  const { offsetX, offsetY, blur, spread } = shadowShapeOf(seed, d.level);
1314
1339
  return `${offsetX} ${offsetY} ${blur} ${spread} ${rgba([
@@ -1333,7 +1358,8 @@ function seedRefsOf(d) {
1333
1358
  case "cubicBezier": return [d.seedRef];
1334
1359
  case "offsetSteps": return [d.seedRef, d.stepRef];
1335
1360
  case "alphaScale": return [d.stepRef];
1336
- case "literal": return [];
1361
+ case "literal":
1362
+ case "cssEnvironment": return [];
1337
1363
  case "shadowLayer": return ["effect.shadow.base", "size.opacity.alphaStep"];
1338
1364
  }
1339
1365
  }
@@ -1345,6 +1371,7 @@ function sourceKindOf(d) {
1345
1371
  case "cubicBezier": return "seed-alias";
1346
1372
  case "figmaOklchScale": return "design-calibrated";
1347
1373
  case "literal": return "standard-constant";
1374
+ case "cssEnvironment": return "external-standard";
1348
1375
  default: return "seed-derived";
1349
1376
  }
1350
1377
  }
@@ -1364,6 +1391,7 @@ function formatDerivation(d) {
1364
1391
  case "seedAlpha": return `= seed.${d.seedRef} / ${round2(d.alpha * 100)}%`;
1365
1392
  case "alphaScale": return `= ${d.channel === "black" ? "黑" : "白"} / seed.${d.stepRef} × ${d.multiplier}`;
1366
1393
  case "literal": return `= ${d.value}(canonical 标准常量)`;
1394
+ case "cssEnvironment": return `= env(${d.name}, ${d.fallback})(UA external standard)`;
1367
1395
  case "shadowLayer": {
1368
1396
  const f = SHADOW_LEVELS[d.level];
1369
1397
  return `= seed.effect.shadow.base 形状 × {offset ${f.offset}, blur ${f.blur}} · 色 = 黑 / alphaStep × ${f.alphaMult}`;
@@ -1418,11 +1446,32 @@ function buildColorRampRecipes() {
1418
1446
  seedRef
1419
1447
  }));
1420
1448
  }
1449
+ out.push(ref([
1450
+ "color",
1451
+ "black",
1452
+ T2_BLACK_CONTRAST_ENDPOINT.step
1453
+ ], {
1454
+ kind: "literal",
1455
+ value: T2_BLACK_CONTRAST_ENDPOINT.value
1456
+ }, {
1457
+ source: T2_BLACK_CONTRAST_ENDPOINT.source,
1458
+ rationale: T2_BLACK_CONTRAST_ENDPOINT.rationale
1459
+ }));
1421
1460
  return out;
1422
1461
  }
1423
1462
  /** 4. 间距刻度 = spacing.step × N (N=1..8)。 */
1424
1463
  function buildSpacingRecipes() {
1425
- const out = [];
1464
+ const out = [ref([
1465
+ "size",
1466
+ "spacing",
1467
+ "0"
1468
+ ], {
1469
+ kind: "literal",
1470
+ value: "0px"
1471
+ }, {
1472
+ source: "CSS Values zero length",
1473
+ rationale: "A canonical zero needs no theme Seed dependency."
1474
+ })];
1426
1475
  for (let n = 1; n <= 8; n++) out.push(ref([
1427
1476
  "size",
1428
1477
  "spacing",
@@ -1434,6 +1483,25 @@ function buildSpacingRecipes() {
1434
1483
  }));
1435
1484
  return out;
1436
1485
  }
1486
+ function buildSafeAreaInsetRecipes() {
1487
+ return [
1488
+ "top",
1489
+ "right",
1490
+ "bottom",
1491
+ "left"
1492
+ ].map((edge) => ref([
1493
+ "size",
1494
+ "safeAreaInset",
1495
+ edge
1496
+ ], {
1497
+ kind: "cssEnvironment",
1498
+ name: `safe-area-inset-${edge}`,
1499
+ fallback: "0px"
1500
+ }, {
1501
+ source: "https://www.w3.org/TR/css-env-1/#safe-area-insets",
1502
+ rationale: "Dynamic UA-owned viewport inset; a CSS expression, not a fixed device dimension or Seed input."
1503
+ }));
1504
+ }
1437
1505
  /** 圆角档位系数(sm/lg/xl 从 base 派生;默认 base=8px → 4/8/14/16)。 */
1438
1506
  const RADIUS_FACTORS = {
1439
1507
  sm: .5,
@@ -1489,6 +1557,48 @@ function buildRadiusRecipes() {
1489
1557
  })
1490
1558
  ];
1491
1559
  }
1560
+ /** Existing public placeholder geometry: font-relative text and calibrated media. */
1561
+ function buildPlaceholderSizeRecipes() {
1562
+ return [ref([
1563
+ "size",
1564
+ "placeholderTextHeight",
1565
+ "base"
1566
+ ], {
1567
+ kind: "literal",
1568
+ value: "1em"
1569
+ }, {
1570
+ source: "CSS Values font-relative em unit; qw-ui Skeleton public text default",
1571
+ rationale: "Text placeholder follows inherited font size without adding a theme seed."
1572
+ }), ref([
1573
+ "size",
1574
+ "placeholderMediaHeight",
1575
+ "base"
1576
+ ], {
1577
+ kind: "literal",
1578
+ value: "120px"
1579
+ }, {
1580
+ source: "qw-ui Skeleton existing public media placeholder geometry; specs/036-native-display",
1581
+ rationale: "Preserves the existing calibrated media default independently of control/avatar scales."
1582
+ })];
1583
+ }
1584
+ /** Loading logo boxes follow the independent existing public size contract. */
1585
+ function buildLoadingSizeRecipes() {
1586
+ return Object.entries({
1587
+ sm: 32,
1588
+ md: 48,
1589
+ lg: 60
1590
+ }).map(([size, value]) => ref([
1591
+ "size",
1592
+ "loadingLogoSize",
1593
+ size
1594
+ ], {
1595
+ kind: "literal",
1596
+ value: `${value}px`
1597
+ }, {
1598
+ source: "qw-ui Loading public logo-size contract; specs/037-native-loading",
1599
+ rationale: "Independent calibrated logo boxes must not change with unrelated control or icon size seeds."
1600
+ }));
1601
+ }
1492
1602
  /** 6. 控件/图标尺寸:md = base 直通,sm/lg = base ∓ spacing.step × n(控件 ×2、图标 ×1)。 */
1493
1603
  function buildControlIconSizeRecipes() {
1494
1604
  const step = "size.spacing.step";
@@ -1674,6 +1784,18 @@ function buildLineHeightRecipes() {
1674
1784
  kind: "alias",
1675
1785
  seedRef: "typography.lineHeight.base"
1676
1786
  }),
1787
+ ref([
1788
+ "typography",
1789
+ "lineHeight",
1790
+ "comfortable"
1791
+ ], {
1792
+ kind: "literal",
1793
+ value: 1.625
1794
+ }, {
1795
+ sourceKind: "design-calibrated",
1796
+ source: "Figma RihMzcoamEChrLxQa0xjmH / I22378:53258;17214:12764 / frozen scene-22378-53247-reference.tsx",
1797
+ rationale: "Exact 26px / 16px title ratio; independent of lineHeight Seed anchor."
1798
+ }),
1677
1799
  ref([
1678
1800
  "typography",
1679
1801
  "lineHeight",
@@ -1820,6 +1942,37 @@ function buildMotionRecipes() {
1820
1942
  seedRef: "motion.duration.base",
1821
1943
  factor: DURATION_FACTORS.slow
1822
1944
  }),
1945
+ ref([
1946
+ "motion",
1947
+ "duration",
1948
+ "shimmer"
1949
+ ], {
1950
+ kind: "scale",
1951
+ seedRef: "motion.duration.base",
1952
+ factor: 10
1953
+ }),
1954
+ ref([
1955
+ "motion",
1956
+ "easing",
1957
+ "linear"
1958
+ ], {
1959
+ kind: "literal",
1960
+ value: "linear"
1961
+ }, {
1962
+ source: "CSS Easing Functions: linear identity function",
1963
+ rationale: "Uniform decorative spin without an entrance or symmetric pulse curve."
1964
+ }),
1965
+ ref([
1966
+ "motion",
1967
+ "easing",
1968
+ "symmetric"
1969
+ ], {
1970
+ kind: "literal",
1971
+ value: "ease-in-out"
1972
+ }, {
1973
+ source: "CSS Easing Functions: ease-in-out equals cubic-bezier(0.42, 0, 0.58, 1)",
1974
+ rationale: "Symmetric repeating shimmer preserves the existing easing; distinct from entrance easing."
1975
+ }),
1823
1976
  ref([
1824
1977
  "motion",
1825
1978
  "easing",
@@ -1852,6 +2005,9 @@ function allReferenceRecipes() {
1852
2005
  return [
1853
2006
  ...buildColorRampRecipes(),
1854
2007
  ...buildSpacingRecipes(),
2008
+ ...buildSafeAreaInsetRecipes(),
2009
+ ...buildPlaceholderSizeRecipes(),
2010
+ ...buildLoadingSizeRecipes(),
1855
2011
  ...buildRadiusRecipes(),
1856
2012
  ...buildControlIconSizeRecipes(),
1857
2013
  ...buildStrokeWidthRecipes(),
@@ -1862,6 +2018,15 @@ function allReferenceRecipes() {
1862
2018
  ...buildZIndexRecipes(),
1863
2019
  ...buildTypographyRecipes(),
1864
2020
  ...buildMotionRecipes(),
2021
+ ref([
2022
+ "effect",
2023
+ "blur",
2024
+ "sm"
2025
+ ], {
2026
+ kind: "scale",
2027
+ seedRef: "size.spacing.step",
2028
+ factor: .5
2029
+ }),
1865
2030
  ...buildShadowRecipes()
1866
2031
  ];
1867
2032
  }
@@ -1890,40 +2055,44 @@ function pick(tokens, dottedRefPath) {
1890
2055
  function semVar(path) {
1891
2056
  return toVar("sem", path);
1892
2057
  }
1893
- const RADIUS_ROLES = {
1894
- sm: "size.radius.sm",
1895
- control: "size.radius.md",
1896
- card: "size.radius.lg",
1897
- xl: "size.radius.xl",
1898
- pill: "size.radius.full",
1899
- full: "size.radius.full"
1900
- };
1901
- const CONTROL_HEIGHT_ROLES = {
1902
- sm: "size.controlSize.sm",
1903
- md: "size.controlSize.md",
1904
- lg: "size.controlSize.lg"
1905
- };
1906
- const ICON_SIZE_ROLES = {
1907
- sm: "size.iconSize.sm",
1908
- md: "size.iconSize.md",
1909
- lg: "size.iconSize.lg"
1910
- };
1911
- const ACTION_CONTROL_HEIGHT_ROLES = {
1912
- "2xs": "size.actionControlHeight.2xs",
1913
- xs: "size.actionControlHeight.xs",
1914
- sm: "size.actionControlHeight.sm",
1915
- md: "size.actionControlHeight.md",
1916
- lg: "size.actionControlHeight.lg",
1917
- xl: "size.actionControlHeight.xl"
1918
- };
1919
- const ACTION_CONTROL_PADDING_INLINE_ROLES = {
1920
- "2xs": "size.spacing.2",
1921
- xs: "size.spacing.2",
1922
- sm: "size.spacing.4",
1923
- md: "size.spacing.4",
1924
- lg: "size.spacing.5",
1925
- xl: "size.spacing.8"
1926
- };
2058
+ /** Create independent ordinary role tables; prefix compression preserves each literal reference. */
2059
+ function referenceRoles(prefix, values) {
2060
+ return Object.fromEntries(Object.entries(values).map(([role, value]) => [role, `${prefix}.${value}`]));
2061
+ }
2062
+ const RADIUS_ROLES = /* @__PURE__ */ referenceRoles("size.radius", {
2063
+ sm: "sm",
2064
+ control: "md",
2065
+ card: "lg",
2066
+ xl: "xl",
2067
+ pill: "full",
2068
+ full: "full"
2069
+ });
2070
+ const CONTROL_HEIGHT_ROLES = /* @__PURE__ */ referenceRoles("size.controlSize", {
2071
+ sm: "sm",
2072
+ md: "md",
2073
+ lg: "lg"
2074
+ });
2075
+ const ICON_SIZE_ROLES = /* @__PURE__ */ referenceRoles("size.iconSize", {
2076
+ sm: "sm",
2077
+ md: "md",
2078
+ lg: "lg"
2079
+ });
2080
+ const ACTION_CONTROL_HEIGHT_ROLES = /* @__PURE__ */ referenceRoles("size.actionControlHeight", {
2081
+ "2xs": "2xs",
2082
+ xs: "xs",
2083
+ sm: "sm",
2084
+ md: "md",
2085
+ lg: "lg",
2086
+ xl: "xl"
2087
+ });
2088
+ const ACTION_CONTROL_PADDING_INLINE_ROLES = /* @__PURE__ */ referenceRoles("size.spacing", {
2089
+ "2xs": "2",
2090
+ xs: "2",
2091
+ sm: "4",
2092
+ md: "4",
2093
+ lg: "5",
2094
+ xl: "8"
2095
+ });
1927
2096
  const ACTION_CONTROL_ICON_ROLES = {
1928
2097
  "2xs": "size.actionControlIcon.2xs",
1929
2098
  xs: "size.iconSize.sm",
@@ -1932,124 +2101,123 @@ const ACTION_CONTROL_ICON_ROLES = {
1932
2101
  lg: "size.iconSize.sm",
1933
2102
  xl: "size.iconSize.md"
1934
2103
  };
1935
- const ACTION_CONTROL_GAP_ROLES = {
1936
- "2xs": "size.spacing.1",
1937
- xs: "size.spacing.1",
1938
- sm: "size.spacing.2",
1939
- md: "size.spacing.2",
1940
- lg: "size.spacing.2",
1941
- xl: "size.spacing.2"
1942
- };
1943
- const BORDER_WIDTH_ROLES = {
1944
- default: "size.strokeWidth.base",
1945
- strong: "size.strokeWidth.double"
1946
- };
1947
- const DIVIDER_WIDTH_ROLES = {
1948
- thin: "size.strokeWidth.half",
1949
- default: "size.strokeWidth.base"
1950
- };
1951
- const FOCUS_RING_ROLES = {
1952
- width: "size.strokeWidth.double",
1953
- offset: "size.strokeWidth.double"
1954
- };
1955
- const INDICATOR_ROLES = { loading: "size.strokeWidth.double" };
2104
+ const ACTION_CONTROL_GAP_ROLES = /* @__PURE__ */ referenceRoles("size.spacing", {
2105
+ "2xs": "1",
2106
+ xs: "1",
2107
+ sm: "2",
2108
+ md: "2",
2109
+ lg: "2",
2110
+ xl: "2"
2111
+ });
2112
+ const BORDER_WIDTH_ROLES = /* @__PURE__ */ referenceRoles("size.strokeWidth", {
2113
+ default: "base",
2114
+ strong: "double"
2115
+ });
2116
+ const DIVIDER_WIDTH_ROLES = /* @__PURE__ */ referenceRoles("size.strokeWidth", {
2117
+ thin: "half",
2118
+ default: "base"
2119
+ });
2120
+ const FOCUS_RING_ROLES = /* @__PURE__ */ referenceRoles("size.strokeWidth", {
2121
+ width: "double",
2122
+ offset: "double"
2123
+ });
2124
+ const INDICATOR_ROLES = /* @__PURE__ */ referenceRoles("size.strokeWidth", { loading: "double" });
1956
2125
  /** 展示类专用尺寸:Avatar 直径三档(Figma 头像 S/M/L)。 */
1957
- const AVATAR_DIAMETER_ROLES = {
1958
- sm: "size.avatarDiameter.sm",
1959
- md: "size.avatarDiameter.md",
1960
- lg: "size.avatarDiameter.lg"
1961
- };
1962
- const INSET_STACK_ROLES = {
1963
- sm: "size.spacing.2",
1964
- md: "size.spacing.4",
1965
- lg: "size.spacing.6"
1966
- };
1967
- const Z_INDEX_ROLES = {
1968
- dropdown: "size.zIndex.1",
1969
- sticky: "size.zIndex.2",
1970
- overlay: "size.zIndex.3",
1971
- toast: "size.zIndex.4"
1972
- };
1973
- const MOTION_DURATION_ROLES = {
1974
- fast: "motion.duration.fast",
1975
- base: "motion.duration.base",
1976
- slow: "motion.duration.slow"
2126
+ const AVATAR_DIAMETER_ROLES = /* @__PURE__ */ referenceRoles("size.avatarDiameter", {
2127
+ sm: "sm",
2128
+ md: "md",
2129
+ lg: "lg"
2130
+ });
2131
+ /** Independent placeholder roles reference T2 directly, never other T3 roles. */
2132
+ const SKELETON_SIZE_ROLES = {
2133
+ textHeight: "size.placeholderTextHeight.base",
2134
+ rectHeight: "size.placeholderMediaHeight.base",
2135
+ circleDiameter: "size.avatarDiameter.sm"
1977
2136
  };
2137
+ const MOTION_EASING_ROLES = /* @__PURE__ */ referenceRoles("motion.easing", {
2138
+ standard: "standard",
2139
+ symmetric: "symmetric",
2140
+ linear: "linear"
2141
+ });
2142
+ const LOADING_SIZE_ROLES = /* @__PURE__ */ referenceRoles("size.loadingLogoSize", {
2143
+ sm: "sm",
2144
+ md: "md",
2145
+ lg: "lg"
2146
+ });
2147
+ const LAYOUT_GAP_ROLES = /* @__PURE__ */ referenceRoles("size.spacing", {
2148
+ sm: "2",
2149
+ md: "4",
2150
+ lg: "6"
2151
+ });
2152
+ const SAFE_AREA_INSET_ROLES = /* @__PURE__ */ referenceRoles("size.safeAreaInset", {
2153
+ top: "top",
2154
+ right: "right",
2155
+ bottom: "bottom",
2156
+ left: "left"
2157
+ });
2158
+ const SAFE_AREA_MINIMUM_ROLES = /* @__PURE__ */ referenceRoles("size.spacing", {
2159
+ none: "0",
2160
+ sm: "2",
2161
+ md: "4",
2162
+ lg: "6"
2163
+ });
2164
+ const INSET_STACK_ROLES = /* @__PURE__ */ referenceRoles("size.spacing", {
2165
+ sm: "2",
2166
+ md: "4",
2167
+ lg: "6"
2168
+ });
2169
+ const Z_INDEX_ROLES = /* @__PURE__ */ referenceRoles("size.zIndex", {
2170
+ dropdown: "1",
2171
+ sticky: "2",
2172
+ overlay: "3",
2173
+ toast: "4"
2174
+ });
2175
+ const MOTION_DURATION_ROLES = /* @__PURE__ */ referenceRoles("motion.duration", {
2176
+ fast: "fast",
2177
+ base: "base",
2178
+ slow: "slow",
2179
+ shimmer: "shimmer",
2180
+ spin: "shimmer"
2181
+ });
1978
2182
  /** 字体族角色:text(正文缺省)/ number(数字数据)/ code(等宽)。 */
1979
- const FONT_FAMILY_ROLES = {
1980
- text: "typography.fontFamily.text",
1981
- number: "typography.fontFamily.number",
1982
- code: "typography.fontFamily.code"
1983
- };
2183
+ const FONT_FAMILY_ROLES = /* @__PURE__ */ referenceRoles("typography.fontFamily", {
2184
+ text: "text",
2185
+ number: "number",
2186
+ code: "code"
2187
+ });
1984
2188
  const T_LETTER = "typography.letterSpacing.base";
2189
+ /** Fresh records retain independent mutable role values and original property order. */
2190
+ function typographyRole(size, weight = "regular", lineHeight = "normal", family = "text") {
2191
+ return {
2192
+ fontFamilyRef: `typography.fontFamily.${family}`,
2193
+ fontSizeRef: `typography.fontSize.${size}`,
2194
+ fontWeightRef: `typography.fontWeight.${weight}`,
2195
+ lineHeightRef: `typography.lineHeight.${lineHeight}`,
2196
+ letterSpacingRef: T_LETTER
2197
+ };
2198
+ }
1985
2199
  /** typography.* 复合角色:每个字段各自引用 Reference(无任何裸字面量)。 */
1986
2200
  const TYPOGRAPHY_ROLES = {
1987
- body: {
1988
- fontFamilyRef: "typography.fontFamily.text",
1989
- fontSizeRef: "typography.fontSize.base",
1990
- fontWeightRef: "typography.fontWeight.regular",
1991
- lineHeightRef: "typography.lineHeight.normal",
1992
- letterSpacingRef: T_LETTER
1993
- },
1994
- bodyStrong: {
1995
- fontFamilyRef: "typography.fontFamily.text",
1996
- fontSizeRef: "typography.fontSize.base",
1997
- fontWeightRef: "typography.fontWeight.medium",
1998
- lineHeightRef: "typography.lineHeight.normal",
1999
- letterSpacingRef: T_LETTER
2000
- },
2001
- headingSm: {
2002
- fontFamilyRef: "typography.fontFamily.text",
2003
- fontSizeRef: "typography.fontSize.lg",
2004
- fontWeightRef: "typography.fontWeight.semibold",
2005
- lineHeightRef: "typography.lineHeight.tight",
2006
- letterSpacingRef: T_LETTER
2007
- },
2008
- headingMd: {
2009
- fontFamilyRef: "typography.fontFamily.text",
2010
- fontSizeRef: "typography.fontSize.xl",
2011
- fontWeightRef: "typography.fontWeight.semibold",
2012
- lineHeightRef: "typography.lineHeight.tight",
2013
- letterSpacingRef: T_LETTER
2014
- },
2015
- headingLg: {
2016
- fontFamilyRef: "typography.fontFamily.text",
2017
- fontSizeRef: "typography.fontSize.2xl",
2018
- fontWeightRef: "typography.fontWeight.semibold",
2019
- lineHeightRef: "typography.lineHeight.tight",
2020
- letterSpacingRef: T_LETTER
2021
- },
2022
- caption: {
2023
- fontFamilyRef: "typography.fontFamily.text",
2024
- fontSizeRef: "typography.fontSize.sm",
2025
- fontWeightRef: "typography.fontWeight.regular",
2026
- lineHeightRef: "typography.lineHeight.normal",
2027
- letterSpacingRef: T_LETTER
2028
- },
2201
+ body: /* @__PURE__ */ typographyRole("base"),
2202
+ bodyStrong: /* @__PURE__ */ typographyRole("base", "medium"),
2203
+ productTitle: /* @__PURE__ */ typographyRole("lg", "semibold", "comfortable"),
2204
+ headingSm: /* @__PURE__ */ typographyRole("lg", "semibold", "tight"),
2205
+ headingMd: /* @__PURE__ */ typographyRole("xl", "semibold", "tight"),
2206
+ headingLg: /* @__PURE__ */ typographyRole("2xl", "semibold", "tight"),
2207
+ caption: /* @__PURE__ */ typographyRole("sm"),
2029
2208
  /** 大正文(治理稿 T3 字段「大正文 16px · T2 M」):行高沿用 canonical 比例引擎。 */
2030
- bodyLg: {
2031
- fontFamilyRef: "typography.fontFamily.text",
2032
- fontSizeRef: "typography.fontSize.lg",
2033
- fontWeightRef: "typography.fontWeight.regular",
2034
- lineHeightRef: "typography.lineHeight.normal",
2035
- letterSpacingRef: T_LETTER
2036
- },
2209
+ bodyLg: /* @__PURE__ */ typographyRole("lg"),
2037
2210
  /** 小标签(治理稿 Text/Label/Small = 11/500/16):字号取 T2 xs=11px。 */
2038
- labelSm: {
2039
- fontFamilyRef: "typography.fontFamily.text",
2040
- fontSizeRef: "typography.fontSize.xs",
2041
- fontWeightRef: "typography.fontWeight.medium",
2042
- lineHeightRef: "typography.lineHeight.normal",
2043
- letterSpacingRef: T_LETTER
2044
- },
2045
- code: {
2046
- fontFamilyRef: "typography.fontFamily.code",
2047
- fontSizeRef: "typography.fontSize.sm",
2048
- fontWeightRef: "typography.fontWeight.regular",
2049
- lineHeightRef: "typography.lineHeight.normal",
2050
- letterSpacingRef: T_LETTER
2051
- }
2211
+ labelSm: /* @__PURE__ */ typographyRole("xs", "medium"),
2212
+ code: /* @__PURE__ */ typographyRole("sm", "regular", "normal", "code")
2052
2213
  };
2214
+ const typographyProjectionFields = [...[
2215
+ "fontFamily",
2216
+ "fontSize",
2217
+ "fontWeight",
2218
+ "lineHeight",
2219
+ "letterSpacing"
2220
+ ]];
2053
2221
  /**
2054
2222
  * 颜色角色的 Semantic 变量路径:绝大多数角色落在 `--qw-sem-color-*` 下;
2055
2223
  * `effect.shadow.*.color` 是复合阴影的颜色分量,对应 Figma 变量
@@ -2066,6 +2234,26 @@ const SHADOW_COMPOSITE_ROLES = {
2066
2234
  floating: "md",
2067
2235
  dialog: "lg"
2068
2236
  };
2237
+ /** Keep source table references live; preserve projection order without repeated loops. */
2238
+ const SIZE_ROLE_GROUPS = [
2239
+ ["radius", RADIUS_ROLES],
2240
+ ["controlHeight", CONTROL_HEIGHT_ROLES],
2241
+ ["iconSize", ICON_SIZE_ROLES],
2242
+ ["actionControlHeight", ACTION_CONTROL_HEIGHT_ROLES],
2243
+ ["actionControlPaddingInline", ACTION_CONTROL_PADDING_INLINE_ROLES],
2244
+ ["actionControlIcon", ACTION_CONTROL_ICON_ROLES],
2245
+ ["actionControlGap", ACTION_CONTROL_GAP_ROLES],
2246
+ ["borderWidth", BORDER_WIDTH_ROLES],
2247
+ ["dividerWidth", DIVIDER_WIDTH_ROLES],
2248
+ ["focusRing", FOCUS_RING_ROLES],
2249
+ ["indicator", INDICATOR_ROLES],
2250
+ ["avatarDiameter", AVATAR_DIAMETER_ROLES],
2251
+ ["layoutGap", LAYOUT_GAP_ROLES],
2252
+ ["safeAreaInset", SAFE_AREA_INSET_ROLES],
2253
+ ["safeAreaMinimum", SAFE_AREA_MINIMUM_ROLES],
2254
+ ["loading", LOADING_SIZE_ROLES],
2255
+ ["skeleton", SKELETON_SIZE_ROLES]
2256
+ ];
2069
2257
  /**
2070
2258
  * 全量 Semantic 角色的结构化血缘表——"角色 → 引用的 Reference 路径(含 light/dark 分支)"。
2071
2259
  * 与 buildSemanticStructural/buildSemanticColor 的产出一一对应(顺序一致)。
@@ -2078,64 +2266,9 @@ function semanticRoleEntries() {
2078
2266
  cssVar: semVar(path),
2079
2267
  refPath
2080
2268
  });
2081
- for (const [role, refPath] of Object.entries(RADIUS_ROLES)) structural([
2082
- "size",
2083
- "radius",
2084
- role
2085
- ], refPath);
2086
- for (const [role, refPath] of Object.entries(CONTROL_HEIGHT_ROLES)) structural([
2087
- "size",
2088
- "controlHeight",
2089
- role
2090
- ], refPath);
2091
- for (const [role, refPath] of Object.entries(ICON_SIZE_ROLES)) structural([
2092
- "size",
2093
- "iconSize",
2094
- role
2095
- ], refPath);
2096
- for (const [role, refPath] of Object.entries(ACTION_CONTROL_HEIGHT_ROLES)) structural([
2097
- "size",
2098
- "actionControlHeight",
2099
- role
2100
- ], refPath);
2101
- for (const [role, refPath] of Object.entries(ACTION_CONTROL_PADDING_INLINE_ROLES)) structural([
2269
+ for (const [group, table] of SIZE_ROLE_GROUPS) for (const [role, refPath] of Object.entries(table)) structural([
2102
2270
  "size",
2103
- "actionControlPaddingInline",
2104
- role
2105
- ], refPath);
2106
- for (const [role, refPath] of Object.entries(ACTION_CONTROL_ICON_ROLES)) structural([
2107
- "size",
2108
- "actionControlIcon",
2109
- role
2110
- ], refPath);
2111
- for (const [role, refPath] of Object.entries(ACTION_CONTROL_GAP_ROLES)) structural([
2112
- "size",
2113
- "actionControlGap",
2114
- role
2115
- ], refPath);
2116
- for (const [role, refPath] of Object.entries(BORDER_WIDTH_ROLES)) structural([
2117
- "size",
2118
- "borderWidth",
2119
- role
2120
- ], refPath);
2121
- for (const [role, refPath] of Object.entries(DIVIDER_WIDTH_ROLES)) structural([
2122
- "size",
2123
- "dividerWidth",
2124
- role
2125
- ], refPath);
2126
- for (const [role, refPath] of Object.entries(FOCUS_RING_ROLES)) structural([
2127
- "size",
2128
- "focusRing",
2129
- role
2130
- ], refPath);
2131
- for (const [role, refPath] of Object.entries(INDICATOR_ROLES)) structural([
2132
- "size",
2133
- "indicator",
2134
- role
2135
- ], refPath);
2136
- for (const [role, refPath] of Object.entries(AVATAR_DIAMETER_ROLES)) structural([
2137
- "size",
2138
- "avatarDiameter",
2271
+ group,
2139
2272
  role
2140
2273
  ], refPath);
2141
2274
  for (const [role, refPath] of Object.entries(INSET_STACK_ROLES)) {
@@ -2165,43 +2298,26 @@ function semanticRoleEntries() {
2165
2298
  "fontFamily",
2166
2299
  role
2167
2300
  ], refPath);
2168
- for (const [role, spec] of Object.entries(TYPOGRAPHY_ROLES)) {
2169
- structural([
2170
- "typography",
2171
- role,
2172
- "fontFamily"
2173
- ], spec.fontFamilyRef);
2174
- structural([
2175
- "typography",
2176
- role,
2177
- "fontSize"
2178
- ], spec.fontSizeRef);
2179
- structural([
2180
- "typography",
2181
- role,
2182
- "fontWeight"
2183
- ], spec.fontWeightRef);
2184
- structural([
2185
- "typography",
2186
- role,
2187
- "lineHeight"
2188
- ], spec.lineHeightRef);
2189
- structural([
2190
- "typography",
2191
- role,
2192
- "letterSpacing"
2193
- ], spec.letterSpacingRef);
2194
- }
2301
+ for (const [role, spec] of Object.entries(TYPOGRAPHY_ROLES)) for (const field of typographyProjectionFields) structural([
2302
+ "typography",
2303
+ role,
2304
+ field
2305
+ ], spec[`${field}Ref`]);
2195
2306
  for (const [role, refPath] of Object.entries(MOTION_DURATION_ROLES)) structural([
2196
2307
  "motion",
2197
2308
  "duration",
2198
2309
  role
2199
2310
  ], refPath);
2200
- structural([
2311
+ for (const [role, refPath] of Object.entries(MOTION_EASING_ROLES)) structural([
2201
2312
  "motion",
2202
2313
  "easing",
2203
- "standard"
2204
- ], "motion.easing.standard");
2314
+ role
2315
+ ], refPath);
2316
+ structural([
2317
+ "effect",
2318
+ "blur",
2319
+ "media-control"
2320
+ ], "effect.blur.sm");
2205
2321
  for (const [path, refs] of Object.entries(COLOR_ROLES)) {
2206
2322
  const segs = colorRoleSemPath(path);
2207
2323
  out.push({
@@ -2363,6 +2479,7 @@ function derivationToCssExpr(d) {
2363
2479
  case "seedAlpha": return `rgb(from var(${seedVar(d.seedRef)}) r g b / ${round4$1(d.alpha)})`;
2364
2480
  case "alphaScale": return `rgb(${d.channel === "black" ? "0 0 0" : "255 255 255"} / calc(var(${seedVar(d.stepRef)}) * ${d.multiplier}))`;
2365
2481
  case "literal": return String(d.value);
2482
+ case "cssEnvironment": return `env(${d.name}, ${d.fallback})`;
2366
2483
  case "shadowLayer": {
2367
2484
  const s = shadowShapeExprs(d.level);
2368
2485
  const alpha = `calc(var(${seedVar("size.opacity.alphaStep")}) * ${SHADOW_LEVELS[d.level].alphaMult})`;
@@ -2478,12 +2595,20 @@ function buildT2() {
2478
2595
  });
2479
2596
  t2[seed] = steps;
2480
2597
  }
2598
+ t2.black[T2_BLACK_CONTRAST_ENDPOINT.step] = t2Token(colorValue(T2_BLACK_CONTRAST_ENDPOINT.value), {
2599
+ sourceKind: "standard-constant",
2600
+ seedRefs: [],
2601
+ source: T2_BLACK_CONTRAST_ENDPOINT.source,
2602
+ rationale: T2_BLACK_CONTRAST_ENDPOINT.rationale,
2603
+ formula: "= sRGB(0, 0, 0), alpha 1"
2604
+ });
2481
2605
  return t2;
2482
2606
  }
2483
2607
  function buildT3() {
2484
2608
  const t3 = {};
2485
2609
  for (const [role, refs] of Object.entries(FIGMA_T3_COLOR_ROLES)) {
2486
2610
  const segs = role.split(".");
2611
+ const approval = role === "content.on-brand" ? "RFC0012 user 2026-10-12" : role === "content.danger" ? "RFC0026" : role === "border.control" ? "SPEC085" : void 0;
2487
2612
  const node = {
2488
2613
  $type: "color",
2489
2614
  $value: t2Ref(refs.light),
@@ -2492,7 +2617,11 @@ function buildT3() {
2492
2617
  modes: {
2493
2618
  light: t2Ref(refs.light),
2494
2619
  dark: t2Ref(refs.dark)
2495
- }
2620
+ },
2621
+ ...approval ? {
2622
+ approval,
2623
+ figmaSync: "pending"
2624
+ } : {}
2496
2625
  } }
2497
2626
  };
2498
2627
  let cur = t3;
@@ -2514,7 +2643,11 @@ function buildFigmaColorSnapshot() {
2514
2643
  collection: "Color",
2515
2644
  figmaNode: "22258:49986",
2516
2645
  generated: true,
2517
- owner: "design-system"
2646
+ owner: "design-system",
2647
+ localExtensions: {
2648
+ rfc: "0012",
2649
+ figmaSync: "pending"
2650
+ }
2518
2651
  } },
2519
2652
  qw: {
2520
2653
  t1: buildT1(),
@@ -2587,17 +2720,18 @@ function provenanceOf(leaf) {
2587
2720
  const ext = extensionObject(leaf);
2588
2721
  return isObject(ext.provenance) ? ext.provenance : {};
2589
2722
  }
2590
- function isSolidColorValue(value) {
2723
+ /** Shared structural checks preserve the same short-circuit/getter order in both guards. */
2724
+ function hasColorComponents(value) {
2591
2725
  if (!isObject(value)) return false;
2592
2726
  if (value.colorSpace !== "srgb" && value.colorSpace !== "oklch") return false;
2593
2727
  if (!Array.isArray(value.components) || value.components.length !== 3) return false;
2594
- return value.alpha === 1;
2728
+ return true;
2729
+ }
2730
+ function isSolidColorValue(value) {
2731
+ return hasColorComponents(value) && value.alpha === 1;
2595
2732
  }
2596
2733
  function isColorLikeValue(value) {
2597
- if (!isObject(value)) return false;
2598
- if (value.colorSpace !== "srgb" && value.colorSpace !== "oklch") return false;
2599
- if (!Array.isArray(value.components) || value.components.length !== 3) return false;
2600
- return typeof value.alpha === "number" && value.alpha >= 0 && value.alpha <= 1;
2734
+ return hasColorComponents(value) && typeof value.alpha === "number" && value.alpha >= 0 && value.alpha <= 1;
2601
2735
  }
2602
2736
  function validateNoUnexpectedName(path, errors) {
2603
2737
  const joined = pathKey(path);
@@ -2608,18 +2742,20 @@ function validateT3Path(path, errors) {
2608
2742
  const joined = pathKey(path);
2609
2743
  for (const segment of path.slice(2)) if (!T3_SEGMENT.test(segment)) errors.push(`${joined}: T3 semantic segment "${segment}" must use kebab-case words without numeric suffixes`);
2610
2744
  }
2611
- /** T2 Color 固定为 base / Seed Alpha / OKLCH 三类标准档位。 */
2745
+ /** Existing scales plus the explicitly approved RFC0012 standard endpoint. */
2612
2746
  function classifyT2Step(step) {
2613
2747
  if (step === "base") return "base";
2614
2748
  if (T2_ALPHA_SET.has(step)) return "alpha";
2615
2749
  if (T2_OKLCH_SET.has(step)) return "oklch";
2750
+ if (step === T2_BLACK_CONTRAST_ENDPOINT.step) return "constant";
2616
2751
  return null;
2617
2752
  }
2618
2753
  /** 各 T2 步类对应的 provenance.sourceKind(血缘治理不变量)。 */
2619
2754
  const T2_STEP_SOURCE_KIND = {
2620
2755
  base: "seed-alias",
2621
2756
  alpha: "seed-derived",
2622
- oklch: "design-calibrated"
2757
+ oklch: "design-calibrated",
2758
+ constant: "standard-constant"
2623
2759
  };
2624
2760
  function validateT2Provenance(leaf, seed, step, errors) {
2625
2761
  const joined = pathKey(leaf.path);
@@ -2695,6 +2831,11 @@ function validateFigmaColorTokens(doc) {
2695
2831
  if (rest.length > 0 || !seed || !step) errors.push(`${joined}: T2 token must be qw/t2/{seed}/{base|alpha|oklch-step}`);
2696
2832
  if (seed && !T1_SET.has(seed)) errors.push(`${joined}: T2 seed "${seed}" must exist in T1`);
2697
2833
  const stepKind = step ? classifyT2Step(step) : null;
2834
+ if (stepKind === "constant") {
2835
+ if (seed !== "black") errors.push(`${joined}: RFC0012 endpoint is only permitted under black`);
2836
+ const value = leaf.value;
2837
+ if (!isObject(value) || value.colorSpace !== "srgb" || value.alpha !== 1 || !Array.isArray(value.components) || value.components.length !== 3 || value.components.some((channel) => channel !== 0)) errors.push(`${joined}: contrast endpoint must be the approved opaque sRGB black mathematical endpoint`);
2838
+ }
2698
2839
  if (step && !stepKind) errors.push(`${joined}: unsupported T2 step "${step}"`);
2699
2840
  if (!isColorLikeValue(leaf.value) && step !== "base") errors.push(`${joined}: T2 token must carry a concrete color value`);
2700
2841
  if (seed && step && T1_SET.has(seed) && stepKind) {
@@ -2711,6 +2852,7 @@ function validateFigmaColorTokens(doc) {
2711
2852
  if (missingAlpha.length) errors.push(`qw.t2.${seed}: missing alpha steps ${missingAlpha.join(", ")}`);
2712
2853
  if (missingOklch.length) errors.push(`qw.t2.${seed}: missing OKLCH steps ${missingOklch.join(", ")}`);
2713
2854
  if (unexpected.length) errors.push(`qw.t2.${seed}: unexpected steps ${unexpected.join(", ")}`);
2855
+ if (seed === "black" && !steps.has(T2_BLACK_CONTRAST_ENDPOINT.step)) errors.push("qw.t2.black: missing RFC0012 contrast endpoint");
2714
2856
  }
2715
2857
  const expectedT3 = new Set(figmaT3RolePaths());
2716
2858
  const actualT3 = /* @__PURE__ */ new Set();
@@ -2718,7 +2860,7 @@ function validateFigmaColorTokens(doc) {
2718
2860
  const joined = pathKey(leaf.qwPath);
2719
2861
  actualT3.add(joined);
2720
2862
  validateT3Path(leaf.qwPath, errors);
2721
- if (!expectedT3.has(joined)) errors.push(`${joined}: unexpected T3 role (not in the 60-role Figma set)`);
2863
+ if (!expectedT3.has(joined)) errors.push(`${joined}: unexpected T3 role (not in the canonical Figma/local-approved set)`);
2722
2864
  const modes = modesOf(leaf);
2723
2865
  const light = refToPath(modes.light);
2724
2866
  const dark = refToPath(modes.dark);