@hjmds/design-contracts 1.27.2 → 1.28.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 (143) hide show
  1. package/dist/activity-chip.d.ts +1 -1
  2. package/dist/activity-chip.js +1 -1
  3. package/dist/activity-chip.js.map +1 -1
  4. package/dist/auth-screen.d.ts +77 -5
  5. package/dist/auth-screen.d.ts.map +1 -1
  6. package/dist/auth-screen.js +63 -2
  7. package/dist/auth-screen.js.map +1 -1
  8. package/dist/base-recipes.d.ts +1 -1
  9. package/dist/base-recipes.js +1 -1
  10. package/dist/base-recipes.js.map +1 -1
  11. package/dist/behaviors.d.ts +1 -1
  12. package/dist/breadcrumb.d.ts +1 -1
  13. package/dist/breadcrumb.js +1 -1
  14. package/dist/breadcrumb.js.map +1 -1
  15. package/dist/calendar.d.ts +1 -1
  16. package/dist/calendar.js +2 -2
  17. package/dist/calendar.js.map +1 -1
  18. package/dist/catalog.d.ts +131 -87
  19. package/dist/catalog.d.ts.map +1 -1
  20. package/dist/component-contracts.d.ts +1 -1
  21. package/dist/component-contracts.js +1 -1
  22. package/dist/component-contracts.js.map +1 -1
  23. package/dist/component-recipes.d.ts +85 -57
  24. package/dist/component-recipes.d.ts.map +1 -1
  25. package/dist/component-recipes.js +98 -60
  26. package/dist/component-recipes.js.map +1 -1
  27. package/dist/conversation-row.d.ts +2 -2
  28. package/dist/conversation-row.d.ts.map +1 -1
  29. package/dist/conversation-row.js +6 -4
  30. package/dist/conversation-row.js.map +1 -1
  31. package/dist/counter-badge-recipe.d.ts +1 -1
  32. package/dist/counter-badge-recipe.js +1 -1
  33. package/dist/counter-badge-recipe.js.map +1 -1
  34. package/dist/data-table.d.ts +4 -4
  35. package/dist/data-table.js +4 -4
  36. package/dist/data-table.js.map +1 -1
  37. package/dist/date-picker.d.ts +1 -1
  38. package/dist/design-profile.d.ts.map +1 -1
  39. package/dist/design-profile.js +19 -16
  40. package/dist/design-profile.js.map +1 -1
  41. package/dist/file-picker.d.ts +1 -1
  42. package/dist/file-picker.js +1 -1
  43. package/dist/file-picker.js.map +1 -1
  44. package/dist/foundations.d.ts +18 -14
  45. package/dist/foundations.d.ts.map +1 -1
  46. package/dist/foundations.js +18 -13
  47. package/dist/foundations.js.map +1 -1
  48. package/dist/heading.d.ts +13 -13
  49. package/dist/heading.js.map +1 -1
  50. package/dist/number-field.d.ts +1 -1
  51. package/dist/otp-field.d.ts +1 -1
  52. package/dist/password-field.d.ts +1 -1
  53. package/dist/result.d.ts +1 -1
  54. package/dist/result.js +2 -2
  55. package/dist/result.js.map +1 -1
  56. package/dist/screen-patterns.d.ts +36 -0
  57. package/dist/screen-patterns.d.ts.map +1 -1
  58. package/dist/screen-patterns.js +20 -0
  59. package/dist/screen-patterns.js.map +1 -1
  60. package/dist/semantic-colors.d.ts +10 -0
  61. package/dist/semantic-colors.d.ts.map +1 -1
  62. package/dist/semantic-colors.js +6 -0
  63. package/dist/semantic-colors.js.map +1 -1
  64. package/dist/sheet.d.ts +7 -4
  65. package/dist/sheet.d.ts.map +1 -1
  66. package/dist/sheet.js +8 -5
  67. package/dist/sheet.js.map +1 -1
  68. package/dist/side-panel.d.ts +1 -1
  69. package/dist/side-panel.js +2 -2
  70. package/dist/side-panel.js.map +1 -1
  71. package/dist/steps.d.ts +1 -1
  72. package/dist/steps.js +1 -1
  73. package/dist/steps.js.map +1 -1
  74. package/dist/tag.d.ts +1 -1
  75. package/dist/tag.js +1 -1
  76. package/dist/tag.js.map +1 -1
  77. package/dist/timeline.d.ts +1 -1
  78. package/dist/timeline.js +1 -1
  79. package/dist/timeline.js.map +1 -1
  80. package/dist/top.d.ts +4 -4
  81. package/dist/top.d.ts.map +1 -1
  82. package/dist/top.js +3 -1
  83. package/dist/top.js.map +1 -1
  84. package/dist/version.d.ts +1 -1
  85. package/dist/version.js +1 -1
  86. package/dist/version.js.map +1 -1
  87. package/docs/activity-chip.md +5 -1
  88. package/docs/auth-screen.md +2 -0
  89. package/docs/design-profile.md +14 -10
  90. package/docs/generated/component-maturity.md +1 -1
  91. package/docs/generated/renderer-evidence.json +9 -5
  92. package/docs/generated/renderer-evidence.md +1 -1
  93. package/docs/generated/showcase-manifest.json +1 -1
  94. package/docs/heading.md +14 -9
  95. package/docs/sheet.md +2 -0
  96. package/docs/top.md +4 -3
  97. package/docs/usage/README.md +1 -1
  98. package/docs/usage/SELECTION.md +1 -0
  99. package/docs/usage/components/auth-screen-layout.md +44 -11
  100. package/docs/usage/components/bottom-cta.md +6 -2
  101. package/docs/usage/components/breadcrumb.md +5 -1
  102. package/docs/usage/components/chat-screen.md +1 -0
  103. package/docs/usage/components/checkbox-group.md +5 -1
  104. package/docs/usage/components/collapsible.md +1 -0
  105. package/docs/usage/components/comment-thread-screen.md +2 -0
  106. package/docs/usage/components/dialog.md +1 -0
  107. package/docs/usage/components/editor-screen.md +1 -0
  108. package/docs/usage/components/effect-surface.md +2 -2
  109. package/docs/usage/components/field.md +1 -0
  110. package/docs/usage/components/heading.md +14 -12
  111. package/docs/usage/components/list-detail-screen.md +1 -0
  112. package/docs/usage/components/list-row.md +7 -3
  113. package/docs/usage/components/list.md +25 -4
  114. package/docs/usage/components/notification-item.md +5 -1
  115. package/docs/usage/components/onboarding-screen.md +1 -0
  116. package/docs/usage/components/photo-source-sheet.md +1 -1
  117. package/docs/usage/components/screen-layout.md +28 -3
  118. package/docs/usage/components/search-field.md +1 -0
  119. package/docs/usage/components/search-screen.md +4 -2
  120. package/docs/usage/components/section.md +5 -1
  121. package/docs/usage/components/segmented-control.md +1 -1
  122. package/docs/usage/components/sheet.md +18 -13
  123. package/docs/usage/components/skeleton.md +42 -15
  124. package/docs/usage/components/statistic.md +5 -1
  125. package/docs/usage/components/switch.md +3 -2
  126. package/docs/usage/components/tag.md +5 -1
  127. package/docs/usage/components/text-area.md +1 -0
  128. package/docs/usage/components/text.md +6 -4
  129. package/docs/usage/components/top.md +1 -1
  130. package/docs/usage/compositions/adaptive-content.md +5 -1
  131. package/docs/usage/compositions/compound-controls.md +2 -1
  132. package/docs/usage/compositions/design-profile-comparison.md +1 -1
  133. package/docs/usage/compositions/family-drawer.md +5 -3
  134. package/docs/usage/compositions/input-sheet.md +1 -1
  135. package/docs/usage/compositions/interaction-flow-apply.md +3 -3
  136. package/docs/usage/compositions/photo-source.md +3 -3
  137. package/docs/usage/compositions/selection-motion.md +5 -1
  138. package/docs/usage/compositions/visual-foundations.md +5 -4
  139. package/docs/usage/screens/common-login.md +9 -7
  140. package/docs/usage/screens/landing.md +15 -14
  141. package/docs/usage/screens/notification-settings.md +4 -2
  142. package/docs/usage/tokens/typography.md +21 -13
  143. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { collectionItemContract, fieldFrameContract, floatingSurfaceContract, focusIndicatorContract, formSupportContract, } from "./component-contracts.js";
2
- import { backdrop, breakpoint, control, glyph, layer, largeTextThreshold, layout, fontWeight, motion, motionPreset, opacity, radius, spacing, stroke, } from "./foundations.js";
2
+ import { backdrop, breakpoint, control, glyph, layer, largeTextThreshold, layout, fontWeight, motion, motionPreset, opacity, radius, spacing, stroke, typography, } from "./foundations.js";
3
+ import { buttonRecipe } from "./base-recipes.js";
3
4
  import { semanticColors } from "./semantic-colors.js";
4
5
  import { tooltipDescriptorDefaults } from "./tooltip.js";
5
6
  import { bottomNavigationRecipeDefaults } from "./bottom-navigation-defaults.js";
@@ -19,14 +20,18 @@ export const textRecipe = {
19
20
  danger: semanticColors.content.danger,
20
21
  inverse: semanticColors.content.inverse,
21
22
  },
23
+ // 2026-10-17 user decisions: the strongest inline emphasis matches the title weight. strong=700 made unread
24
+ // notification titles and labels heavier than every title after titles moved to 600 ("왜 폰트가 진해"), so
25
+ // strong became 600; the second round ("굵기 좀더 줄여") moved titles 600→500 and display headings 700→600,
26
+ // so strong follows the title role (500) and equals `medium`. Semibold 600 is the key-number/selected step.
22
27
  emphasis: {
23
28
  regular: fontWeight.regular,
24
- medium: fontWeight.semibold,
25
- strong: fontWeight.bold,
29
+ medium: fontWeight.medium,
30
+ strong: typography.title.fontWeight,
26
31
  },
27
32
  /**
28
33
  * Variants whose omitted `emphasis` renders the variant's typography weight even without a design profile.
29
- * Label's 500 has no emphasis step (`medium` is 600), and the shared `regular` default drew form labels at
34
+ * Label's 500 equals the `medium` emphasis step, but the shared `regular` default drew form labels at
30
35
  * body weight (2026-10-17 user decision: label 500 on Web and Native). Other variants keep `regular`.
31
36
  */
32
37
  variantWeight: ["label"],
@@ -86,7 +91,7 @@ export const linkRecipe = {
86
91
  variants: {
87
92
  inline: {
88
93
  textVariant: "body",
89
- fontWeight: fontWeight.semibold,
94
+ fontWeight: fontWeight.medium,
90
95
  underline: "always",
91
96
  minHeight: null,
92
97
  minWidth: null,
@@ -94,7 +99,7 @@ export const linkRecipe = {
94
99
  /** 그 자체가 동작이므로 두 축 모두 최소 터치 타깃. 높이만 잡으면 짧은 라벨이 좁아진다. */
95
100
  standalone: {
96
101
  textVariant: "body",
97
- fontWeight: fontWeight.bold,
102
+ fontWeight: fontWeight.medium,
98
103
  underline: "hover",
99
104
  minHeight: control.minTouchTarget,
100
105
  minWidth: control.minTouchTarget,
@@ -218,7 +223,7 @@ export const badgeRecipe = {
218
223
  },
219
224
  radius: "full",
220
225
  borderWidth: stroke.default,
221
- fontWeight: fontWeight.bold,
226
+ fontWeight: fontWeight.medium,
222
227
  };
223
228
  /** Search is a field specialization with stable affordance and clear targets. */
224
229
  export const searchFieldRecipe = {
@@ -320,7 +325,8 @@ export const chipRecipe = {
320
325
  },
321
326
  radius: "full",
322
327
  borderWidth: stroke.default,
323
- label: { fontWeight: fontWeight.semibold, selectedFontWeight: fontWeight.bold },
328
+ // Selected keeps one step above idle (500 → 600) so the state still carries a weight cue on the lighter scale.
329
+ label: { fontWeight: fontWeight.medium, selectedFontWeight: fontWeight.semibold },
324
330
  selectionIndicator: {
325
331
  color: semanticColors.content.brand,
326
332
  glyph: "xs",
@@ -346,15 +352,24 @@ export const dividerRecipe = {
346
352
  * VirtualList) keep exactly this much inline room so the bleed is never cut.
347
353
  */
348
354
  const listRowSurfaceBleed = spacing.xs;
355
+ /** ListRow leading frame and the gap before its text column; the indented separator reuses both. */
356
+ const listRowLeadingSize = 40;
357
+ const listRowGap = spacing.sm;
349
358
  export const listRecipe = {
350
359
  slots: ["root", "item", "separator"],
351
360
  defaults: { separator: "indented" },
361
+ /**
362
+ * Separators are measured from the list's content edge, where the row text sits, and each one belongs to the row
363
+ * above it (iOS cell separator). "full" spans the row surface (content edge ± bleed). "indented" follows that row's
364
+ * text column: it starts at the text — `leadingInsetStart` (leading 40 + gap 12) when the row has a leading
365
+ * element, `insetStart` 0 when it has none — and ends on the row's trailing content edge (0).
366
+ * 2026-10-17 user report: a fixed 52 start cut the line off midway under rows without leading (text at 16, line
367
+ * from 68), and the −8 end ran past the trailing content.
368
+ */
352
369
  separators: {
353
370
  none: null,
354
- // Separators are measured from the list's content edge, where the row text now sits. "full" spans the row
355
- // surface (content edge ± bleed); "indented" starts at the text after leading 40 + gap sm and ends with the surface.
356
- full: { insetStart: -listRowSurfaceBleed, insetEnd: -listRowSurfaceBleed },
357
- indented: { insetStart: 40 + spacing.sm, insetEnd: -listRowSurfaceBleed },
371
+ full: { insetStart: -listRowSurfaceBleed, leadingInsetStart: -listRowSurfaceBleed, insetEnd: -listRowSurfaceBleed },
372
+ indented: { insetStart: 0, leadingInsetStart: listRowLeadingSize + listRowGap, insetEnd: 0 },
358
373
  },
359
374
  // A grouped list paints its own rounded surface and clips to it, so it leaves the row bleed as inline padding:
360
375
  // rows keep their full surface inside the radius and the text stays 8 inside the group edge as before.
@@ -406,15 +421,15 @@ export const listRowRecipe = {
406
421
  * the same negative inline margin, so pressed/selected fills keep 8 of room beyond the text.
407
422
  */
408
423
  surfaceBleed: listRowSurfaceBleed,
409
- gap: spacing.sm,
410
- leadingSize: 40,
424
+ gap: listRowGap,
425
+ leadingSize: listRowLeadingSize,
411
426
  /**
412
427
  * Frame geometry for the leading slot. The size was already declared here but
413
428
  * no renderer bound it, so every consumer re-derived the avatar box (radius,
414
429
  * clipping, centering) in product styles.
415
430
  */
416
431
  leadingShapes: { square: null, circle: "full" },
417
- title: { color: semanticColors.content.body, textVariant: "bodyLarge", fontWeight: fontWeight.bold },
432
+ title: { color: semanticColors.content.body, textVariant: "bodyLarge", fontWeight: typography.title.fontWeight },
418
433
  description: { color: semanticColors.content.secondary, textVariant: "body" },
419
434
  trailing: {
420
435
  textColor: semanticColors.content.secondary,
@@ -437,7 +452,7 @@ export const accordionRecipe = {
437
452
  },
438
453
  paddingHorizontal: spacing.xs,
439
454
  gap: spacing.sm,
440
- title: { color: semanticColors.content.body, textVariant: "bodyLarge", fontWeight: fontWeight.bold },
455
+ title: { color: semanticColors.content.body, textVariant: "bodyLarge", fontWeight: typography.title.fontWeight },
441
456
  indicator: { color: semanticColors.content.secondary, glyph: "sm" },
442
457
  panel: {
443
458
  color: semanticColors.content.body,
@@ -577,7 +592,7 @@ export const selectRecipe = {
577
592
  paddingVertical: spacing.xs,
578
593
  },
579
594
  optionLeading: { color: semanticColors.content.secondary, glyph: "sm" },
580
- optionLabel: { fontWeight: fontWeight.semibold, selectedFontWeight: fontWeight.bold },
595
+ optionLabel: { fontWeight: fontWeight.medium, selectedFontWeight: fontWeight.semibold },
581
596
  selectionIndicator: { color: semanticColors.border.focus, glyph: "sm" },
582
597
  stateMessage: {
583
598
  color: semanticColors.content.secondary,
@@ -648,6 +663,8 @@ export const comboboxRecipe = {
648
663
  states: selectRecipe.states,
649
664
  transition: selectRecipe.transition,
650
665
  };
666
+ /** Block-side inset of the connected track: `container.padding` + `container.borderWidth`. */
667
+ const segmentedTrackInset = spacing.xxs + stroke.subtle;
651
668
  export const segmentedControlRecipe = {
652
669
  // Text navigation reuses radio selection, without cloning a product-only tab engine.
653
670
  text: { gap: spacing.md, indicatorWidth: 16, indicatorHeight: 2, indicatorRadius: radius.full,
@@ -684,16 +701,25 @@ export const segmentedControlRecipe = {
684
701
  padding: spacing.xxs,
685
702
  gap: spacing.xxs,
686
703
  },
704
+ /**
705
+ * `height` is the connected control's outer height (track border + padding included) and matches the Button /
706
+ * IconButton / Chip height of the same size step, so a segmented control and a chip in one row line up.
707
+ * `minHeight` is the option inside the track (height − 2 × (`container.padding` + `container.borderWidth`)) and `hitSlop` extends each
708
+ * option vertically to the 44 touch target. Before 2026-10-17 `minHeight` was the option itself, so the track
709
+ * drew 46/54 next to 36/44 chips and buttons (DiscoveryGallery sort row, user request "높이 맞춰").
710
+ */
687
711
  sizes: {
688
712
  small: {
689
- minHeight: 36,
690
- hitSlop: 4,
713
+ height: control.buttonHeight.small,
714
+ minHeight: control.buttonHeight.small - 2 * segmentedTrackInset,
715
+ hitSlop: (control.minTouchTarget - (control.buttonHeight.small - 2 * segmentedTrackInset)) / 2,
691
716
  paddingHorizontal: spacing.sm,
692
717
  textVariant: "label",
693
718
  },
694
719
  medium: {
695
- minHeight: control.minTouchTarget,
696
- hitSlop: 0,
720
+ height: control.buttonHeight.medium,
721
+ minHeight: control.buttonHeight.medium - 2 * segmentedTrackInset,
722
+ hitSlop: (control.minTouchTarget - (control.buttonHeight.medium - 2 * segmentedTrackInset)) / 2,
697
723
  paddingHorizontal: spacing.md,
698
724
  textVariant: "body",
699
725
  },
@@ -702,7 +728,7 @@ export const segmentedControlRecipe = {
702
728
  radius: "md",
703
729
  gap: spacing.xxs,
704
730
  idleContent: semanticColors.content.secondary,
705
- fontWeight: fontWeight.semibold,
731
+ fontWeight: fontWeight.medium,
706
732
  selectedBackground: semanticColors.canvas,
707
733
  selectedContent: semanticColors.content.brand,
708
734
  /**
@@ -712,7 +738,7 @@ export const segmentedControlRecipe = {
712
738
  */
713
739
  selectedBorder: semanticColors.border.focus,
714
740
  selectedBorderWidth: stroke.strong,
715
- selectedFontWeight: fontWeight.bold,
741
+ selectedFontWeight: fontWeight.semibold,
716
742
  focusRing: semanticColors.border.focus,
717
743
  pressedOpacity: opacity.pressed,
718
744
  disabledOpacity: opacity.disabled,
@@ -773,11 +799,16 @@ export const switchRecipe = {
773
799
  pressedOpacity: opacity.pressed,
774
800
  },
775
801
  rowMinHeight: control.minTouchTarget,
776
- // A switch with a description is a two-line row like any other list row,
777
- // and a product author found that letting content decide the height gave
778
- // neighbouring rows different heights — this pins it to the same value as
779
- // `listRowRecipe.density.comfortable.twoLineMinHeight`.
780
- rowTwoLineMinHeight: 68,
802
+ // A described switch is a two-line row: the copy (body 20 + gap 4 + caption 16 = 40) with the
803
+ // comfortable list-row vertical padding (8) on both sides. The padding is drawn by the row itself
804
+ // so it survives larger text, and the minimum keeps neighbouring rows equal (a product author found
805
+ // content-only height gave them different heights). It used to borrow ListRow's 68, which is sized
806
+ // for a bodyLarge title + body description; on the smaller switch copy that left 14 above and below,
807
+ // and stacked rows read as widely spaced (2026-10-17 user report, notification settings: 84 pitch
808
+ // for 40 of copy, together with a Stack gap the rows must not have).
809
+ rowPaddingVertical: listRowRecipe.density.comfortable.paddingVertical,
810
+ rowTwoLineMinHeight: typography.body.lineHeight + spacing.xxs + typography.caption.lineHeight
811
+ + 2 * listRowRecipe.density.comfortable.paddingVertical,
781
812
  };
782
813
  export const selectionControlRecipe = {
783
814
  slots: [
@@ -853,8 +884,8 @@ export const selectionControlRecipe = {
853
884
  },
854
885
  label: {
855
886
  color: semanticColors.content.body,
856
- fontWeight: fontWeight.semibold,
857
- checkedFontWeight: fontWeight.bold,
887
+ fontWeight: fontWeight.medium,
888
+ checkedFontWeight: fontWeight.semibold,
858
889
  },
859
890
  leading: { size: "md", color: semanticColors.content.secondary },
860
891
  description: { color: semanticColors.content.secondary },
@@ -1018,8 +1049,8 @@ export const bottomNavigationRecipe = {
1018
1049
  },
1019
1050
  },
1020
1051
  label: {
1021
- fontWeight: fontWeight.semibold,
1022
- selectedFontWeight: fontWeight.bold,
1052
+ fontWeight: fontWeight.medium,
1053
+ selectedFontWeight: fontWeight.semibold,
1023
1054
  textAlign: "center",
1024
1055
  wrap: true,
1025
1056
  fixedLines: null,
@@ -1090,7 +1121,7 @@ export const tabsRecipe = {
1090
1121
  },
1091
1122
  gap: spacing.xs,
1092
1123
  icon: { glyph: "xs" },
1093
- label: { fontWeight: fontWeight.semibold, selectedFontWeight: fontWeight.bold },
1124
+ label: { fontWeight: fontWeight.medium, selectedFontWeight: fontWeight.semibold },
1094
1125
  layouts: {
1095
1126
  content: { fitted: false },
1096
1127
  fitted: { fitted: true },
@@ -1129,21 +1160,30 @@ export const noticeRecipe = {
1129
1160
  contentGap: spacing.xxs,
1130
1161
  iconSize: "sm",
1131
1162
  borderWidth: stroke.default,
1132
- title: { textVariant: "body", fontWeight: fontWeight.bold },
1163
+ title: { textVariant: "body", fontWeight: typography.title.fontWeight },
1133
1164
  description: { color: semanticColors.content.body, textVariant: "body" },
1134
1165
  };
1135
1166
  export const skeletonRecipe = {
1136
- slots: ["root"],
1167
+ slots: ["root", "line"],
1137
1168
  // animated는 0.9.12까지 false였다. 정지한 블록은 로딩이 아니라 깨진 화면으로
1138
1169
  // 읽힌다. 전환 근거와 소비 측 migration은
1139
1170
  // .changeset/skeleton-pulse-by-default.md에 있다.
1140
- defaults: { shape: "block", animated: true },
1141
- background: semanticColors.canvas,
1171
+ defaults: { shape: "block", animated: true, lines: 1 },
1172
+ // 2026-10-17 사용자 요청: canvas 채움 + 1px 테두리는 흰 배경에서 거의 보이지 않아 로딩 모양을 알 수 없었다.
1173
+ // 2026-09-29 "넓은 회색 면은 canvas" 방향의 예외로, 자리표시는 채운 면이어야 모양이 읽힌다(skeleton.md 근거).
1174
+ background: semanticColors.surface.placeholder,
1175
+ // `aspectRatio`가 있으면 높이 대신 폭에 비례한다(media). `button`은 medium 버튼 높이와 Button 기본 모양의 모서리다.
1176
+ // 2026-10-17: 번뚝처럼 알약 버튼을 쓰는 제품은 자리표시도 알약이어야 하므로 모서리는 `buttonShape` 축
1177
+ // (buttonRecipe.shapes와 같은 이름·같은 기본값)으로 고르고, 이 값은 그 기본값에서 나온다.
1142
1178
  shapes: {
1143
- block: { radius: "md", defaultHeight: spacing.xxl },
1144
- text: { radius: "sm", defaultHeight: spacing.md },
1145
- circle: { radius: "full", defaultHeight: glyph.xxl },
1146
- },
1179
+ block: { radius: "md", defaultHeight: spacing.xxl, aspectRatio: null },
1180
+ text: { radius: "sm", defaultHeight: spacing.md, aspectRatio: null },
1181
+ circle: { radius: "full", defaultHeight: glyph.xxl, aspectRatio: null },
1182
+ media: { radius: "md", defaultHeight: null, aspectRatio: 16 / 9 },
1183
+ button: { radius: buttonRecipe.shapes[buttonRecipe.defaults.shape], defaultHeight: control.buttonHeight.medium, aspectRatio: null },
1184
+ },
1185
+ // `lines` > 1 (text 전용): 줄 사이 간격과 짧게 끝나는 마지막 줄. 16 막대 + 8 간격 = 본문 큰 글자 줄 높이 24.
1186
+ lines: { gap: spacing.xs, lastLineWidthPercent: 60 },
1147
1187
  animation: {
1148
1188
  duration: motion.slow * 4,
1149
1189
  easing: "standard",
@@ -1162,7 +1202,7 @@ export const emptyStateRecipe = {
1162
1202
  paddingHorizontal: spacing.xl,
1163
1203
  gap: spacing.xs,
1164
1204
  icon: { size: "lg", color: semanticColors.content.decorative },
1165
- title: { textVariant: "body", color: semanticColors.content.primary, fontWeight: fontWeight.semibold },
1205
+ title: { textVariant: "body", color: semanticColors.content.primary, fontWeight: typography.title.fontWeight },
1166
1206
  description: { textVariant: "label", color: semanticColors.content.secondary },
1167
1207
  };
1168
1208
  export const spinnerRecipe = {
@@ -1214,7 +1254,7 @@ export const loadMoreRecipe = {
1214
1254
  radius: "md",
1215
1255
  color: semanticColors.content.brand,
1216
1256
  textVariant: "label",
1217
- fontWeight: fontWeight.bold,
1257
+ fontWeight: fontWeight.medium,
1218
1258
  },
1219
1259
  spinner: { size: "small", tone: "brand" },
1220
1260
  states: {
@@ -1236,7 +1276,7 @@ export const avatarRecipe = {
1236
1276
  background: semanticColors.canvas,
1237
1277
  content: semanticColors.content.secondary,
1238
1278
  border: semanticColors.border.default,
1239
- /** Initials are bold; their size follows the avatar (Web `.hjm-avatar[data-size]` steps). */
1279
+ /** Initials use the strong emphasis step; their size follows the avatar (Web `.hjm-avatar[data-size]` steps). */
1240
1280
  initialsWeight: "strong",
1241
1281
  };
1242
1282
  /**
@@ -1294,18 +1334,20 @@ export const statisticRecipe = {
1294
1334
  },
1295
1335
  label: {
1296
1336
  color: semanticColors.content.secondary,
1297
- fontWeight: fontWeight.semibold,
1337
+ fontWeight: fontWeight.medium,
1298
1338
  },
1299
1339
  value: {
1300
1340
  color: semanticColors.content.primary,
1301
- fontWeight: fontWeight.heavy,
1341
+ // The key number keeps the semibold emphasis step (600, 2026-10-17 lighter scale) at the `heading` size.
1342
+ // It used to borrow the display heading weight, which was removed with the display tier the same day.
1343
+ fontWeight: fontWeight.semibold,
1302
1344
  numericVariant: "tabular",
1303
1345
  maxLines: null,
1304
1346
  },
1305
1347
  affix: {
1306
1348
  color: semanticColors.content.body,
1307
1349
  textVariant: "body",
1308
- fontWeight: fontWeight.semibold,
1350
+ fontWeight: fontWeight.medium,
1309
1351
  },
1310
1352
  hint: {
1311
1353
  color: semanticColors.content.secondary,
@@ -1313,7 +1355,7 @@ export const statisticRecipe = {
1313
1355
  },
1314
1356
  trend: {
1315
1357
  textVariant: "caption",
1316
- fontWeight: fontWeight.bold,
1358
+ fontWeight: fontWeight.medium,
1317
1359
  gap: spacing.xxs,
1318
1360
  marks: {
1319
1361
  up: "trendUp",
@@ -1378,12 +1420,8 @@ export const sheetRecipe = {
1378
1420
  showWhen: "swipe-dismiss-enabled",
1379
1421
  },
1380
1422
  header: { minHeight: control.minTouchTarget, gap: spacing.sm },
1381
- title: {
1382
- color: semanticColors.content.primary,
1383
- textVariant: "title",
1384
- fontWeight: fontWeight.bold,
1385
- gap: spacing.xxs,
1386
- },
1423
+ // No visual `title` region: sheets render the title only as the accessible name (2026-10-17 user decision).
1424
+ // The "title" slot stays because the title is still the dialog's name.
1387
1425
  body: {
1388
1426
  color: semanticColors.content.body,
1389
1427
  textVariant: "body",
@@ -1467,7 +1505,7 @@ export const alertDialogRecipe = {
1467
1505
  title: {
1468
1506
  color: semanticColors.content.primary,
1469
1507
  textVariant: "title",
1470
- fontWeight: fontWeight.bold,
1508
+ fontWeight: typography.title.fontWeight,
1471
1509
  },
1472
1510
  description: {
1473
1511
  color: semanticColors.content.secondary,
@@ -1586,7 +1624,7 @@ export const toastRecipe = {
1586
1624
  title: {
1587
1625
  color: semanticColors.content.primary,
1588
1626
  textVariant: "body",
1589
- fontWeight: fontWeight.bold,
1627
+ fontWeight: typography.title.fontWeight,
1590
1628
  },
1591
1629
  // Secondary copy so the title leads; body-on-body read as one grey block.
1592
1630
  description: {
@@ -1594,7 +1632,7 @@ export const toastRecipe = {
1594
1632
  textVariant: "body",
1595
1633
  },
1596
1634
  // Step dots of a running task: done and current are brand, upcoming is the divider colour; the current
1597
- // label is bold. Labels sit beside each dot so the row reads without colour alone (WCAG 1.4.1).
1635
+ // label is emphasized. Labels sit beside each dot so the row reads without colour alone (WCAG 1.4.1).
1598
1636
  progress: {
1599
1637
  gap: spacing.sm,
1600
1638
  dotSize: 6,
@@ -1614,7 +1652,7 @@ export const toastRecipe = {
1614
1652
  radius: "full",
1615
1653
  align: "start",
1616
1654
  textVariant: "body",
1617
- fontWeight: fontWeight.bold,
1655
+ fontWeight: fontWeight.medium,
1618
1656
  minHeight: control.minTouchTarget,
1619
1657
  paddingHorizontal: spacing.md,
1620
1658
  },
@@ -1683,7 +1721,7 @@ export const topBarRecipe = {
1683
1721
  paddingHorizontal: spacing.md,
1684
1722
  gap: spacing.xs,
1685
1723
  largeTextThreshold,
1686
- title: { textVariant: "bodyLarge", color: semanticColors.content.primary, fontWeight: fontWeight.bold },
1724
+ title: { textVariant: "bodyLarge", color: semanticColors.content.primary, fontWeight: typography.title.fontWeight },
1687
1725
  titleAction: {
1688
1726
  minHeight: control.minTouchTarget,
1689
1727
  minWidth: control.minTouchTarget,
@@ -1744,7 +1782,7 @@ export const sectionRecipe = {
1744
1782
  gap: spacing.xs,
1745
1783
  headerGap: spacing.sm,
1746
1784
  copyGap: spacing.xxs,
1747
- title: { textVariant: "title", color: semanticColors.content.body, fontWeight: fontWeight.bold },
1785
+ title: { textVariant: "title", color: semanticColors.content.body, fontWeight: typography.title.fontWeight },
1748
1786
  description: { textVariant: "caption", color: semanticColors.content.secondary },
1749
1787
  };
1750
1788
  //# sourceMappingURL=component-recipes.js.map