Files
networkSetup/output/egbim-installer-preview.html

2794 lines
101 KiB
HTML

<!doctype html>
<html lang="en" data-visualize-standalone>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="referrer" content="no-referrer">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval' blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; style-src 'unsafe-inline' blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; img-src blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; font-src blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; media-src blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; worker-src blob:; connect-src blob: data:; frame-src 'self'; object-src 'none'; base-uri 'none'; form-action 'none'">
<title>Egbim Installer Concepts</title>
<style>:root{color-scheme:light dark;background:light-dark(rgb(255 255 255), rgb(24 24 24))}html,body{margin:0}body{box-sizing:border-box;padding:1rem;background:inherit}iframe{display:block;width:100%;height:calc(100vh - 2rem);margin:0 auto;border:0}</style>
</head>
<body>
<iframe id="codex-visualization" sandbox="allow-scripts" referrerpolicy="no-referrer" title="Egbim Installer Concepts" data-srcdoc="&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot; data-visualize-standalone&gt;
&lt;head&gt;
&lt;meta charset=&quot;utf-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
&lt;meta name=&quot;referrer&quot; content=&quot;no-referrer&quot;&gt;
&lt;meta http-equiv=&quot;Content-Security-Policy&quot; content=&quot;default-src &#x27;none&#x27;; script-src &#x27;unsafe-inline&#x27; &#x27;unsafe-eval&#x27; &#x27;wasm-unsafe-eval&#x27; blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; style-src &#x27;unsafe-inline&#x27; blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; img-src blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; font-src blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; media-src blob: data: https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://esm.sh https://fonts.bunny.net https://fonts.googleapis.com https://fonts.gstatic.com https://unpkg.com; worker-src blob:; connect-src blob: data:; frame-src &#x27;none&#x27;; object-src &#x27;none&#x27;; base-uri &#x27;none&#x27;; form-action &#x27;none&#x27;&quot;&gt;
&lt;title&gt;Egbim Installer Concepts&lt;/title&gt;
&lt;style&gt;:root {
color-scheme: light dark;
background-color: var(
--background,
var(--color-background-primary, light-dark(rgb(255 255 255), rgb(24 24 24)))
) !important;
/* Agent-facing contract; keep in sync with SKILL.md. */
--background: var(
--color-background-primary,
light-dark(rgb(255 255 255), rgb(24 24 24))
);
--foreground: var(
--color-text-primary,
light-dark(rgb(26 28 31), rgb(255 255 255))
);
--card: color-mix(in oklab, var(--foreground) 5%, var(--background));
--card-foreground: var(
--color-text-primary,
light-dark(rgb(26 28 31), rgb(255 255 255))
);
--popover: var(
--color-background-secondary,
light-dark(rgb(255 255 255), rgb(45 45 45))
);
--popover-foreground: var(
--color-text-primary,
light-dark(rgb(26 28 31), rgb(255 255 255))
);
--primary: var(
--color-text-info,
light-dark(rgb(51 156 255), rgb(131 195 255))
);
--primary-foreground: var(
--color-text-inverse,
light-dark(rgb(255 255 255), rgb(13 13 13))
);
--secondary: var(
--color-background-secondary,
light-dark(rgb(255 255 255 / 96%), rgb(54 54 54 / 96%))
);
--secondary-foreground: var(
--color-text-primary,
light-dark(rgb(26 28 31), rgb(255 255 255))
);
--muted: color-mix(in srgb, var(--foreground) 10%, transparent);
--muted-foreground: var(
--color-text-secondary,
light-dark(rgb(26 28 31 / 49.4%), rgb(255 255 255 / 49.8%))
);
--accent: var(
--color-background-info,
light-dark(rgb(229 242 255), rgb(13 39 63))
);
--accent-foreground: var(
--color-text-info,
light-dark(rgb(51 156 255), rgb(131 195 255))
);
--destructive: var(
--color-text-warning,
light-dark(rgb(226 85 7), rgb(255 133 73))
);
--border: var(
--color-border-secondary,
light-dark(rgb(26 28 31 / 8%), rgb(255 255 255 / 8.2%))
);
--input: var(
--color-border-primary,
light-dark(
rgb(26 28 31 / 11.8%),
color-mix(in oklab, rgb(0 0 0) 10%, transparent)
)
);
--ring: var(
--color-ring-primary,
light-dark(rgb(51 156 255), rgb(131 195 255 / 76%))
);
--font-size-base: var(--font-text-md-size, 16px);
--blue: light-dark(rgb(51 156 255), rgb(51 156 255));
--orange: light-dark(rgb(226 85 7), rgb(251 106 34));
--green: light-dark(rgb(0 162 64), rgb(64 201 119));
--red: light-dark(rgb(224 46 42), rgb(255 103 100));
--purple: light-dark(rgb(146 79 247), rgb(173 123 249));
--yellow: light-dark(rgb(255 195 0), rgb(255 210 64));
--viz-series-1: var(--primary);
--viz-series-2: light-dark(rgb(243 136 59), rgb(245 154 86));
--viz-series-3: light-dark(rgb(93 201 119), rgb(116 213 139));
--viz-series-4: light-dark(rgb(235 119 177), rgb(240 143 192));
--viz-series-5: light-dark(rgb(155 121 236), rgb(170 145 239));
--viz-series-6: light-dark(rgb(58 185 177), rgb(90 203 194));
/* Internal implementation variables; not part of the agent contract. */
--font-sans:
-apple-system-body, ui-sans-serif, -apple-system, system-ui, &quot;Segoe UI&quot;,
Helvetica, &quot;Apple Color Emoji&quot;, Arial, sans-serif, &quot;Segoe UI Emoji&quot;,
&quot;Segoe UI Symbol&quot;;
--font-mono:
ui-monospace, SFMono-Regular, &quot;SF Mono&quot;, Menlo, Consolas, &quot;Liberation Mono&quot;,
monospace;
--font-size-normal: max(11px, var(--font-size-base));
--font-size-tooltip: calc(var(--font-size-base) - 1px);
--font-size-small: max(11px, calc(var(--font-size-base) - 2px));
--font-size-h1: calc(var(--font-size-normal) * 1.7142857143);
--font-size-h2: calc(var(--font-size-normal) * 1.4285714286);
--font-size-h3: calc(var(--font-size-normal) * 1.2857142857);
--font-weight-normal: 400;
--font-weight-medium: 500;
--icon-size: 16px;
--line-height-normal: calc(var(--font-size-normal) * 1.5);
--line-height-tooltip: calc(var(--font-size-tooltip) * 1.4285714286);
--line-height-small: calc(var(--font-size-small) + 4px);
--corner-shape: round;
--radius: var(--border-radius-lg, 8px);
--radius-sm: calc(var(--radius) * 0.5);
--radius-md: calc(var(--radius) * 0.75);
--radius-lg: var(--radius);
--radius-2xl: calc(var(--radius) * 2);
--radius-full: 9999px;
--shadow-sm: 0 1px 2px -1px rgb(0 0 0 / 8%);
--checkmark-image: url(&quot;data:image/svg+xml,%3Csvg xmlns=&#x27;http://www.w3.org/2000/svg&#x27; viewBox=&#x27;0 0 17 17&#x27;%3E%3Cpath d=&#x27;M12.8961 3.64101C13.1297 3.41418 13.4984 3.37523 13.7779 3.56581C14.0571 3.75635 14.1554 4.11331 14.0299 4.41347L13.9615 4.53847L7.71151 13.7045C7.59411 13.8767 7.4063 13.9877 7.19881 14.0072C6.99136 14.0267 6.78564 13.9533 6.63826 13.806L2.88826 10.056L2.79842 9.9457C2.6192 9.67407 2.64927 9.30496 2.88826 9.06581C3.12738 8.82669 3.49647 8.79676 3.76815 8.97597L3.8785 9.06581L7.03084 12.2182L12.8053 3.74941L12.8961 3.64101Z&#x27;/%3E%3C/svg%3E&quot;);
/* Legacy aliases; not part of the current agent contract. */
--viz-bg: transparent;
--viz-panel: var(--card);
--viz-border: var(--border);
--viz-text: var(--foreground);
--viz-muted: var(--muted-foreground);
--viz-accent: var(--primary);
--viz-accent-text: var(--primary-foreground);
--viz-accent-bg: var(--accent);
--viz-font-size: var(--font-size-base);
--viz-warning: var(--destructive);
}
:root[data-theme=&quot;light&quot;] {
color-scheme: light;
}
:root[data-theme=&quot;dark&quot;] {
color-scheme: dark;
}
* {
box-sizing: border-box;
}
html &gt; body {
/* Preserve MCP aliases for older inline renderers without creating :root cycles. */
--color-background-primary: var(
--background,
light-dark(rgb(255 255 255), rgb(24 24 24))
);
--color-text-primary: var(
--foreground,
light-dark(rgb(26 28 31), rgb(255 255 255))
);
--color-border-secondary: var(
--border,
light-dark(rgb(26 28 31 / 8%), rgb(255 255 255 / 8.2%))
);
margin: 0;
padding: var(--visualize-paint-gutter, 0px);
color: var(--foreground);
background: transparent !important;
font-family: var(--font-sans) !important;
-webkit-font-smoothing: antialiased;
font-size: var(--font-size-base);
line-height: calc(var(--font-size-base) * 1.625);
font-weight: var(--font-weight-normal);
}
.cursor-interaction {
cursor: var(--cursor-interaction, pointer);
}
a {
color: color-mix(in srgb, var(--viz-accent) 80%, var(--viz-text) 20%);
cursor: var(--cursor-interaction, pointer);
text-decoration: none;
text-underline-offset: 2px;
font-weight: inherit;
}
a:is(:hover, :focus-visible) {
text-decoration-line: underline;
text-decoration-style: dashed;
text-decoration-thickness: 0.5px;
}
h1,
h2,
h3,
h4,
h5,
h6,
p {
margin-block: 0;
}
p,
h1,
h2,
h3,
h4,
h5,
h6,
li,
.table {
font-family: var(--font-sans);
}
h1 {
font-size: calc(var(--font-size-base) * 1.5);
line-height: 1.3333333333;
}
h2 {
font-size: calc(var(--font-size-base) * 1.25);
line-height: 1.4;
}
h3 {
font-size: calc(var(--font-size-base) * 1.125);
line-height: 1.5555555556;
}
h4 {
font-size: var(--font-size-base);
line-height: 1.5;
}
h5,
h6 {
font-size: var(--font-size-base);
line-height: calc(var(--font-size-base) * 1.625);
}
h1,
h2,
h3,
h4,
h5,
h6,
b,
strong,
th {
font-weight: 600;
}
code:not(pre code) {
display: inline;
padding: 1px 6px;
border-radius: var(--radius-sm);
corner-shape: var(--corner-shape);
background: var(--muted);
font-family: var(--font-mono);
font-size: 0.92em;
overflow-wrap: anywhere;
-webkit-box-decoration-break: clone;
box-decoration-break: clone;
word-break: break-word;
}
pre,
pre code {
font-family: var(--font-mono);
font-size: 12px;
line-height: 20px;
}
.table-responsive {
width: 100%;
overflow-x: auto;
scrollbar-width: thin;
}
.table {
width: 100%;
color: var(--foreground);
font: inherit;
text-align: start;
border-collapse: separate;
border-spacing: 0;
font-size: calc(var(--font-size-base) * 0.875);
line-height: calc(var(--font-size-base) * 1.625);
font-family: var(--font-sans);
}
.table :is(th, td) {
padding-block: 10px;
padding-inline: 0 24px;
overflow-wrap: anywhere;
border-bottom: 1px solid var(--border);
text-align: start;
vertical-align: top;
border-bottom-color: color-mix(in srgb, var(--foreground) 5%, transparent);
}
.table :is(th, td):not(:last-child) {
padding-inline-end: calc(var(--font-size-base) * 1.5);
}
.table-responsive &gt; .table :is(th, td) {
overflow-wrap: break-word;
}
.table :is(th, td):last-child {
padding-inline-end: 0;
}
.table :is(caption, thead th) {
font-weight: 600;
}
.table thead :is(th, td) {
border-bottom-color: var(--border-strong, var(--input));
padding-block: calc(var(--font-size-base) / 2);
line-height: var(--font-size-base);
}
.table tbody tr:last-child :is(th, td) {
border-bottom: 0;
}
.table tbody :is(th, td) {
padding-block: calc(var(--font-size-base) * 0.625);
}
.table.table-sm :is(th, td) {
padding-block: 8px;
}
.table.table-sm :is(th, td):not(:last-child) {
padding-inline-end: 16px;
}
.table :is(.text-end, [align=&quot;right&quot;]) {
text-align: end;
font-variant-numeric: tabular-nums;
}
.table :is(.text-center, [align=&quot;center&quot;]) {
text-align: center;
}
.table .text-nowrap {
white-space: nowrap;
}
#widget {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
padding: 0;
background: transparent !important;
}
.card {
min-width: 0;
padding: 12px;
overflow: hidden;
overflow-wrap: break-word;
border-radius: var(--radius-2xl);
corner-shape: var(--corner-shape);
color: var(--card-foreground);
background: var(--card);
}
.widget {
min-width: 0;
overflow: hidden;
border: 0;
border-radius: var(--radius-lg);
corner-shape: var(--corner-shape);
box-shadow: 0 0 0 0.5px var(--border-strong, var(--border));
color: var(--foreground);
background: var(--background);
}
#widget &gt; :not(.card, .widget, .progress) {
width: 100% !important;
max-width: none !important;
margin: 0 !important;
padding: 0 !important;
border: 0 !important;
border-radius: 0 !important;
background: transparent !important;
box-shadow: none !important;
}
.tooltip {
position: fixed;
z-index: 50;
top: 0;
left: 0;
width: max-content;
max-width: min(
20rem,
var(--tooltip-available-width, calc(100vw - 10px)),
calc(100vw - 10px)
);
max-height: min(
var(--tooltip-available-height, calc(100vh - 10px)),
calc(100vh - 10px)
);
border: 1px solid var(--border);
corner-shape: var(--corner-shape);
overflow-wrap: break-word;
white-space: normal;
pointer-events: none;
user-select: none;
font-family: var(--font-sans);
padding: 5px 12px;
border-color: color-mix(
in oklab,
light-dark(var(--primary-foreground), var(--foreground)) 5%,
transparent
);
border-radius: var(--radius-2xl);
color: light-dark(var(--primary-foreground), var(--foreground));
background: light-dark(var(--foreground), var(--popover));
box-shadow: 0 8px 18px rgb(15 23 42 / 20%);
letter-spacing: -0.15px;
font-size: calc(var(--font-size-base) * 14 / 16);
font-size: round(nearest, calc(var(--font-size-base) * 14 / 16), 1px);
font-weight: 600;
line-height: 18px;
text-align: center;
}
.viz-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(max(180px, 24%), 1fr));
gap: 10px;
}
.viz-stat {
display: flex;
flex-direction: column;
gap: 2px;
}
.viz-stat-value {
font-size: var(--font-size-h2);
font-weight: var(--font-weight-medium);
line-height: 1.25;
}
.viz-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
}
hr {
width: 100%;
height: 1px;
margin-block: 6px;
border: 0;
background: var(--border);
}
.nav {
display: flex;
min-width: 0;
align-items: center;
gap: 4px;
}
.progress {
display: flex;
height: 8px;
margin-block: calc((var(--line-height-normal, 1.5em) - 8px) / 2);
overflow: hidden;
border: 0;
border-radius: var(--radius-full);
background: var(--muted);
}
.progress-bar:only-child {
border-radius: var(--radius-full);
}
.progress-bar {
height: 100%;
flex-shrink: 0;
border: 0;
background: var(--viz-series-1);
}
.viz-badge {
display: inline-flex;
align-items: center;
padding: 4px 8px;
border-radius: var(--radius-sm);
color: color-mix(in srgb, var(--foreground) 40%, var(--muted-foreground));
background: color-mix(in srgb, var(--foreground) 5%, transparent);
font-family: var(--font-sans);
font-size: calc(var(--font-size-base) * 14 / 16);
font-size: round(nearest, calc(var(--font-size-base) * 14 / 16), 1px);
font-weight: var(--font-weight-normal);
line-height: 1;
}
small,
.text-small {
font-size: var(--font-size-small);
line-height: var(--line-height-small);
}
.text-muted {
color: var(--muted-foreground);
}
.tabular-nums {
font-variant-numeric: tabular-nums;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.viz-controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.viz-controls &gt; .form-label {
display: grid;
min-width: min(100%, 260px);
flex: 1 1 280px;
grid-template-columns: minmax(0, 1fr) auto;
align-items: baseline;
gap: 2px 12px;
margin-bottom: 0;
}
.viz-controls &gt; .form-label &gt; :is(.form-control, .form-range, .form-select) {
grid-column: 1 / -1;
}
.btn,
.nav-link,
.form-check-input,
.form-control,
.form-range,
.form-select {
font: inherit;
}
/* Match inline alignment without overriding a containing flex or grid layout. */
.btn,
.nav-link,
.form-control,
.form-select,
.form-range,
.form-check {
vertical-align: middle;
}
.btn,
.nav-link {
appearance: button;
display: inline-flex;
inline-size: fit-content;
max-inline-size: 100%;
align-items: center;
justify-content: center;
gap: 4px;
margin: 0;
padding: 0 8px;
-webkit-app-region: no-drag;
border: 1px solid var(--input);
corner-shape: var(--corner-shape);
cursor: var(--cursor-interaction, pointer);
text-align: center;
text-decoration: none;
white-space: nowrap;
user-select: none;
-webkit-font-smoothing: inherit;
font-family: var(--font-sans);
height: fit-content;
min-height: calc(12px + 2 * var(--control-spacing, 12px));
padding-inline: var(--control-spacing, 12px);
border-color: transparent;
border-radius: var(--radius-full);
color: var(--foreground);
background: color-mix(in srgb, var(--foreground) 5%, transparent);
font-size: calc(var(--font-size-base) * 14 / 16);
font-size: round(nearest, calc(var(--font-size-base) * 14 / 16), 1px);
font-weight: var(--font-weight-normal);
line-height: 18px;
}
.btn {
border-color: var(--border);
color: var(--secondary-foreground);
background: var(--secondary);
}
.btn:is(.btn-block, .viz-tile) {
inline-size: 100%;
}
.btn.viz-tile {
min-width: 0;
overflow-wrap: anywhere;
white-space: normal;
}
a.btn {
cursor: var(--cursor-interaction, pointer);
text-decoration: none;
}
.btn:not(:disabled):hover {
background: light-dark(
color-mix(in srgb, var(--foreground) 5.5%, transparent),
color-mix(in srgb, var(--foreground) 8%, transparent)
);
}
.nav-link:not(:disabled):not([aria-disabled=&quot;true&quot;]):hover {
background: color-mix(in srgb, var(--foreground) 10%, transparent);
}
.btn-primary {
border-color: var(--border);
color: var(--primary-foreground);
background: var(--foreground);
background-clip: border-box;
}
.btn-primary .text-muted {
color: color-mix(in srgb, var(--primary-foreground) 50%, transparent);
}
.btn-primary:not(:disabled):hover {
background: color-mix(in srgb, var(--foreground) 80%, transparent);
background-clip: border-box;
}
.btn-ghost {
border-color: transparent;
color: var(--muted-foreground);
background: transparent;
}
.btn-ghost:not(:disabled):hover {
color: var(--muted-foreground);
background: light-dark(
color-mix(in srgb, var(--foreground) 5.5%, transparent),
color-mix(in srgb, var(--foreground) 8%, transparent)
);
}
.nav-pills .nav-link {
min-width: 0;
border-color: transparent;
color: var(--muted-foreground);
background: transparent;
}
.nav-justified .nav-link {
flex: 1 1 0;
overflow-wrap: anywhere;
white-space: normal;
}
.nav-pills .nav-link.active {
color: var(--foreground);
background: var(--muted);
}
/* Shared preview chrome; product styles stay inside each variant. */
.viz-dotted-background {
background-image: radial-gradient(
circle,
color-mix(in srgb, var(--foreground) 14%, transparent) 0.8px,
transparent 0.8px
);
background-position: center;
background-size: 16px 16px;
}
.viz-carousel {
position: relative;
min-width: 0;
}
.viz-carousel:has(&gt; .viz-carousel-controls) {
min-height: calc(60px + var(--viz-carousel-picker-height, 0px));
padding-bottom: 60px;
}
.viz-carousel &gt; [data-variant][hidden] {
display: none !important;
}
.viz-carousel &gt; [data-variant] {
isolation: isolate;
}
.viz-carousel-controls {
--viz-carousel-hover: light-dark(
color-mix(in srgb, var(--foreground) 5.4%, transparent),
color-mix(in srgb, var(--foreground) 7.8%, transparent)
);
position: absolute;
z-index: 2;
bottom: 8px;
left: 50%;
display: flex;
width: max-content;
/* Leave room for the host&#x27;s design-controls launcher at the trailing edge. */
max-width: calc(100% - 96px);
align-items: center;
gap: 0;
padding: 0;
border: 1px solid var(--border);
border-radius: 16px;
color: var(--foreground);
background: var(--background);
font-family: var(--font-sans);
/* Match the settings dropdown chrome independently of the content font size. */
font-size: 13px;
font-weight: var(--font-weight-normal);
line-height: 18px;
transform: translateX(-50%);
user-select: none;
}
/* Keep authored mockup button styles out of the shared carousel chrome. */
.viz-carousel-controls button,
.viz-carousel-picker &gt; summary {
display: flex !important;
height: auto !important;
min-height: 30px;
align-items: center !important;
justify-content: center !important;
gap: 4px;
padding: 0 6px !important;
-webkit-app-region: no-drag;
border: 0 !important;
border-radius: 14px !important;
color: inherit !important;
background: transparent !important;
font: inherit !important;
-webkit-tap-highlight-color: transparent;
}
.viz-carousel-controls &gt; button,
.viz-carousel-picker &gt; summary {
position: relative;
isolation: isolate;
}
.viz-carousel-controls &gt; button {
--icon-size: 16px;
width: 30px !important;
padding: 0 !important;
}
.viz-carousel-picker &gt; summary {
padding-inline: 12px !important;
}
.viz-carousel-controls .viz-carousel-caret {
width: 14px;
height: 14px;
flex: none;
margin: 0;
color: inherit;
transform: rotate(180deg);
}
.viz-carousel-controls &gt; button &gt; svg,
.viz-carousel-controls .viz-carousel-caret,
.viz-carousel-count {
opacity: 0.5;
}
.viz-carousel-controls &gt; button::before,
.viz-carousel-picker &gt; summary::before {
position: absolute;
z-index: -1;
inset: 3px;
border-radius: 12px;
content: &quot;&quot;;
pointer-events: none;
}
.viz-carousel-controls &gt; button:hover::before,
.viz-carousel-picker &gt; summary:hover::before {
background: var(--viz-carousel-hover);
}
.viz-carousel-controls &gt; button,
.viz-carousel-controls [data-lucide],
.viz-carousel-controls .lucide {
flex-shrink: 0;
}
.viz-carousel-picker {
min-width: 0;
}
.viz-carousel-picker &gt; summary {
min-width: 0;
list-style: none;
}
.viz-carousel-picker &gt; summary::-webkit-details-marker {
display: none;
}
.viz-carousel-count {
flex-shrink: 0;
white-space: nowrap;
}
.viz-carousel-name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.viz-carousel-options {
position: absolute;
right: 0;
bottom: calc(100% + 4px);
left: 0;
display: flex;
max-height: 240px;
flex-direction: column;
gap: 2px;
overflow: auto;
padding: 4px;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
background: var(--background);
box-shadow: 0 4px 12px light-dark(rgb(0 0 0 / 6%), rgb(0 0 0 / 25%));
}
.viz-carousel-options &gt; button {
border-radius: max(0px, calc(var(--radius-lg) - 5px)) !important;
width: 100% !important;
min-height: 28px;
justify-content: flex-start !important;
padding: 4px 8px !important;
font-size: var(--font-size-small) !important;
text-align: start;
white-space: normal;
overflow-wrap: anywhere;
}
.viz-carousel-options &gt; [aria-current=&quot;true&quot;],
.viz-carousel-options &gt; button:hover {
background: var(--viz-carousel-hover) !important;
}
.btn:disabled,
.nav-link:disabled,
.nav-link[aria-disabled=&quot;true&quot;] {
cursor: not-allowed;
opacity: 0.4;
}
[data-lucide] {
stroke-width: 1.6;
}
.form-label {
display: block;
margin-bottom: 6px;
color: var(--foreground);
}
.form-control {
display: block;
width: 100%;
padding: 0 8px;
outline: none;
border: 1px solid var(--input);
corner-shape: var(--corner-shape);
color: var(--foreground);
font-family: var(--font-sans);
height: calc(12px + 2 * var(--control-spacing, 12px));
min-height: calc(12px + 2 * var(--control-spacing, 12px));
padding-inline: calc(4px + var(--control-spacing, 12px) / 2);
border-color: var(--input);
border-radius: var(--radius-md);
background: color-mix(in srgb, var(--popover) 96%, transparent);
font-size: calc(var(--font-size-base) * 14 / 16);
font-size: round(nearest, calc(var(--font-size-base) * 14 / 16), 1px);
font-weight: var(--font-weight-normal);
line-height: 1.4285714286;
}
.form-control:is(
[type=&quot;file&quot;],
[type=&quot;date&quot;],
[type=&quot;datetime-local&quot;],
[type=&quot;month&quot;],
[type=&quot;week&quot;],
[type=&quot;color&quot;]
) {
height: calc(12px + 2 * var(--control-spacing, 12px));
min-height: calc(12px + 2 * var(--control-spacing, 12px));
line-height: 18px;
}
.form-control:is(
[type=&quot;date&quot;],
[type=&quot;datetime-local&quot;],
[type=&quot;month&quot;],
[type=&quot;week&quot;]
) {
padding-inline: var(--control-spacing, 12px);
}
.form-control::placeholder {
color: var(--muted-foreground);
}
.form-control[type=&quot;file&quot;] {
overflow: hidden;
cursor: var(--cursor-interaction, pointer);
padding: 0;
}
.form-control[type=&quot;file&quot;]::file-selector-button {
padding: 0 8px;
border: 0;
border-right: 1px solid var(--input);
color: var(--secondary-foreground);
background: var(--secondary);
cursor: inherit;
font: inherit;
min-height: calc(10px + 2 * var(--control-spacing, 12px));
padding-inline: var(--control-spacing, 12px);
margin-inline-end: var(--control-spacing, 12px);
}
.form-control[type=&quot;file&quot;]:not(:disabled):hover::file-selector-button {
background: color-mix(in srgb, var(--foreground) 6%, var(--secondary));
}
.form-control-color[type=&quot;color&quot;] {
width: 40px;
height: calc(12px + 2 * var(--control-spacing, 12px));
padding: 3px;
cursor: var(--cursor-interaction, pointer);
}
.form-control-color[type=&quot;color&quot;]::-webkit-color-swatch-wrapper {
padding: 0;
}
.form-control-color[type=&quot;color&quot;]::-webkit-color-swatch {
border: 0;
border-radius: calc(var(--radius-lg) - 4px);
corner-shape: var(--corner-shape);
}
textarea.form-control {
height: auto;
resize: vertical;
min-height: 96px;
padding: 8px 10px;
}
.form-control:focus-visible {
border-color: var(--ring);
box-shadow: none;
}
.form-control:disabled {
color: var(--muted-foreground);
background: color-mix(in srgb, var(--foreground) 5%, transparent);
opacity: 1;
}
.form-control[aria-invalid=&quot;true&quot;] {
border-color: var(--red);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 20%, transparent);
}
.form-control:disabled,
.form-select:disabled {
cursor: not-allowed;
}
.form-select {
appearance: none;
display: block;
width: 100%;
height: fit-content;
min-height: calc(12px + 2 * var(--control-spacing, 12px));
margin: 0;
/* The secondary icon role is 14px at compact density and 16px at web density. */
--icon-size: calc(10px + var(--control-spacing, 12px) / 2);
/* Settings dropdowns retain a 12px inset and a 4px text-to-chevron gap. */
padding: 0 calc(16px + var(--icon-size)) 0 12px;
outline: none;
border: 1px solid var(--border);
border-radius: var(--radius-full);
corner-shape: var(--corner-shape);
color: var(--foreground);
background-color: var(--secondary);
background-image:
linear-gradient(
45deg,
transparent calc(50% - 0.6px),
var(--muted-foreground) calc(50% - 0.6px) calc(50% + 0.6px),
transparent calc(50% + 0.6px)
),
linear-gradient(
135deg,
transparent calc(50% - 0.6px),
var(--muted-foreground) calc(50% - 0.6px) calc(50% + 0.6px),
transparent calc(50% + 0.6px)
);
background-position:
calc(100% - 12px - var(--icon-size) / 2) 50%,
calc(100% - 12px - var(--icon-size) * 0.18) 50%;
background-repeat: no-repeat;
background-size: calc(var(--icon-size) * 0.32) calc(var(--icon-size) * 0.32);
cursor: var(--cursor-interaction, default);
font-size: calc(var(--font-size-base) * 14 / 16);
font-size: round(nearest, calc(var(--font-size-base) * 14 / 16), 1px);
font-weight: var(--font-weight-normal);
line-height: 18px;
}
.form-select option {
color: var(--popover-foreground);
background-color: var(--popover);
}
.form-select:not(:disabled):hover {
background-color: light-dark(
color-mix(in srgb, var(--foreground) 5.5%, transparent),
color-mix(in srgb, var(--foreground) 8%, transparent)
);
}
.form-select:focus-visible {
box-shadow: 0 0 0 2px var(--ring);
}
.form-select:disabled {
opacity: 0.4;
}
.form-check {
display: flex;
min-height: calc(12px + 2 * var(--control-spacing, 12px));
align-items: center;
gap: 6px;
}
.form-check-input {
appearance: none;
width: 14px;
height: 14px;
flex: 0 0 auto;
margin: 0;
border: 1px solid var(--input);
color: var(--primary-foreground);
background-color: transparent;
cursor: var(--cursor-interaction, default);
transition:
background-color 150ms,
border-color 150ms,
box-shadow 150ms;
}
.form-check:not(.form-switch) .form-check-input[type=&quot;checkbox&quot;] {
corner-shape: round;
box-shadow: var(--shadow-sm);
position: relative;
width: calc(10px + var(--control-spacing, 12px) / 2);
height: calc(10px + var(--control-spacing, 12px) / 2);
border-color: var(--border-strong, var(--input));
border-radius: calc(var(--radius-sm) / 2);
background-color: transparent;
color: var(--foreground);
}
.form-check:not(.form-switch)
.form-check-input:not(:disabled):not(:checked):hover {
background-color: var(--card);
}
.form-check:not(.form-switch)
.form-check-input[type=&quot;checkbox&quot;]:is(:checked, :indeterminate) {
border-color: var(--border-strong, var(--input));
background-color: color-mix(in srgb, var(--popover) 96%, transparent);
}
.form-check:not(.form-switch)
.form-check-input[type=&quot;checkbox&quot;]:checked::before {
display: block;
width: 100%;
height: 100%;
background: var(--primary-foreground);
content: &quot;&quot;;
mask: var(--checkmark-image) center / 12px 12px no-repeat;
background-color: var(--foreground);
}
.form-check:not(.form-switch)
.form-check-input[type=&quot;checkbox&quot;]:indeterminate::before {
position: absolute;
inset: 0;
display: block;
width: 65%;
height: 6.65%;
margin: auto;
border-radius: var(--radius-full);
background: var(--foreground);
mask: none;
content: &quot;&quot;;
}
.form-check-input[type=&quot;radio&quot;] {
border-radius: var(--radius-full);
width: 16px;
height: 16px;
border: 0;
box-shadow: inset 0 0 0 1px var(--input);
}
.form-check-input[type=&quot;radio&quot;]:checked {
border: 0;
background: radial-gradient(
circle,
var(--primary-foreground) 0 3px,
var(--foreground) 3px
);
box-shadow: none;
}
.form-check:not(.form-switch) .form-check-input:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
.form-check:not(.form-switch) .form-check-input[type=&quot;checkbox&quot;]:focus-visible {
border-color: var(--ring);
outline: none;
box-shadow: 0 0 0 2px var(--ring);
}
.form-check:not(.form-switch) .form-check-input:disabled {
cursor: not-allowed;
pointer-events: none;
}
.form-check:not(.form-switch) .form-check-input:disabled + .form-check-label {
cursor: not-allowed;
}
.form-check:not(.form-switch) .form-check-input[type=&quot;radio&quot;]:disabled,
.form-check:not(.form-switch)
.form-check-input[type=&quot;radio&quot;]:disabled
+ .form-check-label {
opacity: 0.5;
}
.form-switch .form-check-input:disabled,
.form-switch .form-check-input:disabled + .form-check-label {
cursor: not-allowed;
opacity: 0.6;
}
.form-check-label {
color: var(--foreground);
cursor: var(--cursor-interaction, default);
}
.form-switch .form-check-input {
position: relative;
width: 32px;
height: 20px;
border: 0;
border-radius: var(--radius-full);
background: var(--muted);
box-shadow: none;
transition: background-color 200ms cubic-bezier(0, 0, 0.2, 1);
}
.form-switch .form-check-input::before {
position: absolute;
top: 50%;
left: 0;
width: 16px;
height: 16px;
box-sizing: border-box;
border: 1px solid light-dark(var(--primary-foreground), var(--foreground));
border-radius: var(--radius-full);
box-shadow: var(--shadow-sm);
content: &quot;&quot;;
transform: translate(2px, -50%);
transition: transform 200ms cubic-bezier(0, 0, 0.2, 1);
border-color: #fff;
background: #fff;
}
.form-switch .form-check-input:checked {
background: var(--blue);
}
.form-switch .form-check-input:checked::before {
transform: translate(14px, -50%);
}
.form-switch .form-check-input:focus-visible {
box-shadow: 0 0 0 2px var(--ring);
outline: none;
}
.form-range {
appearance: none;
display: block;
width: 100%;
flex: 1;
margin: 0;
padding: 0;
border: 0;
accent-color: var(--primary);
height: calc(12px + 2 * var(--control-spacing, 12px));
min-height: calc(12px + 2 * var(--control-spacing, 12px));
outline: none;
border-radius: var(--radius-full);
color: var(--foreground);
background: transparent;
}
.form-range::-webkit-slider-runnable-track {
background: var(--form-range-track, var(--border));
height: 2px;
border-radius: var(--radius-full);
}
.form-range::-webkit-slider-thumb {
appearance: none;
width: 20px;
height: 20px;
border: 1px solid var(--border);
border-radius: var(--radius-full);
margin-top: -9px;
border-color: transparent;
background: currentColor;
box-shadow: var(--shadow-sm);
}
/* Outline the native track and thumb, preserving the larger input hit area. */
.form-range:focus-visible::-webkit-slider-runnable-track {
outline: 2px auto Highlight;
outline-color: -webkit-focus-ring-color;
}
.form-range::-moz-range-track {
background: var(--form-range-track, var(--border));
height: 2px;
border-radius: var(--radius-full);
}
.form-range::-moz-range-thumb {
width: 20px;
height: 20px;
border: 1px solid var(--border);
border-radius: var(--radius-full);
border-color: transparent;
background: currentColor;
box-shadow: var(--shadow-sm);
}
.form-range:focus-visible::-moz-range-thumb {
outline: 2px solid Highlight;
outline-offset: 2px;
}
.form-range:disabled {
opacity: 1;
cursor: default;
}
.btn:not(.btn-primary, .viz-tile):is(
[aria-pressed=&quot;true&quot;],
[aria-selected=&quot;true&quot;],
.is-selected
) {
border-color: transparent;
color: var(--primary-foreground);
background: var(--blue);
}
.btn.viz-tile:is([aria-pressed=&quot;true&quot;], [aria-selected=&quot;true&quot;], .is-selected) {
border-color: var(--primary);
box-shadow: inset 0 0 0 1px var(--primary);
}
.btn:focus-visible,
.nav-link:focus-visible {
outline-offset: 2px;
outline: none;
box-shadow: 0 0 0 2px var(--ring);
}
.viz-carousel-controls :is(button, summary):focus-visible {
outline: 2px solid var(--ring) !important;
outline-offset: 2px !important;
}
/* Web recipes are the default, including on renderers without inline style queries. */
@supports (border-radius: if(style(--corner-shape: round): 1px; else: 2px)) {
/* Match the host&#x27;s proportional text-sm scale at every text preference. */
.tooltip,
.viz-badge,
.btn,
.nav-link,
.form-control,
.form-select {
font-size: if(
style(--corner-shape: round): round(
nearest,
calc(var(--font-size-base) * 14 / 16),
1px
);
else: round(nearest, calc(var(--font-size-base) * 13 / 14), 1px)
);
}
.btn,
.nav-link,
.form-select {
border-radius: if(
style(--corner-shape: round): var(--radius-full); else: var(--radius-lg)
);
}
.form-check:not(.form-switch) .form-check-input[type=&quot;checkbox&quot;] {
border-radius: if(
style(--corner-shape: round): calc(var(--radius-sm) / 2);
else: calc(var(--radius-lg) * 0.4)
);
}
.tooltip {
font-weight: if(
style(--corner-shape: round): 600; else: var(--font-weight-normal)
);
}
}
@media (pointer: coarse) {
.viz-carousel:has(&gt; .viz-carousel-controls) {
min-height: calc(76px + var(--viz-carousel-picker-height, 0px));
padding-bottom: 76px;
}
.btn,
.nav-link,
.viz-carousel-controls button,
.viz-carousel-picker &gt; summary,
.form-control,
.form-select {
min-width: 44px;
min-height: 44px;
}
/* Keep small-screen WebKit from zooming when a text field receives focus. */
.form-control,
.form-select {
font-size: max(16px, calc(var(--font-size-base) * 14 / 16));
font-size: max(
16px,
round(nearest, calc(var(--font-size-base) * 14 / 16), 1px)
);
}
.form-control:is(
[type=&quot;file&quot;],
[type=&quot;date&quot;],
[type=&quot;datetime-local&quot;],
[type=&quot;month&quot;],
[type=&quot;week&quot;],
[type=&quot;time&quot;],
[type=&quot;color&quot;]
) {
height: 44px;
min-height: 44px;
font-size: max(16px, calc(var(--font-size-base) * 14 / 16));
font-size: max(
16px,
round(nearest, calc(var(--font-size-base) * 14 / 16), 1px)
);
}
.form-control[type=&quot;file&quot;]::file-selector-button {
min-width: 44px;
min-height: 44px;
}
.form-control-color[type=&quot;color&quot;] {
width: 44px;
height: 44px;
}
.form-control:is(
[type=&quot;date&quot;],
[type=&quot;datetime-local&quot;],
[type=&quot;month&quot;],
[type=&quot;time&quot;],
[type=&quot;week&quot;]
) {
min-width: 0;
max-width: 100%;
padding-block: 8px;
}
.form-check {
min-height: 44px;
padding-inline: 15px;
}
.form-check-input {
position: relative;
}
.form-check-input::after {
position: absolute;
top: 50%;
left: 50%;
width: 44px;
height: 44px;
content: &quot;&quot;;
transform: translate(-50%, -50%);
}
.form-check-label {
display: inline-flex;
min-width: 44px;
min-height: 44px;
align-items: center;
}
.form-range {
height: 44px;
touch-action: pan-y;
}
}
svg {
display: block;
max-width: 100%;
height: auto;
}
#widget &gt; svg {
width: 100%;
}
/* Model-authored chart rules can otherwise stretch icons after these styles load. */
svg.lucide {
display: block;
width: var(--icon-size) !important;
height: var(--icon-size) !important;
flex: none;
margin: 0 !important;
stroke-width: 1.6;
}
.text-warning,
.text-destructive {
color: var(--destructive);
}
html&gt;body{padding:0}&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;script type=&quot;application/json&quot; id=&quot;codex-visualization-widget-state&quot;&gt;__CODEX_VISUALIZATION_WIDGET_STATE__&lt;/script&gt;
&lt;script&gt;(() =&gt; {
const root = document.documentElement;
const mediaQuery = globalThis.matchMedia(&quot;(prefers-color-scheme: dark)&quot;);
const apply = Reflect.apply;
const isArray = Array.isArray;
const hasOwn = Object.hasOwn;
const parentWindow = globalThis.parent;
const stringify = JSON.stringify;
const parse = JSON.parse;
const encode = TextEncoder.prototype.encode.bind(new TextEncoder());
const initialStateElement = document.getElementById(
&quot;codex-visualization-widget-state&quot;,
);
const initialGlobals = parse(initialStateElement?.textContent ?? &quot;{}&quot;);
initialStateElement?.remove();
/** @type {Record&lt;string, unknown&gt; | null} */
let state = initialGlobals.widgetState ?? null;
const statePersistence = initialGlobals.statePersistence ?? &quot;none&quot;;
let nextStateRequestId = 0;
/** @type {Map&lt;number, { resolve: () =&gt; void; reject: (error: Error) =&gt; void; timeout: ReturnType&lt;typeof globalThis.setTimeout&gt;; serializedState: string }&gt;} */
const pendingStateWrites = new Map();
const persistenceError = () =&gt;
Object.assign(
new Error(&quot;The host could not persist the widget state update&quot;),
{
code: -32000,
data: { code: &quot;widget_state_persistence_failed&quot;, retryable: false },
},
);
/**
* @param {object} prototype
* @param {string} property
*/
const getPrototypeGetter = (prototype, property) =&gt; {
// oxlint-disable-next-line typescript/unbound-method -- invoked through captured Reflect.apply
const getter = Object.getOwnPropertyDescriptor(prototype, property)?.get;
if (getter == null) {
throw new Error(`Missing ${property} getter`);
}
return getter;
};
const getMessageData = getPrototypeGetter(MessageEvent.prototype, &quot;data&quot;);
const getMessagePorts = getPrototypeGetter(MessageEvent.prototype, &quot;ports&quot;);
const getMessageSource = getPrototypeGetter(MessageEvent.prototype, &quot;source&quot;);
// oxlint-disable-next-line typescript/unbound-method -- captured before the untrusted fragment runs
const addHostMessageListener = MessagePort.prototype.addEventListener;
// oxlint-disable-next-line typescript/unbound-method -- captured before the untrusted fragment runs
const postHostMessage = MessagePort.prototype.postMessage;
// oxlint-disable-next-line typescript/unbound-method -- captured before the untrusted fragment runs
const startHostPort = MessagePort.prototype.start;
// oxlint-disable-next-line typescript/unbound-method -- captured before the untrusted fragment runs
const stopMessagePropagation = Event.prototype.stopImmediatePropagation;
/** @type {MessagePort | null} */
let hostPort = null;
const postToHost = (type, payload) =&gt; {
if (hostPort != null) {
apply(postHostMessage, hostPort, [{ type, ...payload }]);
}
};
const openExternal = ({ href }) =&gt; {
if (globalThis.navigator.userActivation?.isActive === true) {
postToHost(&quot;open-external&quot;, { href });
}
};
const sendFollowUpMessage = ({ context, prompt, title }) =&gt; {
if (globalThis.navigator.userActivation?.isActive === true) {
postToHost(&quot;follow-up&quot;, { context, prompt, title });
}
return Promise.resolve();
};
/**
* @param {Record&lt;string, unknown&gt; | ((previous: Record&lt;string, unknown&gt; | null) =&gt; Record&lt;string, unknown&gt;)} next
* @returns {Promise&lt;void&gt;}
*/
const setWidgetState = async (next) =&gt; {
const nextState = typeof next === &quot;function&quot; ? next(state) : next;
if (
nextState == null ||
typeof nextState !== &quot;object&quot; ||
isArray(nextState)
) {
throw new TypeError(&quot;Widget state must be a JSON object&quot;);
}
const inputState = stringify(
nextState,
(_key, /** @type {unknown} */ value) =&gt; {
if (
typeof value === &quot;undefined&quot; ||
typeof value === &quot;function&quot; ||
typeof value === &quot;symbol&quot; ||
(typeof value === &quot;number&quot; &amp;&amp; !Number.isFinite(value))
) {
throw new TypeError(&quot;Widget state must be a JSON object&quot;);
}
return value;
},
);
const parsedState = parse(inputState);
if (
parsedState == null ||
typeof parsedState !== &quot;object&quot; ||
isArray(parsedState)
) {
throw new TypeError(&quot;Widget state must be a JSON object&quot;);
}
const storedState = {
modelContent: null,
privateContent: null,
...parsedState,
};
const serializedState = stringify(storedState);
if (encode(serializedState).length &gt; 16 * 1024) {
throw new TypeError(
&quot;Widget state must be a JSON object of at most 16 KiB&quot;,
);
}
if (
hasOwn(storedState, &quot;imageIds&quot;) &amp;&amp;
(!isArray(storedState.imageIds) ||
storedState.imageIds.some(
(/** @type {unknown} */ id) =&gt; typeof id !== &quot;string&quot;,
))
) {
throw new TypeError(&quot;Widget state imageIds must be an array of strings&quot;);
}
if (storedState.imageIds?.length &gt; 0) {
throw Object.assign(
new TypeError(&quot;Visualization state does not support image attachments&quot;),
{ code: -32602, data: { code: &quot;images_unsupported&quot; } },
);
}
state = storedState;
globalThis.openai.widgetState = state;
globalThis.dispatchEvent(
new CustomEvent(&quot;openai:set_globals&quot;, {
detail: { globals: { widgetState: state } },
}),
);
if (statePersistence === &quot;none&quot;) {
throw persistenceError();
}
const id = ++nextStateRequestId;
return new Promise((resolve, reject) =&gt; {
const timeout = globalThis.setTimeout(() =&gt; {
pendingStateWrites.delete(id);
reject(persistenceError());
}, 30_000);
pendingStateWrites.set(id, { resolve, reject, timeout, serializedState });
postToHost(&quot;widget-state-write&quot;, { id, state: serializedState });
});
};
const syncTheme = () =&gt; {
const theme = mediaQuery.matches ? &quot;dark&quot; : &quot;light&quot;;
root.dataset.theme = theme;
globalThis.openai = {
...globalThis.openai,
openExternal,
sendFollowUpMessage,
setWidgetState,
theme,
visualizationStyleVariables: {},
visualizationTheme: theme,
widgetState: state,
statePersistence,
stateModelContext: &quot;none&quot;,
};
globalThis.dispatchEvent(
new CustomEvent(&quot;openai:set_globals&quot;, {
detail: { globals: globalThis.openai },
}),
);
};
const sendHeight = () =&gt; {
postToHost(&quot;height&quot;, {
height: Math.ceil(
Math.max(
document.body.scrollHeight,
document.body.getBoundingClientRect().height,
),
),
});
};
globalThis.addEventListener(
&quot;message&quot;,
(event) =&gt; {
const data = apply(getMessageData, event, []);
const ports = apply(getMessagePorts, event, []);
const source = apply(getMessageSource, event, []);
if (
hostPort != null ||
source !== parentWindow ||
typeof data !== &quot;object&quot; ||
data?.type !== &quot;codex-visualization-initialize&quot; ||
!isArray(ports) ||
ports.length !== 1
) {
return;
}
apply(stopMessagePropagation, event, []);
const nextHostPort = ports[0];
try {
apply(addHostMessageListener, nextHostPort, [
&quot;message&quot;,
(event) =&gt; {
const data = apply(getMessageData, event, []);
if (typeof data === &quot;object&quot; &amp;&amp; data?.type === &quot;measure&quot;) {
sendHeight();
} else if (
typeof data === &quot;object&quot; &amp;&amp;
data?.type === &quot;widget-state-result&quot;
) {
/** @type {unknown} */
const id = data.id;
if (typeof id !== &quot;number&quot;) {
return;
}
const pending = pendingStateWrites.get(id);
if (pending != null) {
pendingStateWrites.delete(id);
globalThis.clearTimeout(pending.timeout);
if (data.ok === true) {
pending.resolve();
} else {
pending.reject(persistenceError());
}
}
}
},
]);
apply(startHostPort, nextHostPort, []);
} catch {
return;
}
hostPort = nextHostPort;
for (const [id, { serializedState }] of pendingStateWrites) {
postToHost(&quot;widget-state-write&quot;, { id, state: serializedState });
}
sendHeight();
},
{ capture: true },
);
document.currentScript?.remove();
mediaQuery.addEventListener(&quot;change&quot;, syncTheme);
globalThis.addEventListener(&quot;click&quot;, (event) =&gt; {
if (event.defaultPrevented) {
return;
}
let element = null;
if (event.target instanceof globalThis.Element) {
element = event.target;
} else if (event.target instanceof globalThis.Node) {
element = event.target.parentElement;
}
const link = element?.closest(&quot;a[href]&quot;);
const href = link?.getAttribute(&quot;href&quot;);
if (href == null) {
return;
}
if (href.startsWith(&quot;#&quot;)) {
let id;
try {
id = decodeURIComponent(href.slice(1));
} catch {
return;
}
const target = id.length === 0 ? root : document.getElementById(id);
if (
target == null ||
globalThis.navigator.userActivation?.isActive !== true
) {
return;
}
event.preventDefault();
postToHost(&quot;scroll-to&quot;, {
top: target.getBoundingClientRect().top + globalThis.scrollY,
});
return;
}
event.preventDefault();
openExternal({ href });
});
new ResizeObserver(sendHeight).observe(document.body);
syncTheme();
})();
&lt;/script&gt;
&lt;style&gt;
#eg-preview { width:100%; }
#eg-preview .eg-app { --eg-bg:light-dark(#ffffff,#202329); --eg-rail:light-dark(#f5f7fa,#191d23); --eg-text:light-dark(#202c3b,#eff3f8); --eg-sub:light-dark(#637084,#aeb9c9); --eg-line:light-dark(#e1e6ed,#39414d); --eg-accent:light-dark(#2455d6,#96b5ff); --eg-on:light-dark(#ffffff,#14254d); --eg-soft:light-dark(#eef3ff,#27344d); --eg-green:light-dark(#257055,#85d7b6); color:var(--eg-text); background:var(--eg-bg); border:1px solid var(--eg-line); border-radius:12px; font:14px/1.6 &#x27;Segoe UI&#x27;,&#x27;Malgun Gothic&#x27;,sans-serif; overflow-wrap:anywhere; box-shadow:0 8px 26px light-dark(#1324450c,#00000022); }
#eg-preview .eg-app * { box-sizing:border-box; }
#eg-preview .eg-titlebar { display:flex; align-items:center; gap:10px; min-height:52px; padding:12px 20px; border-bottom:1px solid var(--eg-line); padding-right:100px; }
#eg-preview .eg-mark { display:inline-grid; place-items:center; background:var(--eg-accent); color:var(--eg-on); width:27px; height:27px; border-radius:7px; font-size:15px; font-weight:600; flex-shrink:0; }
#eg-preview .eg-name { font-weight:600; letter-spacing:-.3px; }
#eg-preview .eg-demo { color:var(--eg-sub); font-size:11px; margin-left:4px; }
#eg-preview .eg-layout { display:grid; grid-template-columns:174px minmax(0,1fr); min-height:508px; }
#eg-preview .eg-side { background:var(--eg-rail); border-right:1px solid var(--eg-line); padding:28px 22px; display:flex; flex-direction:column; }
#eg-preview .eg-eyebrow { color:var(--eg-sub); font-size:11px; letter-spacing:1.6px; font-weight:600; }
#eg-preview .eg-side h2 { font-size:20px; margin:7px 0 28px; font-weight:600; letter-spacing:-.7px; }
#eg-preview .eg-steps { list-style:none; padding:0; margin:0; display:grid; gap:25px; }
#eg-preview .eg-steps li { display:flex; gap:10px; align-items:center; color:var(--eg-sub); font-size:12px; }
#eg-preview .eg-dot { display:inline-grid; place-items:center; width:24px; height:24px; border-radius:50%; border:1px solid var(--eg-line); flex-shrink:0; font-size:11px; }
#eg-preview .eg-steps li[aria-current=step] { color:var(--eg-accent); font-weight:600; }
#eg-preview .eg-steps li[aria-current=step] .eg-dot { background:var(--eg-accent); color:var(--eg-on); border-color:transparent; }
#eg-preview .eg-steps li.eg-past .eg-dot { color:var(--eg-green); background:var(--eg-bg); }
#eg-preview .eg-side-note { margin-top:auto; padding-top:32px; font-size:11px; color:var(--eg-sub); }
#eg-preview .eg-main { display:flex; flex-direction:column; min-width:0; }
#eg-preview .eg-content { padding:24px 30px 20px; flex:1; }
#eg-preview .eg-mode { display:flex; gap:4px; flex-wrap:wrap; margin-bottom:23px; }
#eg-preview .eg-mode button { background:var(--eg-rail); color:var(--eg-sub); border:0; padding:6px 12px; border-radius:5px; font:inherit; font-size:12px; }
#eg-preview .eg-mode button[aria-pressed=true] { background:var(--eg-soft); color:var(--eg-accent); font-weight:600; }
#eg-preview .eg-mode button:disabled { opacity:.6; }
#eg-preview .eg-heading { font-size:25px; line-height:1.4; font-weight:600; letter-spacing:-1px; margin:0 0 8px; }
#eg-preview .eg-description { color:var(--eg-sub); font-size:13px; margin:0 0 22px; }
#eg-preview .eg-box { border:1px solid var(--eg-line); border-radius:8px; padding:15px 17px; margin:14px 0; background:var(--eg-bg); }
#eg-preview .eg-box-top { display:flex; align-items:center; justify-content:space-between; gap:8px; flex-wrap:wrap; margin-bottom:10px; }
#eg-preview .eg-box strong { font-weight:600; }
#eg-preview .eg-badge { font-size:11px; color:var(--eg-green); background:var(--eg-rail); padding:2px 8px; border-radius:4px; }
#eg-preview .eg-path { color:var(--eg-sub); font:12px/1.7 &#x27;Segoe UI&#x27;,&#x27;Malgun Gothic&#x27;,sans-serif; overflow-wrap:anywhere; }
#eg-preview .eg-small { font-size:12px; color:var(--eg-sub); }
#eg-preview .eg-note { display:flex; gap:10px; padding:12px 14px; background:var(--eg-soft); color:var(--eg-accent); border-radius:7px; font-size:12px; margin-top:16px; }
#eg-preview .eg-note svg { flex-shrink:0; margin-top:2px; }
#eg-preview .eg-checks { list-style:none; padding:0; margin:12px 0 0; }
#eg-preview .eg-checks li { display:flex; justify-content:space-between; align-items:center; gap:12px; padding:8px 0; border-bottom:1px solid var(--eg-line); font-size:12px; }
#eg-preview .eg-checks li:last-child { border:0; }
#eg-preview .eg-status { color:var(--eg-green); font-size:11px; white-space:nowrap; }
#eg-preview .eg-footer { border-top:1px solid var(--eg-line); padding:15px 22px; display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; }
#eg-preview .eg-actions { display:flex; gap:8px; flex-wrap:wrap; margin-left:auto; }
#eg-preview .eg-button { display:inline-flex; align-items:center; justify-content:center; gap:8px; padding:8px 17px; border:1px solid var(--eg-line); background:var(--eg-bg); color:var(--eg-text); border-radius:6px; font:500 13px/1.5 &#x27;Segoe UI&#x27;,&#x27;Malgun Gothic&#x27;,sans-serif; }
#eg-preview .eg-primary { border-color:transparent; background:var(--eg-accent); color:var(--eg-on); }
#eg-preview .eg-quiet { border-color:transparent; color:var(--eg-sub); padding-left:0; }
#eg-preview .eg-field { display:block; font-size:12px; color:var(--eg-sub); }
#eg-preview .eg-field input { display:block; width:100%; padding:10px; margin-top:6px; border:1px solid var(--eg-line); border-radius:5px; font:inherit; color:var(--eg-text); background:var(--eg-bg); }
#eg-preview .eg-agree { display:flex; align-items:flex-start; gap:8px; font-size:12px; margin-top:18px; }
#eg-preview input[type=checkbox] { accent-color:var(--eg-accent); margin:4px 0 0; }
#eg-preview .eg-meter { height:7px; border-radius:4px; background:var(--eg-rail); margin:17px 0 7px; overflow:hidden; }
#eg-preview .eg-meter span { display:block; height:100%; background:var(--eg-accent); }
#eg-preview .eg-progress-label { display:flex; justify-content:space-between; gap:10px; font-size:12px; color:var(--eg-sub); }
#eg-preview .eg-success { width:42px; height:42px; display:grid; place-items:center; color:var(--eg-green); background:var(--eg-rail); border-radius:50%; margin-bottom:18px; font-size:23px; }
#eg-preview .eg-alert { color:light-dark(#a43c27,#ffad98); font-size:12px; }
#eg-preview .eg-log { font-size:11px; color:var(--eg-sub); margin-top:13px; }
#eg-preview .eg-log summary { width:fit-content; }
#eg-preview .eg-log p { margin:8px 0; }
#eg-preview .eg-compact .eg-layout { display:flex; min-height:508px; }
#eg-preview .eg-compact .eg-main { width:100%; }
#eg-preview .eg-compact .eg-side { display:none; }
#eg-preview .eg-compact .eg-content { width:100%; max-width:530px; margin:auto; padding-top:25px; }
#eg-preview .eg-compact .eg-heading { font-size:29px; }
#eg-preview .eg-compact .eg-mode { margin-bottom:19px; }
#eg-preview .eg-compact .eg-footer { padding-left:30px; padding-right:30px; }
#eg-preview .eg-compact .eg-box { background:var(--eg-rail); border-color:transparent; }
#eg-preview .eg-compact .eg-badge { background:var(--eg-bg); }
#eg-preview .eg-mini-step { display:none; font-size:11px; color:var(--eg-accent); margin:0 0 7px; letter-spacing:.8px; }
#eg-preview .eg-compact .eg-mini-step { display:block; }
@media(max-width:580px) { #eg-preview .eg-layout { grid-template-columns:1fr; } #eg-preview .eg-side { padding:14px 18px; border-right:0; border-bottom:1px solid var(--eg-line); } #eg-preview .eg-side h2,#eg-preview .eg-side .eg-eyebrow,#eg-preview .eg-side-note { display:none; } #eg-preview .eg-steps { display:flex; justify-content:space-between; gap:7px; } #eg-preview .eg-steps li { gap:5px; font-size:11px; flex-direction:column; } #eg-preview .eg-content,#eg-preview .eg-compact .eg-content { padding:22px 18px; } #eg-preview .eg-heading,#eg-preview .eg-compact .eg-heading { font-size:23px; } #eg-preview .eg-footer,#eg-preview .eg-compact .eg-footer { padding:14px 18px; } #eg-preview .eg-titlebar { padding-left:16px; } }
@media(pointer:coarse) { #eg-preview button { min-height:44px; } #eg-preview .eg-field input { font-size:16px; } #eg-preview .eg-agree { min-height:44px; align-items:center; } }
&lt;/style&gt;
&lt;div id=&quot;eg-preview&quot; class=&quot;viz-carousel viz-dotted-background&quot; aria-label=&quot;EG-BIM 설치 화면 디자인&quot; data-previous-label=&quot;이전 시안&quot; data-next-label=&quot;다음 시안&quot;&gt;
&lt;section data-variant=&quot;단계형&quot; aria-label=&quot;단계형 설치 디자인&quot;&gt;&lt;div id=&quot;eg-guided&quot; class=&quot;eg-app&quot; aria-label=&quot;단계형 설치 화면&quot;&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section data-variant=&quot;집중형&quot; aria-label=&quot;집중형 설치 디자인&quot; hidden&gt;&lt;div id=&quot;eg-focused&quot; class=&quot;eg-app eg-compact&quot; aria-label=&quot;집중형 설치 화면&quot;&gt;&lt;/div&gt;&lt;/section&gt;
&lt;/div&gt;
&lt;script&gt;
(() =&gt; {
const root = document.getElementById(&#x27;eg-preview&#x27;);
const definitions = [{id:&#x27;eg-guided&#x27;, label:&#x27;단계형&#x27;}, {id:&#x27;eg-focused&#x27;, label:&#x27;집중형&#x27;}];
const states = {};
const defaultPath = &#x27;C:\\Program Files\\Baron\\EG-BIM&#x27;;
const existingPath = &#x27;D:\\Baron\\EG-BIM&#x27;;
const escape = value =&gt; String(value).replace(/[&amp;&lt;&gt;&quot;&#x27;]/g, c =&gt; ({&#x27;&amp;&#x27;:&#x27;&amp;amp;&#x27;,&#x27;&lt;&#x27;:&#x27;&amp;lt;&#x27;,&#x27;&gt;&#x27;:&#x27;&amp;gt;&#x27;,&#x27;&quot;&#x27;:&#x27;&amp;quot;&#x27;,&quot;&#x27;&quot;:&#x27;&amp;#39;&#x27;}[c]));
const saved = window.openai?.widgetState?.privateContent?.installers || {};
const normalize = value =&gt; ({step:Number.isInteger(value?.step) &amp;&amp; value.step&gt;=0 &amp;&amp; value.step&lt;=3 ? value.step : 0, mode:value?.mode===&#x27;new&#x27;?&#x27;new&#x27;:&#x27;upgrade&#x27;, path:typeof value?.path===&#x27;string&#x27;?value.path:defaultPath, agreed:!!value?.agreed, launch:value?.launch!==false, error:false, finished:false});
definitions.forEach(d =&gt; states[d.id]=normalize(saved[d.id]));
function persist(id) {
const s=states[id];
window.openai?.setWidgetState?.({modelContent:{design:definitions.find(d=&gt;d.id===id).label,mode:s.mode,screen:[&#x27;설치 준비&#x27;,&#x27;설정 보호&#x27;,&#x27;설치 진행&#x27;,&#x27;완료&#x27;][s.step]},privateContent:{installers:states}})?.catch(()=&gt;{});
}
function row(label,status=&#x27;확인 완료&#x27;) { return `&lt;li&gt;&lt;span&gt;${label}&lt;/span&gt;&lt;span class=&quot;eg-status&quot;&gt;${status}&lt;/span&gt;&lt;/li&gt;`; }
function content(s) {
const upgrade=s.mode===&#x27;upgrade&#x27;;
if(s.step===0) return `&lt;h1 class=&quot;eg-heading&quot;&gt;${upgrade?&#x27;익숙한 작업 환경, 그대로.&#x27;:&#x27;새로운 설계의 시작.&#x27;}&lt;/h1&gt;&lt;p class=&quot;eg-description&quot;&gt;${upgrade?&#x27;설정과 작업 환경을 보관한 뒤 EG-BIM을 업데이트합니다.&#x27;:&#x27;EG-BIM을 설치하고 설계 작업을 시작하세요.&#x27;}&lt;/p&gt;
${upgrade?`&lt;div class=&quot;eg-box&quot;&gt;&lt;div class=&quot;eg-box-top&quot;&gt;&lt;strong&gt;기존 EG-BIM을 찾았습니다&lt;/strong&gt;&lt;span class=&quot;eg-badge&quot;&gt;설치 경로 유지&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;eg-path&quot;&gt;${escape(existingPath)}&lt;/div&gt;&lt;div class=&quot;eg-small&quot;&gt;사용자 지정 설치 경로를 그대로 사용합니다.&lt;/div&gt;&lt;/div&gt;`:`&lt;div class=&quot;eg-box&quot;&gt;&lt;label class=&quot;eg-field&quot;&gt;설치 위치&lt;input data-path aria-label=&quot;설치 위치&quot; value=&quot;${escape(s.path)}&quot;&gt;&lt;/label&gt;&lt;div class=&quot;eg-small&quot;&gt;프로그램과 사용자 설정은 별도로 보관됩니다.&lt;/div&gt;&lt;/div&gt;`}
&lt;ul class=&quot;eg-checks&quot;&gt;${row(&#x27;Windows 환경 및 필수 구성요소&#x27;)}${row(&#x27;설치 파일 · 서명 · 무결성&#x27;)}${row(&#x27;저장 공간 및 실행 중인 프로그램&#x27;)}&lt;/ul&gt;
${upgrade?&#x27;&lt;div class=&quot;eg-note&quot;&gt;&lt;i data-lucide=&quot;shield-check&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;&lt;span&gt;다음 단계에서 사용자 설정과 추가 리소스를 백업합니다.&lt;/span&gt;&lt;/div&gt;&#x27;:&#x27;&lt;label class=&quot;eg-agree&quot;&gt;&lt;input type=&quot;checkbox&quot; data-agree &#x27;+(s.agreed?&#x27;checked&#x27;:&#x27;&#x27;)+&#x27;&gt; &lt;span&gt;사용권 계약에 동의합니다. &lt;span class=&quot;eg-small&quot;&gt;(시안용 항목)&lt;/span&gt;&lt;/span&gt;&lt;/label&gt;&lt;details class=&quot;eg-log&quot;&gt;&lt;summary class=&quot;cursor-interaction&quot;&gt;사용권 계약 미리보기&lt;/summary&gt;&lt;p&gt;실제 설치 화면에는 승인된 사용권 계약 전문이 표시됩니다.&lt;/p&gt;&lt;/details&gt;&#x27;}
${s.error?&#x27;&lt;p class=&quot;eg-alert&quot; role=&quot;alert&quot;&gt;설치 위치를 입력하고 사용권 계약에 동의해 주세요.&lt;/p&gt;&#x27;:&#x27;&#x27;}`;
if(s.step===1) return `&lt;h1 class=&quot;eg-heading&quot;&gt;${upgrade?&#x27;작업 환경을 안전하게 보관합니다.&#x27;:&#x27;설치 준비가 끝났습니다.&#x27;}&lt;/h1&gt;&lt;p class=&quot;eg-description&quot;&gt;${upgrade?&#x27;백업 파일 확인이 끝난 후에만 기존 제품을 변경합니다.&#x27;:&#x27;아래 구성을 확인하고 설치를 진행하세요.&#x27;}&lt;/p&gt;
&lt;div class=&quot;eg-box&quot;&gt;&lt;div class=&quot;eg-box-top&quot;&gt;&lt;strong&gt;${upgrade?&#x27;사용자 설정 백업&#x27;:&#x27;설치 구성 확인&#x27;}&lt;/strong&gt;&lt;span class=&quot;eg-badge&quot;&gt;${upgrade?&#x27;검증 완료&#x27;:&#x27;준비 완료&#x27;}&lt;/span&gt;&lt;/div&gt;&lt;ul class=&quot;eg-checks&quot;&gt;${upgrade?row(&#x27;환경설정 · 개인 도구 모음&#x27;,&#x27;보관됨&#x27;)+row(&#x27;CUI · 사용자 추가 리소스&#x27;,&#x27;보관됨&#x27;)+row(&#x27;백업 파일 읽기 및 무결성&#x27;,&#x27;정상&#x27;):row(&#x27;EG-BIM 프로그램 파일&#x27;,&#x27;전체 설치&#x27;)+row(&#x27;필수 런타임&#x27;,&#x27;필요 시 설치&#x27;)+row(&#x27;설치 범위&#x27;,&#x27;이 PC의 모든 사용자&#x27;)}&lt;/ul&gt;&lt;/div&gt;
&lt;div class=&quot;eg-small&quot;&gt;${upgrade?&#x27;백업 위치 · 제거 대상과 분리&#x27;:&#x27;설치 위치&#x27;}&lt;/div&gt;&lt;div class=&quot;eg-path&quot;&gt;${escape(upgrade?&#x27;D:\\EG-BIM-Backup\\migration-demo&#x27;:s.path)}&lt;/div&gt;
&lt;div class=&quot;eg-note&quot;&gt;&lt;i data-lucide=&quot;shield-check&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;&lt;span&gt;${upgrade?&#x27;설정 복원과 설치 후 확인이 끝날 때까지 백업을 유지합니다.&#x27;:&#x27;설치에 필요한 경우 Windows에서 관리자 권한을 요청합니다.&#x27;}&lt;/span&gt;&lt;/div&gt;`;
if(s.step===2) return `&lt;h1 class=&quot;eg-heading&quot;&gt;EG-BIM을 설치하고 있습니다.&lt;/h1&gt;&lt;p class=&quot;eg-description&quot;&gt;${upgrade?&#x27;프로그램을 업데이트한 뒤 호환되는 설정을 복원합니다.&#x27;:&#x27;프로그램 파일과 필요한 구성요소를 설치합니다.&#x27;}&lt;/p&gt;&lt;div class=&quot;eg-meter&quot; role=&quot;progressbar&quot; aria-label=&quot;설치 진행률 예시&quot; aria-valuenow=&quot;64&quot; aria-valuemin=&quot;0&quot; aria-valuemax=&quot;100&quot;&gt;&lt;span style=&quot;width:64%&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;eg-progress-label&quot;&gt;&lt;span&gt;프로그램 파일 설치 중&lt;/span&gt;&lt;span&gt;64%&lt;/span&gt;&lt;/div&gt;&lt;ul class=&quot;eg-checks&quot;&gt;${row(&#x27;설치 환경 및 패키지 확인&#x27;,&#x27;완료&#x27;)}${row(upgrade?&#x27;사용자 설정 백업&#x27;:&#x27;필수 런타임 확인&#x27;,&#x27;완료&#x27;)}${row(&#x27;EG-BIM 프로그램 설치&#x27;,&#x27;진행 중&#x27;)}${row(upgrade?&#x27;설정 복원 및 설치 상태 확인&#x27;:&#x27;설치 상태 확인&#x27;,&#x27;대기&#x27;)}&lt;/ul&gt;&lt;details class=&quot;eg-log&quot;&gt;&lt;summary class=&quot;cursor-interaction&quot;&gt;진행 내역&lt;/summary&gt;&lt;p&gt;환경 검사 완료 → 파일 검증 완료 → ${upgrade?&#x27;백업 검증 완료 → &#x27;:&#x27;&#x27;}프로그램 설치 중&lt;br&gt;화면 검토를 위한 예시 상태입니다.&lt;/p&gt;&lt;/details&gt;&lt;div class=&quot;eg-note&quot;&gt;&lt;i data-lucide=&quot;info&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;&lt;span&gt;설치가 끝날 때까지 이 창을 유지해 주세요.&lt;/span&gt;&lt;/div&gt;`;
return `&lt;div class=&quot;eg-success&quot; aria-hidden=&quot;true&quot;&gt;✓&lt;/div&gt;&lt;h1 class=&quot;eg-heading&quot;&gt;설치가 완료되었습니다.&lt;/h1&gt;&lt;p class=&quot;eg-description&quot;&gt;${upgrade?&#x27;사용자 설정을 복원했습니다. 이어서 작업을 시작하세요.&#x27;:&#x27;EG-BIM에서 첫 번째 프로젝트를 시작해 보세요.&#x27;}&lt;/p&gt;&lt;div class=&quot;eg-box&quot;&gt;&lt;ul class=&quot;eg-checks&quot;&gt;${row(&#x27;프로그램 설치&#x27;,&#x27;완료&#x27;)}${upgrade?row(&#x27;호환되는 사용자 설정 복원&#x27;,&#x27;완료&#x27;):&#x27;&#x27;}${row(&#x27;설치 상태 확인&#x27;,&#x27;완료&#x27;)}&lt;/ul&gt;&lt;div class=&quot;eg-path&quot;&gt;${escape(upgrade?existingPath:s.path)}&lt;/div&gt;&lt;/div&gt;${upgrade?&#x27;&lt;p class=&quot;eg-small&quot;&gt;백업은 유지됩니다. 도면 열기·저장·출력과 인증·플러그인 동작을 확인한 후 정리할 수 있습니다.&lt;/p&gt;&#x27;:&#x27;&#x27;}&lt;label class=&quot;eg-agree&quot;&gt;&lt;input type=&quot;checkbox&quot; data-launch ${s.launch?&#x27;checked&#x27;:&#x27;&#x27;}&gt;&lt;span&gt;완료 후 EG-BIM 실행&lt;/span&gt;&lt;/label&gt;${s.finished?&#x27;&lt;p class=&quot;eg-small&quot; role=&quot;status&quot;&gt;미리보기가 끝났습니다. 실제 프로그램은 실행하지 않습니다.&lt;/p&gt;&#x27;:&#x27;&#x27;}`;
}
function render(id) {
const app=document.getElementById(id),s=states[id];
const names=[&#x27;설치 준비&#x27;,s.mode===&#x27;upgrade&#x27;?&#x27;설정 백업&#x27;:&#x27;구성 확인&#x27;,&#x27;설치 진행&#x27;,&#x27;완료&#x27;];
app.innerHTML=`&lt;header class=&quot;eg-titlebar&quot;&gt;&lt;span class=&quot;eg-mark&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;eg-name&quot;&gt;EG-BIM &lt;span class=&quot;eg-demo&quot;&gt;설치 시안&lt;/span&gt;&lt;/span&gt;&lt;/header&gt;&lt;div class=&quot;eg-layout&quot;&gt;&lt;aside class=&quot;eg-side&quot;&gt;&lt;div class=&quot;eg-eyebrow&quot;&gt;BARON SOFTWARE&lt;/div&gt;&lt;h2&gt;${s.mode===&#x27;upgrade&#x27;?&#x27;업데이트&#x27;:&#x27;새로 설치&#x27;}&lt;/h2&gt;&lt;ol class=&quot;eg-steps&quot;&gt;${names.map((n,i)=&gt;`&lt;li ${i===s.step?&#x27;aria-current=&quot;step&quot;&#x27;:&#x27;&#x27;} class=&quot;${i&lt;s.step?&#x27;eg-past&#x27;:&#x27;&#x27;}&quot;&gt;&lt;span class=&quot;eg-dot&quot;&gt;${i&lt;s.step?&#x27;✓&#x27;:i+1}&lt;/span&gt;${n}&lt;/li&gt;`).join(&#x27;&#x27;)}&lt;/ol&gt;&lt;div class=&quot;eg-side-note&quot;&gt;당신의 설계 환경을&lt;br&gt;다음 버전으로.&lt;/div&gt;&lt;/aside&gt;&lt;main class=&quot;eg-main&quot;&gt;&lt;div class=&quot;eg-content&quot;&gt;&lt;div class=&quot;eg-mode&quot; aria-label=&quot;설치 상황 미리보기&quot;&gt;&lt;button class=&quot;cursor-interaction&quot; data-mode=&quot;upgrade&quot; aria-pressed=&quot;${s.mode===&#x27;upgrade&#x27;}&quot; ${s.step&gt;0?&#x27;disabled&#x27;:&#x27;&#x27;}&gt;기존 설치본 전환&lt;/button&gt;&lt;button class=&quot;cursor-interaction&quot; data-mode=&quot;new&quot; aria-pressed=&quot;${s.mode===&#x27;new&#x27;}&quot; ${s.step&gt;0?&#x27;disabled&#x27;:&#x27;&#x27;}&gt;신규 설치&lt;/button&gt;&lt;/div&gt;&lt;p class=&quot;eg-mini-step&quot;&gt;STEP ${s.step+1} / 4 · ${names[s.step]}&lt;/p&gt;&lt;div aria-live=&quot;polite&quot;&gt;${content(s)}&lt;/div&gt;&lt;/div&gt;&lt;footer class=&quot;eg-footer&quot;&gt;&lt;span class=&quot;eg-small&quot;&gt;미리보기 · 실제 변경 없음&lt;/span&gt;&lt;div class=&quot;eg-actions&quot;&gt;${s.step&gt;0?&#x27;&lt;button class=&quot;eg-button eg-quiet cursor-interaction&quot; data-action=&quot;reset&quot;&gt;처음으로&lt;/button&gt;&#x27;:&#x27;&#x27;}&lt;button class=&quot;eg-button eg-primary cursor-interaction&quot; data-action=&quot;next&quot;&gt;${[&#x27;다음&#x27;,s.mode===&#x27;upgrade&#x27;?&#x27;업데이트 시작&#x27;:&#x27;설치 시작&#x27;,&#x27;완료 화면 보기&#x27;,s.finished?&#x27;다시 살펴보기&#x27;:&#x27;완료&#x27;][s.step]}&lt;i data-lucide=&quot;arrow-right&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;&lt;/button&gt;&lt;/div&gt;&lt;/footer&gt;&lt;/main&gt;&lt;/div&gt;`;
app.querySelectorAll(&#x27;[data-mode]&#x27;).forEach(b=&gt;b.addEventListener(&#x27;click&#x27;,()=&gt;{s.mode=b.dataset.mode;s.error=false;render(id);persist(id);}));
app.querySelector(&#x27;[data-path]&#x27;)?.addEventListener(&#x27;input&#x27;,e=&gt;{s.path=e.target.value;});
app.querySelector(&#x27;[data-agree]&#x27;)?.addEventListener(&#x27;change&#x27;,e=&gt;{s.agreed=e.target.checked;persist(id);});
app.querySelector(&#x27;[data-launch]&#x27;)?.addEventListener(&#x27;change&#x27;,e=&gt;{s.launch=e.target.checked;persist(id);});
app.querySelector(&#x27;[data-action=reset]&#x27;)?.addEventListener(&#x27;click&#x27;,()=&gt;{s.step=0;s.finished=false;s.error=false;render(id);persist(id);});
app.querySelector(&#x27;[data-action=next]&#x27;).addEventListener(&#x27;click&#x27;,()=&gt;{
if(s.step===0&amp;&amp;s.mode===&#x27;new&#x27;&amp;&amp;(!s.path.trim()||!s.agreed))s.error=true;
else if(s.step&lt;3){s.step++;s.error=false;}
else if(s.finished){s.step=0;s.finished=false;}
else s.finished=true;
render(id);persist(id);
app.querySelector(&#x27;[data-action=next]&#x27;).focus({preventScroll:true});
});
if(globalThis.lucide)lucide.createIcons({attrs:{width:16,height:16}});
}
definitions.forEach(d=&gt;render(d.id));
window.addEventListener(&#x27;openai:set_globals&#x27;,e=&gt;{
const restored=e.detail?.globals?.widgetState?.privateContent?.installers;
if(restored)definitions.forEach(d=&gt;{states[d.id]=normalize(restored[d.id]);render(d.id);});
});
if(globalThis.Tweak)definitions.forEach(d=&gt;{
const app=document.getElementById(d.id),style={radius:12};
const tweak=new Tweak({container:app,onChange:()=&gt;{app.style.borderRadius=style.radius+&#x27;px&#x27;;}});
tweak.addSlider(style,&#x27;radius&#x27;,{label:&#x27;창 모서리&#x27;,min:0,max:24,unit:&#x27;px&#x27;});
});
})();
&lt;/script&gt;
&lt;script src=&quot;https://unpkg.com/@floating-ui/core@1.7.3/dist/floating-ui.core.umd.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;https://unpkg.com/@floating-ui/dom@1.7.4/dist/floating-ui.dom.umd.min.js&quot;&gt;&lt;/script&gt;
&lt;script&gt;
(() =&gt; {
if (window.FloatingUIDOM == null) {
return;
}
const { autoUpdate, computePosition, flip, offset, shift, size } =
window.FloatingUIDOM;
const tooltipPlacements = new Set([&quot;top&quot;, &quot;right&quot;, &quot;bottom&quot;, &quot;left&quot;]);
const tooltipId = `codex-visualization-tooltip-${
window.crypto?.randomUUID?.() ?? Date.now().toString(36)
}`;
const tooltipGap = 5;
const hoverDelay = 700;
const skipDelay = 300;
let activeTrigger = null;
let pendingTrigger = null;
let tooltip = null;
let openTimeout = null;
let autoUpdateCleanup = null;
let positionRequest = 0;
let skipDelayUntil = 0;
let lastPointerWasTouch = false;
let pinnedByTouch = false;
const getTrigger = (event) =&gt; {
for (const target of event.composedPath()) {
if (target?.nodeType === Node.ELEMENT_NODE) {
const trigger = target.closest(&quot;[data-tooltip]&quot;);
if (trigger) return trigger;
}
}
return null;
};
const containsTarget = (trigger, target) =&gt;
target?.nodeType != null &amp;&amp; trigger.contains(target);
const getContent = (trigger) =&gt;
trigger.getAttribute(&quot;data-tooltip&quot;)?.trim() ?? &quot;&quot;;
const getPlacement = (trigger) =&gt; {
const placement = trigger.getAttribute(&quot;data-tooltip-placement&quot;);
return tooltipPlacements.has(placement) ? placement : &quot;top&quot;;
};
const getTooltip = () =&gt; {
if (tooltip == null) {
tooltip = document.createElement(&quot;div&quot;);
tooltip.id = tooltipId;
tooltip.className = &quot;tooltip&quot;;
tooltip.setAttribute(&quot;role&quot;, &quot;tooltip&quot;);
}
return tooltip;
};
const setDescribedBy = (trigger, described) =&gt; {
const ids = new Set(
(trigger.getAttribute(&quot;aria-describedby&quot;) ?? &quot;&quot;)
.split(/\s+/)
.filter(Boolean),
);
if (described) {
ids.add(tooltipId);
} else {
ids.delete(tooltipId);
}
if (ids.size === 0) {
trigger.removeAttribute(&quot;aria-describedby&quot;);
} else {
trigger.setAttribute(&quot;aria-describedby&quot;, [...ids].join(&quot; &quot;));
}
};
const clearOpenTimeout = () =&gt; {
if (openTimeout != null) {
window.clearTimeout(openTimeout);
openTimeout = null;
}
pendingTrigger = null;
};
const closeTooltip = (activateSkipDelay = true) =&gt; {
clearOpenTimeout();
pinnedByTouch = false;
positionRequest += 1;
autoUpdateCleanup?.();
autoUpdateCleanup = null;
if (activeTrigger == null) {
return;
}
setDescribedBy(activeTrigger, false);
activeTrigger = null;
tooltip?.remove();
if (activateSkipDelay) {
skipDelayUntil = Date.now() + skipDelay;
}
};
const updatePosition = (trigger, floating) =&gt; {
const request = ++positionRequest;
void computePosition(trigger, floating, {
middleware: [
offset(tooltipGap),
flip({ padding: tooltipGap }),
shift({ padding: tooltipGap }),
size({
padding: tooltipGap,
apply({ availableHeight, availableWidth, elements }) {
elements.floating.style.setProperty(
&quot;--tooltip-available-width&quot;,
`${Math.max(0, availableWidth)}px`,
);
elements.floating.style.setProperty(
&quot;--tooltip-available-height&quot;,
`${Math.max(0, availableHeight)}px`,
);
},
}),
],
placement: getPlacement(trigger),
strategy: &quot;fixed&quot;,
})
.then(({ x, y }) =&gt; {
if (activeTrigger !== trigger || request !== positionRequest) {
return;
}
const scale = window.devicePixelRatio || 1;
floating.style.transform = `translate(${Math.round(x * scale) / scale}px, ${Math.round(y * scale) / scale}px)`;
floating.style.visibility = &quot;visible&quot;;
})
.catch(() =&gt; {
if (activeTrigger === trigger &amp;&amp; request === positionRequest) {
closeTooltip(false);
}
});
};
const openTooltip = (trigger) =&gt; {
const content = getContent(trigger);
if (!trigger.isConnected || content.length === 0) {
return;
}
clearOpenTimeout();
if (activeTrigger === trigger) {
return;
}
closeTooltip();
const floating = getTooltip();
floating.textContent = content;
floating.style.visibility = &quot;hidden&quot;;
floating.style.transform = &quot;translate(0, 0)&quot;;
document.body.appendChild(floating);
activeTrigger = trigger;
setDescribedBy(trigger, true);
autoUpdateCleanup = autoUpdate(trigger, floating, () =&gt; {
updatePosition(trigger, floating);
});
};
const requestOpen = (trigger, immediate = false) =&gt; {
if (
getContent(trigger).length === 0 ||
activeTrigger === trigger ||
pendingTrigger === trigger
) {
return;
}
clearOpenTimeout();
pendingTrigger = trigger;
const delay =
immediate || activeTrigger != null || Date.now() &lt; skipDelayUntil
? 0
: hoverDelay;
if (delay === 0) {
openTooltip(trigger);
return;
}
openTimeout = window.setTimeout(() =&gt; {
openTimeout = null;
pendingTrigger = null;
openTooltip(trigger);
}, delay);
};
const handlePointerOpen = (event) =&gt; {
if (event.pointerType === &quot;touch&quot;) {
return;
}
const trigger = getTrigger(event);
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
return;
}
requestOpen(trigger);
};
const handlePointer = (event) =&gt; {
lastPointerWasTouch =
event.type === &quot;pointerdown&quot; &amp;&amp; event.pointerType === &quot;touch&quot;;
};
const handleTouchClick = (event) =&gt; {
const isTouch = event.pointerType === &quot;touch&quot; || lastPointerWasTouch;
lastPointerWasTouch = false;
if (!isTouch || event.detail === 0) {
return;
}
const trigger = getTrigger(event);
if (
trigger == null ||
!trigger.isConnected ||
getContent(trigger).length === 0 ||
(pinnedByTouch &amp;&amp; activeTrigger === trigger)
) {
if (pinnedByTouch) {
closeTooltip();
}
return;
}
openTooltip(trigger);
pinnedByTouch = activeTrigger === trigger;
window.setTimeout(() =&gt; {
if (activeTrigger === trigger &amp;&amp; !trigger.isConnected) {
closeTooltip(false);
}
}, 0);
};
const handleLeave = (event) =&gt; {
if (pinnedByTouch) {
return;
}
const trigger = getTrigger(event);
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
return;
}
if (activeTrigger === trigger || pendingTrigger === trigger) {
closeTooltip();
}
};
const handleFocusIn = (event) =&gt; {
const trigger = getTrigger(event);
if (trigger?.matches(&quot;:focus-visible&quot;)) {
requestOpen(trigger, true);
}
};
const handleKeyDown = (event) =&gt; {
if (event.key === &quot;Escape&quot;) {
closeTooltip();
}
};
const handleContextMenu = (event) =&gt; {
lastPointerWasTouch = false;
const trigger = getTrigger(event);
if (activeTrigger === trigger || pendingTrigger === trigger) {
closeTooltip();
}
};
const handlers = {
pointerover: handlePointerOpen,
pointerdown: handlePointer,
pointercancel: handlePointer,
click: handleTouchClick,
pointerout: handleLeave,
focusin: handleFocusIn,
focusout: handleLeave,
keydown: handleKeyDown,
contextmenu: handleContextMenu,
};
const roots = new Map();
const handledEvents = new WeakSet();
const handleEvent = (event) =&gt; {
if (handledEvents.has(event)) return;
handledEvents.add(event);
handlers[event.type](event);
};
const discoverRoots = (node) =&gt; {
if (node.shadowRoot) listen(node.shadowRoot);
for (const element of node.querySelectorAll(&quot;*&quot;)) {
if (element.shadowRoot) listen(element.shadowRoot);
}
};
const listen = (root) =&gt; {
if (roots.has(root)) return;
for (const type of Object.keys(handlers)) root.addEventListener(type, handleEvent);
const observer = new window.MutationObserver((records) =&gt; {
for (const { addedNodes } of records) {
for (const node of addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) discoverRoots(node);
}
}
for (const [root, cleanup] of roots) {
if (root.host &amp;&amp; !root.host.isConnected) {
cleanup();
roots.delete(root);
}
}
if (activeTrigger &amp;&amp; !activeTrigger.isConnected) closeTooltip(false);
});
observer.observe(root, { childList: true, subtree: true });
roots.set(root, () =&gt; {
observer.disconnect();
for (const type of Object.keys(handlers)) root.removeEventListener(type, handleEvent);
});
discoverRoots(root);
};
const destroy = () =&gt; {
closeTooltip(false);
lastPointerWasTouch = false;
for (const cleanup of roots.values()) cleanup();
roots.clear();
tooltip = null;
};
// Events between children of one shadow root never reach the document.
listen(document);
window.addEventListener(&quot;pagehide&quot;, destroy, { once: true });
})();
&lt;/script&gt;
&lt;script id=&quot;codex-visualization-tabs&quot;&gt;
(() =&gt; {
const selector = &#x27;.nav[role=&quot;tablist&quot;] [role=&quot;tab&quot;]&#x27;;
const disabled = &#x27;:disabled,[aria-disabled=&quot;true&quot;]&#x27;;
const selectTab = (selected) =&gt; {
const panelId = selected.getAttribute(&quot;aria-controls&quot;);
for (const tab of selected.closest(&#x27;[role=&quot;tablist&quot;]&#x27;).querySelectorAll(&#x27;[role=&quot;tab&quot;]&#x27;)) {
const active = tab === selected;
tab.classList.toggle(&quot;active&quot;, active);
tab.setAttribute(&quot;aria-selected&quot;, String(active));
const id = tab.getAttribute(&quot;aria-controls&quot;);
const panel = id &amp;&amp; document.getElementById(id);
if (panel != null) {
panel.hidden = id !== panelId;
if (!panel.hidden &amp;&amp; selected.id) {
panel.setAttribute(&quot;aria-labelledby&quot;, selected.id);
}
}
}
};
const interact = (event) =&gt; {
const tab = event.target?.closest?.(selector);
if (event.defaultPrevented || tab == null || tab.matches(disabled)) {
return;
}
if (event.type === &quot;click&quot;) {
selectTab(tab);
return;
}
const tabs = Array.from(
tab.closest(&#x27;[role=&quot;tablist&quot;]&#x27;).querySelectorAll(&#x27;[role=&quot;tab&quot;]&#x27;),
).filter((candidate) =&gt; !candidate.matches(disabled));
const current = tabs.indexOf(tab);
let next;
if (event.key === &quot;ArrowRight&quot; || event.key === &quot;ArrowDown&quot;) {
next = (current + 1) % tabs.length;
} else if (event.key === &quot;ArrowLeft&quot; || event.key === &quot;ArrowUp&quot;) {
next = (current + tabs.length - 1) % tabs.length;
} else if (event.key === &quot;Home&quot;) {
next = 0;
} else if (event.key === &quot;End&quot;) {
next = tabs.length - 1;
} else {
return;
}
event.preventDefault();
tabs[next].focus();
selectTab(tabs[next]);
};
for (const list of document.querySelectorAll(&#x27;.nav[role=&quot;tablist&quot;]&#x27;)) {
const selected =
list.querySelector(&#x27;[role=&quot;tab&quot;][aria-selected=&quot;true&quot;]&#x27;) ??
list.querySelector(&#x27;[role=&quot;tab&quot;].active&#x27;) ??
list.querySelector(&#x27;[role=&quot;tab&quot;]&#x27;);
if (selected != null) {
selectTab(selected);
}
}
document.addEventListener(&quot;click&quot;, interact);
document.addEventListener(&quot;keydown&quot;, interact);
window.addEventListener(&quot;pagehide&quot;, () =&gt; {
document.removeEventListener(&quot;click&quot;, interact);
document.removeEventListener(&quot;keydown&quot;, interact);
}, { once: true });
})();
&lt;/script&gt;
&lt;script id=&quot;codex-visualization-carousel&quot;&gt;
(() =&gt; {
for (const root of document.querySelectorAll(&quot;.viz-carousel&quot;)) {
const panels = Array.from(
root.querySelectorAll(&quot;:scope &gt; [data-variant]&quot;),
);
if (panels.length === 0) {
continue;
}
root.setAttribute(&quot;role&quot;, &quot;group&quot;);
panels.forEach((panel, index) =&gt; {
panel.hidden = index !== 0;
panel.setAttribute(&quot;role&quot;, &quot;group&quot;);
if (!panel.hasAttribute(&quot;aria-label&quot;)) {
panel.setAttribute(&quot;aria-label&quot;, panel.dataset.variant);
}
});
root.dataset.activeVariant = panels[0].dataset.variant;
if (panels.length === 1) {
continue;
}
const controls = document.createElement(&quot;div&quot;);
controls.className = &quot;viz-carousel-controls&quot;;
// Portable copy of the settings dropdown&#x27;s legacy glyph; no 14px icon-package preset matches it.
// https://github.com/openai/openai/blob/master/codex/codex-apps/webview/src/icons/chevron.svg
controls.innerHTML = `
&lt;button type=&quot;button&quot; class=&quot;viz-carousel-previous cursor-interaction&quot;&gt;&lt;i data-lucide=&quot;chevron-left&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;&lt;/button&gt;
&lt;details class=&quot;viz-carousel-picker&quot;&gt;
&lt;summary class=&quot;cursor-interaction&quot;&gt;
&lt;span class=&quot;viz-carousel-count tabular-nums&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;viz-carousel-name&quot;&gt;&lt;/span&gt;
&lt;svg class=&quot;viz-carousel-caret&quot; width=&quot;14&quot; height=&quot;14&quot; viewBox=&quot;0 0 20 21&quot; fill=&quot;none&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;
&lt;path d=&quot;M15.2793 7.71101C15.539 7.45131 15.961 7.45131 16.2207 7.71101C16.4804 7.97071 16.4804 8.39272 16.2207 8.65242L10.4707 14.4024C10.211 14.6621 9.78902 14.6621 9.52932 14.4024L3.77932 8.65242L3.69436 8.54792C3.52385 8.28979 3.55205 7.93828 3.77932 7.71101C4.00659 7.48374 4.3581 7.45554 4.61623 7.62605L4.72073 7.71101L10 12.9903L15.2793 7.71101Z&quot; fill=&quot;currentColor&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;0.6&quot; /&gt;
&lt;/svg&gt;
&lt;/summary&gt;
&lt;div class=&quot;viz-carousel-options&quot;&gt;&lt;/div&gt;
&lt;/details&gt;
&lt;button type=&quot;button&quot; class=&quot;viz-carousel-next cursor-interaction&quot;&gt;&lt;i data-lucide=&quot;chevron-right&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;&lt;/button&gt;`;
const previous = controls.querySelector(&quot;.viz-carousel-previous&quot;);
const next = controls.querySelector(&quot;.viz-carousel-next&quot;);
const picker = controls.querySelector(&quot;.viz-carousel-picker&quot;);
const summary = picker.querySelector(&quot;summary&quot;);
const count = controls.querySelector(&quot;.viz-carousel-count&quot;);
const name = controls.querySelector(&quot;.viz-carousel-name&quot;);
const options = controls.querySelector(&quot;.viz-carousel-options&quot;);
const status = document.createElement(&quot;span&quot;);
status.className = &quot;sr-only&quot;;
status.setAttribute(&quot;role&quot;, &quot;status&quot;);
status.setAttribute(&quot;aria-live&quot;, &quot;polite&quot;);
status.setAttribute(&quot;aria-atomic&quot;, &quot;true&quot;);
previous.setAttribute(
&quot;aria-label&quot;,
root.dataset.previousLabel ?? &quot;Previous variant&quot;,
);
next.setAttribute(&quot;aria-label&quot;, root.dataset.nextLabel ?? &quot;Next variant&quot;);
let active = 0;
let mousePointerId = null;
let summaryPointerHandled = false;
summary.addEventListener(&quot;pointerdown&quot;, (event) =&gt; {
summaryPointerHandled =
event.pointerType === &quot;mouse&quot; &amp;&amp; event.button === 0 &amp;&amp; !event.ctrlKey;
if (!summaryPointerHandled) {
return;
}
event.preventDefault();
summary.focus({ preventScroll: true });
picker.open = !picker.open;
mousePointerId = picker.open ? event.pointerId : null;
});
summary.addEventListener(&quot;click&quot;, (event) =&gt; {
if (summaryPointerHandled &amp;&amp; event.detail &gt; 0) {
event.preventDefault();
}
});
const choices = panels.map((panel, index) =&gt; {
const choice = document.createElement(&quot;button&quot;);
choice.type = &quot;button&quot;;
choice.className = &quot;cursor-interaction&quot;;
choice.textContent = panel.dataset.variant;
choice.addEventListener(&quot;click&quot;, () =&gt; {
select(index);
summary.focus();
});
choice.addEventListener(&quot;pointerup&quot;, (event) =&gt; {
if (
picker.open &amp;&amp;
event.pointerType === &quot;mouse&quot; &amp;&amp;
event.button === 0 &amp;&amp;
event.pointerId === mousePointerId
) {
choice.click();
}
});
options.appendChild(choice);
return choice;
});
const select = (index) =&gt; {
const changed = active !== index;
active = index;
panels.forEach((panel, position) =&gt; {
panel.hidden = position !== active;
choices[position].setAttribute(
&quot;aria-current&quot;,
String(position === active),
);
});
root.dataset.activeVariant = panels[active].dataset.variant;
count.textContent = `${active + 1} / ${panels.length}`;
name.textContent = panels[active].dataset.variant;
picker.open = false;
mousePointerId = null;
if (changed) {
status.textContent = `${count.textContent}: ${name.textContent}`;
}
};
previous.addEventListener(&quot;click&quot;, () =&gt;
select((active + panels.length - 1) % panels.length),
);
next.addEventListener(&quot;click&quot;, () =&gt;
select((active + 1) % panels.length),
);
picker.addEventListener(&quot;keydown&quot;, (event) =&gt; {
if (event.key === &quot;Escape&quot; &amp;&amp; picker.open) {
event.preventDefault();
picker.open = false;
mousePointerId = null;
summary.focus();
return;
}
if (event.altKey || event.ctrlKey || event.metaKey) {
return;
}
const index = choices.indexOf(document.activeElement);
let nextIndex;
if (event.key === &quot;ArrowDown&quot;) {
nextIndex = index &lt; 0 ? active : (index + 1) % choices.length;
} else if (event.key === &quot;ArrowUp&quot;) {
nextIndex = index &lt; 0 ? active : (index + choices.length - 1) % choices.length;
} else if (picker.open &amp;&amp; event.key === &quot;Home&quot;) {
nextIndex = 0;
} else if (picker.open &amp;&amp; event.key === &quot;End&quot;) {
nextIndex = choices.length - 1;
} else {
return;
}
event.preventDefault();
picker.open = true;
choices[nextIndex].focus();
});
picker.addEventListener(&quot;focusout&quot;, (event) =&gt; {
if (!picker.contains(event.relatedTarget)) {
picker.open = false;
mousePointerId = null;
}
});
const dismiss = (event) =&gt; {
if (!summary.contains(event.target)) {
mousePointerId = null;
}
if (!picker.contains(event.target)) {
picker.open = false;
}
};
const endPointerSelection = () =&gt; {
mousePointerId = null;
};
const sizePicker = () =&gt; {
root.style.setProperty(
&quot;--viz-carousel-picker-height&quot;,
`${picker.open ? options.offsetHeight : 0}px`,
);
};
picker.addEventListener(&quot;toggle&quot;, sizePicker);
window.addEventListener(&quot;resize&quot;, sizePicker);
document.addEventListener(&quot;pointerdown&quot;, dismiss);
document.addEventListener(&quot;pointerup&quot;, endPointerSelection);
document.addEventListener(&quot;pointercancel&quot;, endPointerSelection);
window.addEventListener(&quot;blur&quot;, endPointerSelection);
window.addEventListener(
&quot;pagehide&quot;,
() =&gt; {
document.removeEventListener(&quot;pointerdown&quot;, dismiss);
document.removeEventListener(&quot;pointerup&quot;, endPointerSelection);
document.removeEventListener(&quot;pointercancel&quot;, endPointerSelection);
window.removeEventListener(&quot;blur&quot;, endPointerSelection);
window.removeEventListener(&quot;resize&quot;, sizePicker);
},
{ once: true },
);
root.append(controls, status);
select(0);
}
})();
&lt;/script&gt;
&lt;script id=&quot;codex-visualization-lucide&quot; async src=&quot;https://unpkg.com/lucide@1.17.0/dist/umd/lucide.js&quot;&gt;&lt;/script&gt;
&lt;script&gt;
(() =&gt; {
const initialize = () =&gt; {
globalThis.lucide?.createIcons({ attrs: { width: 16, height: 16 } });
};
if (globalThis.lucide != null) {
initialize();
return;
}
document
.getElementById(&quot;codex-visualization-lucide&quot;)
?.addEventListener(&quot;load&quot;, initialize, { once: true });
})();
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
"></iframe>
<script>(() => {
const frame = document.getElementById("codex-visualization");
if (!(frame instanceof HTMLIFrameElement)) {
return;
}
const isShare = document.documentElement.classList.contains(
"visualization-share-root",
);
let viewportHeight = globalThis.innerHeight;
let sizeFingerprint = null;
let sizeWriteTimeout;
let lastSize = null;
let pendingSize = null;
const storageKey = `codex:visualization-widget-state-v2:${JSON.stringify([
globalThis.location.pathname,
globalThis.location.search,
])}`;
const sizeStorageKey = `${storageKey}:share-size-v1`;
const parseState = (serializedState) => {
if (
typeof serializedState !== "string" ||
new TextEncoder().encode(serializedState).length > 16 * 1024
) {
throw new TypeError(
"Widget state must be a JSON object of at most 16 KiB",
);
}
/** @type {unknown} */
const state = JSON.parse(serializedState);
if (
state == null ||
typeof state !== "object" ||
Array.isArray(state) ||
("imageIds" in state &&
(!Array.isArray(state.imageIds) || state.imageIds.length > 0))
) {
throw new TypeError("Widget state must be a JSON object");
}
const storedState = { modelContent: null, privateContent: null, ...state };
const canonicalState = JSON.stringify(
storedState,
(_key, /** @type {unknown} */ value) => {
if (typeof value === "number" && !Number.isFinite(value)) {
throw new TypeError("Widget state must contain finite numbers");
}
return value;
},
);
if (new TextEncoder().encode(canonicalState).length > 16 * 1024) {
throw new TypeError(
"Widget state must be a JSON object of at most 16 KiB",
);
}
return storedState;
};
let storage = null;
let state = null;
try {
const localStorage = globalThis.localStorage;
const probeKey = `${storageKey}:probe`;
localStorage.setItem(probeKey, "1");
localStorage.removeItem(probeKey);
storage = localStorage;
const savedState = storage.getItem(storageKey);
if (savedState != null) {
try {
state = parseState(savedState);
} catch {
// Invalid saved state does not disable future writes.
}
}
} catch {
storage = null;
}
const persistSize = () => {
globalThis.clearTimeout(sizeWriteTimeout);
if (pendingSize == null) {
return;
}
try {
storage?.setItem(sizeStorageKey, pendingSize);
} catch {
// A denied or full cache must not affect the visualization.
}
pendingSize = null;
};
if (isShare) {
globalThis.addEventListener("pagehide", persistSize);
}
const initialGlobals = {
widgetState: state,
statePersistence: storage == null ? "none" : "local",
};
const channel = new MessageChannel();
channel.port1.addEventListener("message", (event) => {
const data = event.data;
if (typeof data !== "object" || data == null) {
return;
}
if (data.type === "widget-state-write") {
if (!Number.isSafeInteger(data.id) || data.id <= 0) {
return;
}
let ok = false;
try {
if (storage != null) {
const state = parseState(data.state);
storage.setItem(storageKey, JSON.stringify(state));
ok = true;
}
} catch {
// Storage denial, quota limits, and invalid state return a failed receipt.
}
channel.port1.postMessage({
type: "widget-state-result",
id: data.id,
ok,
});
return;
}
if (data.type === "height") {
const height = data.height;
if (
typeof height !== "number" ||
!Number.isFinite(height) ||
height < 0
) {
return;
}
if (height <= 10_000) {
const width = isShare && storage != null ? frame.clientWidth : null;
frame.style.height = Math.ceil(height) + "px";
if (isShare && storage != null) {
const size = JSON.stringify({
fingerprint: sizeFingerprint,
width,
viewportHeight: globalThis.innerHeight,
dark: globalThis.matchMedia("(prefers-color-scheme: dark)").matches,
scale: globalThis.devicePixelRatio,
height: Math.ceil(height),
});
if (size !== lastSize) {
lastSize = size;
pendingSize = size;
globalThis.clearTimeout(sizeWriteTimeout);
sizeWriteTimeout = globalThis.setTimeout(persistSize, 100);
}
}
}
if (isShare) {
// Keep unusually tall content accessible without accepting an unbounded height.
if (height > 10_000 && Number.parseFloat(frame.style.height) === 0) {
frame.style.removeProperty("height");
}
frame.scrolling = height > 10_000 ? "auto" : "no";
document.documentElement.classList.add("visualization-share-ready");
}
return;
}
if (data.type === "scroll-to") {
const top = data.top;
if (
typeof top === "number" &&
Number.isFinite(top) &&
top >= 0 &&
globalThis.navigator.userActivation?.isActive === true
) {
globalThis.scrollTo({
top: Math.ceil(
globalThis.scrollY + frame.getBoundingClientRect().top + top,
),
});
}
return;
}
if (data.type === "open-external") {
const rawHref = data.href;
if (typeof rawHref !== "string") {
return;
}
let href;
try {
const url = new URL(rawHref, document.baseURI);
if (url.protocol !== "https:") {
return;
}
href = url.href;
} catch {
return;
}
if (globalThis.navigator.userActivation?.isActive === true) {
const anchor = document.createElement("a");
anchor.href = href;
anchor.rel = "noopener noreferrer";
anchor.target = "_blank";
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
}
return;
}
if (data.type === "follow-up") {
const prompt = data.prompt;
if (
typeof prompt !== "string" ||
globalThis.navigator.userActivation?.isActive !== true
) {
return;
}
const title = data.title;
globalThis.prompt(typeof title === "string" ? title : prompt, prompt);
}
});
channel.port1.start();
const srcdoc = frame.dataset.srcdoc;
if (srcdoc == null) {
return;
}
globalThis.addEventListener("resize", () => {
const height = Number.parseFloat(frame.style.height);
if (!isShare && (!Number.isFinite(height) || height <= viewportHeight)) {
frame.style.removeProperty("height");
}
viewportHeight = globalThis.innerHeight;
channel.port1.postMessage({ type: "measure" });
});
frame.addEventListener(
"load",
() => {
frame.contentWindow?.postMessage(
{ type: "codex-visualization-initialize" },
"*",
[channel.port2],
);
},
{ once: true },
);
// Match the trusted bootstrap element, not the same text in an authored title.
const frameHtml = srcdoc.replace(
'<script type="application/json" id="codex-visualization-widget-state">__CODEX_VISUALIZATION_WIDGET_STATE__<\/script>',
() =>
'<script type="application/json" id="codex-visualization-widget-state">' +
JSON.stringify(initialGlobals).replaceAll("<", "\\u003c") +
"<\/script>",
);
if (isShare && storage != null) {
// Fingerprint the rendered document, including restored widget state, without
// storing its content. This is a layout cache, not a security identity.
let hash = 5381;
for (let index = 0; index < frameHtml.length; index += 1) {
hash = (hash * 33 + frameHtml.charCodeAt(index)) % 4_294_967_296;
}
sizeFingerprint = hash.toString(36);
try {
const size = JSON.parse(storage.getItem(sizeStorageKey) ?? "null");
if (
size?.fingerprint === sizeFingerprint &&
size.width === frame.clientWidth &&
size.viewportHeight === globalThis.innerHeight &&
size.dark ===
globalThis.matchMedia("(prefers-color-scheme: dark)").matches &&
size.scale === globalThis.devicePixelRatio &&
typeof size.height === "number" &&
Number.isFinite(size.height) &&
size.height >= 0 &&
size.height <= 10_000
) {
frame.style.height = size.height + "px";
}
} catch {
// Ignore missing or invalid cache entries and measure the live frame.
}
}
frame.srcdoc = frameHtml;
delete frame.dataset.srcdoc;
})();
</script>
</body>
</html>