uivisor 0.2.1 → 0.2.2

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 (2) hide show
  1. package/dist/overlay/index.js +316 -148
  2. package/package.json +1 -1
@@ -255,8 +255,8 @@ function suggestUtility(property, value) {
255
255
  return `${sp}-[${n}px]`;
256
256
  }
257
257
  if (property === "line-height") {
258
- const t = value.trim();
259
- const unitless = /^(\d*\.?\d+)$/.exec(t);
258
+ const t2 = value.trim();
259
+ const unitless = /^(\d*\.?\d+)$/.exec(t2);
260
260
  if (unitless) {
261
261
  const named = {
262
262
  "1": "leading-none",
@@ -266,16 +266,16 @@ function suggestUtility(property, value) {
266
266
  "1.625": "leading-relaxed",
267
267
  "2": "leading-loose"
268
268
  };
269
- return named[String(parseFloat(unitless[1]))] ?? `leading-[${t}]`;
269
+ return named[String(parseFloat(unitless[1]))] ?? `leading-[${t2}]`;
270
270
  }
271
- const n = px(t);
271
+ const n = px(t2);
272
272
  if (n != null) return `leading-[${n}px]`;
273
- return `leading-[${t}]`;
273
+ return `leading-[${t2}]`;
274
274
  }
275
275
  if (property === "letter-spacing") {
276
- const t = value.trim();
277
- if (t === "normal" || t === "0" || t === "0px" || t === "0em") return "tracking-normal";
278
- const em = /^(-?\d*\.?\d+)em$/.exec(t);
276
+ const t2 = value.trim();
277
+ if (t2 === "normal" || t2 === "0" || t2 === "0px" || t2 === "0em") return "tracking-normal";
278
+ const em = /^(-?\d*\.?\d+)em$/.exec(t2);
279
279
  if (em) {
280
280
  const named = {
281
281
  "-0.05": "tracking-tighter",
@@ -284,9 +284,9 @@ function suggestUtility(property, value) {
284
284
  "0.05": "tracking-wider",
285
285
  "0.1": "tracking-widest"
286
286
  };
287
- return named[String(parseFloat(em[1]))] ?? `tracking-[${t}]`;
287
+ return named[String(parseFloat(em[1]))] ?? `tracking-[${t2}]`;
288
288
  }
289
- return `tracking-[${t}]`;
289
+ return `tracking-[${t2}]`;
290
290
  }
291
291
  if (property === "color" || property === "background-color") {
292
292
  const hex = rgbToHex(value) ?? (value.startsWith("#") ? value : null);
@@ -340,11 +340,11 @@ function suggestUtility(property, value) {
340
340
  };
341
341
  if (SIZE_PREFIX[property]) {
342
342
  const pre = SIZE_PREFIX[property];
343
- const t = value.trim();
344
- if (t === "auto") return `${pre}-auto`;
345
- if (t === "100%") return `${pre}-full`;
346
- const n = px(t);
347
- if (n == null) return `${pre}-[${t}]`;
343
+ const t2 = value.trim();
344
+ if (t2 === "auto") return `${pre}-auto`;
345
+ if (t2 === "100%") return `${pre}-full`;
346
+ const n = px(t2);
347
+ if (n == null) return `${pre}-[${t2}]`;
348
348
  const scale = n / 4;
349
349
  if (Number.isInteger(scale) && scale >= 0 && scale <= 96) return `${pre}-${scale}`;
350
350
  return `${pre}-[${n}px]`;
@@ -424,6 +424,8 @@ var SECTIONS = [
424
424
  label: "Display",
425
425
  options: ["block", "inline-block", "inline", "flex", "inline-flex", "grid", "inline-grid", "none"]
426
426
  },
427
+ // Justify / Align are handled by the bigger visual button-rows injected right
428
+ // below Display (flexControlsHtml) — not duplicated here as plain selects.
427
429
  {
428
430
  kind: "select",
429
431
  css: "flex-direction",
@@ -431,20 +433,6 @@ var SECTIONS = [
431
433
  options: ["row", "row-reverse", "column", "column-reverse"],
432
434
  requires: "flexgrid"
433
435
  },
434
- {
435
- kind: "select",
436
- css: "justify-content",
437
- label: "Justify",
438
- options: ["normal", "flex-start", "center", "flex-end", "space-between", "space-around", "space-evenly"],
439
- requires: "flexgrid"
440
- },
441
- {
442
- kind: "select",
443
- css: "align-items",
444
- label: "Align",
445
- options: ["normal", "stretch", "flex-start", "center", "flex-end", "baseline"],
446
- requires: "flexgrid"
447
- },
448
436
  {
449
437
  kind: "select",
450
438
  css: "flex-wrap",
@@ -666,7 +654,7 @@ function buildDesignSystem(vars, rootPx = 16) {
666
654
  });
667
655
  }
668
656
  const byCategory = {};
669
- for (const t of tokens) (byCategory[t.category] ||= []).push(t);
657
+ for (const t2 of tokens) (byCategory[t2.category] ||= []).push(t2);
670
658
  for (const cat of Object.keys(byCategory)) {
671
659
  byCategory[cat].sort(
672
660
  (a, b) => a.px != null && b.px != null ? a.px - b.px : a.name.localeCompare(b.name)
@@ -679,22 +667,22 @@ function nearestToken(ds, category, target) {
679
667
  if (!list || !list.length) return null;
680
668
  if (target.value != null) {
681
669
  const norm = (s) => {
682
- const t = s.trim();
683
- return (rgbToHex(t) || t).replace(/\s+/g, "").toLowerCase();
670
+ const t2 = s.trim();
671
+ return (rgbToHex(t2) || t2).replace(/\s+/g, "").toLowerCase();
684
672
  };
685
673
  const want = norm(target.value);
686
- const hit = list.find((t) => norm(t.value) === want);
674
+ const hit = list.find((t2) => norm(t2.value) === want);
687
675
  if (hit) return { token: hit, exact: true };
688
676
  }
689
- if (target.px != null && list.some((t) => t.px != null)) {
677
+ if (target.px != null && list.some((t2) => t2.px != null)) {
690
678
  let best = list[0];
691
679
  let bestD = Infinity;
692
- for (const t of list) {
693
- if (t.px == null) continue;
694
- const d = Math.abs(t.px - target.px);
680
+ for (const t2 of list) {
681
+ if (t2.px == null) continue;
682
+ const d = Math.abs(t2.px - target.px);
695
683
  if (d < bestD) {
696
684
  bestD = d;
697
- best = t;
685
+ best = t2;
698
686
  }
699
687
  }
700
688
  return { token: best, exact: bestD < 0.5 };
@@ -805,6 +793,115 @@ function mk(primaryMechanism, evidence, sourceClassNames) {
805
793
  return { primaryMechanism, evidence, sourceClassNames };
806
794
  }
807
795
 
796
+ // src/overlay/i18n.ts
797
+ var RU = {
798
+ // chrome / buttons
799
+ "Toggle uivisor (Alt+U)": "\u0412\u043A\u043B/\u0432\u044B\u043A\u043B uivisor (Alt+U)",
800
+ "Turn uivisor off (Alt+U)": "\u0412\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C uivisor (Alt+U)",
801
+ Close: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C",
802
+ "Drag to resize": "\u041F\u043E\u0442\u044F\u043D\u0438\u0442\u0435, \u0447\u0442\u043E\u0431\u044B \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0448\u0438\u0440\u0438\u043D\u0443",
803
+ "Drag to change": "\u041F\u043E\u0442\u044F\u043D\u0438\u0442\u0435, \u0447\u0442\u043E\u0431\u044B \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C",
804
+ "Copy prompt for agent": "\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043F\u0440\u043E\u043C\u043F\u0442 \u0434\u043B\u044F \u0430\u0433\u0435\u043D\u0442\u0430",
805
+ "Copy JSON": "\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C JSON",
806
+ Reset: "\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C",
807
+ "Revert tweaks on selected element": "\u041E\u0442\u043A\u0430\u0442\u0438\u0442\u044C \u043F\u0440\u0430\u0432\u043A\u0438 \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u043E\u0433\u043E \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u0430",
808
+ Clear: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C",
809
+ "Clear all": "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0432\u0441\u0451",
810
+ Unit: "\u0415\u0434\u0438\u043D\u0438\u0446\u0430",
811
+ Token: "\u0422\u043E\u043A\u0435\u043D",
812
+ "pick token": "\u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u043E\u043A\u0435\u043D",
813
+ Mixed: "\u0440\u0430\u0437\u043D\u044B\u0435",
814
+ "Edit each side individually": "\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u0436\u0434\u0443\u044E \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E",
815
+ // apply-changes-to
816
+ "Apply changes to": "\u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u043A",
817
+ "This element": "\u042D\u0442\u043E\u043C\u0443 \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u0443",
818
+ "Only this one": "\u0422\u043E\u043B\u044C\u043A\u043E \u044D\u0442\u043E\u043C\u0443",
819
+ "All {n} like this": "\u0412\u0441\u0435\u043C \u0442\u0430\u043A\u0438\u043C ({n})",
820
+ "New class\u2026": "\u041D\u043E\u0432\u044B\u0439 \u043A\u043B\u0430\u0441\u0441\u2026",
821
+ "new class name": "\u0438\u043C\u044F \u043D\u043E\u0432\u043E\u0433\u043E \u043A\u043B\u0430\u0441\u0441\u0430",
822
+ "Create a new class instead of touching the existing ones": "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043D\u043E\u0432\u044B\u0439 \u043A\u043B\u0430\u0441\u0441 \u0432\u043C\u0435\u0441\u0442\u043E \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0445",
823
+ // legend
824
+ file: "\u0438\u0437 \u0444\u0430\u0439\u043B\u0430",
825
+ edited: "\u0438\u0437\u043C\u0435\u043D\u0435\u043D\u043E",
826
+ inherited: "\u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F",
827
+ auto: "\u0430\u0432\u0442\u043E",
828
+ // empty / hint
829
+ "Click any element in the frame to select it.": "\u041A\u043B\u0438\u043A\u043D\u0438\u0442\u0435 \u043F\u043E \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u0443 \u0432\u043E \u0444\u0440\u0435\u0439\u043C\u0435, \u0447\u0442\u043E\u0431\u044B \u0432\u044B\u0431\u0440\u0430\u0442\u044C \u0435\u0433\u043E.",
830
+ "Click any element on the page to select it.": "\u041A\u043B\u0438\u043A\u043D\u0438\u0442\u0435 \u043F\u043E \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u0443 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435, \u0447\u0442\u043E\u0431\u044B \u0432\u044B\u0431\u0440\u0430\u0442\u044C \u0435\u0433\u043E.",
831
+ "Alt+U toggles \xB7 Esc deselects \xB7 \u2318/Ctrl+Z undo, \u21E7 to redo. Tweaks stay in the browser \u2014 nothing is written to your code.": "Alt+U \u2014 \u0432\u043A\u043B/\u0432\u044B\u043A\u043B \xB7 Esc \u2014 \u0441\u043D\u044F\u0442\u044C \u0432\u044B\u0431\u043E\u0440 \xB7 \u2318/Ctrl+Z \u2014 \u043E\u0442\u043C\u0435\u043D\u0438\u0442\u044C, \u21E7 \u2014 \u0432\u0435\u0440\u043D\u0443\u0442\u044C. \u041F\u0440\u0430\u0432\u043A\u0438 \u0436\u0438\u0432\u0443\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u2014 \u0432 \u043A\u043E\u0434 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043F\u0438\u0448\u0435\u0442\u0441\u044F.",
832
+ // floating "all styles" readout
833
+ "all styles": "\u0432\u0441\u0435 \u0441\u0442\u0438\u043B\u0438",
834
+ computed: "\u0432\u044B\u0447\u0438\u0441\u043B\u0435\u043D\u043E",
835
+ // design system
836
+ "Design system": "\u0414\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0430",
837
+ "tokens detected": "\u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u043D\u0430\u0439\u0434\u0435\u043D\u043E",
838
+ // frame readout
839
+ editing: "\u043F\u0440\u0430\u0432\u0438\u043C",
840
+ "No breakpoint \u2014 applies to every size by default": "\u0411\u0435\u0437 \u0431\u0440\u0435\u0439\u043A\u043F\u043E\u0438\u043D\u0442\u0430 \u2014 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0435\u0442\u0441\u044F \u043A\u043E \u0432\u0441\u0435\u043C \u0440\u0430\u0437\u043C\u0435\u0440\u0430\u043C",
841
+ // toasts
842
+ "No tweaks recorded yet": "\u041F\u043E\u043A\u0430 \u043D\u0435\u0442 \u043D\u0438 \u043E\u0434\u043D\u043E\u0439 \u043F\u0440\u0430\u0432\u043A\u0438",
843
+ "Prompt copied \u2713": "\u041F\u0440\u043E\u043C\u043F\u0442 \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D \u2713",
844
+ "JSON copied \u2713": "JSON \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D \u2713",
845
+ "Nothing to undo": "\u041D\u0435\u0447\u0435\u0433\u043E \u043E\u0442\u043C\u0435\u043D\u044F\u0442\u044C",
846
+ "Undo \u21A9": "\u041E\u0442\u043C\u0435\u043D\u0435\u043D\u043E \u21A9",
847
+ "Nothing to redo": "\u041D\u0435\u0447\u0435\u0433\u043E \u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044C",
848
+ "Redo \u21AA": "\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u043E \u21AA",
849
+ // section titles
850
+ Layout: "\u0420\u0430\u0441\u043A\u043B\u0430\u0434\u043A\u0430",
851
+ Size: "\u0420\u0430\u0437\u043C\u0435\u0440",
852
+ Spacing: "\u041E\u0442\u0441\u0442\u0443\u043F\u044B",
853
+ Border: "\u0413\u0440\u0430\u043D\u0438\u0446\u0430",
854
+ Typography: "\u0422\u0438\u043F\u043E\u0433\u0440\u0430\u0444\u0438\u043A\u0430",
855
+ Fill: "\u0417\u0430\u043B\u0438\u0432\u043A\u0430",
856
+ // control labels
857
+ Display: "\u0422\u0438\u043F",
858
+ Direction: "\u041D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435",
859
+ Justify: "\u0420\u0430\u0441\u043F\u0440\u0435\u0434.",
860
+ Align: "\u0412\u044B\u0440\u0430\u0432\u043D\u0438\u0432.",
861
+ Wrap: "\u041F\u0435\u0440\u0435\u043D\u043E\u0441",
862
+ Width: "\u0428\u0438\u0440\u0438\u043D\u0430",
863
+ Height: "\u0412\u044B\u0441\u043E\u0442\u0430",
864
+ "Max W": "\u041C\u0430\u043A\u0441 \u0428",
865
+ "Min H": "\u041C\u0438\u043D \u0412",
866
+ Padding: "\u0412\u043D\u0443\u0442\u0440. \u043E\u0442\u0441\u0442\u0443\u043F",
867
+ Margin: "\u0412\u043D\u0435\u0448\u043D. \u043E\u0442\u0441\u0442\u0443\u043F",
868
+ Gap: "\u041F\u0440\u043E\u043C\u0435\u0436\u0443\u0442\u043E\u043A",
869
+ Radius: "\u0421\u043A\u0440\u0443\u0433\u043B\u0435\u043D\u0438\u0435",
870
+ Weight: "\u041D\u0430\u0441\u044B\u0449\u0435\u043D\u043D\u043E\u0441\u0442\u044C",
871
+ Line: "\u0412\u044B\u0441\u043E\u0442\u0430 \u0441\u0442\u0440\u043E\u043A\u0438",
872
+ Text: "\u0422\u0435\u043A\u0441\u0442",
873
+ Background: "\u0424\u043E\u043D"
874
+ };
875
+ var DICT = { en: {}, ru: RU };
876
+ function detect() {
877
+ try {
878
+ const saved = localStorage.getItem("uiv-lang");
879
+ if (saved === "en" || saved === "ru") return saved;
880
+ } catch {
881
+ }
882
+ try {
883
+ if (typeof navigator !== "undefined" && /^ru\b/i.test(navigator.language || "")) return "ru";
884
+ } catch {
885
+ }
886
+ return "en";
887
+ }
888
+ var lang = detect();
889
+ function getLang() {
890
+ return lang;
891
+ }
892
+ function setLang(next) {
893
+ lang = next;
894
+ try {
895
+ localStorage.setItem("uiv-lang", next);
896
+ } catch {
897
+ }
898
+ }
899
+ function t(key, vars) {
900
+ let out = DICT[lang][key] ?? key;
901
+ if (vars) for (const k of Object.keys(vars)) out = out.replace(`{${k}}`, String(vars[k]));
902
+ return out;
903
+ }
904
+
808
905
  // src/overlay/prompt.ts
809
906
  function collapseChanges(changes) {
810
907
  const byBp = groupByBreakpoint(changes);
@@ -1027,12 +1124,12 @@ var INTERNAL_COMPONENTS = /* @__PURE__ */ new Set([
1027
1124
  function isInternalName(name) {
1028
1125
  return INTERNAL_COMPONENTS.has(name) || /(?:Boundary|Router)$/.test(name) || name.startsWith("_");
1029
1126
  }
1030
- function fiberName(t) {
1031
- if (typeof t === "function") return t.displayName || t.name || null;
1032
- if (t && typeof t === "object") {
1033
- const inner = t.type || t.render;
1034
- if (typeof inner === "function") return t.displayName || inner.displayName || inner.name || null;
1035
- if (typeof t.displayName === "string") return t.displayName;
1127
+ function fiberName(t2) {
1128
+ if (typeof t2 === "function") return t2.displayName || t2.name || null;
1129
+ if (t2 && typeof t2 === "object") {
1130
+ const inner = t2.type || t2.render;
1131
+ if (typeof inner === "function") return t2.displayName || inner.displayName || inner.name || null;
1132
+ if (typeof t2.displayName === "string") return t2.displayName;
1036
1133
  }
1037
1134
  return null;
1038
1135
  }
@@ -1203,22 +1300,23 @@ var CSS = (
1203
1300
  border-bottom: 1px solid #242428; position: sticky; top: 0; z-index: 5; background: #141416;
1204
1301
  }
1205
1302
  .uiv-head b { font-size: 13px; color: #fafafa; letter-spacing: .2px; }
1206
- .uiv-bp { margin-left: auto; font-size: 10px; padding: 2px 8px; border-radius: 999px;
1303
+ .uiv-lang { margin-left: auto; cursor: pointer; font-size: 10px; font-weight: 700; letter-spacing: .4px;
1304
+ padding: 2px 7px; border-radius: 6px; background: #1c1c20; border: 1px solid #313138; color: #a1a1aa; }
1305
+ .uiv-lang:hover { color: #fff; border-color: #4f46e5; background: #26262c; }
1306
+ .uiv-bp { font-size: 10px; padding: 2px 8px; border-radius: 999px;
1207
1307
  background: #312e81; color: #c7d2fe; font-weight: 600; }
1208
1308
  .uiv-x { cursor: pointer; color: #71717a; padding: 2px 4px; }
1209
1309
  .uiv-x:hover { color: #fff; }
1210
1310
 
1211
1311
  .uiv-sec { padding: 11px 13px; border-bottom: 1px solid #1f1f22; }
1212
1312
 
1213
- /* ---- Framer-style top alignment toolbar ---- */
1214
- .uiv-toolbar { display: flex; align-items: center; gap: 8px; padding: 8px 13px;
1215
- border-bottom: 1px solid #1f1f22; }
1216
- .uiv-tgroup { display: flex; gap: 2px; }
1217
- .uiv-tbtn { display: flex; align-items: center; justify-content: center; width: 26px; height: 22px;
1218
- border-radius: 5px; background: transparent; border: 1px solid transparent; color: #8b8b94; cursor: pointer; }
1219
- .uiv-tbtn:hover { background: #1f1f23; color: #e4e4e7; }
1220
- .uiv-tbtn.on { background: #312e81; border-color: #4f46e5; color: #fff; }
1221
- .uiv-tsep { width: 1px; height: 16px; background: #2a2a2e; }
1313
+ /* ---- Framer-style flex/grid alignment buttons (Justify / Align rows) ---- */
1314
+ .uiv-fbtns { display: flex; gap: 5px; }
1315
+ .uiv-fbtn { flex: 1; display: flex; align-items: center; justify-content: center; height: 30px;
1316
+ border-radius: 8px; background: #1c1c20; border: 1px solid #313138; color: #8b8b94;
1317
+ cursor: pointer; transition: background .12s ease, color .12s ease, border-color .12s ease; }
1318
+ .uiv-fbtn:hover { background: #26262c; color: #e4e4e7; border-color: #45454d; }
1319
+ .uiv-fbtn.on { background: #312e81; border-color: #6366f1; color: #fff; box-shadow: 0 0 0 1px #4f46e5 inset; }
1222
1320
  .uiv-empty { color: #71717a; padding: 18px 12px; text-align: center; }
1223
1321
 
1224
1322
  .uiv-meta { line-height: 1.5; }
@@ -1242,14 +1340,20 @@ var CSS = (
1242
1340
  .uiv-chip.on, .uiv-clschip.on { background: #4f46e5; border-color: #6366f1; color: #fff; }
1243
1341
  .uiv-bphint { margin-top: 7px; font-size: 10px; color: #71717a; line-height: 1.4; }
1244
1342
  .uiv-bphint b { color: #c7d2fe; }
1343
+ /* "Apply changes to" dropdown (sits outside .uiv-ctl, so style it directly) */
1344
+ .uiv-targetsel { width: 100%; box-sizing: border-box; cursor: pointer;
1345
+ background: #1c1c20; border: 1px solid #313138; color: #fff;
1346
+ border-radius: 7px; padding: 7px 9px; font-size: 12px; outline: none; }
1347
+ .uiv-targetsel:hover { border-color: #45454d; }
1348
+ .uiv-targetsel:focus { border-color: #6366f1; }
1245
1349
  .uiv-newclass {
1246
- border: 1px dashed #52525b; background: transparent; color: #a1a1aa;
1247
- border-radius: 6px; padding: 3px 8px; font-size: 11px; width: 104px; outline: none;
1350
+ margin-top: 7px; box-sizing: border-box; width: 100%;
1351
+ border: 1px dashed #52525b; background: transparent; color: #e4e4e7;
1352
+ border-radius: 7px; padding: 6px 9px; font-size: 12px; outline: none;
1248
1353
  font-family: ui-monospace, monospace;
1249
1354
  }
1250
1355
  .uiv-newclass::placeholder { color: #71717a; }
1251
1356
  .uiv-newclass:focus { border-style: solid; border-color: #6366f1; color: #fff; }
1252
- .uiv-newclass.on { border-style: solid; border-color: #22d3ee; color: #fff; }
1253
1357
 
1254
1358
  /* ---- design-system indicator + token pickers ---- */
1255
1359
  .uiv-dsbar { padding: 7px 12px; border-bottom: 1px solid #27272a;
@@ -1323,8 +1427,9 @@ var CSS = (
1323
1427
  .uiv-acc:hover .uiv-chev { color: #818cf8; }
1324
1428
  .uiv-acc:not(.collapsed) .uiv-chev { transform: rotate(90deg); }
1325
1429
 
1326
- .uiv-ctl { display: grid; grid-template-columns: 70px 1fr 26px; gap: 8px;
1430
+ .uiv-ctl { display: grid; grid-template-columns: 84px 1fr 26px; gap: 8px;
1327
1431
  align-items: center; margin-bottom: 7px; }
1432
+ .uiv-ctl > .clabel { overflow: hidden; text-overflow: ellipsis; }
1328
1433
  .uiv-ctl:last-child { margin-bottom: 0; }
1329
1434
  .uiv-ctl > .clabel { font-size: 11px; color: #a1a1aa; }
1330
1435
  .uiv-ctl > .cfield { min-width: 0; }
@@ -1385,7 +1490,7 @@ var CSS = (
1385
1490
  .uiv-jchg .bp { color: #818cf8; }
1386
1491
  .uiv-jchg .tok { color: #4ade80; }
1387
1492
 
1388
- .uiv-foot { display: flex; gap: 6px; padding: 10px 12px; position: sticky; bottom: 0;
1493
+ .uiv-foot { display: flex; gap: 6px; padding: 10px 12px; position: sticky; bottom: 0; z-index: 8;
1389
1494
  background: #18181b; border-top: 1px solid #27272a; flex-wrap: wrap; }
1390
1495
  .uiv-btn { flex: 1; cursor: pointer; border: 1px solid #3f3f46; background: #27272a;
1391
1496
  color: #e4e4e7; border-radius: 7px; padding: 7px 8px; font-size: 11px; font-weight: 600;
@@ -1394,10 +1499,11 @@ var CSS = (
1394
1499
  .uiv-btn.primary { background: #4f46e5; border-color: #6366f1; color: #fff; flex-basis: 100%; }
1395
1500
  .uiv-btn.primary:hover { background: #4338ca; }
1396
1501
  .uiv-btn.ghost { flex: 0 0 auto; }
1397
- /* floating read-only "all styles" block, docked bottom-right, left of the panel */
1398
- .uiv-info { position: fixed; right: 384px; bottom: 16px; z-index: 2147483646;
1399
- width: 216px; max-height: 52vh; overflow: auto; display: none;
1400
- background: rgba(24,24,27,0.86); color: #e4e4e7;
1502
+ /* floating read-only "all styles" block \u2014 top-LEFT of the screen, its top edge on
1503
+ the same line as the breakpoint toolbar (which is centred up top). */
1504
+ .uiv-info { position: fixed; left: 14px; top: 8px; z-index: 2147483646;
1505
+ width: 216px; max-height: 84vh; overflow: auto; display: none;
1506
+ background: rgba(24,24,27,0.92); color: #e4e4e7;
1401
1507
  border: 1px solid #3f3f46; border-radius: 10px; padding: 8px 10px;
1402
1508
  font-size: 11px; box-shadow: 0 8px 28px rgba(0,0,0,0.4); }
1403
1509
  .uiv-info.show { display: block; }
@@ -1620,32 +1726,33 @@ var Uivisor = class {
1620
1726
  this.root.innerHTML = `
1621
1727
  <style>${CSS}</style>
1622
1728
  <div class="uiv-framewrap">
1623
- <div class="uiv-framebar"><div class="uiv-framechips uiv-chips"></div><span class="uiv-framew">768px</span><span class="uiv-framex" title="Turn uivisor off (Alt+U)">\u2715</span></div>
1729
+ <div class="uiv-framebar"><div class="uiv-framechips uiv-chips"></div><span class="uiv-framew">768px</span><span class="uiv-framex" title="${escapeAttr(t("Turn uivisor off (Alt+U)"))}">\u2715</span></div>
1624
1730
  <div class="uiv-framestage">
1625
1731
  <div class="uiv-framehost">
1626
1732
  <iframe class="uiv-frame" data-uiv-frame="1"></iframe>
1627
- <div class="uiv-framehandle" title="Drag to resize"></div>
1733
+ <div class="uiv-framehandle" title="${escapeAttr(t("Drag to resize"))}"></div>
1628
1734
  </div>
1629
1735
  </div>
1630
1736
  </div>
1631
1737
  <div class="uiv-box hover"></div>
1632
1738
  <div class="uiv-box sel"></div>
1633
1739
  <div class="uiv-tag"></div>
1634
- <div class="uiv-fab" title="Toggle uivisor (Alt+U)">\u25CE</div>
1740
+ <div class="uiv-fab" title="${escapeAttr(t("Toggle uivisor (Alt+U)"))}">\u25CE</div>
1635
1741
  <div class="uiv-info"></div>
1636
1742
  <div class="uiv-toast"></div>
1637
1743
  <div class="uiv-panel">
1638
1744
  <div class="uiv-head">
1639
1745
  <b>uivisor</b>
1746
+ <button class="uiv-lang" title="Language / \u042F\u0437\u044B\u043A">${getLang().toUpperCase()}</button>
1640
1747
  <span class="uiv-bp">base</span>
1641
- <span class="uiv-x" title="Close">\u2715</span>
1748
+ <span class="uiv-x" title="${escapeAttr(t("Close"))}">\u2715</span>
1642
1749
  </div>
1643
1750
  <div class="uiv-body"></div>
1644
1751
  <div class="uiv-foot">
1645
- <button class="uiv-btn primary copy-prompt">Copy prompt for agent</button>
1646
- <button class="uiv-btn copy-json">Copy JSON</button>
1647
- <button class="uiv-btn ghost reset" title="Revert tweaks on selected element">Reset</button>
1648
- <button class="uiv-btn ghost clear" title="Clear all">Clear</button>
1752
+ <button class="uiv-btn primary copy-prompt">${t("Copy prompt for agent")}</button>
1753
+ <button class="uiv-btn copy-json">${t("Copy JSON")}</button>
1754
+ <button class="uiv-btn ghost reset" title="${escapeAttr(t("Revert tweaks on selected element"))}">${t("Reset")}</button>
1755
+ <button class="uiv-btn ghost clear" title="${escapeAttr(t("Clear all"))}">${t("Clear")}</button>
1649
1756
  </div>
1650
1757
  </div>
1651
1758
  `;
@@ -1660,6 +1767,7 @@ var Uivisor = class {
1660
1767
  this.frame = this.q(".uiv-frame");
1661
1768
  this.fab.addEventListener("click", () => this.toggle());
1662
1769
  this.q(".uiv-x").addEventListener("click", () => this.toggle(false));
1770
+ this.q(".uiv-lang").addEventListener("click", () => this.setLanguage(getLang() === "ru" ? "en" : "ru"));
1663
1771
  this.q(".uiv-framex").addEventListener("click", () => this.toggle(false));
1664
1772
  this.q(".copy-prompt").addEventListener("click", () => this.copyPrompt());
1665
1773
  this.q(".copy-json").addEventListener("click", () => this.copyJSON());
@@ -1698,6 +1806,29 @@ var Uivisor = class {
1698
1806
  isOurs(e) {
1699
1807
  return e.composedPath().includes(this.host);
1700
1808
  }
1809
+ /** Switch the UI language: persist it, refresh the static chrome built once in
1810
+ * mount() (buttons, titles, the toggle label) and re-render the live panel. */
1811
+ setLanguage(next) {
1812
+ setLang(next);
1813
+ this.q(".uiv-lang").textContent = next.toUpperCase();
1814
+ this.q(".uiv-x").setAttribute("title", t("Close"));
1815
+ this.q(".uiv-framex").setAttribute("title", t("Turn uivisor off (Alt+U)"));
1816
+ this.q(".uiv-framehandle").setAttribute("title", t("Drag to resize"));
1817
+ this.fab.setAttribute("title", t("Toggle uivisor (Alt+U)"));
1818
+ this.q(".copy-prompt").textContent = t("Copy prompt for agent");
1819
+ this.q(".copy-json").textContent = t("Copy JSON");
1820
+ const reset = this.q(".reset");
1821
+ reset.textContent = t("Reset");
1822
+ reset.setAttribute("title", t("Revert tweaks on selected element"));
1823
+ const clear = this.q(".clear");
1824
+ clear.textContent = t("Clear");
1825
+ clear.setAttribute("title", t("Clear all"));
1826
+ this.renderBody();
1827
+ if (this.responsive) {
1828
+ this.renderFrameBar();
1829
+ this.setFrameWidth(this.frameWidth);
1830
+ }
1831
+ }
1701
1832
  // ---- enable / disable ----
1702
1833
  toggle(force) {
1703
1834
  this.enabled = force ?? !this.enabled;
@@ -1712,12 +1843,12 @@ var Uivisor = class {
1712
1843
  this.scheduleBpRefresh();
1713
1844
  if (!this.responsive) {
1714
1845
  this.pickedBp = "base";
1715
- this.frameWidth = this.defaultFrameWidth();
1846
+ this.frameWidth = this.baseFrameWidth();
1716
1847
  this.toggleResponsive(true);
1717
1848
  }
1718
1849
  }
1719
1850
  }
1720
- /** Frame width on enable: the real window width (≈ the current breakpoint). */
1851
+ /** The real window width used as the desktop reference for a desktop-first base. */
1721
1852
  defaultFrameWidth() {
1722
1853
  return typeof window !== "undefined" ? window.innerWidth : 1280;
1723
1854
  }
@@ -1737,6 +1868,21 @@ var Uivisor = class {
1737
1868
  };
1738
1869
  for (const d of [250, 900, 2200]) window.setTimeout(refresh, d);
1739
1870
  }
1871
+ /** Frame width for the "all"/base chip — the screen the project's UNPREFIXED
1872
+ * (base) styles target, which depends on the cascade direction:
1873
+ * • mobile-first (min-width) → base is the SMALLEST view → a phone width;
1874
+ * • desktop-first (max-width) → base is the WIDEST view → a desktop width.
1875
+ * The EDIT SCOPE stays decoupled (a base edit applies at every size regardless). */
1876
+ baseFrameWidth() {
1877
+ const sys = this.bpSystem();
1878
+ const bps = sys.breakpoints;
1879
+ if (sys.dir === "min") {
1880
+ const firstBp = bps.length ? bps[0].minWidth : 640;
1881
+ return Math.min(390, firstBp - 1);
1882
+ }
1883
+ const lastBp = bps.length ? bps[bps.length - 1].minWidth : 1024;
1884
+ return Math.max(this.defaultFrameWidth(), lastBp + 80);
1885
+ }
1740
1886
  // ---- responsive (virtual screen) mode ----
1741
1887
  /** The document the inspector currently targets: the iframe in responsive mode. */
1742
1888
  doc() {
@@ -1815,7 +1961,7 @@ var Uivisor = class {
1815
1961
  this.frameWidth = Math.max(280, Math.min(2400, Math.round(w)));
1816
1962
  const host = this.q(".uiv-framehost");
1817
1963
  host.style.width = `${this.frameWidth}px`;
1818
- this.q(".uiv-framew").textContent = `${this.frameWidth}px \xB7 editing ${this.bpLabel(this.scopeName())}`;
1964
+ this.q(".uiv-framew").textContent = `${this.frameWidth}px \xB7 ${t("editing")} ${this.bpLabel(this.scopeName())}`;
1819
1965
  this.updateBp();
1820
1966
  this.reposition();
1821
1967
  }
@@ -1875,9 +2021,10 @@ var Uivisor = class {
1875
2021
  this.reposition();
1876
2022
  this.renderBody();
1877
2023
  }
1878
- /** Properties to snapshot on selection — the curated set the controls can edit. */
2024
+ /** Properties to snapshot on selection — the curated set the controls can edit,
2025
+ * plus the flex-alignment props handled by the visual button-rows. */
1879
2026
  snapshotProps() {
1880
- return ALL_CSS;
2027
+ return [.../* @__PURE__ */ new Set([...ALL_CSS, "justify-content", "align-items"])];
1881
2028
  }
1882
2029
  // ---- value helpers ----
1883
2030
  st() {
@@ -1916,8 +2063,8 @@ var Uivisor = class {
1916
2063
  tokenNameFor(css) {
1917
2064
  const ch = this.effectiveChange(css);
1918
2065
  if (!ch?.after.designToken) return null;
1919
- const t = this.designSystem().tokens.find((x) => x.cssVar === ch.after.designToken);
1920
- return t ? t.name : ch.after.designToken;
2066
+ const t2 = this.designSystem().tokens.find((x) => x.cssVar === ch.after.designToken);
2067
+ return t2 ? t2.name : ch.after.designToken;
1921
2068
  }
1922
2069
  liveNum(css) {
1923
2070
  const v = this.liveVal(css).trim();
@@ -2174,8 +2321,8 @@ var Uivisor = class {
2174
2321
  if (!this.selected) {
2175
2322
  body.innerHTML = `
2176
2323
  ${this.breakpointBarHtml()}
2177
- <div class="uiv-empty">Click any element ${this.responsive ? "in the frame" : "on the page"} to select it.</div>
2178
- <div class="uiv-hint">Alt+U toggles \xB7 Esc deselects \xB7 \u2318/Ctrl+Z undo, \u21E7 to redo. Tweaks stay in the browser \u2014 nothing is written to your code.</div>
2324
+ <div class="uiv-empty">${t(this.responsive ? "Click any element in the frame to select it." : "Click any element on the page to select it.")}</div>
2325
+ <div class="uiv-hint">${t("Alt+U toggles \xB7 Esc deselects \xB7 \u2318/Ctrl+Z undo, \u21E7 to redo. Tweaks stay in the browser \u2014 nothing is written to your code.")}</div>
2179
2326
  ${this.journalHtml()}
2180
2327
  `;
2181
2328
  if (this.responsive) this.renderFrameBar();
@@ -2192,7 +2339,6 @@ var Uivisor = class {
2192
2339
  <div class="uiv-src">${escapeHtml(src)}</div>
2193
2340
  <span class="uiv-mech">${st.record.styling.primaryMechanism}</span>
2194
2341
  </div>
2195
- ${this.alignToolbarHtml()}
2196
2342
  ${this.dsIndicatorHtml()}
2197
2343
  ${this.breakpointBarHtml()}
2198
2344
  ${this.targetHtml(st)}
@@ -2208,7 +2354,7 @@ var Uivisor = class {
2208
2354
  const ds = this.designSystem();
2209
2355
  if (ds.source === "none") return "";
2210
2356
  const cats = Object.keys(ds.byCategory).map((c) => `${ds.byCategory[c].length} ${c}`).join(" \xB7 ");
2211
- return `<div class="uiv-dsbar" title="${escapeAttr(cats)}">\u25C6 Design system \xB7 ${ds.tokens.length} tokens detected</div>`;
2357
+ return `<div class="uiv-dsbar" title="${escapeAttr(cats)}">\u25C6 ${t("Design system")} \xB7 ${ds.tokens.length} ${t("tokens detected")}</div>`;
2212
2358
  }
2213
2359
  /** Longhand properties an authoring rule (or non-uivisor inline) sets on `el`.
2214
2360
  * Reimplements getMatchedCSSRules over same-origin sheets (incl. matching
@@ -2275,7 +2421,7 @@ var Uivisor = class {
2275
2421
  ctlLabel(label, props) {
2276
2422
  const from = this.inheritedFrom(props);
2277
2423
  const badge = from ? ` <span class="uiv-inh" title="inherited from ${escapeAttr(this.bpLabel(from))} \u2014 not set at this breakpoint">\u2923${escapeHtml(this.bpLabel(from))}</span>` : "";
2278
- return `<span class="clabel">${label}${badge}</span>`;
2424
+ return `<span class="clabel">${escapeHtml(t(label))}${badge}</span>`;
2279
2425
  }
2280
2426
  /** Is any of `props` authored in the project CSS? For inherited properties we
2281
2427
  * also walk ancestors (a body/parent font rule still counts as "from the file"). */
@@ -2309,10 +2455,10 @@ var Uivisor = class {
2309
2455
  return /^#|rgb/.test(h) ? `<span class="uiv-sw" style="background:${h}"></span>${h}` : h;
2310
2456
  };
2311
2457
  const sides = (parts) => {
2312
- const [t, r, b, l] = parts.map(g);
2313
- if (t === r && r === b && b === l) return t;
2314
- if (t === b && r === l) return `${t} ${r}`;
2315
- return `${t} ${r} ${b} ${l}`;
2458
+ const [t2, r, b, l] = parts.map(g);
2459
+ if (t2 === r && r === b && b === l) return t2;
2460
+ if (t2 === b && r === l) return `${t2} ${r}`;
2461
+ return `${t2} ${r} ${b} ${l}`;
2316
2462
  };
2317
2463
  const px4 = (pre, suf = "") => [`${pre}-top${suf}`, `${pre}-right${suf}`, `${pre}-bottom${suf}`, `${pre}-left${suf}`];
2318
2464
  const clip = (s, n = 30) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
@@ -2415,7 +2561,7 @@ var Uivisor = class {
2415
2561
  info.innerHTML = "";
2416
2562
  return;
2417
2563
  }
2418
- info.innerHTML = `<div class="uiv-info-h">all styles <span class="uiv-info-sub">computed</span></div><div class="uiv-readout">${this.styleRows()}</div>`;
2564
+ info.innerHTML = `<div class="uiv-info-h">${t("all styles")} <span class="uiv-info-sub">${t("computed")}</span></div><div class="uiv-readout">${this.styleRows()}</div>`;
2419
2565
  info.classList.add("show");
2420
2566
  }
2421
2567
  /** Display label for a breakpoint name — the unprefixed "base" scope reads "all"
@@ -2439,7 +2585,7 @@ var Uivisor = class {
2439
2585
  const winBp = currentBreakpoint(sys).name;
2440
2586
  const isActive = (n) => this.responsive ? n === this.scopeName() : n === winBp;
2441
2587
  const chip = (n, on, title) => `<button class="uiv-chip${on ? " on" : ""}" data-bp="${n}" title="${escapeAttr(title)}">${this.bpIcon(n)}<span>${this.bpLabel(n)}</span></button>`;
2442
- const all = chip("base", isActive("base"), "No breakpoint \u2014 applies to every size by default");
2588
+ const all = chip("base", isActive("base"), t("No breakpoint \u2014 applies to every size by default"));
2443
2589
  const rest = sys.breakpoints.map((b) => chip(b.name, isActive(b.name), sys.dir === "min" ? `\u2265 ${b.minWidth}px` : `\u2264 ${b.minWidth}px`)).join("");
2444
2590
  return all + rest;
2445
2591
  }
@@ -2467,12 +2613,10 @@ var Uivisor = class {
2467
2613
  const isNew = target.startsWith("new:");
2468
2614
  const newName = isNew ? target.slice(4) : "";
2469
2615
  const n = st.record.identity.instanceCount;
2470
- const chip = (val, label, on) => `<button class="uiv-clschip${on ? " on" : ""}" data-target="${escapeAttr(val)}">${escapeHtml(label)}</button>`;
2471
- const allChip = n > 1 ? chip("all", `All ${n} like this`, target === "all") : "";
2472
- const elChip = chip("element", n > 1 ? "Only this one" : "This element", target === "element");
2473
- const classChips = classes.map((c) => chip(c, `.${c}`, target === c)).join("");
2474
- const newInput = `<input class="uiv-newclass${isNew ? " on" : ""}" placeholder="+ new class" value="${escapeAttr(newName)}" title="Create a new class instead of touching the existing ones">`;
2475
- return `<div class="uiv-sec"><div class="uiv-sectitle">Apply changes to</div><div class="uiv-chips">${allChip}${elChip}${classChips}${newInput}</div></div>`;
2616
+ const opt = (val, label) => `<option value="${escapeAttr(val)}"${target === val ? " selected" : ""}>${escapeHtml(label)}</option>`;
2617
+ const opts = (n > 1 ? opt("all", t("All {n} like this", { n })) : "") + opt("element", n > 1 ? t("Only this one") : t("This element")) + classes.map((c) => opt(c, `.${c}`)).join("") + `<option value="__new__"${isNew ? " selected" : ""}>${escapeHtml(`\uFF0B ${t("New class\u2026")}`)}</option>`;
2618
+ const newInput = isNew ? `<input class="uiv-newclass" placeholder="${escapeAttr(t("new class name"))}" value="${escapeAttr(newName)}" title="${escapeAttr(t("Create a new class instead of touching the existing ones"))}" spellcheck="false">` : "";
2619
+ return `<div class="uiv-sec"><div class="uiv-sectitle">${t("Apply changes to")}</div><div class="cfield"><select class="uiv-sel uiv-targetsel">${opts}</select></div>${newInput}</div>`;
2476
2620
  }
2477
2621
  /** Decide which controls are relevant to the selected element. */
2478
2622
  context(el) {
@@ -2494,28 +2638,35 @@ var Uivisor = class {
2494
2638
  }
2495
2639
  /** Framer-style top alignment toolbar — justify (horizontal) + align (vertical)
2496
2640
  * icon buttons. Shown for flex/grid containers (where they apply). */
2497
- alignToolbarHtml() {
2641
+ /** Visual flex/grid alignment controls — bigger Framer-style icon buttons in two
2642
+ * labelled rows (Justify, Align). Injected right below the Display dropdown and
2643
+ * shown only for flex/grid containers; replaces the duplicate Justify/Align
2644
+ * selects entirely. Commits through the engine via the shared `.uiv-fbtn` binding. */
2645
+ flexControlsHtml() {
2498
2646
  const el = this.selected;
2499
2647
  if (!el || !this.context(el).flexGrid) return "";
2500
2648
  const j = this.liveVal("justify-content").trim();
2501
2649
  const a = this.liveVal("align-items").trim();
2502
- const g = (r) => `<svg viewBox="0 0 14 14" width="13" height="13" fill="currentColor">${r}</svg>`;
2503
- const JI = {
2504
- "flex-start": g('<rect x="1" y="3" width="2" height="8"/><rect x="4" y="3" width="2" height="8"/>'),
2505
- center: g('<rect x="4" y="3" width="2" height="8"/><rect x="8" y="3" width="2" height="8"/>'),
2506
- "flex-end": g('<rect x="8" y="3" width="2" height="8"/><rect x="11" y="3" width="2" height="8"/>'),
2507
- "space-between": g('<rect x="1" y="3" width="2" height="8"/><rect x="11" y="3" width="2" height="8"/>')
2508
- };
2509
- const AI = {
2510
- "flex-start": g('<rect x="3" y="1" width="8" height="2"/><rect x="3" y="4" width="8" height="2"/>'),
2511
- center: g('<rect x="3" y="4" width="8" height="2"/><rect x="3" y="8" width="8" height="2"/>'),
2512
- "flex-end": g('<rect x="3" y="8" width="8" height="2"/><rect x="3" y="11" width="8" height="2"/>'),
2513
- stretch: g('<rect x="3" y="1" width="8" height="12"/>')
2650
+ const g = (r) => `<svg viewBox="0 0 18 18" width="17" height="17" fill="currentColor">${r}</svg>`;
2651
+ const JI = [
2652
+ ["flex-start", g('<rect x="2" y="4" width="2.4" height="10" rx="1"/><rect x="5.6" y="4" width="2.4" height="10" rx="1"/>')],
2653
+ ["center", g('<rect x="5" y="4" width="2.4" height="10" rx="1"/><rect x="10.6" y="4" width="2.4" height="10" rx="1"/>')],
2654
+ ["flex-end", g('<rect x="10" y="4" width="2.4" height="10" rx="1"/><rect x="13.6" y="4" width="2.4" height="10" rx="1"/>')],
2655
+ ["space-between", g('<rect x="2" y="4" width="2.4" height="10" rx="1"/><rect x="13.6" y="4" width="2.4" height="10" rx="1"/>')]
2656
+ ];
2657
+ const AI = [
2658
+ ["flex-start", g('<rect x="4" y="2" width="10" height="2.4" rx="1"/><rect x="4" y="5.6" width="10" height="2.4" rx="1"/>')],
2659
+ ["center", g('<rect x="4" y="5" width="10" height="2.4" rx="1"/><rect x="4" y="10.6" width="10" height="2.4" rx="1"/>')],
2660
+ ["flex-end", g('<rect x="4" y="10" width="10" height="2.4" rx="1"/><rect x="4" y="13.6" width="10" height="2.4" rx="1"/>')],
2661
+ ["stretch", g('<rect x="4" y="2" width="10" height="14" rx="1"/>')]
2662
+ ];
2663
+ const row = (label, prop, cur, entries) => {
2664
+ const btns = entries.map(
2665
+ ([v, ic]) => `<button class="uiv-fbtn${cur === v ? " on" : ""}" data-prop="${prop}" data-val="${v}" title="${prop}: ${v}">${ic}</button>`
2666
+ ).join("");
2667
+ return `<div class="uiv-ctl${this.controlStateClass([prop])}">${this.ctlLabel(label, [prop])}<div class="cfield uiv-fbtns">${btns}</div><span></span></div>`;
2514
2668
  };
2515
- const btn = (prop, val, icon, cur) => `<button class="uiv-tbtn${cur === val ? " on" : ""}" data-prop="${prop}" data-val="${val}" title="${prop}: ${val}">${icon}</button>`;
2516
- const jb = Object.entries(JI).map(([v, ic]) => btn("justify-content", v, ic, j)).join("");
2517
- const ab = Object.entries(AI).map(([v, ic]) => btn("align-items", v, ic, a)).join("");
2518
- return `<div class="uiv-toolbar"><div class="uiv-tgroup">${jb}</div><div class="uiv-tsep"></div><div class="uiv-tgroup">${ab}</div></div>`;
2669
+ return row("Justify", "justify-content", j, JI) + row("Align", "align-items", a, AI);
2519
2670
  }
2520
2671
  /** Figma/Framer-style nested box-model widget: MARGIN ring around a PADDING ring,
2521
2672
  * with an editable number on each of the 8 sides. Commits via the engine. */
@@ -2531,11 +2682,11 @@ var Uivisor = class {
2531
2682
  return `<input class="uiv-bm-i ${pos}${this.controlStateClass([css])}${tok ? " uiv-bm-tok" : ""}" data-css="${css}" value="${escapeAttr(val)}" title="${escapeAttr(title)}" inputmode="decimal" spellcheck="false">`;
2532
2683
  };
2533
2684
  const spaceTokens = this.designSystem().byCategory["spacing"] ?? [];
2534
- const pop = spaceTokens.length ? `<div class="uiv-bm-pop" hidden><span class="uiv-bm-poplabel">Token</span>` + spaceTokens.map((t) => `<button class="uiv-bm-chip" data-var="${escapeAttr(t.cssVar)}" title="${escapeAttr(t.value)}">${escapeHtml(t.name)} \xB7 ${escapeHtml(t.value)}</button>`).join("") + `</div>` : "";
2685
+ const pop = spaceTokens.length ? `<div class="uiv-bm-pop" hidden><span class="uiv-bm-poplabel">${t("Token")}</span>` + spaceTokens.map((tok) => `<button class="uiv-bm-chip" data-var="${escapeAttr(tok.cssVar)}" title="${escapeAttr(tok.value)}">${escapeHtml(tok.name)} \xB7 ${escapeHtml(tok.value)}</button>`).join("") + `</div>` : "";
2535
2686
  return `<div class="uiv-bm"><span class="uiv-bm-tag">MARGIN</span>` + side("margin-top", "bm-top") + side("margin-right", "bm-right") + side("margin-bottom", "bm-bottom") + side("margin-left", "bm-left") + `<div class="uiv-bm-pad"><span class="uiv-bm-tag">PADDING</span>` + side("padding-top", "bm-top") + side("padding-right", "bm-right") + side("padding-bottom", "bm-bottom") + side("padding-left", "bm-left") + `<div class="uiv-bm-content"></div></div></div>` + pop;
2536
2687
  }
2537
2688
  controlsHtml(ctx) {
2538
- const legend = `<div class="uiv-leg"><span class="uiv-lg">file</span><span class="uiv-lg edit">edited</span><span class="uiv-lg inh">inherited</span><span class="uiv-lg auto">auto</span></div>`;
2689
+ const legend = `<div class="uiv-leg"><span class="uiv-lg">${t("file")}</span><span class="uiv-lg edit">${t("edited")}</span><span class="uiv-lg inh">${t("inherited")}</span><span class="uiv-lg auto">${t("auto")}</span></div>`;
2539
2690
  const secs = SECTIONS.map((sec) => {
2540
2691
  const controls = sec.controls.filter((c) => this.relevant(c, ctx));
2541
2692
  if (!controls.length) return "";
@@ -2548,9 +2699,10 @@ var Uivisor = class {
2548
2699
  if (isSpacing && c.kind === "box" && (c.key === "padding" || c.key === "margin")) continue;
2549
2700
  const css = c.css;
2550
2701
  if (c.kind === "len" && c.hideWhenAuto && css && !this.revealedCtls.has(css) && this.controlState([css]) === "auto") {
2551
- adds.push(`<button class="uiv-addctl" data-css="${css}">+ ${escapeHtml(c.label)}</button>`);
2702
+ adds.push(`<button class="uiv-addctl" data-css="${css}">+ ${escapeHtml(t(c.label))}</button>`);
2552
2703
  } else {
2553
2704
  rows.push(this.controlRow(c));
2705
+ if (css === "display") rows.push(this.flexControlsHtml());
2554
2706
  }
2555
2707
  }
2556
2708
  const addRow = adds.length ? `<div class="uiv-adds">${adds.join("")}</div>` : "";
@@ -2561,10 +2713,10 @@ var Uivisor = class {
2561
2713
  /** A collapsible section header. Clicking it hides/shows the section's controls. */
2562
2714
  accordionTitle(title) {
2563
2715
  const collapsed = this.collapsedSecs.has(title);
2564
- return `<button class="uiv-sectitle uiv-acc${collapsed ? " collapsed" : ""}" data-sec="${escapeAttr(title)}"><span class="uiv-chev">${ICONS.chevron}</span>${title}</button>`;
2716
+ return `<button class="uiv-sectitle uiv-acc${collapsed ? " collapsed" : ""}" data-sec="${escapeAttr(title)}"><span class="uiv-chev">${ICONS.chevron}</span>${escapeHtml(t(title))}</button>`;
2565
2717
  }
2566
2718
  numField(cssAttr, value, handle, changed, isSide, placeholder) {
2567
- return `<div class="uiv-num${changed ? " changed" : ""}" data-css="${cssAttr}"><span class="uiv-scrub${isSide ? " txt" : ""}" title="Drag to change">${handle}</span><input type="number" value="${escapeAttr(value)}" placeholder="${escapeAttr(placeholder)}"></div>`;
2719
+ return `<div class="uiv-num${changed ? " changed" : ""}" data-css="${cssAttr}"><span class="uiv-scrub${isSide ? " txt" : ""}" title="${escapeAttr(t("Drag to change"))}">${handle}</span><input type="number" value="${escapeAttr(value)}" placeholder="${escapeAttr(placeholder)}"></div>`;
2568
2720
  }
2569
2721
  fontSizePx() {
2570
2722
  const el = this.selected;
@@ -2619,10 +2771,24 @@ var Uivisor = class {
2619
2771
  if (unit === "%") return px2 / fs * 100;
2620
2772
  return px2;
2621
2773
  }
2774
+ /** A dim value's own number + unit ('' = unitless, e.g. line-height: 1.5). */
2775
+ parseDim(v) {
2776
+ const m = /^(-?\d*\.?\d+)(px|em|rem|%)?$/.exec(v.trim());
2777
+ return m ? { num: parseFloat(m[1]), unit: m[2] ?? "" } : null;
2778
+ }
2622
2779
  dimDisplay(c) {
2623
2780
  const st = this.st();
2624
- const computed = this.liveVal(c.css);
2625
- const unit = st.dimUnit[c.css] ?? c.defaultUnit;
2781
+ const computed = this.liveVal(c.css).trim();
2782
+ const native = this.parseDim(computed);
2783
+ const picked = st.dimUnit[c.css];
2784
+ if (picked != null) {
2785
+ const px3 = this.currentPx(c.css);
2786
+ if (px3 != null) return { num: String(round2(this.pxToUnit(px3, picked))), unit: picked, placeholder: computed || "\u2014" };
2787
+ }
2788
+ if (native && c.units.includes(native.unit)) {
2789
+ return { num: String(round2(native.num)), unit: native.unit, placeholder: computed || "\u2014" };
2790
+ }
2791
+ const unit = picked ?? c.defaultUnit;
2626
2792
  const px2 = this.currentPx(c.css);
2627
2793
  if (px2 == null) return { num: "", unit, placeholder: computed || "normal" };
2628
2794
  return { num: String(round2(this.pxToUnit(px2, unit))), unit, placeholder: computed || "\u2014" };
@@ -2631,7 +2797,7 @@ var Uivisor = class {
2631
2797
  const d = this.dimDisplay(c);
2632
2798
  const changed = this.isChanged([c.css]);
2633
2799
  const units = c.units.map((u) => `<option value="${u}"${u === d.unit ? " selected" : ""}>${UNIT_LABELS[u] ?? u}</option>`).join("");
2634
- return `<div class="uiv-ctl${this.controlStateClass([c.css])}">${this.ctlLabel(c.label, [c.css])}<div class="cfield"><div class="uiv-num uiv-dim${changed ? " changed" : ""}" data-css="${c.css}"><span class="uiv-scrub" title="Drag to change">${c.icon}</span><input type="number" step="any" value="${escapeAttr(d.num)}" placeholder="${escapeAttr(d.placeholder)}"><select class="uiv-unit" title="Unit">${units}</select></div></div><span></span></div>`;
2800
+ return `<div class="uiv-ctl${this.controlStateClass([c.css])}">${this.ctlLabel(c.label, [c.css])}<div class="cfield"><div class="uiv-num uiv-dim${changed ? " changed" : ""}" data-css="${c.css}"><span class="uiv-scrub" title="${escapeAttr(t("Drag to change"))}">${c.icon}</span><input type="number" step="any" value="${escapeAttr(d.num)}" placeholder="${escapeAttr(d.placeholder)}"><select class="uiv-unit" title="${escapeAttr(t("Unit"))}">${units}</select></div></div><span></span></div>`;
2635
2801
  }
2636
2802
  /** A design-token picker row for a property, shown only when the project exposes
2637
2803
  * tokens for that category. Picking a token applies its value + tags the prompt. */
@@ -2647,17 +2813,17 @@ var Uivisor = class {
2647
2813
  (c) => c.property === css && c.after.designToken
2648
2814
  );
2649
2815
  if (cat === "color") {
2650
- const swatches = list.map((t) => {
2651
- const on = near?.exact && near.token.cssVar === t.cssVar;
2652
- return `<button class="uiv-swatch${on ? " on" : ""}" data-css="${css}" data-var="${escapeAttr(t.cssVar)}" title="${escapeAttr(`${t.name} \xB7 ${t.value}`)}" style="background:${escapeAttr(t.value)}"></button>`;
2816
+ const swatches = list.map((tok) => {
2817
+ const on = near?.exact && near.token.cssVar === tok.cssVar;
2818
+ return `<button class="uiv-swatch${on ? " on" : ""}" data-css="${css}" data-var="${escapeAttr(tok.cssVar)}" title="${escapeAttr(`${tok.name} \xB7 ${tok.value}`)}" style="background:${escapeAttr(tok.value)}"></button>`;
2653
2819
  }).join("");
2654
- return `<div class="uiv-ctl"><span class="clabel uiv-tlabel">${label}</span><div class="cfield uiv-swatches">${swatches}</div><span></span></div>`;
2820
+ return `<div class="uiv-ctl"><span class="clabel uiv-tlabel">${escapeHtml(t(label))}</span><div class="cfield uiv-swatches">${swatches}</div><span></span></div>`;
2655
2821
  }
2656
- const head = `<option value="">${near && !near.exact ? `\u2248 ${escapeHtml(near.token.name)} \xB7 pick token` : "\u2014 pick token \u2014"}</option>`;
2822
+ const head = `<option value="">${near && !near.exact ? `\u2248 ${escapeHtml(near.token.name)} \xB7 ${t("pick token")}` : `\u2014 ${t("pick token")} \u2014`}</option>`;
2657
2823
  const opts = list.map(
2658
- (t) => `<option value="${escapeAttr(t.cssVar)}"${near?.exact && near.token.cssVar === t.cssVar ? " selected" : ""}>${escapeHtml(`${t.name} \xB7 ${t.value}`)}</option>`
2824
+ (tok) => `<option value="${escapeAttr(tok.cssVar)}"${near?.exact && near.token.cssVar === tok.cssVar ? " selected" : ""}>${escapeHtml(`${tok.name} \xB7 ${tok.value}`)}</option>`
2659
2825
  ).join("");
2660
- return `<div class="uiv-ctl"><span class="clabel uiv-tlabel">${label}</span><div class="cfield"><select class="uiv-sel uiv-tokensel${edited ? " changed" : ""}" data-css="${css}">${head}${opts}</select></div><span></span></div>`;
2826
+ return `<div class="uiv-ctl"><span class="clabel uiv-tlabel">${escapeHtml(t(label))}</span><div class="cfield"><select class="uiv-sel uiv-tokensel${edited ? " changed" : ""}" data-css="${css}">${head}${opts}</select></div><span></span></div>`;
2661
2827
  }
2662
2828
  controlRow(c) {
2663
2829
  if (c.kind === "box") {
@@ -2665,7 +2831,7 @@ var Uivisor = class {
2665
2831
  const info = this.numInfo(cssList);
2666
2832
  const changed = this.isChanged(cssList);
2667
2833
  const open = this.expanded.has(c.key);
2668
- let html = `<div class="uiv-ctl${this.controlStateClass(cssList)}">` + this.ctlLabel(c.label, cssList) + `<div class="cfield">${this.numField(cssList.join(","), info.mixed ? "" : info.value, c.icon, changed, false, info.mixed ? "Mixed" : "\u2014")}</div><button class="uiv-expand${open ? " on" : ""}" data-key="${c.key}" title="Edit each side individually">${open ? ICONS.collapse : ICONS.expand}</button></div>`;
2834
+ let html = `<div class="uiv-ctl${this.controlStateClass(cssList)}">` + this.ctlLabel(c.label, cssList) + `<div class="cfield">${this.numField(cssList.join(","), info.mixed ? "" : info.value, c.icon, changed, false, info.mixed ? t("Mixed") : "\u2014")}</div><button class="uiv-expand${open ? " on" : ""}" data-key="${c.key}" title="${escapeAttr(t("Edit each side individually"))}">${open ? ICONS.collapse : ICONS.expand}</button></div>`;
2669
2835
  if (open) {
2670
2836
  html += `<div class="uiv-sides">` + c.sides.map((s) => {
2671
2837
  const v = this.liveNum(s.css);
@@ -2702,7 +2868,7 @@ var Uivisor = class {
2702
2868
  }
2703
2869
  bindControls() {
2704
2870
  const root = this.root;
2705
- root.querySelectorAll(".uiv-tbtn").forEach((node) => {
2871
+ root.querySelectorAll(".uiv-fbtn").forEach((node) => {
2706
2872
  const btn = node;
2707
2873
  const prop = btn.getAttribute("data-prop");
2708
2874
  const val = btn.getAttribute("data-val");
@@ -2770,7 +2936,7 @@ var Uivisor = class {
2770
2936
  const cssVar = btn.getAttribute("data-var");
2771
2937
  btn.addEventListener("mousedown", (e) => {
2772
2938
  e.preventDefault();
2773
- const token = this.designSystem().tokens.find((t) => t.cssVar === cssVar);
2939
+ const token = this.designSystem().tokens.find((t2) => t2.cssVar === cssVar);
2774
2940
  if (token) this.applyToken(this.lastBmSide, token);
2775
2941
  });
2776
2942
  });
@@ -2826,7 +2992,7 @@ var Uivisor = class {
2826
2992
  const css = sel.getAttribute("data-css");
2827
2993
  sel.addEventListener("change", () => {
2828
2994
  if (!sel.value) return;
2829
- const token = this.designSystem().tokens.find((t) => t.cssVar === sel.value);
2995
+ const token = this.designSystem().tokens.find((t2) => t2.cssVar === sel.value);
2830
2996
  if (token) this.applyToken(css, token);
2831
2997
  });
2832
2998
  });
@@ -2835,7 +3001,7 @@ var Uivisor = class {
2835
3001
  const css = btn.getAttribute("data-css");
2836
3002
  const cssVar = btn.getAttribute("data-var");
2837
3003
  btn.addEventListener("click", () => {
2838
- const token = this.designSystem().tokens.find((t) => t.cssVar === cssVar);
3004
+ const token = this.designSystem().tokens.find((t2) => t2.cssVar === cssVar);
2839
3005
  if (token) this.applyToken(css, token);
2840
3006
  });
2841
3007
  });
@@ -2870,22 +3036,24 @@ var Uivisor = class {
2870
3036
  const bp = btn.getAttribute("data-bp");
2871
3037
  btn.addEventListener("click", () => {
2872
3038
  this.pickedBp = bp;
2873
- this.setFrameWidth(bp === "base" ? this.defaultFrameWidth() : this.scopeWidth(bp));
3039
+ this.setFrameWidth(bp === "base" ? this.baseFrameWidth() : this.scopeWidth(bp));
2874
3040
  this.reapplyScope();
2875
3041
  this.renderBody();
2876
3042
  });
2877
3043
  });
2878
- root.querySelectorAll(".uiv-clschip").forEach((node) => {
2879
- const btn = node;
2880
- const target = btn.getAttribute("data-target");
2881
- btn.addEventListener("click", () => {
3044
+ const targetSel = root.querySelector(".uiv-targetsel");
3045
+ if (targetSel) {
3046
+ targetSel.addEventListener("change", () => {
2882
3047
  const st = this.st();
2883
- if (st && st.record.target !== target) this.pushHistory();
2884
- if (st) st.record.target = target;
3048
+ if (!st) return;
3049
+ const next = targetSel.value === "__new__" ? st.record.target.startsWith("new:") ? st.record.target : "new:" : targetSel.value;
3050
+ if (st.record.target !== next) this.pushHistory();
3051
+ st.record.target = next;
2885
3052
  this.reapplyForTarget();
2886
3053
  this.renderBody();
3054
+ if (next === "new:") this.root.querySelector(".uiv-newclass")?.focus();
2887
3055
  });
2888
- });
3056
+ }
2889
3057
  root.querySelectorAll(".uiv-newclass").forEach((node) => {
2890
3058
  const input = node;
2891
3059
  input.addEventListener("change", () => {
@@ -3041,13 +3209,13 @@ var Uivisor = class {
3041
3209
  }
3042
3210
  async copyPrompt() {
3043
3211
  const recs = this.records();
3044
- if (!recs.length) return this.showToast("No tweaks recorded yet");
3212
+ if (!recs.length) return this.showToast(t("No tweaks recorded yet"));
3045
3213
  await this.copy(renderPrompt(recs));
3046
- this.showToast("Prompt copied \u2713");
3214
+ this.showToast(t("Prompt copied \u2713"));
3047
3215
  }
3048
3216
  async copyJSON() {
3049
3217
  const recs = this.records();
3050
- if (!recs.length) return this.showToast("No tweaks recorded yet");
3218
+ if (!recs.length) return this.showToast(t("No tweaks recorded yet"));
3051
3219
  const spec = renderSpec(recs, {
3052
3220
  url: location.href,
3053
3221
  width: window.innerWidth,
@@ -3056,7 +3224,7 @@ var Uivisor = class {
3056
3224
  now: (/* @__PURE__ */ new Date()).toISOString()
3057
3225
  });
3058
3226
  await this.copy(JSON.stringify(spec, null, 2));
3059
- this.showToast("JSON copied \u2713");
3227
+ this.showToast(t("JSON copied \u2713"));
3060
3228
  }
3061
3229
  resetSelected() {
3062
3230
  const el = this.selected;
@@ -3120,16 +3288,16 @@ var Uivisor = class {
3120
3288
  this.renderBody();
3121
3289
  }
3122
3290
  undo() {
3123
- if (!this.undoStack.length) return this.showToast("Nothing to undo");
3291
+ if (!this.undoStack.length) return this.showToast(t("Nothing to undo"));
3124
3292
  this.redoStack.push(this.cloneSnap());
3125
3293
  this.applySnap(this.undoStack.pop());
3126
- this.showToast("Undo \u21A9");
3294
+ this.showToast(t("Undo \u21A9"));
3127
3295
  }
3128
3296
  redo() {
3129
- if (!this.redoStack.length) return this.showToast("Nothing to redo");
3297
+ if (!this.redoStack.length) return this.showToast(t("Nothing to redo"));
3130
3298
  this.undoStack.push(this.cloneSnap());
3131
3299
  this.applySnap(this.redoStack.pop());
3132
- this.showToast("Redo \u21AA");
3300
+ this.showToast(t("Redo \u21AA"));
3133
3301
  }
3134
3302
  async copy(text) {
3135
3303
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uivisor",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "description": "Dev-only visual UI tweaker that turns mouse edits into a precise, breakpoint-aware prompt for your AI coding agent — without touching your source.",
6
6
  "license": "MIT",