@q1travel/design-system 0.2.0 → 0.3.1

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.
Files changed (32) hide show
  1. package/.agents/skills/product-design/AGENTS.md +9 -0
  2. package/.agents/skills/product-design/SKILL.md +63 -0
  3. package/.agents/skills/product-design/exemplars/README.md +12 -0
  4. package/.agents/skills/product-design/references/copy.md +7 -0
  5. package/.agents/skills/product-design/references/coverage-gaps.md +9 -0
  6. package/.agents/skills/product-design/references/glossary.md +5 -0
  7. package/.agents/skills/product-design/references/interface-quality.md +7 -0
  8. package/.agents/skills/product-design/references/patterns.md +13 -0
  9. package/.agents/skills/product-design/references/product-judgment.md +7 -0
  10. package/.agents/skills/product-design/references/resilience.md +13 -0
  11. package/.agents/skills/product-design/references/review-geist.md +94 -0
  12. package/.agents/skills/product-design/references/rules.md +17 -0
  13. package/.agents/skills/product-design/references/surfaces-forms.md +14 -0
  14. package/.agents/skills/product-design/references/surfaces-overlays.md +12 -0
  15. package/.agents/skills/product-design/references/surfaces.md +14 -0
  16. package/bin/q1-design.mjs +464 -0
  17. package/contracts/design-engineering-principles.md +39 -0
  18. package/contracts/design.dark.md +333 -0
  19. package/contracts/design.md +333 -0
  20. package/contracts/icon-system.md +77 -0
  21. package/contracts/implementation-map.md +29 -0
  22. package/contracts/interface-guidelines.md +122 -0
  23. package/contracts/token-naming.md +82 -0
  24. package/{dist → dist-pkg}/design-system.d.ts +2 -2
  25. package/{dist → dist-pkg}/design-system.js +630 -628
  26. package/{dist → dist-pkg}/style.css +76 -78
  27. package/package.json +19 -9
  28. package/tooling/README.md +9 -0
  29. package/tooling/scripts/evals/fixtures.json +7 -0
  30. package/tooling/scripts/evals/rules-checklist.json +23 -0
  31. package/tooling/scripts/evals/transition-all/after/example.css +4 -0
  32. package/tooling/scripts/evals/transition-all/before/example.css +4 -0
@@ -1,7 +1,6 @@
1
1
  /* q1travel design system — 发布样式(tokens + 字体兜底 + foundations + components)。构建产物,勿手改。 */
2
2
  :root {
3
- /* 钉死到站点主题而非 OS 偏好:原生控件(select 弹层/滚动条/search ✕)随 data-theme 渲染。 */
4
- color-scheme: light;
3
+ /* color-scheme 不在此声明:tokens.css 随包发布,全局副作用交消费方应用掌管(门户侧在 global.css)。 */
5
4
  --q1-color-background-100: #ffffff;
6
5
  --q1-color-background-200: #fafafa;
7
6
  --q1-color-gray-100: #f2f2f2;
@@ -206,7 +205,6 @@
206
205
  }
207
206
 
208
207
  :root[data-theme='dark'] {
209
- color-scheme: dark;
210
208
  /* 画布抬灰(对标 Geist 暗色 #0a0a0a):表面自带浮起感,纯黑仅留给 background-200 底壳。 */
211
209
  --q1-color-background-100: #0a0a0a;
212
210
  --q1-color-background-200: #000000;
@@ -340,7 +338,7 @@
340
338
 
341
339
 
342
340
 
343
- .sr-only {
341
+ .q1-sr-only {
344
342
  position: absolute;
345
343
  width: 1px;
346
344
  height: 1px;
@@ -956,7 +954,7 @@
956
954
  color: var(--q1-color-status-danger);
957
955
  }
958
956
 
959
- .kbd {
957
+ .q1-kbd {
960
958
  display: inline-flex;
961
959
  align-items: center;
962
960
  justify-content: center;
@@ -972,7 +970,7 @@
972
970
  line-height: 1;
973
971
  }
974
972
 
975
- .kbd--small {
973
+ .q1-kbd--small {
976
974
  min-width: 20px;
977
975
  height: 20px;
978
976
  padding: 0 4px;
@@ -2230,14 +2228,14 @@ a.breadcrumb__link:hover {
2230
2228
  padding-right: 48px;
2231
2229
  }
2232
2230
 
2233
- .tabs-specimen {
2231
+ .tabs-root {
2234
2232
  border: 1px solid var(--q1-color-border);
2235
2233
  border-radius: var(--q1-radius-3);
2236
2234
  background: var(--q1-color-bg-panel);
2237
2235
  padding: var(--q1-space-6);
2238
2236
  }
2239
2237
 
2240
- /* 分段控件 Switch(对标 Geist Switch):一组互斥选项,选中段抬起;底层 sr-only 单选组,方向键原生可用。 */
2238
+ /* 分段控件 Switch(对标 Geist Switch):一组互斥选项,选中段抬起;底层 q1-sr-only 单选组,方向键原生可用。 */
2241
2239
  .switch-control {
2242
2240
  display: inline-flex;
2243
2241
  align-items: center;
@@ -2308,7 +2306,7 @@ a.breadcrumb__link:hover {
2308
2306
  align-items: center;
2309
2307
  }
2310
2308
 
2311
- .tabs-specimen {
2309
+ .tabs-root {
2312
2310
  display: grid;
2313
2311
  gap: var(--q1-space-4);
2314
2312
  }
@@ -2419,7 +2417,7 @@ a.breadcrumb__link:hover {
2419
2417
  padding-inline: 10px;
2420
2418
  }
2421
2419
 
2422
- /* 带 tooltip 的禁用 tab 外层包了一个 span:真正的 flex item 是它而不是内层 button,
2420
+ /* 带 q1-tooltip 的禁用 tab 外层包了一个 span:真正的 flex item 是它而不是内层 button,
2423
2421
  基础规则里固定 flex: none,这里要覆盖成跟裸 tab 一样按比例伸展,否则窄屏下完全不随宽度变化
2424
2422
  (实测:加了这条后仍比裸 tab 窄约 10-20px,是嵌套 flex 容器的已知残留误差,量级上远好于
2425
2423
  修复前的 flex: none,暂不继续深挖)。 */
@@ -3624,7 +3622,7 @@ a.breadcrumb__link:hover {
3624
3622
  }
3625
3623
 
3626
3624
  /* 外层只负责宽度约束与视觉样式;实际的 head/tail flex 布局交给 __visual——
3627
- sr-only 完整文本节点与 __visual 是并列的两个子节点,sr-only 绝对定位不占布局空间。 */
3625
+ q1-sr-only 完整文本节点与 __visual 是并列的两个子节点,q1-sr-only 绝对定位不占布局空间。 */
3628
3626
  .middle-truncate {
3629
3627
  display: block;
3630
3628
  min-width: 0;
@@ -4229,120 +4227,120 @@ a.breadcrumb__link:hover {
4229
4227
  color: var(--q1-tone-fg-danger);
4230
4228
  }
4231
4229
 
4232
- .card {
4230
+ .q1-card {
4233
4231
  border: 1px solid var(--q1-color-border);
4234
4232
  border-radius: var(--q1-radius-3);
4235
4233
  background: var(--q1-color-bg-panel);
4236
4234
  max-width: 400px;
4237
4235
  }
4238
4236
 
4239
- .card__text {
4237
+ .q1-card__text {
4240
4238
  margin: 0;
4241
4239
  padding: var(--q1-space-4);
4242
4240
  font-size: 14px;
4243
4241
  color: var(--q1-color-text-secondary);
4244
4242
  }
4245
4243
 
4246
- .card--hover {
4244
+ .q1-card--hover {
4247
4245
  cursor: pointer;
4248
4246
  transition: transform 0.15s ease;
4249
4247
  }
4250
4248
 
4251
4249
  @media (prefers-reduced-motion: no-preference) {
4252
- .card--hover:hover {
4250
+ .q1-card--hover:hover {
4253
4251
  transform: translateY(-2px);
4254
4252
  }
4255
4253
  }
4256
4254
 
4257
- .card--hover:hover {
4255
+ .q1-card--hover:hover {
4258
4256
  border-color: var(--q1-color-border-strong);
4259
4257
  box-shadow: 0 6px 18px color-mix(in srgb, var(--q1-color-mask-opaque) 12%, transparent);
4260
4258
  }
4261
4259
 
4262
- .card--secondary {
4260
+ .q1-card--secondary {
4263
4261
  background: var(--q1-color-bg-subtle);
4264
4262
  }
4265
4263
 
4266
4264
  /* 分段卡片给容器 16px 内边距(对齐 Geist p-4):section 随之内缩,分隔线不贯满卡片边、左端对齐文字。
4267
4265
  这是分隔线"内缩"的来源——不是给线加 inset,而是容器留边、section 无水平 padding,线随 section 内缩。 */
4268
- .card--divided {
4266
+ .q1-card--divided {
4269
4267
  padding: var(--q1-space-4);
4270
4268
  }
4271
4269
 
4272
- .card--divided .card__section {
4270
+ .q1-card--divided .q1-card__section {
4273
4271
  /* 竖排段:只留垂直 padding(对齐 Geist py-2),水平交给容器内边距,分隔线因此内缩对齐文字。 */
4274
4272
  padding: var(--q1-space-2) 0;
4275
4273
  font-size: 14px;
4276
4274
  color: var(--q1-color-text-primary);
4277
4275
  }
4278
4276
 
4279
- .card--divided .card__section + .card__section {
4277
+ .q1-card--divided .q1-card__section + .q1-card__section {
4280
4278
  border-top: 1px solid var(--q1-color-border);
4281
4279
  }
4282
4280
 
4283
4281
  /* 横向铺开(direction="row"):段横向排布;等分占位,窄屏也不挤成一条缝。
4284
- 特异性用 .card.card--row(0,2,0) 压过可点卡片的 a.card(0,1,1),否则 <a> 卡片的 display:block 会把 flex 打回竖排。 */
4285
- .card.card--row {
4282
+ 特异性用 .q1-card.q1-card--row(0,2,0) 压过可点卡片的 a.q1-card(0,1,1),否则 <a> 卡片的 display:block 会把 flex 打回竖排。 */
4283
+ .q1-card.q1-card--row {
4286
4284
  display: flex;
4287
4285
  /* 横排指标块并列需要空间,解除默认 400px 限宽,充满容器(对齐 Geist 横排充满 demo 框)。 */
4288
4286
  max-width: none;
4289
4287
  }
4290
4288
 
4291
- .card--row .card__section {
4289
+ .q1-card--row .q1-card__section {
4292
4290
  flex: 1 1 0;
4293
4291
  }
4294
4292
 
4295
- .card--divided.card--row .card__section {
4293
+ .q1-card--divided.q1-card--row .q1-card__section {
4296
4294
  /* 横排段:垂直 padding 归 0(内缩交给容器)、水平 8px 给文字到竖线的间距(对齐 Geist row section 0 8px)。 */
4297
4295
  padding: 0 var(--q1-space-2);
4298
4296
  }
4299
4297
 
4300
- .card--divided.card--row .card__section + .card__section {
4298
+ .q1-card--divided.q1-card--row .q1-card__section + .q1-card__section {
4301
4299
  border-top: none;
4302
4300
  border-left: 1px solid var(--q1-color-border);
4303
4301
  }
4304
4302
 
4305
4303
  /* 指标块:数字 + 标签垂直堆叠,横排铺开成仪表盘式 KPI 概览——横排分段放并列指标才填得满、才有意义。 */
4306
- .card__metric {
4304
+ .q1-card__metric {
4307
4305
  display: flex;
4308
4306
  flex-direction: column;
4309
4307
  gap: var(--q1-space-1);
4310
4308
  }
4311
4309
 
4312
- .card__metric-value {
4310
+ .q1-card__metric-value {
4313
4311
  font-size: 20px;
4314
4312
  font-weight: 600;
4315
4313
  line-height: 1.2;
4316
4314
  color: var(--q1-color-text-primary);
4317
4315
  }
4318
4316
 
4319
- .card__metric-label {
4317
+ .q1-card__metric-label {
4320
4318
  font-size: 12px;
4321
4319
  color: var(--q1-color-text-secondary);
4322
4320
  }
4323
4321
 
4324
4322
  /* 常驻柔和投影(对齐 Geist shadow):贴地薄影 + 扩散软影两层,让卡片浮于背景。 */
4325
- .card--shadow {
4323
+ .q1-card--shadow {
4326
4324
  box-shadow:
4327
4325
  0 1px 2px color-mix(in srgb, var(--q1-color-shadow) 6%, transparent),
4328
4326
  0 8px 24px color-mix(in srgb, var(--q1-color-shadow) 8%, transparent);
4329
4327
  }
4330
4328
 
4331
4329
  /* 无框卡片(bordered={false}):去边框,靠底色/投影表达边界(对齐 Geist border 可选)。 */
4332
- .card--borderless {
4330
+ .q1-card--borderless {
4333
4331
  border-color: transparent;
4334
4332
  }
4335
4333
 
4336
4334
  /* 整卡可点(传 href → <a>):去链接默认样式、继承文本色,补键盘焦点环让其可达。 */
4337
- a.card {
4335
+ a.q1-card {
4338
4336
  display: block;
4339
4337
  text-decoration: none;
4340
4338
  color: inherit;
4341
4339
  }
4342
4340
 
4343
- /* 可点卡片焦点环用 outline 独立层:不占 box-shadow,避免顶掉 .card--shadow 的常驻投影;
4341
+ /* 可点卡片焦点环用 outline 独立层:不占 box-shadow,避免顶掉 .q1-card--shadow 的常驻投影;
4344
4342
  且 outline 在 forced-colors 强制颜色模式仍可见(box-shadow 在该模式不渲染)。 */
4345
- a.card:focus-visible {
4343
+ a.q1-card:focus-visible {
4346
4344
  outline: 2px solid var(--q1-color-primary-700);
4347
4345
  outline-offset: 2px;
4348
4346
  }
@@ -5524,11 +5522,11 @@ a.card:focus-visible {
5524
5522
  background: color-mix(in srgb, var(--q1-color-green-700) 16%, transparent);
5525
5523
  color: var(--q1-color-green-700);
5526
5524
  }
5527
- .tooltip {
5525
+ .q1-tooltip {
5528
5526
  position: relative;
5529
5527
  display: inline-flex;
5530
5528
  }
5531
- .tooltip__trigger {
5529
+ .q1-tooltip__trigger {
5532
5530
  min-width: 56px;
5533
5531
  padding: var(--q1-space-2) var(--q1-space-4);
5534
5532
  border: 1px solid var(--q1-color-border-strong);
@@ -5539,11 +5537,11 @@ a.card:focus-visible {
5539
5537
  font-size: 14px;
5540
5538
  cursor: default;
5541
5539
  }
5542
- .tooltip__trigger:focus-visible {
5540
+ .q1-tooltip__trigger:focus-visible {
5543
5541
  outline: none;
5544
5542
  box-shadow: var(--q1-shadow-focus);
5545
5543
  }
5546
- .tooltip__bubble {
5544
+ .q1-tooltip__bubble {
5547
5545
  position: fixed;
5548
5546
  z-index: 1000;
5549
5547
  width: max-content;
@@ -5558,11 +5556,11 @@ a.card:focus-visible {
5558
5556
  pointer-events: none;
5559
5557
  transition: opacity 0.12s ease, transform 0.12s ease;
5560
5558
  }
5561
- .tooltip__bubble--open {
5559
+ .q1-tooltip__bubble--open {
5562
5560
  opacity: 1;
5563
5561
  }
5564
5562
  /* caret 指示箭头 */
5565
- .tooltip__bubble::after {
5563
+ .q1-tooltip__bubble::after {
5566
5564
  content: '';
5567
5565
  position: absolute;
5568
5566
  width: 8px;
@@ -5571,66 +5569,66 @@ a.card:focus-visible {
5571
5569
  transform: rotate(45deg);
5572
5570
  }
5573
5571
  /* 方位:仅入场位移;坐标由 JS 以 fixed 视口坐标给出,箭头用 --tt-caret 绝对定位 */
5574
- .tooltip__bubble--top {
5572
+ .q1-tooltip__bubble--top {
5575
5573
  transform: translateY(4px);
5576
5574
  }
5577
- .tooltip__bubble--top.tooltip__bubble--open {
5575
+ .q1-tooltip__bubble--top.q1-tooltip__bubble--open {
5578
5576
  transform: translateY(0);
5579
5577
  }
5580
- .tooltip__bubble--top::after {
5578
+ .q1-tooltip__bubble--top::after {
5581
5579
  top: 100%;
5582
5580
  left: var(--tt-caret, 50%);
5583
5581
  margin-left: -4px;
5584
5582
  margin-top: -4px;
5585
5583
  }
5586
- .tooltip__bubble--bottom {
5584
+ .q1-tooltip__bubble--bottom {
5587
5585
  transform: translateY(-4px);
5588
5586
  }
5589
- .tooltip__bubble--bottom.tooltip__bubble--open {
5587
+ .q1-tooltip__bubble--bottom.q1-tooltip__bubble--open {
5590
5588
  transform: translateY(0);
5591
5589
  }
5592
- .tooltip__bubble--bottom::after {
5590
+ .q1-tooltip__bubble--bottom::after {
5593
5591
  bottom: 100%;
5594
5592
  left: var(--tt-caret, 50%);
5595
5593
  margin-left: -4px;
5596
5594
  margin-bottom: -4px;
5597
5595
  }
5598
- .tooltip__bubble--left {
5596
+ .q1-tooltip__bubble--left {
5599
5597
  transform: translateX(4px);
5600
5598
  }
5601
- .tooltip__bubble--left.tooltip__bubble--open {
5599
+ .q1-tooltip__bubble--left.q1-tooltip__bubble--open {
5602
5600
  transform: translateX(0);
5603
5601
  }
5604
- .tooltip__bubble--left::after {
5602
+ .q1-tooltip__bubble--left::after {
5605
5603
  left: 100%;
5606
5604
  top: var(--tt-caret, 50%);
5607
5605
  margin-top: -4px;
5608
5606
  margin-left: -4px;
5609
5607
  }
5610
- .tooltip__bubble--right {
5608
+ .q1-tooltip__bubble--right {
5611
5609
  transform: translateX(-4px);
5612
5610
  }
5613
- .tooltip__bubble--right.tooltip__bubble--open {
5611
+ .q1-tooltip__bubble--right.q1-tooltip__bubble--open {
5614
5612
  transform: translateX(0);
5615
5613
  }
5616
- .tooltip__bubble--right::after {
5614
+ .q1-tooltip__bubble--right::after {
5617
5615
  right: 100%;
5618
5616
  top: var(--tt-caret, 50%);
5619
5617
  margin-top: -4px;
5620
5618
  margin-right: -4px;
5621
5619
  }
5622
- .tooltip__bubble--no-tip::after {
5620
+ .q1-tooltip__bubble--no-tip::after {
5623
5621
  display: none;
5624
5622
  }
5625
- .tooltip__bubble--center {
5623
+ .q1-tooltip__bubble--center {
5626
5624
  text-align: center;
5627
5625
  }
5628
5626
  @media (prefers-reduced-motion: reduce) {
5629
- .tooltip__bubble {
5627
+ .q1-tooltip__bubble {
5630
5628
  transition: opacity 0.12s ease;
5631
5629
  }
5632
5630
  }
5633
- .toast-region {
5631
+ .q1-toast-region {
5634
5632
  position: fixed;
5635
5633
  right: var(--q1-space-6);
5636
5634
  bottom: var(--q1-space-6);
@@ -5639,7 +5637,7 @@ a.card:focus-visible {
5639
5637
  width: min(420px, calc(100vw - 2 * var(--q1-space-6)));
5640
5638
  height: var(--toast-stack-h, 0px);
5641
5639
  }
5642
- .toast {
5640
+ .q1-toast {
5643
5641
  position: absolute;
5644
5642
  right: 0;
5645
5643
  bottom: 0;
@@ -5660,58 +5658,58 @@ a.card:focus-visible {
5660
5658
  transform: translateY(calc(var(--depth, 0) * -14px)) scale(calc(1 - var(--depth, 0) * 0.06));
5661
5659
  opacity: calc(1 - max(0, var(--depth, 0) - 2));
5662
5660
  }
5663
- .toast-region[data-expanded='true'] .toast {
5661
+ .q1-toast-region[data-expanded='true'] .q1-toast {
5664
5662
  transform: translateY(var(--y, 0px)) scale(1);
5665
5663
  opacity: 1;
5666
5664
  }
5667
- /* 状态 toast:实心 tone 底色 + 浅色文字(对标 Geist 的 success/warning/error) */
5668
- .toast--success,
5669
- .toast--warning,
5670
- .toast--error {
5665
+ /* 状态 q1-toast:实心 tone 底色 + 浅色文字(对标 Geist 的 success/warning/error) */
5666
+ .q1-toast--success,
5667
+ .q1-toast--warning,
5668
+ .q1-toast--error {
5671
5669
  color: var(--q1-color-mask-opaque);
5672
5670
  box-shadow:
5673
5671
  0 6px 16px -4px color-mix(in srgb, var(--q1-color-mask-opaque) 28%, transparent),
5674
5672
  0 16px 30px -10px color-mix(in srgb, var(--q1-color-mask-opaque) 20%, transparent);
5675
5673
  }
5676
- .toast--success {
5674
+ .q1-toast--success {
5677
5675
  background: var(--q1-color-green-600);
5678
5676
  }
5679
- .toast--warning {
5677
+ .q1-toast--warning {
5680
5678
  background: var(--q1-color-amber-600);
5681
5679
  }
5682
- .toast--error {
5680
+ .q1-toast--error {
5683
5681
  background: var(--q1-color-red-600);
5684
5682
  }
5685
- .toast__icon {
5683
+ .q1-toast__icon {
5686
5684
  flex: none;
5687
5685
  }
5688
- .toast__body {
5686
+ .q1-toast__body {
5689
5687
  flex: 1 1 auto;
5690
5688
  min-width: 0;
5691
5689
  }
5692
- .toast__title {
5690
+ .q1-toast__title {
5693
5691
  margin: 0;
5694
5692
  font-size: 14px;
5695
5693
  font-weight: 500;
5696
5694
  }
5697
- .toast__description {
5695
+ .q1-toast__description {
5698
5696
  margin: 4px 0 0;
5699
5697
  font-size: 14px;
5700
5698
  color: currentColor;
5701
5699
  opacity: 0.74;
5702
5700
  }
5703
- .toast__description a {
5701
+ .q1-toast__description a {
5704
5702
  color: inherit;
5705
5703
  opacity: 1;
5706
5704
  text-decoration: underline;
5707
5705
  text-underline-offset: 2px;
5708
5706
  }
5709
- .toast__description a:focus-visible {
5707
+ .q1-toast__description a:focus-visible {
5710
5708
  outline: none;
5711
5709
  box-shadow: var(--q1-shadow-focus);
5712
5710
  border-radius: 2px;
5713
5711
  }
5714
- .toast__action {
5712
+ .q1-toast__action {
5715
5713
  flex: none;
5716
5714
  align-self: center;
5717
5715
  padding: var(--q1-space-1) var(--q1-space-3);
@@ -5724,15 +5722,15 @@ a.card:focus-visible {
5724
5722
  font-weight: 500;
5725
5723
  cursor: pointer;
5726
5724
  }
5727
- .toast__action:hover {
5725
+ .q1-toast__action:hover {
5728
5726
  background: color-mix(in srgb, currentColor 12%, transparent);
5729
5727
  }
5730
- .toast__action:focus-visible,
5731
- .toast__close:focus-visible {
5728
+ .q1-toast__action:focus-visible,
5729
+ .q1-toast__close:focus-visible {
5732
5730
  outline: none;
5733
5731
  box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 55%, transparent);
5734
5732
  }
5735
- .toast__close {
5733
+ .q1-toast__close {
5736
5734
  flex: none;
5737
5735
  display: grid;
5738
5736
  place-items: center;
@@ -5745,15 +5743,15 @@ a.card:focus-visible {
5745
5743
  opacity: 0.7;
5746
5744
  cursor: pointer;
5747
5745
  }
5748
- .toast__close:hover {
5746
+ .q1-toast__close:hover {
5749
5747
  opacity: 1;
5750
5748
  background: color-mix(in srgb, currentColor 12%, transparent);
5751
5749
  }
5752
5750
  @media (prefers-reduced-motion: no-preference) {
5753
- .toast {
5751
+ .q1-toast {
5754
5752
  transition: transform 0.32s cubic-bezier(0.25, 0.75, 0.6, 0.98), opacity 0.28s ease;
5755
5753
  }
5756
- .toast-region {
5754
+ .q1-toast-region {
5757
5755
  transition: height 0.32s cubic-bezier(0.25, 0.75, 0.6, 0.98);
5758
5756
  }
5759
5757
  }
package/package.json CHANGED
@@ -1,20 +1,29 @@
1
1
  {
2
2
  "name": "@q1travel/design-system",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "private": false,
5
5
  "type": "module",
6
- "main": "./dist/design-system.js",
7
- "module": "./dist/design-system.js",
8
- "types": "./dist/design-system.d.ts",
6
+ "main": "./dist-pkg/design-system.js",
7
+ "module": "./dist-pkg/design-system.js",
8
+ "types": "./dist-pkg/design-system.d.ts",
9
+ "bin": {
10
+ "q1-design": "bin/q1-design.mjs"
11
+ },
9
12
  "exports": {
10
13
  ".": {
11
- "types": "./dist/design-system.d.ts",
12
- "import": "./dist/design-system.js"
14
+ "types": "./dist-pkg/design-system.d.ts",
15
+ "import": "./dist-pkg/design-system.js"
13
16
  },
14
- "./style.css": "./dist/style.css"
17
+ "./style.css": "./dist-pkg/style.css",
18
+ "./contracts/*": "./contracts/*",
19
+ "./package.json": "./package.json"
15
20
  },
16
21
  "files": [
17
- "dist"
22
+ "dist-pkg",
23
+ "contracts",
24
+ ".agents",
25
+ "tooling",
26
+ "bin"
18
27
  ],
19
28
  "sideEffects": [
20
29
  "*.css"
@@ -29,7 +38,8 @@
29
38
  "check": "npm run build && npm run check:shell && npm run check:browser",
30
39
  "build:lib": "vite build --config vite.lib.config.ts",
31
40
  "build:style": "node scripts/build-style.mjs",
32
- "build:pkg": "npm run build:lib && npm run build:style"
41
+ "build:pkg": "npm run build:lib && npm run build:style",
42
+ "prepack": "npm run build:pkg"
33
43
  },
34
44
  "dependencies": {
35
45
  "lucide-react": "^0.468.0"
@@ -0,0 +1,9 @@
1
+ # tooling/scripts/evals
2
+
3
+ 产品设计框架的确定性工具层(规则 + eval)。
4
+
5
+ - `evals/rules-checklist.json` —— 稳定 ID 规则清单(`Q1-DS-*`);`kind=deterministic` 可静态强制,`kind=judgment` 留给 product-design skill / Review。
6
+ - `evals/fixtures.json` —— eval fixture 索引。
7
+ - `evals/<fixture>/{before,after}/` —— 每条规则的前后态样例,用来测 skill / linter 是否产出预期改动。
8
+
9
+ **现状**:`q1-design lint` 已实现 `Q1-DS-001`(`--ds-*`)/`Q1-DS-401`(`transition: all`)/`Q1-DS-501`(裸颜色字面量)三条纯机械规则;`q1-design eval` 已实现,对每个 fixture 校验 before 命中且 after 零命中。其余 `kind=deterministic` 规则(含判断成分)与全部 `kind=judgment` 规则待后续批次。见 `../.agents/skills/product-design/references/coverage-gaps.md`。
@@ -0,0 +1,7 @@
1
+ {
2
+ "note": "eval 种子:每个 fixture 引一条规则,给 before/after 两态,用来测 skill/linter 是否产出预期改动。runner 即 `q1-design eval`(bin/q1-design.mjs,已实现);expectedHits 为 before 的精确命中数,after 恒 0。",
3
+ "version": 0,
4
+ "fixtures": [
5
+ { "id": "transition-all", "rule": "Q1-DS-401", "dir": "transition-all", "summary": "transition: all → 只过渡具体属性", "expectedHits": 1 }
6
+ ]
7
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "note": "q1travel 产品设计确定性规则清单。每条带稳定 ID + canonical 出处。kind=deterministic 的可由 linter 静态强制;kind=judgment 的留给 product-design skill / Review。linter CLI 与 eval runner 已实现:`q1-design lint`(Q1-DS-001/401/501)与 `q1-design eval`(bin/q1-design.mjs);覆盖缺口见 ../../../.agents/skills/product-design/references/coverage-gaps.md。",
3
+ "version": 0,
4
+ "idScheme": {
5
+ "0xx": "token 与事实源",
6
+ "1xx": "交互与命中区",
7
+ "2xx": "表单",
8
+ "3xx": "内容与可访问性",
9
+ "4xx": "动效",
10
+ "5xx": "主题"
11
+ },
12
+ "rules": [
13
+ { "id": "Q1-DS-001", "title": "新代码用 --q1-*,不用 --ds-*", "source": "contracts/token-naming.md", "severity": "error", "kind": "deterministic", "detect": "新增/改动代码里出现 --ds-* 声明或作为新决策源名", "fix": "改用 --q1-* canonical token" },
14
+ { "id": "Q1-DS-101", "title": "导航用原生 link、动作用原生 button", "source": "contracts/interface-guidelines.md#interactions", "severity": "error", "kind": "judgment", "detect": "用 div/span + onClick 承载导航或动作", "fix": "导航用 <a href>,动作用 <button>" },
15
+ { "id": "Q1-DS-102", "title": "命中区 ≥24px(触摸 44px)", "source": "contracts/interface-guidelines.md#interactions", "severity": "warn", "kind": "judgment", "detect": "可点击元素视觉/命中区小于 24px 且未扩大命中区", "fix": "min-width/height + 负 margin 扩大命中区,不撑高容器" },
16
+ { "id": "Q1-DS-301", "title": "icon-only 控件必须有可访问名", "source": "contracts/interface-guidelines.md#content-and-accessibility", "severity": "error", "kind": "judgment", "detect": "只含图标的 button/link 无 aria-label / 可访问名", "fix": "补 aria-label 或视觉隐藏文本" },
17
+ { "id": "Q1-DS-401", "title": "禁用 transition: all", "source": "contracts/interface-guidelines.md#animation", "severity": "error", "kind": "deterministic", "detect": "CSS 出现 `transition: all` 或 `transition-property: all`", "fix": "只列需要过渡的具体属性(opacity / transform)" },
18
+ { "id": "Q1-DS-402", "title": "必须 honor prefers-reduced-motion", "source": "contracts/interface-guidelines.md#animation", "severity": "warn", "kind": "judgment", "detect": "有非平凡动效但无 prefers-reduced-motion 兜底", "fix": "加 @media (prefers-reduced-motion: reduce) 降级" },
19
+ { "id": "Q1-DS-403", "title": "只动 opacity / transform", "source": "contracts/interface-guidelines.md#animation", "severity": "warn", "kind": "deterministic", "detect": "transition/animation 作用于非 opacity/transform 属性且无书面理由", "fix": "改用 opacity/transform,或记书面理由" },
20
+ { "id": "Q1-DS-501", "title": "主题用 token,不用页面局部颜色常量", "source": "contracts/interface-guidelines.md#theming", "severity": "error", "kind": "deterministic", "detect": "组件 CSS 出现裸 hex / rgba() 颜色字面量", "fix": "改用 --q1-color-* / 语义 token;需要混色用 color-mix" },
21
+ { "id": "Q1-DS-502", "title": "native select 显式设前景/背景", "source": "contracts/interface-guidelines.md#theming", "severity": "warn", "kind": "judgment", "detect": "样式化的 <select> 未显式设 color / background", "fix": "显式设 select 的 fg/bg 以匹配当前主题" }
22
+ ]
23
+ }
@@ -0,0 +1,4 @@
1
+ /* after —— 只过渡具体属性(opacity / transform) */
2
+ .q1-card {
3
+ transition: opacity 200ms ease, transform 200ms ease;
4
+ }
@@ -0,0 +1,4 @@
1
+ /* before —— 违反 Q1-DS-401(注释不得含规则字面量,否则自身会被 lint 命中) */
2
+ .q1-card {
3
+ transition: all 200ms ease;
4
+ }