@wix/himalaya-cli 0.1019.0 → 0.1021.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.
package/dist/cli.mjs CHANGED
@@ -2127,7 +2127,8 @@ var init_preview_surfaces = __esm({
2127
2127
  // iPhone 14/15 logical points
2128
2128
  orientations: ["portrait"],
2129
2129
  platformKey: null,
2130
- // iOS/Android are implicit
2130
+ // iOS/Android are implicit — named explicitly in idiomPlatforms
2131
+ idiomPlatforms: ["ios", "android"],
2131
2132
  gate: { kind: "none" }
2132
2133
  },
2133
2134
  {
@@ -2142,6 +2143,7 @@ var init_preview_surfaces = __esm({
2142
2143
  // iPad Pro 11" logical points
2143
2144
  orientations: ["portrait", "landscape"],
2144
2145
  platformKey: null,
2146
+ idiomPlatforms: ["ios", "android"],
2145
2147
  gate: { kind: "none" }
2146
2148
  },
2147
2149
  {
@@ -5293,6 +5295,10 @@ var init_quickjs_web_globals = __esm({
5293
5295
  });
5294
5296
 
5295
5297
  // ../../core/ts/src/tokens.ts
5298
+ function tonalElevationAlpha(level) {
5299
+ if (typeof level !== "number" || !Number.isFinite(level)) return 0;
5300
+ return TONAL_ELEVATION_ALPHA[Math.min(5, Math.max(0, Math.round(level)))];
5301
+ }
5296
5302
  function buildTokenRegistry(tokens) {
5297
5303
  return {
5298
5304
  colors: new Set(Object.keys(tokens.colors ?? {})),
@@ -5342,11 +5348,12 @@ function walk(node, registry, path2, out) {
5342
5348
  walk(value, registry, `${path2}.${key2}`, out);
5343
5349
  }
5344
5350
  }
5345
- var SYSTEM_FONT_FAMILY;
5351
+ var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA;
5346
5352
  var init_tokens = __esm({
5347
5353
  "../../core/ts/src/tokens.ts"() {
5348
5354
  "use strict";
5349
5355
  SYSTEM_FONT_FAMILY = "$system";
5356
+ TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
5350
5357
  }
5351
5358
  });
5352
5359
 
@@ -11079,6 +11086,29 @@ function tokenColorIssues(dir, tokensPathOverride) {
11079
11086
  );
11080
11087
  }
11081
11088
  }
11089
+ const shadowNames = (layer) => {
11090
+ const sh = layer?.shadows;
11091
+ return new Set(sh !== null && typeof sh === "object" && !Array.isArray(sh) ? Object.keys(sh) : []);
11092
+ };
11093
+ const checkElevations = (layer, where, declared) => {
11094
+ const els = layer?.elevations;
11095
+ if (els === null || typeof els !== "object" || Array.isArray(els)) return;
11096
+ for (const [name, token] of Object.entries(els)) {
11097
+ const shadow = token?.shadow;
11098
+ if (shadow === void 0 || typeof shadow === "string" && declared.has(shadow)) continue;
11099
+ error(
11100
+ `${tokensPath}: ${where}elevations.${name}.shadow is ${JSON.stringify(shadow)}, which names no \`shadows\` token \u2014 every renderer would paint it with no shadow at all. Declare shadows.${typeof shadow === "string" ? shadow : "<name>"} or name one that exists.`
11101
+ );
11102
+ }
11103
+ };
11104
+ const baseShadows = shadowNames(raw);
11105
+ checkElevations(raw, "", baseShadows);
11106
+ const idiom = raw.idiom;
11107
+ if (idiom !== null && typeof idiom === "object" && !Array.isArray(idiom)) {
11108
+ for (const [overlay, layer] of Object.entries(idiom)) {
11109
+ checkElevations(layer, `idiom.${overlay}.`, /* @__PURE__ */ new Set([...baseShadows, ...shadowNames(layer)]));
11110
+ }
11111
+ }
11082
11112
  const colors = raw.colors;
11083
11113
  if (colors === void 0) return issues;
11084
11114
  if (colors === null || typeof colors !== "object" || Array.isArray(colors)) {
@@ -12659,23 +12689,130 @@ function resolveIdiom(tokens, ctx = {}) {
12659
12689
  const { idiomPolicy: _applied, ...final } = merged;
12660
12690
  return final;
12661
12691
  }
12662
- var UNSAFE_KEYS;
12692
+ var IDIOM_CONTEXTS, UNSAFE_KEYS;
12663
12693
  var init_idiom = __esm({
12664
12694
  "../../core/ts/src/idiom.ts"() {
12665
12695
  "use strict";
12696
+ IDIOM_CONTEXTS = {
12697
+ ios: { native: ["ios"] },
12698
+ android: { native: ["material"] },
12699
+ mac: { native: ["desktop", "ios"], surface: "mac" }
12700
+ };
12666
12701
  UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
12667
12702
  }
12668
12703
  });
12669
12704
 
12705
+ // ../../core/web-renderer/src/idiom-controls.ts
12706
+ function pickRole(colors, role) {
12707
+ if (typeof role === "string") return role;
12708
+ return role.find((name) => colors?.[name] != null) ?? role[role.length - 1];
12709
+ }
12710
+ function controlIdiomFor(context) {
12711
+ for (const name of context?.native ?? []) {
12712
+ if (name !== "base" && Object.hasOwn(IDIOM_CONTROLS, name)) return name;
12713
+ }
12714
+ return "base";
12715
+ }
12716
+ function idiomControls(idiom) {
12717
+ return idiom != null && Object.hasOwn(IDIOM_CONTROLS, idiom) ? IDIOM_CONTROLS[idiom] : IDIOM_CONTROLS.base;
12718
+ }
12719
+ var STOCK_ROLES, FILLED_SEGMENT_ROLES, IDIOM_CONTROLS;
12720
+ var init_idiom_controls = __esm({
12721
+ "../../core/web-renderer/src/idiom-controls.ts"() {
12722
+ STOCK_ROLES = { onThumb: null, offTrack: "surfaceTertiary", offThumb: null, outline: null };
12723
+ FILLED_SEGMENT_ROLES = { outline: "border", inactive: "surfaceTertiary" };
12724
+ IDIOM_CONTROLS = {
12725
+ // The web's own look — today's values, unchanged.
12726
+ base: {
12727
+ font: { defaultStack: '"Wix Madefor Text", system-ui', system: null, bundled: null, faithfulOn: "any" },
12728
+ switch: { width: 51, height: 31, thumbOn: 27, thumbOff: 27, outline: 0, roles: STOCK_ROLES, thumbShadow: true },
12729
+ segmented: { height: 50, trackPadding: 3, trackRadius: null, segmentRadius: null, outlined: false, selectedCheck: false, roles: FILLED_SEGMENT_ROLES },
12730
+ sheet: { cornerRadius: 16, grabberWidth: 36, grabberHeight: 5 },
12731
+ navBar: { rootTitle: "inline", pushedTitle: "inline", back: "chevron", inlineTitleSize: 15, largeTitleSize: 15, hideWithStickyChrome: false },
12732
+ press: { button: "none", tappable: "none", highlightOpacity: 1, stateLayerOpacity: 0 }
12733
+ },
12734
+ // iOS: the stock UIKit / SwiftUI controls. The switch is MEASURED against
12735
+ // HimalayaDemoAppSnapshotTests' gallery_inputs golden (51×31, 27pt thumb, 2pt inset).
12736
+ ios: {
12737
+ // SF Pro reaches a page only as the Apple system font (`-apple-system`); it is not
12738
+ // redistributable as a webfont, so elsewhere this frame falls back and says so.
12739
+ font: { defaultStack: '-apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif', system: '-apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif', bundled: null, faithfulOn: "apple" },
12740
+ switch: { width: 51, height: 31, thumbOn: 27, thumbOff: 27, outline: 0, roles: STOCK_ROLES, thumbShadow: true },
12741
+ // UISegmentedControl: 32pt tall, a 2pt-inset selected segment inside a filled track.
12742
+ segmented: { height: 32, trackPadding: 2, trackRadius: 9, segmentRadius: 7, outlined: false, selectedCheck: false, roles: FILLED_SEGMENT_ROLES },
12743
+ // UISheetPresentationController: a 36×5 grabber and 10pt top corners (iOS 15–18).
12744
+ sheet: { cornerRadius: 10, grabberWidth: 36, grabberHeight: 5 },
12745
+ // NavigationStack: a large title on the root screen, inline once pushed, and NO bar when the
12746
+ // screen pins sticky chrome (HimalayaScreenHost.swift `RootTitleDisplayMode`,
12747
+ // `HideNavigationBarWhen(hasStickyChrome)`). UINavigationBar: 17pt semibold inline, 34pt
12748
+ // bold large.
12749
+ navBar: { rootTitle: "large", pushedTitle: "inline", back: "chevron", inlineTitleSize: 17, largeTitleSize: 34, hideWithStickyChrome: true },
12750
+ // A Button is `.buttonStyle(.borderless)`, which dims its content while pressed; any other
12751
+ // tappable is `.onTapGesture` (SDUIRenderer.swift `TapGestureIfPresent`), which shows
12752
+ // nothing. The dim is approximate — no golden captures a pressed state.
12753
+ press: { button: "highlight", tappable: "none", highlightOpacity: 0.3, stateLayerOpacity: 0 }
12754
+ },
12755
+ // Android: the stock Material 3 controls. The switch's ON state is MEASURED against the
12756
+ // Paparazzi gallery_inputs golden (52×32 track, 24dp thumb, 4dp inset); its OFF state —
12757
+ // 16dp thumb, 2dp outline — is Compose Material 3's SwitchTokens (no golden shows one).
12758
+ material: {
12759
+ // Roboto is the font catalog's variable face (100–900); the web host bundles that exact file
12760
+ // (index.html @font-face), so the frame is faithful on any OS, offline included.
12761
+ font: {
12762
+ defaultStack: "Roboto, sans-serif",
12763
+ system: "Roboto, sans-serif",
12764
+ bundled: { family: "Roboto", file: "fonts/Roboto-Variable.ttf", sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
12765
+ faithfulOn: "any"
12766
+ },
12767
+ switch: {
12768
+ width: 52,
12769
+ height: 32,
12770
+ thumbOn: 24,
12771
+ thumbOff: 16,
12772
+ outline: 2,
12773
+ thumbShadow: false,
12774
+ roles: {
12775
+ onThumb: "textOnAccent",
12776
+ offTrack: ["surfaceElevated", "surfaceSecondary", "surfacePrimary"],
12777
+ offThumb: ["border", "surfaceTertiary", "surfaceSecondary", "surfacePrimary"],
12778
+ outline: ["border", "surfaceTertiary", "surfaceSecondary", "surfacePrimary"]
12779
+ }
12780
+ },
12781
+ // SegmentedButton (SDUIComponents.kt `SingleChoiceSegmentedButtonRow`): 40dp, pill-ended
12782
+ // adjoining outlined buttons, no inset track.
12783
+ segmented: {
12784
+ height: 40,
12785
+ trackPadding: 0,
12786
+ trackRadius: "pill",
12787
+ segmentRadius: "pill",
12788
+ outlined: true,
12789
+ selectedCheck: true,
12790
+ roles: { outline: ["border", "surfaceTertiary", "surfaceSecondary", "surfacePrimary"], inactive: ["surfaceTertiary", "surfaceSecondary", "surfacePrimary"] }
12791
+ },
12792
+ // ModalBottomSheet + BottomSheetDefaults.DragHandle: a 32×4 handle, 28dp top corners.
12793
+ sheet: { cornerRadius: 28, grabberWidth: 32, grabberHeight: 4 },
12794
+ // HimalayaAppShell draws NO title bar — a floating ArrowBack IconButton, the title unused.
12795
+ // The floating control reserves a top band in the content's flow unless the screen is
12796
+ // immersive (ScreenView.kt `reservesBand`), so nothing slides under it.
12797
+ navBar: { rootTitle: "none", pushedTitle: "none", back: "arrow", inlineTitleSize: 0, largeTitleSize: 0, hideWithStickyChrome: false },
12798
+ // M3 Buttons and `Modifier.clickable` (every `onTap`, SDUIRenderer.kt) ripple. The web draws
12799
+ // the M3 pressed state layer (10% of the content colour), not the expanding circle.
12800
+ press: { button: "ripple", tappable: "ripple", highlightOpacity: 1, stateLayerOpacity: 0.1 }
12801
+ }
12802
+ };
12803
+ }
12804
+ });
12805
+
12670
12806
  // ../../core/web-renderer/src/tokens.ts
12671
12807
  function isSerifFamily(family) {
12672
12808
  return !!family && SERIF_FAMILY_RE.test(family);
12673
12809
  }
12674
- function fontStack(family) {
12675
- if (!family) return DEFAULT_FONT_STACK;
12676
- if (family === SYSTEM_FONT_FAMILY) return SYSTEM_FONT_STACK;
12810
+ function fontStack(family, idiom) {
12811
+ const fallback = idiom ? idiomControls(idiom).font.defaultStack : DEFAULT_FONT_STACK;
12812
+ if (!family) return fallback;
12813
+ if (family === SYSTEM_FONT_FAMILY) return (idiom ? idiomControls(idiom).font.system : null) ?? SYSTEM_FONT_STACK;
12677
12814
  if (isSerifFamily(family)) return `"${family}", serif`;
12678
- return `"${family}", ${DEFAULT_FONT_STACK}`;
12815
+ return `"${family}", ${fallback}`;
12679
12816
  }
12680
12817
  function clamp(n, lo = 0, hi = 1) {
12681
12818
  return Math.max(lo, Math.min(hi, n));
@@ -12844,6 +12981,32 @@ function resolveSchemeColor(o, scheme) {
12844
12981
  if (typeof o?.light === "string") return o.light;
12845
12982
  return typeof o?.dark === "string" ? o.dark : void 0;
12846
12983
  }
12984
+ function mixColor(base, over, alpha2) {
12985
+ const a = hexToRgba(base), b = hexToRgba(over);
12986
+ if (a && b) {
12987
+ const t = alpha2 * b[3];
12988
+ const hex2 = (v) => Math.round(v).toString(16).padStart(2, "0");
12989
+ const rgb = [0, 1, 2].map((i) => hex2(a[i] * (1 - t) + b[i] * t)).join("");
12990
+ return a[3] < 1 ? `#${rgb}${hex2(a[3] * 255)}` : `#${rgb}`;
12991
+ }
12992
+ return `color-mix(in srgb, ${over} ${+(alpha2 * 100).toFixed(2)}%, ${base})`;
12993
+ }
12994
+ function hexToRgba(hex2) {
12995
+ const h = hex2.trim().replace(/^#/, "");
12996
+ if (/^[\da-f]{8}$/i.test(h)) {
12997
+ const rgb2 = hexToRgb2(h.slice(0, 6));
12998
+ return rgb2 ? [...rgb2, parseInt(h.slice(6), 16) / 255] : null;
12999
+ }
13000
+ const rgb = hexToRgb2(h);
13001
+ return rgb ? [...rgb, 1] : null;
13002
+ }
13003
+ function elevationBoxShadow(e) {
13004
+ const parts = [
13005
+ ...e.border && e.border.width > 0 ? [`inset 0 0 0 ${e.border.width}px ${e.border.color}`] : [],
13006
+ ...e.shadow ? [e.shadow] : []
13007
+ ];
13008
+ return parts.length ? parts.join(", ") : void 0;
13009
+ }
12847
13010
  function makeTokenResolvers(source, scheme, idiom = WEB_IDIOM_CONTEXT) {
12848
13011
  const tokens = resolveIdiom(source, idiom);
12849
13012
  const color = (name, fallback = "#1a1a1a") => {
@@ -12934,6 +13097,17 @@ function makeTokenResolvers(source, scheme, idiom = WEB_IDIOM_CONTEXT) {
12934
13097
  }
12935
13098
  return tint ? { background: rgba(tint, m.opacity ?? 1) } : void 0;
12936
13099
  };
13100
+ const elevation = (name) => {
13101
+ const e = name ? tokens.elevations?.[name] : void 0;
13102
+ if (!e) return void 0;
13103
+ const alpha2 = tonalElevationAlpha(e.level);
13104
+ const width = e.border ? typeof e.border.width === "number" && Number.isFinite(e.border.width) ? Math.max(0, e.border.width) : 0.5 : 0;
13105
+ return {
13106
+ ...alpha2 > 0 ? { tint: { color: color(e.tint ?? "accentPrimary", "#6F3BFF"), alpha: alpha2 } } : {},
13107
+ ...e.shadow && shadow(e.shadow) ? { shadow: shadow(e.shadow) } : {},
13108
+ ...e.border && width > 0 ? { border: { color: color(e.border.color, "#E0E0E0"), width } } : {}
13109
+ };
13110
+ };
12937
13111
  const densityScale = (density) => {
12938
13112
  switch (density) {
12939
13113
  case "compact":
@@ -12975,6 +13149,7 @@ function makeTokenResolvers(source, scheme, idiom = WEB_IDIOM_CONTEXT) {
12975
13149
  gradient,
12976
13150
  meshGradient,
12977
13151
  material,
13152
+ elevation,
12978
13153
  densityScale,
12979
13154
  motionTransition,
12980
13155
  appearMotion,
@@ -12988,6 +13163,7 @@ var REM_ROOT_PX, pxToRem, DEFAULT_FONT_STACK, SYSTEM_FONT_STACK, SERIF_FAMILY_RE
12988
13163
  var init_tokens2 = __esm({
12989
13164
  "../../core/web-renderer/src/tokens.ts"() {
12990
13165
  init_idiom();
13166
+ init_idiom_controls();
12991
13167
  init_tokens();
12992
13168
  REM_ROOT_PX = 16;
12993
13169
  pxToRem = (px) => `${px / REM_ROOT_PX}rem`;
@@ -17129,7 +17305,17 @@ async function wixVisitorCall(method, path2, body, token, fetchImpl = fetch, for
17129
17305
  });
17130
17306
  return { status: res.status, body: await res.text() };
17131
17307
  }
17132
- function normalizeWixForwardedHeaders(input) {
17308
+ function isChatWebTarget(path2) {
17309
+ if (!path2 || !/^https:\/\//i.test(path2)) return false;
17310
+ try {
17311
+ const url = new URL(path2);
17312
+ return url.host === "manage.wix.com" && url.pathname.startsWith("/_api/chat-web/");
17313
+ } catch {
17314
+ return false;
17315
+ }
17316
+ }
17317
+ function normalizeWixForwardedHeaders(input, path2) {
17318
+ const chatWeb = isChatWebTarget(path2);
17133
17319
  let value = input;
17134
17320
  if (typeof value === "string") {
17135
17321
  try {
@@ -17139,10 +17325,12 @@ function normalizeWixForwardedHeaders(input) {
17139
17325
  }
17140
17326
  }
17141
17327
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
17142
- const blocked = /* @__PURE__ */ new Set(["authorization", "cookie", "host", "content-length", "content-type", "connection"]);
17143
17328
  const out = {};
17144
17329
  for (const [name, headerValue] of Object.entries(value)) {
17145
- if (!/^[a-z0-9-]+$/i.test(name) || blocked.has(name.toLowerCase()) || typeof headerValue !== "string") continue;
17330
+ const lower = name.toLowerCase();
17331
+ const allowed2 = WIX_FORWARDABLE_HEADERS.has(lower) || chatWeb && lower === "chattoken";
17332
+ if (!/^[a-z0-9-]+$/i.test(name) || !allowed2 || typeof headerValue !== "string") continue;
17333
+ if (/[\r\n\0]/.test(headerValue)) continue;
17146
17334
  out[name] = headerValue;
17147
17335
  }
17148
17336
  return out;
@@ -17181,7 +17369,7 @@ function wixClientIdOf(appConfigJson) {
17181
17369
  if (typeof clientId !== "string" || !clientId) return { error: "no_client_id" };
17182
17370
  return { clientId };
17183
17371
  }
17184
- var WIX_API, ONE_APP_SESSION, OWNER_SCOPED_WIX_HOST, VisitorTokenCache, OWNER_REFRESH_MARGIN_MS, OWNER_RENEW_MIN_AGE_MS, OWNER_MINT_TIMEOUT_MS, OwnerInstanceCache, OWNER_SITES_QUERY_PATH, OWNER_SITES_COUNT_PATH, OwnerSitesCache;
17372
+ var WIX_API, ONE_APP_SESSION, OWNER_SCOPED_WIX_HOST, VisitorTokenCache, WIX_FORWARDABLE_HEADERS, OWNER_REFRESH_MARGIN_MS, OWNER_RENEW_MIN_AGE_MS, OWNER_MINT_TIMEOUT_MS, OwnerInstanceCache, OWNER_SITES_QUERY_PATH, OWNER_SITES_COUNT_PATH, OwnerSitesCache;
17185
17373
  var init_wix_bridge = __esm({
17186
17374
  "../../core/serve-runtime/src/wix-bridge.ts"() {
17187
17375
  init_sha256();
@@ -17218,6 +17406,22 @@ var init_wix_bridge = __esm({
17218
17406
  this.cache.delete(clientId);
17219
17407
  }
17220
17408
  };
17409
+ WIX_FORWARDABLE_HEADERS = /* @__PURE__ */ new Set([
17410
+ "x-wix-language",
17411
+ "x-wix-linguist",
17412
+ "wixlanguage",
17413
+ "accept-language",
17414
+ "x-wix-currency",
17415
+ "x-wix-consistent",
17416
+ "x-wix-client-api-revision",
17417
+ "x-wix-site-revision",
17418
+ "x-wix-bi-gateway",
17419
+ "x-wix-brand",
17420
+ "consent-policy",
17421
+ "commonconfig",
17422
+ "x-wix-client-artifact-id",
17423
+ "wix-site-id"
17424
+ ]);
17221
17425
  OWNER_REFRESH_MARGIN_MS = 6e4;
17222
17426
  OWNER_RENEW_MIN_AGE_MS = 5e3;
17223
17427
  OWNER_MINT_TIMEOUT_MS = 8e3;
@@ -22360,6 +22564,7 @@ var init_controls = __esm({
22360
22564
  "use strict";
22361
22565
  init_helpers();
22362
22566
  init_tokens2();
22567
+ init_idiom_controls();
22363
22568
  init_bindings2();
22364
22569
  init_icons();
22365
22570
  init_formBag();
@@ -22368,7 +22573,7 @@ var init_controls = __esm({
22368
22573
  const r = useResolvedScalar(typeof v === "string" ? v : void 0, ctx);
22369
22574
  return r != null ? String(r) : void 0;
22370
22575
  };
22371
- FONT = (ctx) => fontStack(ctx.font("body").family);
22576
+ FONT = (ctx) => fontStack(ctx.font("body").family, ctx.idiom);
22372
22577
  rowShareFloor = (style, rowStretch, floor) => rowStretch ? { ...style, minWidth: floor } : style;
22373
22578
  A11ySwitch = ({ props, ctx, wrap }) => {
22374
22579
  const C = ctx.color;
@@ -22397,11 +22602,18 @@ var init_controls = __esm({
22397
22602
  bag.register(fieldId, props.validation);
22398
22603
  return () => bag.unregister(fieldId);
22399
22604
  }, [fieldId, bag, ruleKey]);
22400
- const w = typeof props.width === "number" ? props.width : 51;
22401
- const h = typeof props.height === "number" ? props.height : 31;
22402
- const tSize = Math.max(0, h - 4);
22403
- const trackColor = on3 ? C(props.trackOn ?? "accentPrimary", "#116DFF") : C(props.trackOff ?? "surfaceTertiary", "#ddd");
22404
- const thumbColor = props.thumb ? C(props.thumb, "#fff") : "#fff";
22605
+ const hasAppearance = props.trackOn != null || props.trackOff != null || props.thumb != null || props.width != null || props.height != null;
22606
+ const shape = idiomControls(hasAppearance ? void 0 : ctx.idiom).switch;
22607
+ const w = typeof props.width === "number" ? props.width : shape.width;
22608
+ const h = typeof props.height === "number" ? props.height : shape.height;
22609
+ const tSize = hasAppearance ? Math.max(0, h - 4) : Math.max(0, on3 ? shape.thumbOn : shape.thumbOff);
22610
+ const roles = shape.roles;
22611
+ const role = (r) => pickRole(ctx.tokens.colors, r);
22612
+ const trackColor = on3 ? C(props.trackOn ?? "accentPrimary", "#116DFF") : C(props.trackOff ?? role(roles.offTrack), "#ddd");
22613
+ const thumbRole = on3 ? roles.onThumb : roles.offThumb;
22614
+ const thumbColor = props.thumb ? C(props.thumb, "#fff") : thumbRole ? C(role(thumbRole), "#fff") : "#fff";
22615
+ const outlineWidth = !on3 && roles.outline ? shape.outline : 0;
22616
+ const thumbInset = (h - tSize) / 2 - outlineWidth;
22405
22617
  const act = props.onChange ?? props.action;
22406
22618
  const canFire = !props.disabled && Boolean(ctx.onAction) && (inForm || Boolean(act));
22407
22619
  const fire = canFire ? () => {
@@ -22426,8 +22638,8 @@ var init_controls = __esm({
22426
22638
  "aria-label": props.label != null ? asText2(props.label) : void 0,
22427
22639
  disabled: props.disabled === true,
22428
22640
  onClick: fire,
22429
- style: { all: "unset", boxSizing: "border-box", width: w, height: h, borderRadius: 999, background: trackColor, position: "relative", flex: "none", cursor: fire ? "pointer" : "default", display: "inline-block", outlineOffset: 2 },
22430
- children: /* @__PURE__ */ jsx4("span", { "aria-hidden": true, style: { position: "absolute", top: 2, [on3 ? "right" : "left"]: 2, width: tSize, height: tSize, borderRadius: "50%", background: thumbColor, boxShadow: "0 1px 3px rgba(0,0,0,.3)" } })
22641
+ style: { all: "unset", boxSizing: "border-box", width: w, height: h, borderRadius: 999, background: trackColor, ...outlineWidth > 0 ? { border: `${outlineWidth}px solid ${C(role(roles.outline), "#79747e")}` } : {}, position: "relative", flex: "none", cursor: fire ? "pointer" : "default", display: "inline-block", outlineOffset: 2 },
22642
+ children: /* @__PURE__ */ jsx4("span", { "aria-hidden": true, style: { position: "absolute", top: thumbInset, [on3 ? "right" : "left"]: thumbInset, width: tSize, height: tSize, borderRadius: "50%", background: thumbColor, boxShadow: shape.thumbShadow ? "0 1px 3px rgba(0,0,0,.3)" : "none" } })
22431
22643
  }
22432
22644
  )
22433
22645
  ] });
@@ -22590,6 +22802,8 @@ var init_controls = __esm({
22590
22802
  };
22591
22803
  A11ySegmented = ({ props, ctx, wrap }) => {
22592
22804
  const C = ctx.color;
22805
+ const seg = idiomControls(ctx.idiom).segmented;
22806
+ const radius = (r, token, fallback) => r === null ? ctx.radius(token, fallback) : r === "pill" ? 999 : r;
22593
22807
  const optionsPath = props.optionsPath ?? props.options_path;
22594
22808
  const boundOpts = useBoundValue(ctx, typeof optionsPath === "string" && isBinding(optionsPath) ? optionsPath : "");
22595
22809
  const resolvedSel = useResolvedId(props.selectedId, ctx);
@@ -22609,9 +22823,15 @@ var init_controls = __esm({
22609
22823
  e.preventDefault();
22610
22824
  if (ni !== selIdx) select(ni);
22611
22825
  };
22612
- return /* @__PURE__ */ jsx4("div", { role: "radiogroup", onKeyDown, style: { display: "flex", background: C("surfaceTertiary", "#eee"), borderRadius: ctx.radius("md", 8), padding: 3, width: "100%", boxSizing: "border-box", ...wrap }, children: opts.map((o, i) => {
22826
+ return /* @__PURE__ */ jsx4("div", { role: "radiogroup", onKeyDown, style: { display: "flex", background: seg.outlined ? "transparent" : C("surfaceTertiary", "#eee"), borderRadius: radius(seg.trackRadius, "md", 8), padding: seg.trackPadding, width: "100%", boxSizing: "border-box", ...wrap }, children: opts.map((o, i) => {
22613
22827
  const on3 = o.id === selId;
22614
- return /* @__PURE__ */ jsx4(
22828
+ const outerR = radius(seg.segmentRadius, "sm", 6);
22829
+ const outlinedShape = seg.outlined ? {
22830
+ border: `1px solid ${C(pickRole(ctx.tokens.colors, seg.roles.outline), "#d8dee4")}`,
22831
+ marginLeft: i > 0 ? -1 : 0,
22832
+ borderRadius: `${i === 0 ? outerR : 0}px ${i === opts.length - 1 ? outerR : 0}px ${i === opts.length - 1 ? outerR : 0}px ${i === 0 ? outerR : 0}px`
22833
+ } : {};
22834
+ return /* @__PURE__ */ jsxs(
22615
22835
  "button",
22616
22836
  {
22617
22837
  type: "button",
@@ -22619,8 +22839,11 @@ var init_controls = __esm({
22619
22839
  "aria-checked": on3,
22620
22840
  tabIndex: on3 || selId == null && i === 0 ? 0 : -1,
22621
22841
  onClick: () => select(i),
22622
- style: { all: "unset", flex: 1, minWidth: 44, minHeight: 44, textAlign: "center", padding: "7px 4px", borderRadius: ctx.radius("sm", 6), fontFamily: FONT(ctx), fontWeight: 600, fontSize: 13, cursor: "pointer", boxSizing: "border-box", background: on3 ? C("surfacePrimary", "#fff") : "transparent", color: on3 ? C("textPrimary", "#111") : C("textSecondary", "#777"), boxShadow: on3 ? "0 1px 3px rgba(0,0,0,.12)" : "none" },
22623
- children: asText2(o.label ?? o.title ?? o.id) || `Option ${i + 1}`
22842
+ style: { all: "unset", flex: 1, minWidth: 44, minHeight: seg.height - 2 * seg.trackPadding, textAlign: "center", padding: "7px 4px", borderRadius: outerR, fontFamily: FONT(ctx), fontWeight: 600, fontSize: 13, cursor: "pointer", boxSizing: "border-box", background: on3 ? C("surfacePrimary", "#fff") : seg.outlined ? C(pickRole(ctx.tokens.colors, seg.roles.inactive), "#eee") : "transparent", color: on3 ? C("textPrimary", "#111") : C("textSecondary", "#777"), boxShadow: on3 && !seg.outlined ? "0 1px 3px rgba(0,0,0,.12)" : "none", ...outlinedShape },
22843
+ children: [
22844
+ on3 && seg.selectedCheck ? /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "\u2713 " }) : null,
22845
+ asText2(o.label ?? o.title ?? o.id) || `Option ${i + 1}`
22846
+ ]
22624
22847
  },
22625
22848
  o.id ?? i
22626
22849
  );
@@ -23280,7 +23503,7 @@ function seededWidth(measured, seed2) {
23280
23503
  return measured || (typeof seed2 === "number" && seed2 > 0 ? seed2 : 0);
23281
23504
  }
23282
23505
  function makeCtx(tokens, scheme = "light", opts = {}) {
23283
- const tk = makeTokenResolvers(tokens, scheme);
23506
+ const tk = makeTokenResolvers(tokens, scheme, opts.idiomContext);
23284
23507
  const provider = opts.provider ?? null;
23285
23508
  return {
23286
23509
  ...tk,
@@ -23303,7 +23526,9 @@ function makeCtx(tokens, scheme = "light", opts = {}) {
23303
23526
  emitTelemetry: opts.emitTelemetry,
23304
23527
  assetBaseUrl: opts.assetBaseUrl,
23305
23528
  runtimeDegraded: opts.runtimeDegraded,
23306
- previewSurface: opts.previewSurface
23529
+ previewSurface: opts.previewSurface,
23530
+ idiomContext: opts.idiomContext,
23531
+ idiom: controlIdiomFor(opts.idiomContext)
23307
23532
  };
23308
23533
  }
23309
23534
  function resolveAssetUrl(url, base) {
@@ -23327,7 +23552,7 @@ function withTheme(ctx, theme) {
23327
23552
  radius: { ...base.radius ?? {}, ...theme.radius ?? {} },
23328
23553
  spacing: { ...base.spacing ?? {}, ...theme.spacing ?? {} }
23329
23554
  };
23330
- const themed = makeCtx(merged, ctx.scheme, { provider: ctx.provider, onAction: ctx.onAction, intents: ctx.intents, empty: ctx.empty, screenId: ctx.screenId, emitTelemetry: ctx.emitTelemetry, previewSurface: ctx.previewSurface });
23555
+ const themed = makeCtx(merged, ctx.scheme, { provider: ctx.provider, onAction: ctx.onAction, intents: ctx.intents, empty: ctx.empty, screenId: ctx.screenId, emitTelemetry: ctx.emitTelemetry, previewSurface: ctx.previewSurface, idiomContext: ctx.idiomContext });
23331
23556
  themed.itemScope = ctx.itemScope;
23332
23557
  return themed;
23333
23558
  }
@@ -23344,9 +23569,29 @@ function ensurePressStyles() {
23344
23569
  const el = document.createElement("style");
23345
23570
  el.textContent = // WS-N3 — the duration/easing read from per-node CSS vars (set when a
23346
23571
  // `pressMotion` token is present) with the original 160ms/standard-ease default.
23347
- '[data-himalaya-press]{transition:transform var(--hm-press-dur,160ms) var(--hm-press-ease,cubic-bezier(0.2,0,0,1)),filter var(--hm-press-dur,160ms) var(--hm-press-ease,cubic-bezier(0.2,0,0,1))}[data-himalaya-press="scale"]:active,[data-himalaya-press="auto"]:active,[data-himalaya-press="scaleHighlight"]:active{transform:scale(0.96)}[data-himalaya-press="scaleHighlight"]:active,[data-himalaya-press="highlight"]:active{filter:brightness(1.08)}[data-himalaya-press="lift"]:active{transform:translateY(-4px) scale(1.02)}@media (hover:hover){[data-himalaya-tap]{transition:filter 120ms ease}[data-himalaya-tap]:hover{filter:brightness(0.97)}[data-himalaya-press="scale"]:hover,[data-himalaya-press="auto"]:hover,[data-himalaya-press="scaleHighlight"]:hover{transform:scale(0.985)}[data-himalaya-press="lift"]:hover{transform:translateY(-2px) scale(1.01)}}';
23572
+ '[data-himalaya-press]{transition:transform var(--hm-press-dur,160ms) var(--hm-press-ease,cubic-bezier(0.2,0,0,1)),filter var(--hm-press-dur,160ms) var(--hm-press-ease,cubic-bezier(0.2,0,0,1))}[data-himalaya-press="scale"]:active,[data-himalaya-press="auto"]:active,[data-himalaya-press="scaleHighlight"]:active{transform:scale(0.96)}[data-himalaya-press="scaleHighlight"]:active,[data-himalaya-press="highlight"]:active{filter:brightness(1.08)}[data-himalaya-press="lift"]:active{transform:translateY(-4px) scale(1.02)}' + idiomPressRules() + // RFC 0051 track D — desktop hover analogs. Guarded by `(hover: hover)` so a
23573
+ // touch device never sees a sticky-hover state; pointer-only affordance for
23574
+ // the real web app. Tap-bearing nodes get a subtle dim (the hover analog of
23575
+ // pressFeedback's dim family); press-styled nodes preview their :active
23576
+ // recipe at reduced amplitude.
23577
+ '@media (hover:hover){[data-himalaya-tap]{transition:filter 120ms ease}[data-himalaya-tap]:hover{filter:brightness(0.97)}[data-himalaya-press="scale"]:hover,[data-himalaya-press="auto"]:hover,[data-himalaya-press="scaleHighlight"]:hover{transform:scale(0.985)}[data-himalaya-press="lift"]:hover{transform:translateY(-2px) scale(1.01)}}';
23348
23578
  document.head.appendChild(el);
23349
23579
  }
23580
+ function idiomPressRules() {
23581
+ let css = "";
23582
+ for (const [name, row] of Object.entries(IDIOM_CONTROLS)) {
23583
+ const layer = `color-mix(in srgb,currentColor ${Math.round(row.press.stateLayerOpacity * 100)}%,transparent)`;
23584
+ css += `[data-himalaya-idiom-press="${name}-highlight"]:active{opacity:${row.press.highlightOpacity}!important}`;
23585
+ css += `[data-himalaya-idiom-press="${name}-ripple"]:active{background-image:linear-gradient(${layer},${layer})!important}`;
23586
+ }
23587
+ return css;
23588
+ }
23589
+ function idiomPress(ctx, part) {
23590
+ const kind = idiomControls(ctx.idiom).press[part];
23591
+ if (kind === "none") return void 0;
23592
+ ensurePressStyles();
23593
+ return `${ctx.idiom}-${kind}`;
23594
+ }
23350
23595
  function ensureFocusStyles() {
23351
23596
  if (focusStylesInjected || typeof document === "undefined") return;
23352
23597
  focusStylesInjected = true;
@@ -24617,6 +24862,8 @@ function renderNodeInner(node, ctx) {
24617
24862
  }
24618
24863
  ensurePressStyles();
24619
24864
  extra["data-himalaya-tap"] = "1";
24865
+ const idiomPressed = idiomPress(ctx, "tappable");
24866
+ if (idiomPressed) extra["data-himalaya-idiom-press"] = idiomPressed;
24620
24867
  }
24621
24868
  const dtap = node?.modifiers?.onDoubleTap ?? node?.modifiers?.on_double_tap;
24622
24869
  if (dtap && ctx.onAction) {
@@ -24959,8 +25206,17 @@ function renderInner(node, ctx) {
24959
25206
  }
24960
25207
  case "Grid":
24961
25208
  return /* @__PURE__ */ jsx6(GridNode, { props, ctx, wrap, gap });
24962
- case "Card":
24963
- return /* @__PURE__ */ jsx6("div", { style: { display: "flex", flexDirection: "column", gap: props.spacing == null ? 8 : ctx.sp(props.spacing), padding: props.padding == null ? 12 : ctx.sp(props.padding), background: C(props.background ?? "surfaceSecondary", "#f5f7fa"), borderRadius: ctx.radius(props.cornerRadius ?? "md", 10), overflow: "hidden", alignSelf: "stretch", ...props.alignment ? { alignItems: props.alignment === "center" ? "center" : props.alignment === "trailing" ? "flex-end" : "flex-start" } : {}, ...wrap }, children: kids(props.children, ctx) });
25209
+ case "Card": {
25210
+ const m = modifiers;
25211
+ const ownSurface = !!(m?.backgroundGradient ?? m?.background_gradient ?? m?.material) || m?.animatedGradient === true || m?.meshGradient === true;
25212
+ const fill = C(props.background ?? "surfaceSecondary", "#f5f7fa");
25213
+ const card = ctx.elevation("card");
25214
+ const elevation = card && ownSurface ? { border: card.border } : card;
25215
+ const wrapShadow = wrap.boxShadow;
25216
+ const explicit = m?.shadow != null || m?.boxShadow != null || m?.box_shadow != null;
25217
+ const elevationShadow = !elevation ? wrapShadow : explicit ? elevationBoxShadow({ border: elevation.border, shadow: wrapShadow }) : [elevationBoxShadow(elevation), wrapShadow].filter(Boolean).join(", ") || void 0;
25218
+ return /* @__PURE__ */ jsx6("div", { style: { display: "flex", flexDirection: "column", gap: props.spacing == null ? 8 : ctx.sp(props.spacing), padding: props.padding == null ? 12 : ctx.sp(props.padding), background: elevation?.tint ? mixColor(fill, elevation.tint.color, elevation.tint.alpha) : fill, borderRadius: ctx.radius(props.cornerRadius ?? "md", 10), overflow: "hidden", alignSelf: "stretch", ...props.alignment ? { alignItems: props.alignment === "center" ? "center" : props.alignment === "trailing" ? "flex-end" : "flex-start" } : {}, ...wrap, ...elevationShadow ? { boxShadow: elevationShadow } : {} }, children: kids(props.children, ctx) });
25219
+ }
24964
25220
  case "SplitView":
24965
25221
  return /* @__PURE__ */ jsx6(SplitViewNode, { props, ctx, wrap });
24966
25222
  case "Divider":
@@ -24989,7 +25245,7 @@ function renderInner(node, ctx) {
24989
25245
  return /* @__PURE__ */ jsx6("div", { style: { fontFamily: bodyFont(ctx), lineHeight: 1.5, overflowWrap: "break-word", color: C(resolveScalarStatic(props.color, ctx) ?? "textPrimary", "#13171e"), ...wrap }, children: runs.map((r, i) => {
24990
25246
  const marks = Array.isArray(r.marks) ? r.marks : [];
24991
25247
  const f = ctx.font(r.style);
24992
- return /* @__PURE__ */ jsx6("span", { style: { fontSize: f.remSize, fontWeight: marks.includes("bold") ? 700 : f.weight, fontStyle: marks.includes("italic") ? "italic" : void 0, textDecoration: marks.includes("underline") ? "underline" : void 0, ...f.family ? { fontFamily: fontStack(f.family) } : {}, ...r.color ? { color: C(r.color) } : {}, ...marks.includes("code") ? { fontFamily: "ui-monospace, monospace", background: C("surfaceTertiary", "#eee"), padding: "1px 5px", borderRadius: 4 } : {} }, children: txt(r.text, ctx) }, i);
25248
+ return /* @__PURE__ */ jsx6("span", { style: { fontSize: f.remSize, fontWeight: marks.includes("bold") ? 700 : f.weight, fontStyle: marks.includes("italic") ? "italic" : void 0, textDecoration: marks.includes("underline") ? "underline" : void 0, ...f.family ? { fontFamily: fontStack(f.family, ctx.idiom) } : {}, ...r.color ? { color: C(r.color) } : {}, ...marks.includes("code") ? { fontFamily: "ui-monospace, monospace", background: C("surfaceTertiary", "#eee"), padding: "1px 5px", borderRadius: 4 } : {} }, children: txt(r.text, ctx) }, i);
24993
25249
  }) });
24994
25250
  }
24995
25251
  case "Image":
@@ -25030,8 +25286,8 @@ function renderInner(node, ctx) {
25030
25286
  props.leading && /* @__PURE__ */ jsx6("div", { style: { flex: "none", display: "flex" }, children: renderNode(props.leading, ctx) }),
25031
25287
  /* @__PURE__ */ jsxs3("div", { style: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 2 }, children: [
25032
25288
  props.overline != null && /* @__PURE__ */ jsx6("span", { style: { fontFamily: bodyFont(ctx), fontSize: 11, fontWeight: 600, letterSpacing: 0.4, textTransform: "uppercase", color: C(props.overlineColor ?? "textSecondary", "#888"), ...ell }, children: txt(props.overline, ctx) }),
25033
- /* @__PURE__ */ jsx6("span", { style: { fontFamily: fontStack(titleF.family), fontSize: titleF.remSize, fontWeight: titleF.weight, color: C(props.titleColor ?? "textPrimary", "#13171e"), ...ell }, children: txt(props.title, ctx) ?? (ctx.empty ? "" : "") }),
25034
- props.subtitle != null && /* @__PURE__ */ jsx6("span", { style: { fontFamily: fontStack(subF.family), fontSize: subF.remSize, color: C(props.subtitleColor ?? "textSecondary", "#888"), ...ell }, children: txt(props.subtitle, ctx) })
25289
+ /* @__PURE__ */ jsx6("span", { style: { fontFamily: fontStack(titleF.family, ctx.idiom), fontSize: titleF.remSize, fontWeight: titleF.weight, color: C(props.titleColor ?? "textPrimary", "#13171e"), ...ell }, children: txt(props.title, ctx) ?? (ctx.empty ? "" : "") }),
25290
+ props.subtitle != null && /* @__PURE__ */ jsx6("span", { style: { fontFamily: fontStack(subF.family, ctx.idiom), fontSize: subF.remSize, color: C(props.subtitleColor ?? "textSecondary", "#888"), ...ell }, children: txt(props.subtitle, ctx) })
25035
25291
  ] }),
25036
25292
  props.trailing ? (
25037
25293
  // #1010 — the row's `onTap` lives on the outer flex div, and the trailing
@@ -25097,7 +25353,7 @@ function renderInner(node, ctx) {
25097
25353
  case "Label": {
25098
25354
  const f = ctx.font(props.style ?? "body");
25099
25355
  const col = C(resolveScalarStatic(props.color, ctx) ?? "textPrimary", "#13171e");
25100
- return /* @__PURE__ */ jsxs3("span", { style: { display: "inline-flex", alignItems: "center", gap: 6, fontFamily: fontStack(f.family), fontSize: f.remSize, fontWeight: f.weight, color: col, ...wrap }, children: [
25356
+ return /* @__PURE__ */ jsxs3("span", { style: { display: "inline-flex", alignItems: "center", gap: 6, fontFamily: fontStack(f.family, ctx.idiom), fontSize: f.remSize, fontWeight: f.weight, color: col, ...wrap }, children: [
25101
25357
  props.icon && /* @__PURE__ */ jsx6("span", { style: { display: "inline-grid", placeItems: "center", width: f.size, height: f.size }, children: iconSvg(props.icon, { size: f.size, color: col }) }),
25102
25358
  txt(props.text, ctx)
25103
25359
  ] });
@@ -25126,7 +25382,7 @@ function renderInner(node, ctx) {
25126
25382
  ensureMarqueeKeyframes();
25127
25383
  const f = ctx.font(props.style ?? "body");
25128
25384
  const dur = props.durationMs ?? 8e3;
25129
- return /* @__PURE__ */ jsx6("div", { style: { width: "100%", overflow: "hidden", whiteSpace: "nowrap", ...wrap }, children: /* @__PURE__ */ jsx6("span", { style: { display: "inline-block", paddingLeft: "100%", fontFamily: fontStack(f.family), fontSize: f.remSize, color: C(resolveScalarStatic(props.color, ctx) ?? "textPrimary", "#13171e"), animation: ctx.empty ? void 0 : `himalaya-marquee ${dur}ms linear infinite` }, children: txt(props.text, ctx) }) });
25385
+ return /* @__PURE__ */ jsx6("div", { style: { width: "100%", overflow: "hidden", whiteSpace: "nowrap", ...wrap }, children: /* @__PURE__ */ jsx6("span", { style: { display: "inline-block", paddingLeft: "100%", fontFamily: fontStack(f.family, ctx.idiom), fontSize: f.remSize, color: C(resolveScalarStatic(props.color, ctx) ?? "textPrimary", "#13171e"), animation: ctx.empty ? void 0 : `himalaya-marquee ${dur}ms linear infinite` }, children: txt(props.text, ctx) }) });
25130
25386
  }
25131
25387
  case "Timeline":
25132
25388
  return /* @__PURE__ */ jsx6(TimelineNode, { props, ctx, wrap });
@@ -25365,6 +25621,7 @@ var init_renderNode = __esm({
25365
25621
  "../../core/web-renderer/src/renderNode.tsx"() {
25366
25622
  "use strict";
25367
25623
  init_tokens2();
25624
+ init_idiom_controls();
25368
25625
  init_modifiers();
25369
25626
  init_helpers();
25370
25627
  init_bindings2();
@@ -25495,7 +25752,7 @@ var init_renderNode = __esm({
25495
25752
  return "center";
25496
25753
  }
25497
25754
  };
25498
- bodyFont = (ctx) => fontStack(ctx.font("body").family);
25755
+ bodyFont = (ctx) => fontStack(ctx.font("body").family, ctx.idiom);
25499
25756
  kids = (arr, ctx) => Array.isArray(arr) ? arr.map((n, i) => /* @__PURE__ */ jsx6(React5.Fragment, { children: renderNode(n, ctx) }, n?.id ?? i)) : null;
25500
25757
  rowKids = (arr, ctx) => Array.isArray(arr) ? arr.map((n, i) => /* @__PURE__ */ jsx6(React5.Fragment, { children: renderNode(n, wantsRowStretch(n) ? { ...ctx, rowStretch: true } : ctx) }, n?.id ?? i)) : null;
25501
25758
  staggeredKids = (arr, ctx, staggerMs) => Array.isArray(arr) ? arr.map((n, i) => {
@@ -26983,7 +27240,7 @@ var init_renderNode = __esm({
26983
27240
  const tAlign = props.textAlign === "center" ? "center" : props.textAlign === "trailing" ? "right" : props.textAlign === "justify" ? "justify" : props.textAlign === "leading" ? "left" : void 0;
26984
27241
  const tDecor = props.decoration === "strikethrough" ? "line-through" : props.decoration === "underline" ? "underline" : void 0;
26985
27242
  const ln = typeof props.lineSpacing === "number" ? pxToRem((f.line ?? 1.4) * f.size + props.lineSpacing) : f.line;
26986
- return /* @__PURE__ */ jsx6("div", { ...countAttrs, style: { fontFamily: fontStack(f.family), fontSize: f.remSize, fontWeight: f.weight, lineHeight: ln, letterSpacing: f.letterSpacing, color: C(resolveScalarStatic(props.color, ctx) ?? "textPrimary", "#13171e"), whiteSpace: "pre-wrap", overflowWrap: "anywhere", wordBreak: "break-word", minWidth: 0, ...tAlign ? { textAlign: tAlign } : {}, ...tDecor ? { textDecoration: tDecor } : {}, ...props.maxLines ? { display: "-webkit-box", WebkitLineClamp: props.maxLines, WebkitBoxOrient: "vertical", overflow: "hidden" } : {}, ...wrap }, children: content == null || content === "" ? collapse ? null : "\xA0" : content });
27243
+ return /* @__PURE__ */ jsx6("div", { ...countAttrs, style: { fontFamily: fontStack(f.family, ctx.idiom), fontSize: f.remSize, fontWeight: f.weight, lineHeight: ln, letterSpacing: f.letterSpacing, color: C(resolveScalarStatic(props.color, ctx) ?? "textPrimary", "#13171e"), whiteSpace: "pre-wrap", overflowWrap: "anywhere", wordBreak: "break-word", minWidth: 0, ...tAlign ? { textAlign: tAlign } : {}, ...tDecor ? { textDecoration: tDecor } : {}, ...props.maxLines ? { display: "-webkit-box", WebkitLineClamp: props.maxLines, WebkitBoxOrient: "vertical", overflow: "hidden" } : {}, ...wrap }, children: content == null || content === "" ? collapse ? null : "\xA0" : content });
26987
27244
  });
26988
27245
  ImageNode = withExtras("ImageNode", ({ props, ctx, wrap }) => {
26989
27246
  const C = ctx.color;
@@ -27087,6 +27344,7 @@ var init_renderNode = __esm({
27087
27344
  disabled,
27088
27345
  "aria-label": accessibleLabel,
27089
27346
  "aria-busy": props.loading === true ? true : void 0,
27347
+ "data-himalaya-idiom-press": idiomPress(ctx, "button"),
27090
27348
  onClick,
27091
27349
  style: rowShareFloor({
27092
27350
  // reset first, so every token-driven value below still wins
@@ -27129,7 +27387,7 @@ var init_renderNode = __esm({
27129
27387
  gap: 8,
27130
27388
  padding: pad2,
27131
27389
  borderRadius: props.iconOnly ? "50%" : pillRadius,
27132
- fontFamily: fontStack(f.family),
27390
+ fontFamily: fontStack(f.family, ctx.idiom),
27133
27391
  fontWeight: f.weight,
27134
27392
  fontSize: f.remSize,
27135
27393
  background: primary ? accent : "transparent",
@@ -27196,8 +27454,9 @@ var init_renderNode = __esm({
27196
27454
  const presented = props.presentedPath != null ? bound : props.presented;
27197
27455
  if (gated && presented !== true && presented !== "true") return null;
27198
27456
  const grabber = props.showGrabber !== false;
27199
- return /* @__PURE__ */ jsx6("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,.35)", zIndex: 50, display: "flex", flexDirection: "column", justifyContent: "flex-end", ...wrap }, children: /* @__PURE__ */ jsxs3("div", { role: "dialog", "aria-modal": "true", "aria-label": props.title != null ? sheetTitle : "Sheet", style: { width: "100%", background: C("surfacePrimary", "#fff"), borderRadius: "16px 16px 0 0", boxShadow: "0 -4px 24px rgba(0,0,0,.12)", overflow: "hidden" }, children: [
27200
- grabber && /* @__PURE__ */ jsx6("div", { style: { display: "flex", justifyContent: "center", padding: "8px 0 4px" }, children: /* @__PURE__ */ jsx6("div", { style: { width: 36, height: 5, borderRadius: 999, background: C("surfaceTertiary", "#d8dce2") } }) }),
27457
+ const sheet = idiomControls(ctx.idiom).sheet;
27458
+ return /* @__PURE__ */ jsx6("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,.35)", zIndex: 50, display: "flex", flexDirection: "column", justifyContent: "flex-end", ...wrap }, children: /* @__PURE__ */ jsxs3("div", { role: "dialog", "aria-modal": "true", "aria-label": props.title != null ? sheetTitle : "Sheet", style: { width: "100%", background: C("surfacePrimary", "#fff"), borderRadius: `${sheet.cornerRadius}px ${sheet.cornerRadius}px 0 0`, boxShadow: "0 -4px 24px rgba(0,0,0,.12)", overflow: "hidden" }, children: [
27459
+ grabber && /* @__PURE__ */ jsx6("div", { style: { display: "flex", justifyContent: "center", padding: "8px 0 4px" }, children: /* @__PURE__ */ jsx6("div", { style: { width: sheet.grabberWidth, height: sheet.grabberHeight, borderRadius: 999, background: C("surfaceTertiary", "#d8dce2") } }) }),
27201
27460
  /* @__PURE__ */ jsx6("div", { style: { padding: "8px 16px 16px" }, children: props.content ? renderNode(props.content, ctx) : null })
27202
27461
  ] }) });
27203
27462
  });
@@ -27281,7 +27540,7 @@ var init_renderNode = __esm({
27281
27540
  const capF = ctx.font("caption");
27282
27541
  return /* @__PURE__ */ jsx6("div", { style: { display: "inline-flex", flexDirection: "column", alignItems: "center", gap: 4, ...wrap }, children: /* @__PURE__ */ jsxs3("div", { style: { position: "relative", width: 72, height: 72 }, children: [
27283
27542
  /* @__PURE__ */ jsx6("div", { style: { width: 72, height: 72, borderRadius: "50%", background: `conic-gradient(${tint} ${pct * 360}deg, ${track} 0deg)`, WebkitMask: ringMask, mask: ringMask, ...gaugeAnim } }),
27284
- /* @__PURE__ */ jsx6("div", { style: { position: "absolute", inset: 0, display: "grid", placeItems: "center", fontFamily: fontStack(capF.family), fontSize: capF.remSize, fontWeight: capF.weight, color: C("textPrimary", "#13171e") }, children: txt(props.label, ctx) || `${Math.round(pct * 100)}%` })
27543
+ /* @__PURE__ */ jsx6("div", { style: { position: "absolute", inset: 0, display: "grid", placeItems: "center", fontFamily: fontStack(capF.family, ctx.idiom), fontSize: capF.remSize, fontWeight: capF.weight, color: C("textPrimary", "#13171e") }, children: txt(props.label, ctx) || `${Math.round(pct * 100)}%` })
27285
27544
  ] }) });
27286
27545
  });
27287
27546
  RATING_METRICS = {
@@ -28141,7 +28400,7 @@ var init_renderNode = __esm({
28141
28400
  /* @__PURE__ */ jsxs3("div", { style: { position: "relative", height: Math.min(props.expandedHeight ?? 120, 160), borderRadius: ctx.radius("md", 10), overflow: "hidden", background: imageUrl ? void 0 : `linear-gradient(135deg, ${C("accentPrimary", "#116DFF")}, ${C("accentSecondary", "#0E5FE3")})` }, children: [
28142
28401
  imageUrl && /* @__PURE__ */ jsx6("img", { src: imageUrl, alt: "", style: { width: "100%", height: "100%", objectFit: "cover" } }),
28143
28402
  /* @__PURE__ */ jsxs3("div", { style: { position: "absolute", left: 14, bottom: 12, color: "#fff", textShadow: "0 1px 4px rgba(0,0,0,.5)" }, children: [
28144
- /* @__PURE__ */ jsx6("div", { style: { fontFamily: ctx.font("title1").family ? fontStack(ctx.font("title1").family) : '"Wix Madefor Display", system-ui', fontWeight: 700, fontSize: 20 }, children: txt(props.title, ctx) }),
28403
+ /* @__PURE__ */ jsx6("div", { style: { fontFamily: ctx.font("title1").family || ctx.idiom && ctx.idiom !== "base" ? fontStack(ctx.font("title1").family, ctx.idiom) : '"Wix Madefor Display", system-ui', fontWeight: 700, fontSize: 20 }, children: txt(props.title, ctx) }),
28145
28404
  props.subtitle && /* @__PURE__ */ jsx6("div", { style: { fontSize: 13, opacity: 0.9 }, children: txt(props.subtitle, ctx) })
28146
28405
  ] })
28147
28406
  ] }),
@@ -28759,7 +29018,8 @@ function renderDescriptorToHtml(payload, tokens, scheme = "light", provider = nu
28759
29018
  const ctx = makeCtx(tokens, scheme, {
28760
29019
  provider,
28761
29020
  ...typeof width === "number" && Number.isFinite(width) && width > 0 ? { sizeClass: sizeClassFromWidth(width), containerWidth: width } : {},
28762
- ...rtl ? { isRTL: true } : {}
29021
+ ...rtl ? { isRTL: true } : {},
29022
+ ...opts?.idiomContext ? { idiomContext: opts.idiomContext } : {}
28763
29023
  });
28764
29024
  const FOCUS_STYLE_MARKUP = `<style data-himalaya-focus-ring>${FOCUS_RING_CSS}</style>`;
28765
29025
  const withFocusRing = (markup) => markup ? FOCUS_STYLE_MARKUP + markup : markup;
@@ -28850,16 +29110,19 @@ __export(src_exports, {
28850
29110
  Bound: () => Bound,
28851
29111
  BoundList: () => BoundList,
28852
29112
  FakeServiceProvider: () => FakeServiceProvider,
29113
+ IDIOM_CONTROLS: () => IDIOM_CONTROLS,
28853
29114
  WebAppRegistry: () => WebAppRegistry,
28854
29115
  WebRenderer: () => WebRenderer,
28855
29116
  WebScreenRegistry: () => WebScreenRegistry,
28856
29117
  asText: () => asText2,
28857
29118
  avatarCircle: () => avatarCircle,
29119
+ controlIdiomFor: () => controlIdiomFor,
28858
29120
  controlText: () => controlText,
28859
29121
  fontStack: () => fontStack,
28860
29122
  hasIcon: () => hasIcon,
28861
29123
  iconGlyph: () => iconGlyph,
28862
29124
  iconSvg: () => iconSvg,
29125
+ idiomControls: () => idiomControls,
28863
29126
  isBinding: () => isBinding,
28864
29127
  isNodeLike: () => isNodeLike,
28865
29128
  isSerifFamily: () => isSerifFamily,
@@ -28877,6 +29140,7 @@ var init_src3 = __esm({
28877
29140
  init_renderNode();
28878
29141
  init_renderToHtml();
28879
29142
  init_tokens2();
29143
+ init_idiom_controls();
28880
29144
  init_modifiers();
28881
29145
  init_bindings2();
28882
29146
  init_FakeServiceProvider();
@@ -28894,6 +29158,7 @@ __export(render_descriptor_exports, {
28894
29158
  clientEnvFor: () => clientEnvFor,
28895
29159
  collectNativeOnly: () => collectNativeOnly,
28896
29160
  isMissingPlaywrightExecutable: () => isMissingPlaywrightExecutable,
29161
+ nodeHidden: () => nodeHidden,
28897
29162
  pngAvailable: () => pngAvailable,
28898
29163
  pngPageHtml: () => pngPageHtml,
28899
29164
  renderDescriptorWeb: () => renderDescriptorWeb,
@@ -29082,7 +29347,7 @@ function composeStickyTree(payload, header, footer) {
29082
29347
  if (footer) children.push(footer);
29083
29348
  return { id: "screen", type: "ScreenChrome", children };
29084
29349
  }
29085
- async function renderDescriptorWeb(descriptor, tokens, scheme, wantPng, services = null, width, isRTL) {
29350
+ async function renderDescriptorWeb(descriptor, tokens, scheme, wantPng, services = null, width, isRTL, idiomContext) {
29086
29351
  const desc = descriptor;
29087
29352
  const payload = desc.payload;
29088
29353
  if (!payload || typeof payload !== "object" || !payload.body) {
@@ -29117,7 +29382,7 @@ async function renderDescriptorWeb(descriptor, tokens, scheme, wantPng, services
29117
29382
  provider,
29118
29383
  width,
29119
29384
  stickyHeader || stickyFooter ? { stickyHeader, stickyFooter } : void 0,
29120
- isRTL ? { isRTL: true } : void 0
29385
+ isRTL || idiomContext ? { ...isRTL ? { isRTL: true } : {}, ...idiomContext ? { idiomContext } : {} } : void 0
29121
29386
  );
29122
29387
  } catch (err) {
29123
29388
  return {
@@ -29131,7 +29396,7 @@ async function renderDescriptorWeb(descriptor, tokens, scheme, wantPng, services
29131
29396
  const nativeOnly = collectNativeOnly(renderTree);
29132
29397
  if (nativeOnly.length) result.nativeOnly = nativeOnly;
29133
29398
  if (wantPng) {
29134
- const png = await renderPng(html, scheme, width, isRTL, tokens);
29399
+ const png = await renderPng(html, scheme, width, isRTL, tokens, idiomContext);
29135
29400
  if (png) result.pngBase64 = png;
29136
29401
  else result.pngUnavailable = true;
29137
29402
  }
@@ -29148,8 +29413,8 @@ async function withRetry2(op, attempts = 2) {
29148
29413
  }
29149
29414
  throw lastErr;
29150
29415
  }
29151
- async function renderPng(html, scheme, width, isRTL, tokens) {
29152
- const out = await renderPngBatch([{ html, scheme, width, isRTL, tokens }]);
29416
+ async function renderPng(html, scheme, width, isRTL, tokens, idiomContext) {
29417
+ const out = await renderPngBatch([{ html, scheme, width, isRTL, tokens, idiomContext }]);
29153
29418
  return out?.[0] ?? null;
29154
29419
  }
29155
29420
  function setPngFontCss(source) {
@@ -29160,7 +29425,7 @@ function setPngFontCss(source) {
29160
29425
  async function pngPageHtml(item) {
29161
29426
  let faces = "";
29162
29427
  try {
29163
- faces = await pngFontCss?.(item.tokens ?? null) ?? "";
29428
+ faces = await pngFontCss?.(item.tokens ?? null, item.idiomContext) ?? "";
29164
29429
  } catch {
29165
29430
  faces = "";
29166
29431
  }
@@ -29358,6 +29623,17 @@ async function pngFontFaceCss(tokens, opts = {}) {
29358
29623
  });
29359
29624
  return rules.join("");
29360
29625
  }
29626
+ async function idiomFontFaceCss(idiomContext) {
29627
+ const { controlIdiomFor: controlIdiomFor2, idiomControls: idiomControls2 } = await Promise.resolve().then(() => (init_src3(), src_exports));
29628
+ const bundled = idiomControls2(controlIdiomFor2(idiomContext)).font.bundled;
29629
+ if (!bundled) return "";
29630
+ try {
29631
+ const bytes2 = readFileSync34(new URL(`../../web-host/public/${bundled.file}`, import.meta.url));
29632
+ return `@font-face { font-family: "${bundled.family}"; font-weight: 100 900; font-style: normal; src: url(data:font/ttf;base64,${bytes2.toString("base64")}) format("truetype"); }`;
29633
+ } catch {
29634
+ return "";
29635
+ }
29636
+ }
29361
29637
  var encoded, cssString, FORMATS;
29362
29638
  var init_render_fonts = __esm({
29363
29639
  "../../core/dev-server/src/render-fonts.ts"() {
@@ -32762,7 +33038,7 @@ async function handleWebWix(req, res, state) {
32762
33038
  const method = String(b.method ?? "GET").toUpperCase();
32763
33039
  const requestedPath = String(b.path ?? "/");
32764
33040
  const body = b.body === void 0 ? void 0 : typeof b.body === "string" ? b.body : JSON.stringify(b.body);
32765
- const forwardedHeaders = normalizeWixForwardedHeaders(b.headers);
33041
+ const forwardedHeaders = normalizeWixForwardedHeaders(b.headers, requestedPath);
32766
33042
  const target = new URL(requestedPath, "https://www.wixapis.com");
32767
33043
  const query = Object.fromEntries(target.searchParams.entries());
32768
33044
  const started = Date.now();
@@ -32902,15 +33178,25 @@ async function handleDev(req, res, state) {
32902
33178
  }
32903
33179
  const screenId = descriptor.screenId ?? body.screenId ?? "render";
32904
33180
  const diagnostics = await runScreenDiagnostics(state, screenId, descriptor);
32905
- const appTokens = readAppTokens(state);
33181
+ let idiomContext;
33182
+ if (body.platform !== void 0) {
33183
+ if (body.platform !== "ios" && body.platform !== "android") {
33184
+ return sendJson(res, 400, { error: "invalid_platform", allowed: ["ios", "android"] });
33185
+ }
33186
+ idiomContext = { ...IDIOM_CONTEXTS[body.platform], surface: "phone" };
33187
+ }
33188
+ const baseTokens = readAppTokens(state);
33189
+ const policy = state.app?.module.config && "idiom" in state.app.module.config ? state.app.module.config.idiom : void 0;
33190
+ const stamped = idiomContext && baseTokens && policy !== void 0 ? { ...baseTokens, idiomPolicy: policy } : baseTokens;
32906
33191
  const render = await renderDescriptorWeb(
32907
33192
  descriptor,
32908
- appTokens && state.app ? withServedFontFaces(appTokens, state.app.dir) : appTokens,
33193
+ stamped && state.app ? withServedFontFaces(stamped, state.app.dir) : stamped,
32909
33194
  scheme,
32910
33195
  body.png === true,
32911
33196
  body.services ?? null,
32912
33197
  width,
32913
- body.rtl === true
33198
+ body.rtl === true,
33199
+ idiomContext
32914
33200
  );
32915
33201
  const legacy = { screenId, scheme, ...render, diagnostics };
32916
33202
  const report2 = foldLegacyReport(legacy, {
@@ -34368,6 +34654,7 @@ var init_src6 = __esm({
34368
34654
  init_inject();
34369
34655
  init_face_source();
34370
34656
  init_himi_report();
34657
+ init_idiom();
34371
34658
  init_store2();
34372
34659
  init_derive_transport_capability();
34373
34660
  init_wix_gateway();
@@ -34400,7 +34687,7 @@ var init_src6 = __esm({
34400
34687
  init_kits();
34401
34688
  init_widgets();
34402
34689
  ROOT6 = resolve30(fileURLToPath18(new URL("../../..", import.meta.url)));
34403
- setPngFontCss((tokens) => pngFontFaceCss(tokens));
34690
+ setPngFontCss(async (tokens, idiomContext) => await pngFontFaceCss(tokens) + (idiomContext ? await idiomFontFaceCss(idiomContext) : ""));
34404
34691
  TIER3_ROOT = resolve30(ROOT6, "core/dev-server/web");
34405
34692
  TIER4_ROOT = resolve30(ROOT6, "core/dev-server/tier4-bundles");
34406
34693
  tier4Verifier = new TierFourVerifier(TIER4_ROOT);
@@ -35064,6 +35351,7 @@ __export(screen_crawl_exports, {
35064
35351
  netTransport: () => netTransport,
35065
35352
  parseWorkerAction: () => parseWorkerAction,
35066
35353
  recordScreenTraffic: () => recordScreenTraffic,
35354
+ renderedNameGaps: () => renderedNameGaps,
35067
35355
  rootOnAppear: () => rootOnAppear,
35068
35356
  snapshot: () => snapshot,
35069
35357
  structuralSignature: () => structuralSignature,
@@ -35493,6 +35781,185 @@ function scanTreeValues(node, hit) {
35493
35781
  }
35494
35782
  node.children?.forEach((child) => scanTreeValues(child, hit));
35495
35783
  }
35784
+ function joinPath(base, rel) {
35785
+ if (!base) return rel;
35786
+ if (!rel) return base;
35787
+ return rel.startsWith("[") ? `${base}${rel}` : `${base}.${rel}`;
35788
+ }
35789
+ function withinPath(p, root) {
35790
+ return !root || p === root || p.startsWith(`${root}.`) || p.startsWith(`${root}[`);
35791
+ }
35792
+ function resolveNameBinding(v, ctx) {
35793
+ if (v.startsWith("$service:")) return ctx.provider.value(v);
35794
+ if (v.startsWith("$item") && ctx.scope) {
35795
+ const key2 = v.replace(/^\$item\.?/, "");
35796
+ return key2 ? pathValue(ctx.scope, key2) : void 0;
35797
+ }
35798
+ return void 0;
35799
+ }
35800
+ function collapsesWhenEmpty(n, path2, ctx) {
35801
+ const wire = !!n.body && typeof n.body === "object" && !Array.isArray(n.body);
35802
+ const body = wire ? n.body : n;
35803
+ if (body.type !== "Text") return false;
35804
+ const props = body.props;
35805
+ if (!props || props.collapseWhenEmpty !== true) return false;
35806
+ const raw = props.text;
35807
+ const bound = typeof raw === "string" && (raw.startsWith("$service:") || raw.startsWith("$item"));
35808
+ if (bound && raw.startsWith("$item") && !ctx.scope) return false;
35809
+ const eff = bound ? resolveNameBinding(raw, ctx) : raw;
35810
+ const s = eff == null ? "" : String(eff).trim();
35811
+ if (s !== "" && s !== "\u2014") return false;
35812
+ if (bound && typeof eff === "string" && s === "") {
35813
+ ctx.emptied.push({ at: joinPath(path2, wire ? "body.props" : "props"), field: "text", binding: raw });
35814
+ }
35815
+ return true;
35816
+ }
35817
+ function resolveGateValue(raw, ctx) {
35818
+ return typeof raw === "string" && (raw.startsWith("$service:") || raw.startsWith("$item")) ? resolveNameBinding(raw, ctx) : raw;
35819
+ }
35820
+ function sheetNotPresented(n, ctx) {
35821
+ const body = n.body && typeof n.body === "object" && !Array.isArray(n.body) ? n.body : n;
35822
+ if (body.type !== "BottomSheet") return false;
35823
+ const props = body.props;
35824
+ if (!props || props.presentedPath == null && props.presented == null) return false;
35825
+ const presented = resolveGateValue(props.presentedPath != null ? props.presentedPath : props.presented, ctx);
35826
+ return presented !== true && presented !== "true";
35827
+ }
35828
+ function selectedFragmentCase(props, ctx) {
35829
+ const cases = Array.isArray(props.cases) ? props.cases : [];
35830
+ if (!cases.length) return null;
35831
+ const casePath = props.casePath ?? props.case_path;
35832
+ if (typeof casePath === "string" && casePath.startsWith("$") && !casePath.startsWith("$service:") && !casePath.startsWith("$item")) return null;
35833
+ const disc = resolveGateValue(casePath, ctx);
35834
+ const wanted = typeof disc === "string" ? disc : void 0;
35835
+ const idOf = (c) => c && typeof c === "object" ? c.id : void 0;
35836
+ let i = cases.findIndex((c) => idOf(c) === wanted);
35837
+ if (i < 0) {
35838
+ const fallback = props.defaultCase ?? props.default_case;
35839
+ i = cases.findIndex((c) => idOf(c) === fallback);
35840
+ }
35841
+ return i < 0 ? 0 : i;
35842
+ }
35843
+ function projectRenderedNames(node, path2, key2, ctx) {
35844
+ if (!node || typeof node !== "object") return node;
35845
+ if (Array.isArray(node)) return node.map((v, i) => projectRenderedNames(v, `${path2}[${i}]`, key2, ctx));
35846
+ if (isActionEnvelope(node) || key2 === "args") return node;
35847
+ const n = node;
35848
+ const rawMods = n.modifiers;
35849
+ const mods = rawMods && typeof rawMods === "object" && !Array.isArray(rawMods) ? rawMods : {};
35850
+ if (mods.accessibilityHidden === true) return node;
35851
+ if (ctx.unrendered.has(path2) || nodeHidden(mods, ctx.provider, ctx.scope, null) || collapsesWhenEmpty(n, path2, ctx) || sheetNotPresented(n, ctx)) {
35852
+ return { ...n, modifiers: { ...mods, accessibilityHidden: true } };
35853
+ }
35854
+ const props = n.props && typeof n.props === "object" && !Array.isArray(n.props) ? n.props : null;
35855
+ if (props && n.type === "FragmentSlot") {
35856
+ const selected = selectedFragmentCase(props, ctx);
35857
+ if (selected !== null) {
35858
+ props.cases.forEach((_, i) => {
35859
+ if (i !== selected) ctx.unrendered.add(joinPath(path2, `props.cases[${i}]`));
35860
+ });
35861
+ }
35862
+ }
35863
+ let boundTemplate = false;
35864
+ if (props && (n.type === "LazyList" || n.type === "Grid")) {
35865
+ const itemsPath = props.itemsPath ?? props.items_path;
35866
+ const template = Array.isArray(props.items) ? props.items[0] : void 0;
35867
+ if (typeof itemsPath === "string" && template !== void 0) {
35868
+ boundTemplate = true;
35869
+ const local = itemsPath.startsWith("$item.");
35870
+ const rows = local ? ctx.scope ? pathValue(ctx.scope, itemsPath.slice("$item.".length)) : void 0 : itemsPath.startsWith("$service:") ? ctx.provider.value(itemsPath) : void 0;
35871
+ if (Array.isArray(rows) || local) {
35872
+ const paintsRows = Array.isArray(rows) && rows.length > 0;
35873
+ props.items.forEach((_, i) => {
35874
+ const at = joinPath(path2, `props.items[${i}]`);
35875
+ if (i === 0 && paintsRows) ctx.rowTemplates.push(at);
35876
+ else ctx.unrendered.add(at);
35877
+ });
35878
+ }
35879
+ if (Array.isArray(rows) && rows.length) {
35880
+ ctx.lists.push({
35881
+ templatePath: joinPath(path2, "props.items[0]"),
35882
+ template,
35883
+ rows: rows.slice(0, A11Y_ROWS_PER_LIST)
35884
+ });
35885
+ }
35886
+ }
35887
+ }
35888
+ const out = {};
35889
+ for (const [k, v] of Object.entries(n)) {
35890
+ if (NAME_KEYS.has(k) && typeof v === "string" && (v.startsWith("$service:") || v.startsWith("$item"))) {
35891
+ const r = resolveNameBinding(v, ctx);
35892
+ if (typeof r === "string") {
35893
+ if (r.trim() === "") ctx.emptied.push({ at: path2, field: k, binding: v });
35894
+ out[k] = r;
35895
+ } else if (typeof r === "number" || typeof r === "boolean") {
35896
+ out[k] = String(r);
35897
+ } else {
35898
+ out[k] = v;
35899
+ }
35900
+ continue;
35901
+ }
35902
+ if (k === "props" && boundTemplate && props) {
35903
+ const p = {};
35904
+ for (const [pk, pv] of Object.entries(props)) {
35905
+ p[pk] = projectRenderedNames(
35906
+ pv,
35907
+ joinPath(path2, `props.${pk}`),
35908
+ pk,
35909
+ pk === "items" ? { ...ctx, scope: void 0 } : ctx
35910
+ );
35911
+ }
35912
+ out[k] = p;
35913
+ continue;
35914
+ }
35915
+ out[k] = projectRenderedNames(v, joinPath(path2, k), k, ctx);
35916
+ }
35917
+ return out;
35918
+ }
35919
+ function renderedNameGaps(descriptor, state) {
35920
+ const provider = new FakeServiceProvider(state);
35921
+ const found = /* @__PURE__ */ new Map();
35922
+ const rowsJudged = /* @__PURE__ */ new Map();
35923
+ const rowTemplateOf = /* @__PURE__ */ new Map();
35924
+ const judge = (root, base, scope, depth, inRow) => {
35925
+ if (inRow) rowsJudged.set(base, (rowsJudged.get(base) ?? 0) + 1);
35926
+ const ctx = { provider, scope, emptied: [], lists: [], unrendered: /* @__PURE__ */ new Set(), rowTemplates: [] };
35927
+ const projected = projectRenderedNames(root, "", "", ctx);
35928
+ for (const g of accessibleNameGaps(projected)) {
35929
+ const rel = g.at === "root" ? "" : g.at;
35930
+ const at = joinPath(base, rel) || "root";
35931
+ if (g.reason !== "interactive" && !CONTROL_ROLES.has(g.role)) continue;
35932
+ if (ctx.rowTemplates.some((t) => withinPath(rel, t))) continue;
35933
+ const cause = ctx.emptied.find((e) => withinPath(e.at, rel));
35934
+ if (!cause) continue;
35935
+ const prev = found.get(at);
35936
+ if (prev) {
35937
+ if (prev.rows && inRow) prev.rows.hit += 1;
35938
+ continue;
35939
+ }
35940
+ if (inRow) rowTemplateOf.set(at, base);
35941
+ found.set(at, {
35942
+ gap: { ...g, at },
35943
+ binding: cause.binding,
35944
+ field: cause.field,
35945
+ ...inRow ? { rows: { hit: 1, of: 0 } } : {}
35946
+ });
35947
+ }
35948
+ if (depth >= A11Y_LIST_DEPTH) return;
35949
+ for (const list of ctx.lists) {
35950
+ list.rows.forEach((el, i) => {
35951
+ const rowScope = el && typeof el === "object" && !Array.isArray(el) ? { ...el, __index: i } : { value: el, __index: i };
35952
+ judge(list.template, joinPath(base, list.templatePath), rowScope, depth + 1, true);
35953
+ });
35954
+ }
35955
+ };
35956
+ judge(descriptor, "", void 0, 0, false);
35957
+ for (const [at, template] of rowTemplateOf) {
35958
+ const rows = found.get(at)?.rows;
35959
+ if (rows) rows.of = rowsJudged.get(template) ?? rows.hit;
35960
+ }
35961
+ return [...found.values()];
35962
+ }
35496
35963
  function collectObservations(descriptorOrPayload, state) {
35497
35964
  const out = [];
35498
35965
  const walk2 = (node) => {
@@ -35922,6 +36389,21 @@ async function crawlScreenInner(opts) {
35922
36389
  });
35923
36390
  });
35924
36391
  }
36392
+ for (const r of renderedNameGaps(descriptor, state)) {
36393
+ findings.push({
36394
+ screen: screenId,
36395
+ trigger,
36396
+ invariant: "unnamed-control",
36397
+ severity: "error",
36398
+ // Stable across states on purpose: the row count lives in `actual`, so the same control
36399
+ // seen empty after boot and after a tap dedupes to one finding.
36400
+ message: `${r.gap.type} renders no accessible name: its only name, ${r.binding}, resolved to "" \u2014 a screen reader announces an unnamed \`${r.gap.role}\``,
36401
+ at: r.gap.at,
36402
+ field: r.field,
36403
+ actual: r.rows ? `"" on ${r.rows.hit} of ${r.rows.of} row(s)` : '""',
36404
+ hint: "`himi validate` counts a bound name as a name \u2014 it becomes copy on device \u2014 so only the crawl can see it come out empty. Give the control a name that cannot be empty: a literal `modifiers.accessibilityLabel` (an icon-only control can use `Icon.contentDescription`), or have the worker fall back to real copy instead of an empty string"
36405
+ });
36406
+ }
35925
36407
  for (const ref of collectServiceBindings(descriptor)) {
35926
36408
  if (!(ref.svc in state)) {
35927
36409
  findings.push({
@@ -36703,11 +37185,12 @@ async function crawlScreenRelations(opts) {
36703
37185
  return { findings: dedupe(findings), checked, byKind };
36704
37186
  });
36705
37187
  }
36706
- var ACTION_KEYS, UNTRACKED_ITEMS, NESTED_ITEMS_SEP, REJECT_ALL_MESSAGE, VLQ, WIX_DATA_QUERY, WIX_DATA_ANY, CAPABILITY_ACTION, detached, CRAWL_CLOCK, MEMBER_IDENTITY_ACTION, BLANK_WHEN_BOUND, STALE_SNAPSHOT_HINT, UNBINDABLE_ROW;
37188
+ var ACTION_KEYS, UNTRACKED_ITEMS, NESTED_ITEMS_SEP, REJECT_ALL_MESSAGE, VLQ, WIX_DATA_QUERY, WIX_DATA_ANY, CAPABILITY_ACTION, detached, CRAWL_CLOCK, MEMBER_IDENTITY_ACTION, BLANK_WHEN_BOUND, CONTROL_ROLES, NAME_KEYS, A11Y_ROWS_PER_LIST, A11Y_LIST_DEPTH, STALE_SNAPSHOT_HINT, UNBINDABLE_ROW;
36707
37189
  var init_screen_crawl = __esm({
36708
37190
  "../../core/dev-server/src/screen-crawl.ts"() {
36709
37191
  init_validate_descriptor_core();
36710
37192
  init_render_descriptor();
37193
+ init_a11y_contract();
36711
37194
  init_net_scenario();
36712
37195
  init_media_value();
36713
37196
  init_FakeServiceProvider();
@@ -36743,6 +37226,24 @@ var init_screen_crawl = __esm({
36743
37226
  Marquee: ["items", "itemsPath", "text"],
36744
37227
  MediaPlayer: ["items", "itemsPath"]
36745
37228
  };
37229
+ CONTROL_ROLES = /* @__PURE__ */ new Set([
37230
+ "button",
37231
+ "link",
37232
+ "toggle",
37233
+ "checkbox",
37234
+ "radio",
37235
+ "tab",
37236
+ "tablist",
37237
+ "adjustable",
37238
+ "search",
37239
+ "textfield",
37240
+ "combobox",
37241
+ "menu",
37242
+ "menuitem"
37243
+ ]);
37244
+ NAME_KEYS = new Set(ACCESSIBLE_NAME_KEYS);
37245
+ A11Y_ROWS_PER_LIST = 20;
37246
+ A11Y_LIST_DEPTH = 3;
36746
37247
  STALE_SNAPSHOT_HINT = 'if every path already does, look for a stale-snapshot write-back: `return s` (or `{ ...s }`) after an await or from an early-return guard writes back the state as it was BEFORE the await, loading flag included \u2014 to change nothing, return `undefined` instead (not `null` or `{}`); see `himi docs tier-5-add-a-screen`, "Should this result be committed at all?"';
36747
37248
  UNBINDABLE_ROW = Symbol("unbindable-row");
36748
37249
  }
@@ -46103,6 +46604,16 @@ function assembleLedger(r, options = {}) {
46103
46604
  remedy: "renders on device only \u2014 himi push a preview and check by eye"
46104
46605
  });
46105
46606
  }
46607
+ if ((r.crawled ?? []).length) {
46608
+ const found = [...r.failures, ...r.warnings].filter((f) => f.invariant === "unnamed-control").length;
46609
+ out.push({
46610
+ key: "a11y|app",
46611
+ kind: "a11y",
46612
+ screen: null,
46613
+ gap: `accessibility is judged for ONE property only \u2014 an operable control whose bound name rendered empty (\`unnamed-control\`, ${found} this run). NOT judged: names authored statically in the descriptor, role/state announcement, focus order, contrast, touch-target size \u2014 a clean run is not an accessibility pass`,
46614
+ remedy: "run `himi validate` for statically authored names (its a11y pass), then `himi push` a preview and walk every screen with VoiceOver and TalkBack"
46615
+ });
46616
+ }
46106
46617
  if (!/(^|\+ )mocked/.test(r.transport) && !r.transport.startsWith("offline")) {
46107
46618
  out.push({
46108
46619
  key: "no-user-mocks|app",
@@ -48067,19 +48578,21 @@ var init_preview_assets_generated = __esm({
48067
48578
  "../../packages/serve/src/preview-assets.generated.ts"() {
48068
48579
  init_preview_assets_format();
48069
48580
  PREVIEW_ASSET_MANIFEST = [
48070
- { key: "assets/index-Bsi3iDy5.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1018562, sha256: "8487d8635f18c5a32f124ebc217f0570d5b8242ac964de661fa0644b80843116" },
48071
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1018570, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
48072
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1109494, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
48073
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1200478, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
48074
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1291458, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
48075
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1382330, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
48076
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 1473310, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
48077
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 1581946, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
48078
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 1691030, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
48079
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 1800078, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
48080
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 1907778, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
48081
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 1995886, length: 4448, sha256: "60540583e7c95a5564940f1f05510886f0f59a6ec612af78881031a5e9c04b9a" },
48082
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2000334, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
48581
+ { key: "assets/index-DcmBzFl6.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1030965, sha256: "e587f0db5f5d239871c73ee0976d387d6372768a239d04597e263f3c5bb0f395" },
48582
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1030973, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
48583
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1035583, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
48584
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1524167, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
48585
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1615091, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
48586
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1706075, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
48587
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1797055, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
48588
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1887927, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
48589
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 1978907, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
48590
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2087543, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
48591
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2196627, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
48592
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2305675, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
48593
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2413375, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
48594
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2501483, length: 4947, sha256: "551ec6b86f9e791e00d12beddd0480448ef4ff37fa33d4c6529b21e91406a753" },
48595
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2506430, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
48083
48596
  ];
48084
48597
  }
48085
48598
  });