Files
networkSetup/output/egbim-installer-wizard-preview.html
jychoi4 24b624301c docs: define MSIX-first packaging design and roadmap
Document package formats, release workflows, deployment planning and UI concepts.

Closes #1
2026-10-27 16:30:00 +09:00

2823 lines
116 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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 Wizard</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 Wizard" 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 Wizard&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-wizard-design { width:100%; }
#eg-wizard-design .ew-stage { min-height:690px; }
#eg-wizard-design .ew-preview,#eg-wizard-design .ew-app { --ew-bg:light-dark(#ffffff,#20252d); --ew-soft:light-dark(#f4f6f9,#191e26); --ew-line:light-dark(#dce2ea,#3c4553); --ew-ink:light-dark(#202c3d,#edf2fa); --ew-muted:light-dark(#5f6d80,#b0bdce); --ew-blue:light-dark(#2656bd,#a6c0ff); --ew-on:light-dark(#ffffff,#112956); --ew-wash:light-dark(#edf3ff,#293a57); --ew-good:light-dark(#246b50,#94d9b7); --ew-warn:light-dark(#865517,#efc280); --ew-bad:light-dark(#a63532,#ffb0ac); color:var(--ew-ink); font:14px/1.6 &#x27;Segoe UI&#x27;,&#x27;Malgun Gothic&#x27;,sans-serif; }
#eg-wizard-design * { box-sizing:border-box; }
#eg-wizard-design .ew-preview { display:flex; flex-wrap:wrap; align-items:flex-end; gap:8px; padding:14px 0; padding-right:92px; }
#eg-wizard-design .ew-preview label { display:grid; gap:4px; font-size:11px; color:var(--ew-muted); }
#eg-wizard-design .ew-preview select { max-width:100%; background:var(--ew-bg); color:var(--ew-ink); border:1px solid var(--ew-line); border-radius:5px; padding:6px 8px; font:12px/1.5 &#x27;Segoe UI&#x27;,&#x27;Malgun Gothic&#x27;,sans-serif; }
#eg-wizard-design button { font:500 12px/1.5 &#x27;Segoe UI&#x27;,&#x27;Malgun Gothic&#x27;,sans-serif; }
#eg-wizard-design .ew-button { display:inline-flex; justify-content:center; align-items:center; gap:7px; min-height:34px; padding:7px 16px; border:1px solid var(--ew-line); border-radius:5px; color:var(--ew-ink); background:var(--ew-bg); }
#eg-wizard-design button:disabled { opacity:.48; cursor:not-allowed; }
#eg-wizard-design .ew-primary { background:var(--ew-blue); color:var(--ew-on); border-color:var(--ew-blue); }
#eg-wizard-design .ew-link { background:transparent; border:0; color:var(--ew-blue); padding:5px 0; }
#eg-wizard-design .ew-app { background:var(--ew-bg); border:1px solid var(--ew-line); border-radius:9px; overflow-wrap:anywhere; box-shadow:0 8px 30px light-dark(#17305710,#00000025); }
#eg-wizard-design .ew-title { min-height:44px; display:flex; gap:9px; align-items:center; padding:10px 18px; border-bottom:1px solid var(--ew-line); font-size:12px; }
#eg-wizard-design .ew-mark { display:grid; place-items:center; width:23px; height:23px; background:var(--ew-blue); color:var(--ew-on); border-radius:5px; font-weight:600; flex-shrink:0; }
#eg-wizard-design .ew-tag { margin-left:auto; font-size:11px; color:var(--ew-muted); }
#eg-wizard-design .ew-layout { display:grid; grid-template-columns:154px minmax(0,1fr); min-height:480px; }
#eg-wizard-design .ew-side { background:var(--ew-soft); padding:28px 18px; border-right:1px solid var(--ew-line); }
#eg-wizard-design .ew-side-brand { font-size:17px; font-weight:600; letter-spacing:-.6px; margin-bottom:4px; }
#eg-wizard-design .ew-side-sub { color:var(--ew-muted); font-size:11px; }
#eg-wizard-design .ew-steps { list-style:none; display:grid; gap:21px; margin:34px 0 0; padding:0; }
#eg-wizard-design .ew-steps li { display:flex; align-items:center; gap:9px; color:var(--ew-muted); font-size:12px; }
#eg-wizard-design .ew-number { display:grid; place-items:center; width:22px; height:22px; border:1px solid var(--ew-line); border-radius:50%; font-size:11px; flex-shrink:0; }
#eg-wizard-design .ew-steps [aria-current=step] { color:var(--ew-blue); font-weight:600; }
#eg-wizard-design .ew-steps [aria-current=step] .ew-number { background:var(--ew-blue); color:var(--ew-on); border-color:transparent; }
#eg-wizard-design .ew-past .ew-number { color:var(--ew-good); }
#eg-wizard-design .ew-main { min-width:0; }
#eg-wizard-design .ew-page-heading { padding:23px 26px 18px; border-bottom:1px solid var(--ew-line); }
#eg-wizard-design .ew-page-heading h2 { margin:0 0 5px; font-size:19px; font-weight:600; letter-spacing:-.6px; }
#eg-wizard-design .ew-page-heading p { margin:0; color:var(--ew-muted); font-size:12px; }
#eg-wizard-design .ew-body { padding:22px 26px; }
#eg-wizard-design .ew-body h3 { font-size:15px; margin:0 0 10px; font-weight:600; }
#eg-wizard-design .ew-body p { margin:0 0 14px; font-size:13px; }
#eg-wizard-design .ew-muted { color:var(--ew-muted); }
#eg-wizard-design .ew-small { font-size:11px !important; }
#eg-wizard-design .ew-box { padding:14px 16px; border:1px solid var(--ew-line); border-radius:6px; margin:14px 0; background:var(--ew-bg); }
#eg-wizard-design .ew-box-top { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; margin-bottom:7px; }
#eg-wizard-design .ew-chip { display:inline-block; color:var(--ew-good); background:var(--ew-soft); font-size:11px; padding:2px 7px; border-radius:4px; }
#eg-wizard-design .ew-path { display:block; font-size:12px; color:var(--ew-muted); overflow-wrap:anywhere; }
#eg-wizard-design .ew-note { display:flex; align-items:flex-start; gap:9px; padding:12px; background:var(--ew-wash); color:var(--ew-blue); border-radius:5px; font-size:12px; margin-top:14px; }
#eg-wizard-design .ew-note svg { flex-shrink:0; margin-top:2px; }
#eg-wizard-design .ew-list { padding:0; margin:0; list-style:none; }
#eg-wizard-design .ew-list li { display:flex; justify-content:space-between; gap:12px; padding:9px 0; font-size:12px; border-bottom:1px solid var(--ew-line); }
#eg-wizard-design .ew-list li:last-child { border:0; }
#eg-wizard-design .ew-good { color:var(--ew-good); }
#eg-wizard-design .ew-bad { color:var(--ew-bad); }
#eg-wizard-design .ew-status { font-size:11px; white-space:nowrap; }
#eg-wizard-design .ew-field { display:grid; gap:6px; font-size:12px; margin:0 0 16px; }
#eg-wizard-design .ew-field input { width:100%; border:1px solid var(--ew-line); border-radius:5px; padding:9px 10px; background:var(--ew-bg); color:var(--ew-ink); font:12px/1.5 &#x27;Segoe UI&#x27;,&#x27;Malgun Gothic&#x27;,sans-serif; }
#eg-wizard-design .ew-choice { display:flex; align-items:flex-start; gap:8px; padding:9px 0; font-size:12px; }
#eg-wizard-design .ew-choice input { accent-color:var(--ew-blue); margin:3px 0 0; flex-shrink:0; }
#eg-wizard-design .ew-choice span { display:block; }
#eg-wizard-design .ew-choice small { display:block; color:var(--ew-muted); font-size:11px; margin-top:2px; }
#eg-wizard-design .ew-rule { height:1px; background:var(--ew-line); margin:14px 0; }
#eg-wizard-design .ew-summary { display:grid; grid-template-columns:88px minmax(0,1fr); gap:10px 14px; font-size:12px; margin:0; }
#eg-wizard-design .ew-summary dt { color:var(--ew-muted); }
#eg-wizard-design .ew-summary dd { margin:0; }
#eg-wizard-design .ew-progress { background:var(--ew-soft); height:8px; border-radius:4px; overflow:hidden; margin:18px 0 7px; }
#eg-wizard-design .ew-progress span { display:block; background:var(--ew-blue); height:100%; }
#eg-wizard-design .ew-progress-label { display:flex; justify-content:space-between; gap:12px; color:var(--ew-muted); font-size:12px; margin-bottom:15px; }
#eg-wizard-design .ew-footer { border-top:1px solid var(--ew-line); background:var(--ew-soft); padding:14px 18px; border-radius:0 0 9px 9px; display:flex; justify-content:space-between; align-items:center; gap:10px; flex-wrap:wrap; }
#eg-wizard-design .ew-footer-label { color:var(--ew-muted); font-size:11px; }
#eg-wizard-design .ew-actions { display:flex; gap:7px; flex-wrap:wrap; margin-left:auto; }
#eg-wizard-design .ew-symbol { display:grid; place-items:center; width:42px; height:42px; border-radius:50%; background:var(--ew-wash); color:var(--ew-blue); font-size:23px; margin:0 0 15px; }
#eg-wizard-design .ew-log { margin-top:16px; color:var(--ew-muted); font-size:11px; }
#eg-wizard-design .ew-log summary { width:fit-content; }
#eg-wizard-design .ew-log pre { white-space:pre-wrap; overflow-wrap:anywhere; font:11px/1.7 &#x27;Segoe UI&#x27;,&#x27;Malgun Gothic&#x27;,sans-serif; }
#eg-wizard-design .ew-confirm { background:var(--ew-bg); border:1px solid var(--ew-line); border-radius:6px; padding:16px; margin-top:16px; }
#eg-wizard-design .ew-confirm .ew-actions { margin-top:12px; }
#eg-wizard-design .ew-compact .ew-layout { display:block; }
#eg-wizard-design .ew-compact .ew-side { display:none; }
#eg-wizard-design .ew-compact .ew-page-heading { padding-left:30px; padding-right:30px; background:var(--ew-soft); }
#eg-wizard-design .ew-compact .ew-body { max-width:610px; padding:24px 30px; margin:auto; }
#eg-wizard-design .ew-caption { color:var(--ew-muted); font-size:11px; margin:10px 0 0; }
@media(max-width:580px) { #eg-wizard-design .ew-stage { min-height:850px; } #eg-wizard-design .ew-layout { display:block; } #eg-wizard-design .ew-side { padding:12px 16px; border-right:0; border-bottom:1px solid var(--ew-line); } #eg-wizard-design .ew-side-brand,#eg-wizard-design .ew-side-sub { display:none; } #eg-wizard-design .ew-steps { display:flex; gap:7px; justify-content:space-between; margin:0; } #eg-wizard-design .ew-steps li { flex-direction:column; gap:5px; font-size:11px; } #eg-wizard-design .ew-page-heading,#eg-wizard-design .ew-compact .ew-page-heading { padding:18px; } #eg-wizard-design .ew-body,#eg-wizard-design .ew-compact .ew-body { padding:18px; } #eg-wizard-design .ew-preview { padding-right:0; padding-top:55px; } #eg-wizard-design .ew-title { padding:10px 14px; } #eg-wizard-design .ew-summary { grid-template-columns:70px minmax(0,1fr); } }
@media(pointer:coarse) { #eg-wizard-design button,#eg-wizard-design select,#eg-wizard-design .ew-choice { min-height:44px; } #eg-wizard-design .ew-field input { font-size:16px; } }
&lt;/style&gt;
&lt;div id=&quot;eg-wizard-design&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; class=&quot;ew-stage&quot; aria-label=&quot;진행 안내형 설치 마법사&quot;&gt;&lt;div id=&quot;ew-guided-tools&quot; class=&quot;ew-preview&quot;&gt;&lt;/div&gt;&lt;div id=&quot;ew-guided&quot; class=&quot;ew-app&quot; aria-label=&quot;진행 안내형&quot;&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section data-variant=&quot;표준 마법사형&quot; class=&quot;ew-stage&quot; aria-label=&quot;표준 마법사형 설치 마법사&quot; hidden&gt;&lt;div id=&quot;ew-standard-tools&quot; class=&quot;ew-preview&quot;&gt;&lt;/div&gt;&lt;div id=&quot;ew-standard&quot; class=&quot;ew-app ew-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-wizard-design&#x27;);
const escape=v=&gt;String(v).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 modes={new:&#x27;신규 설치&#x27;,upgrade:&#x27;기존 버전 전환&#x27;,patch:&#x27;버전 패치&#x27;,repair:&#x27;설치 복구&#x27;,remove:&#x27;제거&#x27;};
const scenarios={normal:&#x27;정상 완료&#x27;,backupFail:&#x27;백업 실패&#x27;,applyFail:&#x27;설치 실패&#x27;,reboot:&#x27;재부팅 필요&#x27;,locked:&#x27;실행 중인 앱&#x27;};
const actions={new:&#x27;설치&#x27;,upgrade:&#x27;업데이트&#x27;,patch:&#x27;패치 적용&#x27;,repair:&#x27;복구&#x27;,remove:&#x27;제거&#x27;};
const basePath=&#x27;C:\\Program Files\\Baron\\EG-BIM&#x27;, legacyPath=&#x27;D:\\Baron\\EG-BIM&#x27;;
const defaults=()=&gt;({mode:&#x27;upgrade&#x27;,scenario:&#x27;normal&#x27;,page:&#x27;welcome&#x27;,agreed:false,path:basePath,resources:true,shortcut:true,removeConfirmed:false,launch:true,restart:false,confirmCancel:false,cancelled:false,error:&#x27;&#x27;,closed:false});
function sequence(s) { return s.mode===&#x27;new&#x27;?[&#x27;welcome&#x27;,&#x27;license&#x27;,&#x27;options&#x27;,&#x27;review&#x27;,&#x27;prepare&#x27;,&#x27;apply&#x27;,&#x27;finish&#x27;]:[&#x27;welcome&#x27;,&#x27;review&#x27;,&#x27;prepare&#x27;,&#x27;apply&#x27;,&#x27;finish&#x27;]; }
function normalize(input) { const s=defaults(); if(!input||typeof input!==&#x27;object&#x27;)return s; for(const key of Object.keys(s)){if(typeof input[key]===typeof s[key])s[key]=input[key];} if(!modes[s.mode])s.mode=&#x27;upgrade&#x27;;if(!scenarios[s.scenario])s.scenario=&#x27;normal&#x27;;if(![...sequence(s),&#x27;failed&#x27;,&#x27;cancelled&#x27;].includes(s.page))s.page=&#x27;welcome&#x27;; if(s.scenario===&#x27;backupFail&#x27;&amp;&amp;![&#x27;upgrade&#x27;,&#x27;patch&#x27;].includes(s.mode))s.scenario=&#x27;normal&#x27;;return s; }
const saved=window.openai?.widgetState?.privateContent?.wizards||{};
const states={&#x27;ew-guided&#x27;:normalize(saved[&#x27;ew-guided&#x27;]),&#x27;ew-standard&#x27;:normalize(saved[&#x27;ew-standard&#x27;])};
function next(s) {
s.error=&#x27;&#x27;;
if(s.page===&#x27;license&#x27;&amp;&amp;!s.agreed){s.error=&#x27;사용권 계약에 동의해야 다음 단계로 이동할 수 있습니다.&#x27;;return;}
if(s.page===&#x27;options&#x27;&amp;&amp;!/^[a-zA-Z]:\\[^&lt;&gt;:&quot;|?*\r\n]+$/.test(s.path)){s.error=&#x27;드라이브를 포함한 설치 경로를 입력해 주세요. 예: C:\\Program Files\\Baron\\EG-BIM&#x27;;return;}
if(s.page===&#x27;review&#x27;&amp;&amp;s.mode===&#x27;remove&#x27;&amp;&amp;!s.removeConfirmed){s.error=&#x27;제거할 항목을 확인하고 동의해 주세요.&#x27;;return;}
const seq=sequence(s),index=seq.indexOf(s.page); if(index&gt;=0&amp;&amp;index&lt;seq.length-1)s.page=seq[index+1];
}
function simulate(s) {
if(s.confirmCancel)return;
if(s.page===&#x27;prepare&#x27;) {
if(s.scenario===&#x27;locked&#x27;){s.error=&#x27;EG-BIM에서 도면을 저장하고 종료한 후 다시 확인해 주세요.&#x27;;return;}
s.page=s.scenario===&#x27;backupFail&#x27;?&#x27;failed&#x27;:&#x27;apply&#x27;;
} else if(s.page===&#x27;apply&#x27;)s.page=s.cancelled||s.scenario===&#x27;applyFail&#x27;?&#x27;failed&#x27;:&#x27;finish&#x27;;
}
function row(label,value,tone=&#x27;&#x27;) {return `&lt;li&gt;&lt;span&gt;${label}&lt;/span&gt;&lt;span class=&quot;ew-status ${tone}&quot;&gt;${value}&lt;/span&gt;&lt;/li&gt;`;}
function note(text,icon=&#x27;shield-check&#x27;) {return `&lt;div class=&quot;ew-note&quot;&gt;&lt;i data-lucide=&quot;${icon}&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;&lt;span&gt;${text}&lt;/span&gt;&lt;/div&gt;`;}
function choice(label,key,checked,sub=&#x27;&#x27;,disabled=false) {return `&lt;label class=&quot;ew-choice&quot;&gt;&lt;input type=&quot;checkbox&quot; data-value=&quot;${key}&quot; ${checked?&#x27;checked&#x27;:&#x27;&#x27;} ${disabled?&#x27;disabled&#x27;:&#x27;&#x27;}&gt;&lt;span&gt;${label}${sub?`&lt;small&gt;${sub}&lt;/small&gt;`:&#x27;&#x27;}&lt;/span&gt;&lt;/label&gt;`;}
function title(s) {
const a=actions[s.mode];
return {welcome:[s.mode===&#x27;new&#x27;?&#x27;EG-BIM 설치를 시작합니다&#x27;:s.mode===&#x27;upgrade&#x27;?&#x27;기존 EG-BIM을 업데이트합니다&#x27;:s.mode===&#x27;patch&#x27;?&#x27;새 버전의 변경 사항을 적용합니다&#x27;:s.mode===&#x27;repair&#x27;?&#x27;EG-BIM 설치를 복구합니다&#x27;:&#x27;EG-BIM을 제거합니다&#x27;,&#x27;계속하려면 내용을 확인하고 다음을 선택하세요.&#x27;],license:[&#x27;사용권 계약&#x27;,&#x27;설치하기 전에 사용권 계약을 확인해 주세요.&#x27;],options:[&#x27;설치 위치 및 옵션&#x27;,&#x27;설치할 위치와 추가 항목을 선택하세요.&#x27;],review:[`${a} 준비가 되었습니다`,&#x27;아래 내용을 확인하세요. 이전 단계로 돌아가 선택을 바꿀 수 있습니다.&#x27;],prepare:[&#x27;설치 환경을 준비하고 있습니다&#x27;,&#x27;작업을 저장하고 실행 중인 EG-BIM을 종료해 주세요.&#x27;],apply:[s.cancelled?&#x27;취소 요청을 처리하고 있습니다&#x27;:`${a} 작업을 진행하고 있습니다`,s.cancelled?&#x27;안전한 중단 지점과 현재 설치 상태를 확인합니다.&#x27;:&#x27;작업이 끝날 때까지 PC를 끄지 마세요.&#x27;],finish:[s.scenario===&#x27;reboot&#x27;?&#x27;완료하려면 PC를 다시 시작해야 합니다&#x27;:`${a} 작업이 완료되었습니다`,s.scenario===&#x27;reboot&#x27;?&#x27;열려 있는 작업을 저장한 후 다시 시작해 주세요.&#x27;:&#x27;아래 내용을 확인하고 마침을 선택하세요.&#x27;],failed:[s.scenario===&#x27;backupFail&#x27;?&#x27;설정 백업을 완료하지 못했습니다&#x27;:s.cancelled?&#x27;작업을 중단했습니다&#x27;:&#x27;설치를 완료하지 못했습니다&#x27;,s.scenario===&#x27;backupFail&#x27;?&#x27;기존 제품을 변경하지 않았습니다.&#x27;:&#x27;현재 상태를 확인한 후 복구 절차를 진행해야 합니다.&#x27;],cancelled:[&#x27;설치를 취소했습니다&#x27;,&#x27;프로그램을 변경하기 전에 종료했습니다.&#x27;]}[s.page];
}
function body(s) {
const a=actions[s.mode], existing=s.mode!==&#x27;new&#x27;, backup=[&#x27;upgrade&#x27;,&#x27;patch&#x27;].includes(s.mode), path=existing?legacyPath:s.path;
if(s.page===&#x27;welcome&#x27;)return `&lt;div class=&quot;ew-symbol&quot;&gt;&lt;i data-lucide=&quot;${s.mode===&#x27;remove&#x27;?&#x27;package-minus&#x27;:s.mode===&#x27;repair&#x27;?&#x27;wrench&#x27;:&#x27;package-check&#x27;}&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;&lt;/div&gt;&lt;h3&gt;${s.mode===&#x27;new&#x27;?&#x27;설계에 필요한 도구를 준비하세요.&#x27;:s.mode===&#x27;upgrade&#x27;?&#x27;작업 환경은 유지하고, 프로그램은 새롭게.&#x27;:s.mode===&#x27;patch&#x27;?&#x27;설치된 구성은 그대로 유지합니다.&#x27;:s.mode===&#x27;repair&#x27;?&#x27;프로그램 파일과 설치 정보를 복구합니다.&#x27;:&#x27;프로그램과 설치 항목을 제거합니다.&#x27;}&lt;/h3&gt;&lt;p class=&quot;ew-muted&quot;&gt;${s.mode===&#x27;remove&#x27;?&#x27;개인 도면, 사용자 설정과 보관된 백업은 유지합니다.&#x27;:s.mode===&#x27;new&#x27;?&#x27;마법사의 안내에 따라 설치 위치와 옵션을 선택할 수 있습니다.&#x27;:&#x27;작업 중인 도면을 저장한 후 진행해 주세요.&#x27;}&lt;/p&gt;&lt;div class=&quot;ew-box&quot;&gt;&lt;div class=&quot;ew-box-top&quot;&gt;&lt;strong&gt;EG-BIM&lt;/strong&gt;&lt;span class=&quot;ew-chip&quot;&gt;${existing?&#x27;기존 설치 확인&#x27;:&#x27;Windows · x64&#x27;}&lt;/span&gt;&lt;/div&gt;&lt;span class=&quot;ew-path&quot;&gt;${escape(path)}&lt;/span&gt;&lt;span class=&quot;ew-small ew-muted&quot;&gt;${existing?&#x27;기존 설치 위치를 그대로 사용합니다.&#x27;:&#x27;기본 설치 위치 · 다음 단계에서 변경 가능&#x27;}&lt;/span&gt;&lt;/div&gt;${note(backup?&#x27;설정 백업을 확인한 후 프로그램 변경을 시작합니다.&#x27;:s.mode===&#x27;remove&#x27;?&#x27;타사 CAD 연결과 사용자 데이터는 삭제 대상에서 제외합니다.&#x27;:&#x27;시스템 변경이 필요한 경우 Windows가 관리자 권한을 요청합니다.&#x27;)}`;
if(s.page===&#x27;license&#x27;)return `&lt;div class=&quot;ew-box&quot;&gt;&lt;h3&gt;EG-BIM 소프트웨어 사용권 계약&lt;/h3&gt;&lt;p class=&quot;ew-muted&quot;&gt;설치 화면 검토용 약관 자리표시자입니다.&lt;/p&gt;&lt;p class=&quot;ew-small ew-muted&quot;&gt;실제 설치 파일에는 제품 담당자가 승인한 약관 전문과 버전을 표시합니다. 이 시안의 선택은 법적 계약 동의나 실제 설치를 수행하지 않습니다.&lt;/p&gt;&lt;/div&gt;${choice(&#x27;사용권 계약에 동의합니다.&#x27;,&#x27;agreed&#x27;,s.agreed)}${note(&#x27;동의 여부는 설치 시작 전까지 변경할 수 있습니다.&#x27;,&#x27;info&#x27;)}`;
if(s.page===&#x27;options&#x27;)return `&lt;label class=&quot;ew-field&quot;&gt;설치 위치&lt;input data-path aria-label=&quot;설치 위치&quot; value=&quot;${escape(s.path)}&quot; spellcheck=&quot;false&quot;&gt;&lt;/label&gt;&lt;h3&gt;설치 구성&lt;/h3&gt;${choice(&#x27;EG-BIM 프로그램&#x27;,&#x27;core&#x27;,true,&#x27;필수 구성요소&#x27;,true)}${choice(&#x27;추가 리소스&#x27;,&#x27;resources&#x27;,s.resources,&#x27;기능 선택 예시 · 실제 리소스 목록은 별도 확정&#x27;)}&lt;div class=&quot;ew-rule&quot;&gt;&lt;/div&gt;${choice(&#x27;바탕 화면에 바로가기 만들기&#x27;,&#x27;shortcut&#x27;,s.shortcut)}&lt;p class=&quot;ew-small ew-muted&quot;&gt;기존 CAD 파일 연결은 자동으로 변경하지 않습니다.&lt;/p&gt;`;
if(s.page===&#x27;review&#x27;)return `&lt;dl class=&quot;ew-summary&quot;&gt;&lt;dt&gt;작업&lt;/dt&gt;&lt;dd&gt;${modes[s.mode]}&lt;/dd&gt;&lt;dt&gt;${s.mode===&#x27;remove&#x27;?&#x27;제거 위치&#x27;:&#x27;설치 위치&#x27;}&lt;/dt&gt;&lt;dd&gt;${escape(path)}${existing&amp;&amp;s.mode!==&#x27;remove&#x27;?&#x27; · 유지&#x27;:&#x27;&#x27;}&lt;/dd&gt;&lt;dt&gt;설치 구성&lt;/dt&gt;&lt;dd&gt;${s.mode===&#x27;remove&#x27;?&#x27;현재 설치된 프로그램 제거&#x27;:existing?&#x27;현재 설치된 기능 유지&#x27;:s.resources?&#x27;프로그램 + 추가 리소스&#x27;:&#x27;프로그램&#x27;}&lt;/dd&gt;&lt;dt&gt;사용자 데이터&lt;/dt&gt;&lt;dd&gt;${backup?&#x27;설정·CUI·추가 리소스 백업 후 호환 설정 복원&#x27;:s.mode===&#x27;remove&#x27;?&#x27;개인 도면·설정·백업 유지&#x27;:&#x27;기존 데이터 유지&#x27;}&lt;/dd&gt;${s.mode===&#x27;new&#x27;?`&lt;dt&gt;바로가기&lt;/dt&gt;&lt;dd&gt;${s.shortcut?&#x27;바탕 화면에 생성&#x27;:&#x27;생성 안 함&#x27;}&lt;/dd&gt;`:&#x27;&#x27;}&lt;dt&gt;필수 구성&lt;/dt&gt;&lt;dd&gt;${s.mode===&#x27;remove&#x27;?&#x27;공유 런타임 유지&#x27;:&#x27;필요한 런타임만 준비&#x27;}&lt;/dd&gt;&lt;/dl&gt;${s.mode===&#x27;remove&#x27;?choice(&#x27;프로그램을 제거하고 개인 데이터는 유지합니다.&#x27;,&#x27;removeConfirmed&#x27;,s.removeConfirmed):note(backup?&#x27;파일 확보와 백업 검증에 실패하면 기존 제품을 제거하지 않습니다.&#x27;:&#x27;설치 전 패키지와 저장 공간을 다시 확인합니다.&#x27;)}&lt;details class=&quot;ew-log&quot;&gt;&lt;summary class=&quot;cursor-interaction&quot;&gt;예정 작업 자세히 보기&lt;/summary&gt;&lt;p&gt;환경 검사 → 파일 검증 → ${backup?&#x27;앱 종료 확인 · 설정 백업 → &#x27;:&#x27;&#x27;}${a} → 상태 확인${backup?&#x27; · 설정 복원&#x27;:&#x27;&#x27;}&lt;/p&gt;&lt;/details&gt;`;
if(s.page===&#x27;prepare&#x27;)return `&lt;ul class=&quot;ew-list&quot;&gt;${row(&#x27;지원 환경 · 저장 공간&#x27;,&#x27;확인 완료&#x27;,&#x27;ew-good&#x27;)}${row(&#x27;설치 파일 · 서명 · 무결성&#x27;,&#x27;확인 완료&#x27;,&#x27;ew-good&#x27;)}${row(&#x27;실행 중인 프로그램&#x27;,s.scenario===&#x27;locked&#x27;?&#x27;EG-BIM 실행 중&#x27;:&#x27;종료 확인&#x27;,s.scenario===&#x27;locked&#x27;?&#x27;ew-bad&#x27;:&#x27;ew-good&#x27;)}${backup?row(&#x27;사용자 설정 백업&#x27;,s.scenario===&#x27;locked&#x27;?&#x27;대기&#x27;:&#x27;검사 중&#x27;):row(&#x27;실행 계획 준비&#x27;,&#x27;준비 중&#x27;)}&lt;/ul&gt;${s.scenario===&#x27;locked&#x27;?`${note(&#x27;도면을 직접 저장하고 EG-BIM을 종료해 주세요. 강제 종료하지 않습니다.&#x27;,&#x27;info&#x27;)}&lt;button type=&quot;button&quot; class=&quot;ew-button cursor-interaction&quot; data-action=&quot;recheck&quot; style=&quot;margin-top:12px&quot;&gt;다시 확인&lt;/button&gt;`:note(backup?&#x27;백업 위치: D:\\EG-BIM-Backup\\시도-001 (예시)&#x27;:&#x27;현재까지 프로그램 파일은 변경하지 않았습니다.&#x27;)}&lt;p class=&quot;ew-small ew-muted&quot; style=&quot;margin-top:16px&quot;&gt;시안 진행은 창 위의 ‘작업 진행’으로 확인할 수 있습니다.&lt;/p&gt;`;
if(s.page===&#x27;apply&#x27;)return `&lt;h3&gt;${s.cancelled?&#x27;안전한 중단 지점 확인 중&#x27;:s.mode===&#x27;remove&#x27;?&#x27;등록된 프로그램 파일 제거 중&#x27;:&#x27;프로그램 파일 적용 중&#x27;}&lt;/h3&gt;&lt;div class=&quot;ew-progress&quot; role=&quot;progressbar&quot; aria-label=&quot;진행률 예시&quot; aria-valuemin=&quot;0&quot; aria-valuemax=&quot;100&quot; aria-valuenow=&quot;64&quot;&gt;&lt;span style=&quot;width:64%&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;ew-progress-label&quot;&gt;&lt;span&gt;${s.cancelled?&#x27;취소 요청됨&#x27;:&#x27;현재 작업 진행률&#x27;}&lt;/span&gt;&lt;span&gt;64%&lt;/span&gt;&lt;/div&gt;&lt;ul class=&quot;ew-list&quot;&gt;${row(&#x27;환경 및 설치 파일 확인&#x27;,&#x27;완료&#x27;,&#x27;ew-good&#x27;)}${backup?row(&#x27;사용자 설정 백업 검증&#x27;,&#x27;완료&#x27;,&#x27;ew-good&#x27;):&#x27;&#x27;}${row(a,s.cancelled?&#x27;중단 요청 처리 중&#x27;:&#x27;진행 중&#x27;)}${row(&#x27;설치 상태 확인&#x27;,&#x27;대기&#x27;)}&lt;/ul&gt;&lt;details class=&quot;ew-log&quot;&gt;&lt;summary class=&quot;cursor-interaction&quot;&gt;작업 내역 보기&lt;/summary&gt;&lt;pre&gt;시도: DEMO-001\n환경 검사: 완료\n패키지 검증: 완료\n${backup?&#x27;백업 검증: 완료\n&#x27;:&#x27;&#x27;}작업 상태: ${s.cancelled?&#x27;취소 처리 중&#x27;:&#x27;적용 중&#x27;}\n이 내역은 시안용 예시입니다.&lt;/pre&gt;&lt;/details&gt;`;
if(s.page===&#x27;failed&#x27;)return `&lt;div class=&quot;ew-symbol&quot;&gt;!&lt;/div&gt;&lt;h3&gt;${s.scenario===&#x27;backupFail&#x27;?&#x27;백업 저장 위치를 확인해 주세요.&#x27;:&#x27;진단 기록을 보관해 주세요.&#x27;}&lt;/h3&gt;&lt;p class=&quot;ew-muted&quot;&gt;${s.scenario===&#x27;backupFail&#x27;?&#x27;백업 위치의 공간 또는 쓰기 권한을 확인한 후 다시 검사할 수 있습니다.&#x27;:&#x27;현재 설치 상태가 확인되지 않았습니다. 설치 파일을 다시 실행하거나 전체 설치로 덮어쓰기 전에 복구 상태를 확인해야 합니다.&#x27;}&lt;/p&gt;&lt;ul class=&quot;ew-list&quot;&gt;${row(&#x27;프로그램 상태&#x27;,s.scenario===&#x27;backupFail&#x27;?&#x27;기존 상태 유지&#x27;:&#x27;확인 필요&#x27;,s.scenario===&#x27;backupFail&#x27;?&#x27;ew-good&#x27;:&#x27;ew-bad&#x27;)}${row(&#x27;백업&#x27;,backup?(s.scenario===&#x27;backupFail&#x27;?&#x27;검증되지 않음&#x27;:&#x27;보관됨&#x27;):&#x27;해당 없음&#x27;)}${row(&#x27;다음 조치&#x27;,s.scenario===&#x27;backupFail&#x27;?&#x27;백업 위치 확인&#x27;:&#x27;진단 및 복구&#x27;)}&lt;/ul&gt;&lt;details class=&quot;ew-log&quot; open&gt;&lt;summary class=&quot;cursor-interaction&quot;&gt;진단 기록&lt;/summary&gt;&lt;pre&gt;시도: DEMO-001\n상태: ${s.scenario===&#x27;backupFail&#x27;?&#x27;BackupValidationFailed&#x27;:s.cancelled?&#x27;CancellationStateUncertain&#x27;:&#x27;InstallStateUncertain&#x27;}\n로그: 설치 로그 보관 위치 (예시)\n이 시안은 실제 시스템 상태를 조회하지 않습니다.&lt;/pre&gt;&lt;/details&gt;`;
if(s.page===&#x27;cancelled&#x27;)return `&lt;div class=&quot;ew-symbol&quot;&gt;−&lt;/div&gt;&lt;h3&gt;다음에 다시 시작할 수 있습니다.&lt;/h3&gt;&lt;p class=&quot;ew-muted&quot;&gt;설치 전 취소되어 기존 프로그램과 사용자 데이터는 유지됩니다.&lt;/p&gt;`;
const reboot=s.scenario===&#x27;reboot&#x27;;
return `&lt;div class=&quot;ew-symbol&quot;&gt;${reboot?&#x27;↻&#x27;:&#x27;✓&#x27;}&lt;/div&gt;&lt;ul class=&quot;ew-list&quot;&gt;${row(a,&#x27;완료&#x27;,&#x27;ew-good&#x27;)}${backup?row(&#x27;호환 설정 복원&#x27;,&#x27;완료&#x27;,&#x27;ew-good&#x27;):&#x27;&#x27;}${row(&#x27;설치 상태&#x27;,reboot?&#x27;재부팅 후 확인&#x27;:&#x27;확인 완료&#x27;,reboot?&#x27;&#x27;:&#x27;ew-good&#x27;)}&lt;/ul&gt;${reboot?`${choice(&#x27;지금 다시 시작&#x27;,&#x27;restart&#x27;,s.restart,&#x27;선택하지 않으면 나중에 직접 재부팅합니다.&#x27;)}&lt;p class=&quot;ew-small ew-muted&quot;&gt;시안에서는 실제 재부팅하지 않습니다.&lt;/p&gt;`:s.mode!==&#x27;remove&#x27;?choice(&#x27;마침 후 EG-BIM 실행&#x27;,&#x27;launch&#x27;,s.launch,&#x27;원래 사용자 권한으로 실행&#x27;):&#x27;&lt;p class=&quot;ew-small ew-muted&quot; style=&quot;margin-top:15px&quot;&gt;개인 도면·사용자 설정·백업은 유지됩니다.&lt;/p&gt;&#x27;}${backup?note(&#x27;백업은 유지됩니다. 도면·출력·인증·플러그인을 확인한 후 정리하세요.&#x27;):&#x27;&#x27;}${s.closed?&#x27;&lt;p class=&quot;ew-small ew-muted&quot; role=&quot;status&quot; style=&quot;margin-top:12px&quot;&gt;미리보기를 마쳤습니다. 실제 실행이나 재부팅은 하지 않았습니다.&lt;/p&gt;&#x27;:&#x27;&#x27;}`;
}
function persist(id) {window.openai?.setWidgetState?.({modelContent:{design:id===&#x27;ew-guided&#x27;?&#x27;진행 안내형&#x27;:&#x27;표준 마법사형&#x27;,mode:states[id].mode,page:states[id].page,scenario:states[id].scenario},privateContent:{wizards:states}})?.catch(()=&gt;{});}
function render(id,focus=false) {
const s=states[id],app=document.getElementById(id),tools=document.getElementById(id+&#x27;-tools&#x27;);
tools.innerHTML=`&lt;label&gt;시안 · 설치 상황&lt;select class=&quot;cursor-interaction&quot; data-mode aria-label=&quot;시안 설치 상황&quot;&gt;${Object.entries(modes).map(([k,v])=&gt;`&lt;option value=&quot;${k}&quot; ${s.mode===k?&#x27;selected&#x27;:&#x27;&#x27;}&gt;${v}&lt;/option&gt;`).join(&#x27;&#x27;)}&lt;/select&gt;&lt;/label&gt;&lt;label&gt;시험할 상태&lt;select class=&quot;cursor-interaction&quot; data-scenario aria-label=&quot;시험할 상태&quot;&gt;${Object.entries(scenarios).filter(([k])=&gt;k!==&#x27;backupFail&#x27;||[&#x27;upgrade&#x27;,&#x27;patch&#x27;].includes(s.mode)).map(([k,v])=&gt;`&lt;option value=&quot;${k}&quot; ${s.scenario===k?&#x27;selected&#x27;:&#x27;&#x27;}&gt;${v}&lt;/option&gt;`).join(&#x27;&#x27;)}&lt;/select&gt;&lt;/label&gt;&lt;button class=&quot;ew-button cursor-interaction&quot; data-demo=&quot;advance&quot; ${![&#x27;prepare&#x27;,&#x27;apply&#x27;].includes(s.page)||s.confirmCancel?&#x27;disabled&#x27;:&#x27;&#x27;}&gt;작업 진행&lt;/button&gt;&lt;button class=&quot;ew-button cursor-interaction&quot; data-demo=&quot;reset&quot;&gt;처음부터&lt;/button&gt;`;
tools.querySelector(&#x27;[data-mode]&#x27;).addEventListener(&#x27;change&#x27;,e=&gt;{states[id]=defaults();states[id].mode=e.target.value;render(id);persist(id);});
tools.querySelector(&#x27;[data-scenario]&#x27;).addEventListener(&#x27;change&#x27;,e=&gt;{s.scenario=e.target.value;s.page=&#x27;welcome&#x27;;s.error=&#x27;&#x27;;s.cancelled=false;s.confirmCancel=false;s.closed=false;render(id);persist(id);});
tools.querySelector(&#x27;[data-demo=advance]&#x27;).addEventListener(&#x27;click&#x27;,()=&gt;{simulate(s);render(id);persist(id);});
tools.querySelector(&#x27;[data-demo=reset]&#x27;).addEventListener(&#x27;click&#x27;,()=&gt;{states[id]={...defaults(),mode:s.mode,scenario:s.scenario};render(id);persist(id);});
const [heading,sub]=title(s),seq=sequence(s),index=seq.indexOf(s.page);
const groups=s.mode===&#x27;new&#x27;?[&#x27;시작&#x27;,&#x27;선택&#x27;,&#x27;확인&#x27;,&#x27;진행&#x27;,&#x27;완료&#x27;]:[&#x27;시작&#x27;,&#x27;확인&#x27;,&#x27;진행&#x27;,&#x27;완료&#x27;];
const current=(s.mode===&#x27;new&#x27;?{welcome:0,license:1,options:1,review:2,prepare:3,apply:3,finish:4,failed:3,cancelled:0}:{welcome:0,review:1,prepare:2,apply:2,finish:3,failed:2,cancelled:0})[s.page];
const busy=[&#x27;prepare&#x27;,&#x27;apply&#x27;].includes(s.page),end=[&#x27;finish&#x27;,&#x27;failed&#x27;,&#x27;cancelled&#x27;].includes(s.page);
const button= end?(s.page===&#x27;failed&#x27;&amp;&amp;s.scenario===&#x27;backupFail&#x27;?&#x27;다시 검사&#x27;:s.page===&#x27;finish&#x27;?&#x27;마침&#x27;:&#x27;닫기&#x27;):s.page===&#x27;review&#x27;?`${actions[s.mode]} 시작`:busy?s.cancelled?&#x27;취소 처리 중…&#x27;:&#x27;진행 중…&#x27;:&#x27;다음&#x27;;
app.innerHTML=`&lt;header class=&quot;ew-title&quot;&gt;&lt;span class=&quot;ew-mark&quot;&gt;E&lt;/span&gt;&lt;span&gt;EG-BIM 설치 마법사&lt;/span&gt;&lt;span class=&quot;ew-tag&quot;&gt;${modes[s.mode]}&lt;/span&gt;&lt;/header&gt;&lt;div class=&quot;ew-layout&quot;&gt;&lt;aside class=&quot;ew-side&quot;&gt;&lt;div class=&quot;ew-side-brand&quot;&gt;EG-BIM&lt;/div&gt;&lt;div class=&quot;ew-side-sub&quot;&gt;BARON SOFTWARE&lt;/div&gt;&lt;ol class=&quot;ew-steps&quot;&gt;${groups.map((n,i)=&gt;`&lt;li ${i===current?&#x27;aria-current=&quot;step&quot;&#x27;:&#x27;&#x27;} class=&quot;${i&lt;current?&#x27;ew-past&#x27;:&#x27;&#x27;}&quot;&gt;&lt;span class=&quot;ew-number&quot;&gt;${i&lt;current?&#x27;✓&#x27;:i+1}&lt;/span&gt;${n}&lt;/li&gt;`).join(&#x27;&#x27;)}&lt;/ol&gt;&lt;/aside&gt;&lt;main class=&quot;ew-main&quot;&gt;&lt;header class=&quot;ew-page-heading&quot;&gt;&lt;h2&gt;${heading}&lt;/h2&gt;&lt;p&gt;${sub}&lt;/p&gt;&lt;/header&gt;&lt;div class=&quot;ew-body&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;${body(s)}&lt;/div&gt;${s.error?`&lt;p class=&quot;ew-small ew-bad&quot; role=&quot;alert&quot;&gt;${escape(s.error)}&lt;/p&gt;`:&#x27;&#x27;}${s.confirmCancel?`&lt;section class=&quot;ew-confirm&quot; aria-label=&quot;취소 확인&quot;&gt;&lt;h3&gt;작업을 취소하시겠습니까?&lt;/h3&gt;&lt;p class=&quot;ew-small ew-muted&quot;&gt;${s.page===&#x27;apply&#x27;?&#x27;안전한 중단 지점까지 기다린 후 설치 상태를 확인합니다.&#x27;:&#x27;아직 프로그램을 변경하지 않았습니다.&#x27;}&lt;/p&gt;&lt;div class=&quot;ew-actions&quot;&gt;&lt;button class=&quot;ew-button cursor-interaction&quot; data-action=&quot;keep&quot;&gt;계속 진행&lt;/button&gt;&lt;button class=&quot;ew-button cursor-interaction&quot; data-action=&quot;confirm&quot;&gt;취소 요청&lt;/button&gt;&lt;/div&gt;&lt;/section&gt;`:&#x27;&#x27;}&lt;/div&gt;&lt;/main&gt;&lt;/div&gt;&lt;footer class=&quot;ew-footer&quot;&gt;&lt;span class=&quot;ew-footer-label&quot;&gt;시안 · 표시 정보는 예시&lt;/span&gt;&lt;div class=&quot;ew-actions&quot;&gt;&lt;button class=&quot;ew-button cursor-interaction&quot; data-action=&quot;back&quot; ${index&lt;=0||busy||end||s.confirmCancel?&#x27;disabled&#x27;:&#x27;&#x27;}&gt;이전&lt;/button&gt;&lt;button class=&quot;ew-button ew-primary cursor-interaction&quot; data-action=&quot;next&quot; ${busy||s.confirmCancel||s.page===&#x27;license&#x27;&amp;&amp;!s.agreed||s.page===&#x27;review&#x27;&amp;&amp;s.mode===&#x27;remove&#x27;&amp;&amp;!s.removeConfirmed?&#x27;disabled&#x27;:&#x27;&#x27;}&gt;${button}&lt;/button&gt;${!end?`&lt;button class=&quot;ew-button cursor-interaction&quot; data-action=&quot;cancel&quot; ${s.cancelled||s.confirmCancel?&#x27;disabled&#x27;:&#x27;&#x27;}&gt;취소&lt;/button&gt;`:&#x27;&#x27;}&lt;/div&gt;&lt;/footer&gt;`;
app.querySelector(&#x27;[data-path]&#x27;)?.addEventListener(&#x27;input&#x27;,e=&gt;{s.path=e.target.value;});
app.querySelectorAll(&#x27;[data-value]&#x27;).forEach(el=&gt;el.addEventListener(&#x27;change&#x27;,e=&gt;{s[el.dataset.value]=e.target.checked;render(id);persist(id);app.querySelector(`[data-value=&quot;${el.dataset.value}&quot;]`)?.focus({preventScroll:true});}));
app.querySelector(&#x27;[data-action=back]&#x27;).addEventListener(&#x27;click&#x27;,()=&gt;{if(index&gt;0&amp;&amp;!busy&amp;&amp;!end){s.page=seq[index-1];s.error=&#x27;&#x27;;render(id,true);persist(id);}});
app.querySelector(&#x27;[data-action=next]&#x27;).addEventListener(&#x27;click&#x27;,()=&gt;{
if(s.page===&#x27;failed&#x27;&amp;&amp;s.scenario===&#x27;backupFail&#x27;){s.scenario=&#x27;normal&#x27;;s.page=&#x27;prepare&#x27;;}
else if(end){s.closed=true;if(s.page!==&#x27;finish&#x27;){s.page=&#x27;welcome&#x27;;s.cancelled=false;s.closed=false;}}
else next(s);
render(id,true);persist(id);
});
app.querySelector(&#x27;[data-action=cancel]&#x27;)?.addEventListener(&#x27;click&#x27;,()=&gt;{s.confirmCancel=true;render(id);app.querySelector(&#x27;[data-action=keep]&#x27;)?.focus({preventScroll:true});});
app.querySelector(&#x27;[data-action=keep]&#x27;)?.addEventListener(&#x27;click&#x27;,()=&gt;{s.confirmCancel=false;render(id,true);});
app.querySelector(&#x27;[data-action=confirm]&#x27;)?.addEventListener(&#x27;click&#x27;,()=&gt;{s.confirmCancel=false;if(s.page===&#x27;apply&#x27;)s.cancelled=true;else s.page=&#x27;cancelled&#x27;;render(id,true);persist(id);});
app.querySelector(&#x27;[data-action=recheck]&#x27;)?.addEventListener(&#x27;click&#x27;,()=&gt;{s.scenario=&#x27;normal&#x27;;s.error=&#x27;&#x27;;render(id);persist(id);});
if(globalThis.lucide)lucide.createIcons({attrs:{width:16,height:16}});
if(focus&amp;&amp;!busy)app.querySelector(&#x27;[data-action=next]&#x27;)?.focus({preventScroll:true});
}
Object.keys(states).forEach(id=&gt;render(id));
window.addEventListener(&#x27;openai:set_globals&#x27;,e=&gt;{const restored=e.detail?.globals?.widgetState?.privateContent?.wizards;if(restored)Object.keys(states).forEach(id=&gt;{states[id]=normalize(restored[id]);render(id);});});
if(globalThis.Tweak)Object.keys(states).forEach(id=&gt;{const app=document.getElementById(id),design={radius:9};const tweak=new Tweak({container:app,onChange:()=&gt;{app.style.borderRadius=design.radius+&#x27;px&#x27;;}});tweak.addSlider(design,&#x27;radius&#x27;,{label:&#x27;창 모서리&#x27;,min:0,max:16,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>