@open-slot-ui/pixi 0.9.0 → 0.11.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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ControlView, ButtonView, SliderView, buildBlockColumn, OpenUIPixi } from './chunk-Z7X5JH7H.js';
2
2
  export { AutoplayDrawerView, AutoplayView, ButtonView, ControlView, DialogView, MenuView, OpenUIPixi, ReadoutView, SelectView, SliderView, SpinView, StepperView, TextCellRenderer, ToggleView, TurboView, Tweener, ValueDisplayView, buildBlockColumn, defaultSpinSkin, drawSpin, isDesktop, svgSpinSkin } from './chunk-Z7X5JH7H.js';
3
3
  import { Text, Graphics, Container, Rectangle } from 'pixi.js';
4
- import { LOCALE_LABELS, createUI, composeMenu, buildPanel, modeStatsItems, formatAmountPrecise, factsVars, auditRules } from '@open-slot-ui/core';
4
+ import { INFO_MENU_VARS, escapeHtml, LOCALE_LABELS, renderBlocksHtml, INFO_MENU_CSS, createUI, composeMenu, buildPanel, formatAmountPrecise, factsVars, renderRulesAuditHtml, auditRules } from '@open-slot-ui/core';
5
5
 
6
6
  var RISE = 26;
7
7
  var STAGGER = 38;
@@ -278,101 +278,16 @@ var PanelBodyView = class extends ControlView {
278
278
  super.dispose();
279
279
  }
280
280
  };
281
- var esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
282
- var rich = (s) => esc(s).replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
283
- function statGridHtml(items, tr) {
284
- const rows = items.map((it) => `<div><dt>${esc(tr(it.label))}</dt><dd>${esc(tr(it.value))}</dd></div>`).join("");
285
- return `<dl class="ohm-stats">${rows}</dl>`;
286
- }
287
- function renderBlocks(blocks, tr, facts) {
288
- const out = [];
289
- for (const b of blocks) {
290
- switch (b.kind) {
291
- case "text":
292
- out.push(`<p>${rich(tr(b.text))}</p>`);
293
- break;
294
- case "heading":
295
- out.push(`<div class="ohm-sec"><span>${esc(tr(b.text))}</span></div>`);
296
- break;
297
- case "subheading":
298
- out.push(`<h4 class="ohm-subh">${esc(tr(b.text))}</h4>`);
299
- break;
300
- case "legal":
301
- out.push(`<p class="ohm-legal">${rich(tr(b.text))}</p>`);
302
- break;
303
- case "divider":
304
- out.push('<hr class="ohm-hr">');
305
- break;
306
- case "image":
307
- out.push(`<img class="ohm-feature" alt="${esc(tr(b.alt ?? ""))}" src="${b.src}" loading="lazy">`);
308
- break;
309
- case "media": {
310
- const img = `<img alt="${esc(tr(b.alt ?? ""))}" src="${b.src}" loading="lazy">`;
311
- const body = `<div class="ohm-media-body">${b.title ? `<h4>${esc(tr(b.title))}</h4>` : ""}<p>${rich(tr(b.text))}</p></div>`;
312
- out.push(`<div class="ohm-media ohm-media--${b.side ?? "left"}">${img}${body}</div>`);
313
- break;
314
- }
315
- case "cards": {
316
- const cards = b.items.map((it) => `<div class="ohm-fcard">${it.icon ? `<img src="${it.icon}" alt="" loading="lazy">` : ""}<h5>${esc(tr(it.title))}</h5>${it.text ? `<p>${rich(tr(it.text))}</p>` : ""}</div>`).join("");
317
- out.push(`<div class="ohm-cards">${cards}</div>`);
318
- break;
319
- }
320
- case "paytable":
321
- out.push(`<div class="ohm-grid">${renderPaytable(b.rows, tr)}</div>`);
322
- break;
323
- case "table": {
324
- const head = b.columns?.length ? `<thead><tr>${b.columns.map((c) => `<th>${esc(tr(c))}</th>`).join("")}</tr></thead>` : "";
325
- const body = b.rows.map((r) => `<tr>${r.map((c) => `<td>${esc(tr(c))}</td>`).join("")}</tr>`).join("");
326
- out.push(`<table class="ohm-table">${head}<tbody>${body}</tbody></table>`);
327
- break;
328
- }
329
- case "stat-grid":
330
- out.push(statGridHtml(b.items, tr));
331
- break;
332
- // The AUTO per-mode RTP / Max-win grid — rendered straight from the declared
333
- // game facts (+ any host extras), so the table can never drift from the config.
334
- // `modeStatsItems` localizes the label PARTS itself ("Max win" · the mode name),
335
- // so the grid renderer gets identity-tr — no double translation.
336
- case "mode-stats": {
337
- const extras = (b.extras ?? []).map((e) => ({ label: tr(e.label), value: tr(e.value) }));
338
- out.push(statGridHtml([...modeStatsItems(facts, tr), ...extras], (s) => s));
339
- break;
340
- }
341
- case "steps": {
342
- const items = b.items.map((s) => `<li>${rich(tr(s))}</li>`).join("");
343
- out.push(b.ordered ? `<ol class="ohm-steps">${items}</ol>` : `<ul class="ohm-steps">${items}</ul>`);
344
- break;
345
- }
346
- case "callout":
347
- out.push(`<div class="ohm-callout ohm-callout--${b.tone ?? "info"}">${b.title ? `<b>${esc(tr(b.title))}</b>` : ""}<p>${rich(tr(b.text))}</p></div>`);
348
- break;
349
- case "group":
350
- out.push(`<div class="ohm-group">${b.title ? `<h4 class="ohm-subh">${esc(tr(b.title))}</h4>` : ""}${renderBlocks(b.children, tr)}</div>`);
351
- break;
352
- }
353
- }
354
- return out.join("\n");
355
- }
356
- function renderPaytable(rows, tr) {
357
- return rows.map((r) => {
358
- const icon = r.icon ? `<img class="ohm-symimg" src="${r.icon}" alt="" loading="lazy">` : `<span class="ohm-emoji">${esc(tr(r.symbol ?? ""))}</span>`;
359
- const lines = r.payouts.split("\n").map((line) => {
360
- const i = line.indexOf(":");
361
- return i >= 0 ? `<div><b>${esc(line.slice(0, i))}</b><span>${esc(line.slice(i + 1))}</span></div>` : `<div><span>${esc(line)}</span></div>`;
362
- }).join("");
363
- return `<div class="ohm-sym">${icon}<div class="ohm-pay">${lines}</div></div>`;
364
- }).join("");
365
- }
366
281
  function renderSettingBlock(b, tr) {
367
- const hint = "hint" in b && b.hint ? `<div class="ohm-hint">${esc(tr(b.hint))}</div>` : "";
368
- const label = "label" in b && b.label ? esc(tr(b.label)) : "";
282
+ const hint = "hint" in b && b.hint ? `<div class="ohm-hint">${escapeHtml(tr(b.hint))}</div>` : "";
283
+ const label = "label" in b && b.label ? escapeHtml(tr(b.label)) : "";
369
284
  switch (b.kind) {
370
285
  case "toggle":
371
286
  return `<div class="ohm-setting"><label class="ohm-row ohm-check"><span>${label}</span><span class="ohm-ctl"><input data-set="${b.id}" type="checkbox"${b.on ? " checked" : ""}></span></label>${hint}</div>`;
372
287
  case "slider":
373
288
  return `<div class="ohm-setting"><label class="ohm-row"><span>${label}</span><input data-set="${b.id}" type="range" min="0" max="1" step="0.01" value="${b.initial ?? 1}"></label>${hint}</div>`;
374
289
  case "select": {
375
- const opts = b.options.map((o, i) => `<option value="${esc(o.value)}"${i === (b.index ?? 0) ? " selected" : ""}>${esc(tr(o.label))}</option>`).join("");
290
+ const opts = b.options.map((o, i) => `<option value="${escapeHtml(o.value)}"${i === (b.index ?? 0) ? " selected" : ""}>${escapeHtml(tr(o.label))}</option>`).join("");
376
291
  return `<div class="ohm-setting"><label class="ohm-row"><span>${label}</span><select data-set="${b.id}">${opts}</select></label>${hint}</div>`;
377
292
  }
378
293
  default:
@@ -385,25 +300,16 @@ function mountInfoMenu(app, ui) {
385
300
  const disposers = [];
386
301
  const host2 = document.createElement("div");
387
302
  host2.className = "ohm-root";
388
- const vars = {
389
- "--accent": "#d99000",
390
- "--accent-text": "#1a1200",
391
- "--surface": "#ffffff",
392
- "--surface-alt": "#eef1f6",
393
- "--text": "#181b20",
394
- "--text-dim": "#5b6472",
395
- "--card-radius": "8px",
396
- "--font": ui.theme.type.family
397
- };
303
+ const vars = { ...INFO_MENU_VARS, "--font": ui.theme.type.family };
398
304
  for (const [k, v] of Object.entries(vars)) host2.style.setProperty(k, v);
399
305
  const locales = ui.spec?.locale ? Array.from(/* @__PURE__ */ new Set([ui.spec.locale.locale, ...Object.keys(ui.spec.locale.messages)])) : [];
400
- const langRow = locales.length > 1 ? `<div class="ohm-setting"><label class="ohm-row"><span>${tr("Language")}</span><select id="ohm-lang">${locales.map((c) => `<option value="${c}"${c === ui.locale.get() ? " selected" : ""}>${esc(LOCALE_LABELS[c] ?? c)}</option>`).join("")}</select></label></div>` : "";
306
+ const langRow = locales.length > 1 ? `<div class="ohm-setting"><label class="ohm-row"><span>${tr("Language")}</span><select id="ohm-lang">${locales.map((c) => `<option value="${c}"${c === ui.locale.get() ? " selected" : ""}>${escapeHtml(LOCALE_LABELS[c] ?? c)}</option>`).join("")}</select></label></div>` : "";
401
307
  const turbo = ui.turbo;
402
308
  const cap = (m) => m.charAt(0).toUpperCase() + m.slice(1);
403
- const turboCtl = turbo.modeCount <= 2 ? `<span class="ohm-ctl"><input id="ohm-turbo" type="checkbox"></span>` : `<span class="ohm-ctl"><div class="ohm-segmented" id="ohm-turbo-seg">${turbo.modes.map((m, i) => `<button class="ohm-seg" data-i="${i}">${esc(tr(cap(m)))}</button>`).join("")}</div></span>`;
309
+ const turboCtl = turbo.modeCount <= 2 ? `<span class="ohm-ctl"><input id="ohm-turbo" type="checkbox"></span>` : `<span class="ohm-ctl"><div class="ohm-segmented" id="ohm-turbo-seg">${turbo.modes.map((m, i) => `<button class="ohm-seg" data-i="${i}">${escapeHtml(tr(cap(m)))}</button>`).join("")}</div></span>`;
404
310
  const customSettings = (menu.settings ?? []).map((b) => renderSettingBlock(b, tr)).join("");
405
311
  const soundMode = menu.sound ?? "sliders";
406
- const volRow = (id, label, hint, val) => `<div class="ohm-setting"><label class="ohm-row"><span>${esc(tr(label))}</span><input class="ohm-slider" data-vol="${id}" type="range" min="0" max="1" step="0.01" value="${val}"></label><div class="ohm-hint">${esc(tr(hint))}</div></div>`;
312
+ const volRow = (id, label, hint, val) => `<div class="ohm-setting"><label class="ohm-row"><span>${escapeHtml(tr(label))}</span><input class="ohm-slider" data-vol="${id}" type="range" min="0" max="1" step="0.01" value="${val}"></label><div class="ohm-hint">${escapeHtml(tr(hint))}</div></div>`;
407
313
  const soundSliders = soundMode === "master" ? volRow("master", "Volume", "Adjust the overall game volume.", ui.sfxSlider.value.get()) : soundMode === "sliders" ? volRow("music", "Music", "Adjust the background music volume.", ui.musicSlider.value.get()) + volRow("sfx", "Effects", "Adjust the sound-effects volume.", ui.sfxSlider.value.get()) : "";
408
314
  const rulesTr = () => {
409
315
  const vars2 = factsVars(ui.facts.get(), { "game.name": ui.gameInfo.name ?? "", "game.version": ui.gameInfo.version ?? "" });
@@ -411,25 +317,16 @@ function mountInfoMenu(app, ui) {
411
317
  };
412
318
  const auditCardHtml = (trr) => {
413
319
  if (!menu.rules?.length) return "";
414
- const issues = auditRules(ui.facts.get(), menu.rules, { resolve: trr });
415
- if (!issues.length) return "";
416
- const req = issues.filter((i) => i.level === "required");
417
- const rec = issues.filter((i) => i.level === "recommended");
418
- const list = (items) => `<ul>${items.map((i) => `<li>${esc(tr(i.message))}</li>`).join("")}</ul>`;
419
- return `<div class="ohm-audit" role="alert">
420
- <div class="ohm-audit-title">\u26A0 ${esc(tr("openui.rulesAudit.title"))}</div>
421
- ${req.length ? `<div class="ohm-audit-sub">${esc(tr("openui.rulesAudit.required"))}</div>${list(req)}` : ""}
422
- ${rec.length ? `<div class="ohm-audit-sub ohm-audit-sub--rec">${esc(tr("openui.rulesAudit.recommended"))}</div><div class="ohm-audit-rec">${list(rec)}</div>` : ""}
423
- </div>`;
320
+ return renderRulesAuditHtml(auditRules(ui.facts.get(), menu.rules, { resolve: trr }), tr);
424
321
  };
425
322
  const rulesInnerHtml = () => {
426
323
  if (!menu.rules) return "";
427
324
  const trr = rulesTr();
428
- return auditCardHtml(trr) + renderBlocks(menu.rules, trr, ui.facts.get());
325
+ return auditCardHtml(trr) + renderBlocksHtml(menu.rules, trr, ui.facts.get());
429
326
  };
430
- const paytableHtml = menu.paytable ? renderBlocks(menu.paytable, tr, ui.facts.get()) : "";
327
+ const paytableHtml = menu.paytable ? renderBlocksHtml(menu.paytable, tr, ui.facts.get()) : "";
431
328
  const rulesHtml = rulesInnerHtml();
432
- const banner = menu.banner ? `<img class="ohm-logo" alt="" src="${menu.banner.src}"${menu.banner.width ? ` width="${menu.banner.width}"` : ""}${menu.banner.height ? ` height="${menu.banner.height}"` : ""}>` : ui.gameInfo.name ? `<h1 class="ohm-logo-text">${esc(ui.gameInfo.name)}</h1>` : "";
329
+ const banner = menu.banner ? `<img class="ohm-logo" alt="" src="${menu.banner.src}"${menu.banner.width ? ` width="${menu.banner.width}"` : ""}${menu.banner.height ? ` height="${menu.banner.height}"` : ""}>` : ui.gameInfo.name ? `<h1 class="ohm-logo-text">${escapeHtml(ui.gameInfo.name)}</h1>` : "";
433
330
  host2.innerHTML = `
434
331
  <div class="ohm-backdrop" data-close></div>
435
332
  <button class="ohm-x" data-close aria-label="Close">\u2715</button>
@@ -447,7 +344,7 @@ function mountInfoMenu(app, ui) {
447
344
  </div>
448
345
  </div>`;
449
346
  const style = document.createElement("style");
450
- style.textContent = OHM_CSS;
347
+ style.textContent = INFO_MENU_CSS;
451
348
  host2.appendChild(style);
452
349
  document.body.appendChild(host2);
453
350
  const $ = (sel) => host2.querySelector(sel);
@@ -537,91 +434,6 @@ function mountInfoMenu(app, ui) {
537
434
  host2.remove();
538
435
  };
539
436
  }
540
- var OHM_CSS = `
541
- .ohm-root { position: fixed; inset: 0; z-index: 10000; display: grid; place-items: center; font-family: var(--font); opacity: 0; pointer-events: none; transition: opacity .18s ease; }
542
- .ohm-root.open { opacity: 1; pointer-events: auto; }
543
- .ohm-backdrop { position: absolute; inset: 0; background: rgba(8,6,4,0); backdrop-filter: blur(0px) saturate(1); -webkit-backdrop-filter: blur(0px) saturate(1); transition: background .4s ease, backdrop-filter .4s ease, -webkit-backdrop-filter .4s ease; }
544
- .ohm-root.open .ohm-backdrop { background: rgba(8,6,4,.34); backdrop-filter: blur(6px) saturate(1.1); -webkit-backdrop-filter: blur(6px) saturate(1.1); }
545
- .ohm-card { position: relative; width: min(92%, 1100px); max-height: 86vh; display: flex; flex-direction: column; background: var(--surface); color: var(--text); border: 1.5px solid #000; border-radius: var(--card-radius); box-shadow: 0 30px 80px rgba(0,0,0,.5); overflow: hidden; transform: translateY(8px) scale(.99); transition: transform .18s ease; }
546
- .ohm-root.open .ohm-card { transform: none; }
547
- .ohm-x { position: absolute; top: 18px; right: 22px; width: 46px; height: 46px; border-radius: 999px; border: 0; background: rgba(18,14,10,.82); color: #fff; font-size: 18px; cursor: pointer; display: grid; place-items: center; box-shadow: 0 6px 18px rgba(0,0,0,.45); z-index: 2; transition: transform .12s, background .12s; }
548
- .ohm-x:hover { transform: scale(1.08); background: rgba(18,14,10,.95); }
549
- .ohm-body { padding: 24px 26px 26px; overflow-y: scroll; }
550
- .ohm-body::-webkit-scrollbar { width: 18px; }
551
- .ohm-body::-webkit-scrollbar-track { background: transparent; margin: 12px 0; }
552
- .ohm-body::-webkit-scrollbar-thumb { background-color: #111; border: 6px solid transparent; background-clip: padding-box; border-radius: 999px; min-height: 44px; }
553
- .ohm-body::-webkit-scrollbar-thumb:hover { background-color: #000; }
554
- .ohm-logo { display: block; margin: 6px auto 18px; max-width: 64%; height: auto; }
555
- .ohm-logo-text { margin: 6px 0 18px; text-align: center; font-size: 30px; font-weight: 900; letter-spacing: 1px; color: var(--text); }
556
- .ohm-sec { display: flex; align-items: center; gap: 14px; margin: 26px 0 14px; color: var(--text); font-weight: 800; letter-spacing: 1px; }
557
- .ohm-sec::before, .ohm-sec::after { content: ""; flex: 1; height: 2px; background: color-mix(in srgb, var(--text) 80%, transparent); border-radius: 2px; }
558
- .ohm-root *, .ohm-root *::before, .ohm-root *::after { box-sizing: border-box; }
559
- .ohm-row { display: flex; align-items: center; gap: 16px; margin: 14px 0; font-weight: 700; }
560
- .ohm-setting { margin: 14px 0; }
561
- .ohm-setting .ohm-row { margin: 0; }
562
- .ohm-hint { margin: 3px 0 0; font-size: 12.5px; font-weight: 500; color: var(--text-dim); }
563
- .ohm-row > span:first-child { flex: none; min-width: 110px; }
564
- .ohm-row input[type=range] { flex: 1; min-width: 0; max-width: min(440px, 60dvw); accent-color: var(--accent); height: 6px; }
565
- .ohm-row select { flex: 1; min-width: 0; max-width: min(440px, 60dvw); padding: 11px 14px; border-radius: 4px; border: 2px solid var(--accent); background: var(--surface-alt); color: var(--text); font-weight: 700; font-size: 15px; cursor: pointer; }
566
- .ohm-row select option { background: var(--surface); color: var(--text); font-weight: 600; }
567
- .ohm-row select option:checked { background: var(--accent); color: var(--accent-text); }
568
- .ohm-ctl { flex: 1; min-width: 0; max-width: min(440px, 60dvw); display: flex; align-items: center; justify-content: flex-end; }
569
- .ohm-check input[type=checkbox] { appearance: none; -webkit-appearance: none; width: 50px; height: 28px; border-radius: 999px; background: color-mix(in srgb, var(--text-dim) 38%, transparent); position: relative; cursor: pointer; transition: background .15s; flex: none; }
570
- .ohm-check input[type=checkbox]:checked { background: var(--accent); }
571
- .ohm-check input[type=checkbox]::before { content: ""; position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: 999px; background: #fff; transition: left .15s; box-shadow: 0 1px 3px rgba(0,0,0,.3); }
572
- .ohm-check input[type=checkbox]:checked::before { left: 25px; }
573
- .ohm-segmented { display: inline-flex; gap: 4px; padding: 4px; background: var(--surface-alt); border-radius: 999px; border: 1px solid color-mix(in srgb, var(--text-dim) 30%, transparent); }
574
- .ohm-seg { border: 0; background: transparent; color: var(--text-dim); font-weight: 700; font-size: 14px; padding: 8px 18px; border-radius: 999px; cursor: pointer; transition: background .12s, color .12s; }
575
- .ohm-seg.active { background: var(--accent); color: var(--accent-text); }
576
- .ohm-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; }
577
- .ohm-sym { display: flex; align-items: center; gap: 16px; padding: 6px 4px; }
578
- .ohm-emoji { font-size: 48px; line-height: 1; filter: drop-shadow(0 2px 4px rgba(0,0,0,.3)); }
579
- .ohm-symimg { width: 56px; height: 56px; object-fit: contain; flex: none; }
580
- .ohm-pay { font-size: 13px; line-height: 1.6; }
581
- .ohm-pay div { display: flex; gap: 6px; }
582
- .ohm-pay b { min-width: 42px; }
583
- .ohm-pay span { color: var(--accent); font-weight: 700; }
584
- .ohm-body p { color: var(--text-dim); line-height: 1.6; }
585
- .ohm-body p b { color: var(--text); }
586
- .ohm-feature { display: block; width: 100%; height: auto; margin: 10px 0; }
587
- .ohm-stats { margin: 12px 0; display: grid; grid-template-columns: 1fr 1fr; gap: 0 28px; }
588
- .ohm-stats > div { display: flex; justify-content: space-between; padding: 9px 0; border-bottom: 1px solid color-mix(in srgb, var(--text-dim) 20%, transparent); }
589
- .ohm-stats dt { color: var(--text-dim); margin: 0; } .ohm-stats dd { margin: 0; font-weight: 700; }
590
- .ohm-audit { margin: 12px 0 18px; padding: 14px 16px; border-radius: 12px; border: 2px solid #d03131; background: color-mix(in srgb, #d03131 8%, transparent); }
591
- .ohm-audit-title { font-weight: 900; letter-spacing: .5px; color: #b31d1d; }
592
- .ohm-audit-sub { margin-top: 8px; font-size: 13px; font-weight: 800; color: #b31d1d; }
593
- .ohm-audit-sub--rec { color: #b07d09; }
594
- .ohm-audit ul { margin: 6px 0 0; padding-left: 20px; color: var(--text); font-size: 13.5px; line-height: 1.55; }
595
- .ohm-audit li { margin: 3px 0; }
596
- .ohm-audit-rec ul { color: var(--text-dim); }
597
- .ohm-callout { margin: 16px 0 4px; padding: 14px 16px; border-radius: 12px; border-left: 4px solid var(--accent); background: color-mix(in srgb, var(--accent) 9%, transparent); }
598
- .ohm-callout b { color: var(--accent); } .ohm-callout p { margin: 4px 0 0; color: var(--text); }
599
- .ohm-callout--warning { border-left-color: #e0a106; background: color-mix(in srgb, #e0a106 10%, transparent); }
600
- .ohm-callout--warning b { color: #b07d09; }
601
- .ohm-subh { margin: 22px 0 8px; font-size: 15px; font-weight: 800; letter-spacing: .5px; color: var(--text); }
602
- .ohm-legal { font-size: 12px; line-height: 1.6; color: var(--text-dim); opacity: .85; }
603
- .ohm-hr { border: 0; border-top: 1px solid color-mix(in srgb, var(--text-dim) 30%, transparent); margin: 18px 0; }
604
- .ohm-media { display: flex; align-items: center; gap: 18px; margin: 14px 0; }
605
- .ohm-media--right { flex-direction: row-reverse; }
606
- .ohm-media > img { width: 40%; max-width: 320px; height: auto; flex: none; }
607
- .ohm-media-body { flex: 1; }
608
- .ohm-media-body h4 { margin: 0 0 6px; font-size: 16px; color: var(--text); }
609
- .ohm-media-body p { margin: 0; }
610
- .ohm-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin: 12px 0; }
611
- .ohm-fcard { padding: 8px 6px; text-align: center; }
612
- .ohm-fcard img { display: block; width: 48px; height: 48px; margin: 0 auto 8px; }
613
- .ohm-fcard h5 { margin: 0 0 4px; font-size: 14px; color: var(--text); }
614
- .ohm-fcard p { margin: 0; font-size: 12px; line-height: 1.5; }
615
- .ohm-table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 14px; }
616
- .ohm-table th { text-align: left; padding: 8px 10px; font-weight: 800; color: var(--text); border-bottom: 2px solid color-mix(in srgb, var(--text-dim) 35%, transparent); }
617
- .ohm-table td { padding: 8px 10px; border-bottom: 1px solid color-mix(in srgb, var(--text-dim) 18%, transparent); }
618
- .ohm-table td:first-child { color: var(--text); font-weight: 700; }
619
- .ohm-table td:not(:first-child) { color: var(--accent); font-weight: 700; }
620
- .ohm-steps { margin: 12px 0; padding-left: 22px; color: var(--text-dim); line-height: 1.7; }
621
- .ohm-steps li { margin: 5px 0; }
622
- .ohm-steps b { color: var(--text); }
623
- .ohm-group { margin: 8px 0; }
624
- `;
625
437
 
626
438
  // src/confirmModal.ts
627
439
  var esc2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");