代码: 全选
<!doctype html>
<html lang="en">
<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>Balanced Quadrilaterals</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 sandbox="allow-scripts" referrerpolicy="no-referrer" title="Balanced Quadrilaterals" srcdoc="<!doctype html>
<html lang="en">
<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 'none'; object-src 'none'; base-uri 'none'; form-action 'none'">
<title>Balanced Quadrilaterals</title>
<style>: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%, transparent);
--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, 14px);
--red: light-dark(rgb(186 38 35), rgb(250 66 62));
--green: light-dark(rgb(0 162 64), rgb(64 201 119));
--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, system-ui, "Segoe UI", sans-serif;
--font-mono:
ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono",
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: 430;
--font-weight-medium: 500;
--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);
--radius: var(--border-radius-lg, 12.5px);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-2xl: calc(var(--radius) * 1.6);
--radius-full: 9999px;
--shadow-sm: 0 1px 2px -1px rgb(0 0 0 / 8%);
--checkmark-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 17 17'%3E%3Cpath d='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'/%3E%3C/svg%3E");
/* 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="light"] {
color-scheme: light;
}
:root[data-theme="dark"] {
color-scheme: dark;
}
* {
box-sizing: border-box;
}
html > 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: 5px;
color: var(--foreground);
background: transparent !important;
font-family: var(--font-sans);
font-size: var(--font-size-normal);
font-weight: var(--font-weight-normal);
line-height: var(--line-height-normal);
}
a {
color: color-mix(in srgb, var(--viz-accent) 80%, var(--viz-text) 20%);
cursor: pointer;
font-weight: var(--font-weight-medium, 500);
text-decoration: none;
text-underline-offset: 2px;
}
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;
}
h1 {
font-size: var(--font-size-h1);
font-weight: var(--font-weight-medium);
line-height: 1.25;
}
h2 {
font-size: var(--font-size-h2);
font-weight: var(--font-weight-medium);
line-height: 1.25;
}
h3,
h4,
h5,
h6 {
font-size: var(--font-size-h3);
font-weight: var(--font-weight-medium);
line-height: 1.3;
}
b,
strong,
th {
font-weight: var(--font-weight-medium);
}
code:not(pre code) {
display: inline;
padding: 1px 6px;
border-radius: var(--radius-sm);
corner-shape: superellipse(1.5);
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;
}
.table-responsive {
width: 100%;
overflow-x: auto;
scrollbar-width: thin;
}
.table {
width: 100%;
border-collapse: collapse;
color: var(--foreground);
font: inherit;
text-align: start;
}
.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;
}
.table-responsive > .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 th {
padding-block: 8px;
border-bottom-color: color-mix(in srgb, var(--foreground) 16%, transparent);
}
.table tbody tr:last-child :is(th, td) {
border-bottom: 0;
}
.table.table-sm :is(th, td) {
padding-block: 6px;
}
.table.table-sm :is(th, td):not(:last-child) {
padding-inline-end: 16px;
}
.table :is(.text-end, [align="right"]) {
text-align: end;
font-variant-numeric: tabular-nums;
}
.table :is(.text-center, [align="center"]) {
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: superellipse(1.5);
color: var(--card-foreground);
background: var(--card);
}
#widget > :not(.card) {
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)
);
padding: 4px 8px;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
corner-shape: superellipse(1.5);
color: var(--popover-foreground);
background: var(--popover);
box-shadow: none;
font-size: var(--font-size-tooltip);
line-height: var(--line-height-tooltip);
overflow-wrap: break-word;
white-space: normal;
pointer-events: none;
user-select: none;
}
.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;
}
.viz-badge {
padding: 3px 8px;
border-radius: var(--radius-full);
color: var(--accent-foreground);
background: var(--accent);
font-weight: var(--font-weight-medium);
}
small,
.text-small,
.viz-badge {
font-size: var(--font-size-small);
line-height: var(--line-height-small);
}
.text-muted {
color: var(--muted-foreground);
}
.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 > .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 > .form-label > :is(.form-control, .form-range, .form-select) {
grid-column: 1 / -1;
}
.btn,
.form-check-input,
.form-control,
.form-range,
.form-select {
font: inherit;
}
.btn {
appearance: button;
display: inline-flex;
inline-size: fit-content;
max-inline-size: 100%;
min-height: 28px;
align-items: center;
justify-content: center;
gap: 4px;
margin: 0;
padding: 0 8px;
-webkit-app-region: no-drag;
border: 1px solid var(--input);
border-radius: var(--radius-lg);
corner-shape: superellipse(1.5);
color: var(--secondary-foreground);
background: var(--secondary);
cursor: var(--cursor-interaction, pointer);
text-align: center;
text-decoration: none;
white-space: nowrap;
user-select: none;
-webkit-font-smoothing: antialiased;
}
.btn:is(.btn-block, .viz-tile) {
inline-size: 100%;
}
.btn.viz-tile {
min-width: 0;
overflow-wrap: anywhere;
white-space: normal;
}
a.btn {
cursor: pointer;
text-decoration: none;
}
.btn:not(:disabled):hover {
background: color-mix(in srgb, var(--foreground) 6%, var(--secondary));
}
.btn-primary {
border-color: transparent;
color: var(--primary-foreground);
background: var(--foreground);
background-clip: padding-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: padding-box;
}
.btn-ghost {
border-color: transparent;
color: var(--muted-foreground);
background: transparent;
}
.btn-ghost:not(:disabled):hover {
color: var(--foreground);
background: color-mix(in srgb, var(--foreground) 6%, var(--secondary));
}
.btn:disabled {
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%;
min-height: 28px;
padding: 0 8px;
outline: none;
border: 1px solid var(--input);
border-radius: var(--radius-lg);
corner-shape: superellipse(1.5);
color: var(--foreground);
background: var(--secondary);
}
.form-control::placeholder {
color: var(--muted-foreground);
}
.form-control[type="file"] {
padding: 0;
overflow: hidden;
cursor: var(--cursor-interaction, pointer);
}
.form-control[type="file"]::file-selector-button {
min-height: 26px;
margin-right: 8px;
padding: 0 8px;
border: 0;
border-right: 1px solid var(--input);
color: var(--secondary-foreground);
background: var(--secondary);
cursor: inherit;
font: inherit;
}
.form-control[type="file"]:not(:disabled):hover::file-selector-button {
background: color-mix(in srgb, var(--foreground) 6%, var(--secondary));
}
.form-control-color[type="color"] {
width: 40px;
height: 28px;
padding: 3px;
cursor: var(--cursor-interaction, pointer);
}
.form-control-color[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
}
.form-control-color[type="color"]::-webkit-color-swatch {
border: 0;
border-radius: calc(var(--radius-lg) - 4px);
corner-shape: superellipse(1.5);
}
textarea.form-control {
height: auto;
min-height: 72px;
padding: 8px 10px;
resize: vertical;
}
.form-control:focus-visible {
border-color: var(--ring);
box-shadow: inset 0 0 0 1px var(--ring);
}
.form-control:disabled,
.form-select:disabled {
cursor: not-allowed;
opacity: 0.4;
}
.form-select {
appearance: none;
display: block;
width: 100%;
min-height: 28px;
margin: 0;
padding: 0 32px 0 8px;
outline: none;
border: 1px solid var(--input);
border-radius: var(--radius-lg);
corner-shape: superellipse(1.5);
color: var(--foreground);
background-color: var(--secondary);
background-image:
linear-gradient(45deg, transparent 50%, var(--muted-foreground) 50%),
linear-gradient(135deg, var(--muted-foreground) 50%, transparent 50%);
background-position:
calc(100% - 14px) 50%,
calc(100% - 10px) 50%;
background-repeat: no-repeat;
background-size: 4px 4px;
cursor: var(--cursor-interaction, default);
}
.form-select:not(:disabled):hover {
background-color: color-mix(in srgb, var(--foreground) 6%, var(--secondary));
}
.form-select:focus-visible {
border-color: var(--ring);
box-shadow: inset 0 0 0 1px var(--ring);
}
.form-check {
display: flex;
min-height: 20px;
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="checkbox"] {
border-color: var(--input);
border-radius: var(--radius-sm);
corner-shape: superellipse(1.5);
background-color: var(--secondary);
box-shadow: var(--shadow-sm);
}
.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="checkbox"]:checked {
border-color: var(--primary);
background-color: var(--primary);
}
.form-check:not(.form-switch)
.form-check-input[type="checkbox"]:checked::before {
display: block;
width: 100%;
height: 100%;
background: var(--primary-foreground);
content: "";
mask: var(--checkmark-image) center / 12px 12px no-repeat;
}
.form-check-input[type="radio"] {
width: 14px;
height: 14px;
border-radius: var(--radius-full);
}
.form-check-input[type="radio"]:checked {
border: 2px solid var(--primary);
background:
radial-gradient(circle, var(--primary-foreground) 0 2.5px, transparent 3px),
var(--primary);
}
.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:disabled {
cursor: not-allowed;
pointer-events: none;
}
.form-check:not(.form-switch) .form-check-input:disabled + .form-check-label {
cursor: not-allowed;
}
.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);
background: light-dark(var(--primary-foreground), var(--foreground));
box-shadow: var(--shadow-sm);
content: "";
transform: translate(2px, -50%);
transition: transform 200ms cubic-bezier(0, 0, 0.2, 1);
}
.form-switch .form-check-input:checked {
background: var(--primary);
}
.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);
}
.form-range {
appearance: none;
display: block;
width: 100%;
height: 28px;
flex: 1;
margin: 0;
padding: 0;
outline: none;
border: 0;
accent-color: var(--primary);
background: linear-gradient(
color-mix(in srgb, var(--foreground) 7%, transparent),
color-mix(in srgb, var(--foreground) 7%, transparent)
)
center / 100% 2px no-repeat;
}
.form-range::-webkit-slider-runnable-track {
height: 28px;
background: transparent;
}
.form-range::-webkit-slider-thumb {
appearance: none;
width: 20px;
height: 20px;
margin-top: 4px;
border: 1px solid var(--border);
border-radius: var(--radius-full);
background: light-dark(var(--primary-foreground), var(--foreground));
}
.form-range:focus-visible::-webkit-slider-thumb {
border-color: var(--ring);
box-shadow: inset 0 0 0 1px var(--ring);
}
.form-range::-moz-range-track {
height: 28px;
background: transparent;
}
.form-range::-moz-range-thumb {
width: 20px;
height: 20px;
border: 1px solid var(--border);
border-radius: var(--radius-full);
background: light-dark(var(--primary-foreground), var(--foreground));
}
.form-range:focus-visible::-moz-range-thumb {
border-color: var(--ring);
box-shadow: inset 0 0 0 1px var(--ring);
}
.form-range:disabled {
cursor: not-allowed;
opacity: 0.4;
}
.btn:not(.btn-primary, .viz-tile):is(
[aria-pressed="true"],
[aria-selected="true"],
.is-selected
) {
border-color: var(--primary);
color: var(--primary-foreground);
background: var(--primary);
}
.btn.viz-tile:is([aria-pressed="true"], [aria-selected="true"], .is-selected) {
border-color: var(--primary);
box-shadow: inset 0 0 0 1px var(--primary);
}
.btn:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
svg {
display: block;
max-width: 100%;
height: auto;
}
#widget > svg {
width: 100%;
}
.text-warning,
.text-destructive {
color: var(--destructive);
}
html>body{padding:0}</style>
</head>
<body>
<div id="balanced-quadrilaterals">
<div class="viz-controls" role="group" aria-label="Choose the scale used for side labels">
<span class="text-muted">Side-label scale</span>
<button type="button" class="btn btn-primary" data-label-mode="ratio" aria-pressed="true">
Shortest = 1
</button>
<button type="button" class="btn" data-label-mode="perimeter" aria-pressed="false">
Perimeter = 4
</button>
<span class="text-muted">φ = (1 + √5) / 2 ≈ 1.618</span>
</div>
<div class="bq-grid">
<figure class="bq-case" data-case-index="0">
<figcaption class="bq-caption">
<h3>Case I</h3>
<span class="text-muted">A–B–C–D: 36° · 72° · 144° · 108°</span>
</figcaption>
<svg viewBox="0 0 300 230" role="img" aria-labelledby="bq-title-1 bq-desc-1">
<title id="bq-title-1">Case I balanced quadrilateral</title>
<desc id="bq-desc-1">Angles 36, 72, 144, and 108 degrees in order. Side ratio AB to BC to CD to DA is 2 phi to 1 to 1 to phi squared.</desc>
<g class="bq-drawing"></g>
</svg>
<div class="bq-ratio">
<span class="text-muted">AB : BC : CD : DA</span>
<strong>2φ : 1 : 1 : φ²</strong>
</div>
</figure>
<figure class="bq-case" data-case-index="1">
<figcaption class="bq-caption">
<h3>Case II</h3>
<span class="text-muted">A–B–C–D: 36° · 72° · 108° · 144°</span>
</figcaption>
<svg viewBox="0 0 300 230" role="img" aria-labelledby="bq-title-2 bq-desc-2">
<title id="bq-title-2">Case II balanced quadrilateral</title>
<desc id="bq-desc-2">Angles 36, 72, 108, and 144 degrees in order. Side ratio AB to BC to CD to DA is phi squared to 1 to 1 to phi.</desc>
<g class="bq-drawing"></g>
</svg>
<div class="bq-ratio">
<span class="text-muted">AB : BC : CD : DA</span>
<strong>φ² : 1 : 1 : φ</strong>
</div>
</figure>
<figure class="bq-case" data-case-index="2">
<figcaption class="bq-caption">
<h3>Case III</h3>
<span class="text-muted">A–B–C–D: 36° · 144° · 72° · 108°</span>
</figcaption>
<svg viewBox="0 0 300 230" role="img" aria-labelledby="bq-title-3 bq-desc-3">
<title id="bq-title-3">Case III balanced quadrilateral</title>
<desc id="bq-desc-3">Angles 36, 144, 72, and 108 degrees in order. Side ratio AB to BC to CD to DA is phi to 1 to 1 to 2.</desc>
<g class="bq-drawing"></g>
</svg>
<div class="bq-ratio">
<span class="text-muted">AB : BC : CD : DA</span>
<strong>φ : 1 : 1 : 2</strong>
</div>
</figure>
</div>
<p class="sr-only" id="bq-scale-status" aria-live="polite">Side labels are ratios with the shortest side equal to 1.</p>
</div>
<style>
#balanced-quadrilaterals {
width: 100%;
color: var(--foreground);
}
#balanced-quadrilaterals .viz-controls {
margin-bottom: 16px;
}
#balanced-quadrilaterals .bq-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
}
#balanced-quadrilaterals .bq-case {
min-width: 0;
margin: 0;
}
#balanced-quadrilaterals .bq-caption {
min-height: 52px;
}
#balanced-quadrilaterals .bq-caption h3 {
margin: 0 0 4px;
}
#balanced-quadrilaterals .bq-caption span {
display: block;
}
#balanced-quadrilaterals .bq-case svg {
display: block;
width: 100%;
height: auto;
overflow: visible;
}
#balanced-quadrilaterals .bq-polygon {
stroke: none;
fill: color-mix(in srgb, var(--case-color) 15%, transparent);
}
#balanced-quadrilaterals .bq-edge {
stroke: var(--case-color);
stroke-width: 2;
stroke-linecap: round;
vector-effect: non-scaling-stroke;
}
#balanced-quadrilaterals .bq-vertex {
fill: var(--case-color);
}
#balanced-quadrilaterals .bq-angle-arc {
fill: none;
stroke: color-mix(in srgb, var(--case-color) 65%, var(--foreground));
stroke-width: 1.5;
vector-effect: non-scaling-stroke;
}
#balanced-quadrilaterals .bq-side-label,
#balanced-quadrilaterals .bq-angle-label {
fill: var(--foreground);
font-size: var(--font-size-base);
font-weight: 500;
text-anchor: middle;
dominant-baseline: central;
}
#balanced-quadrilaterals .bq-side-label {
paint-order: stroke;
stroke: var(--background);
stroke-width: 4;
stroke-linejoin: round;
}
#balanced-quadrilaterals .bq-angle-label {
font-weight: 400;
}
#balanced-quadrilaterals .bq-ratio {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 4px 10px;
padding-top: 4px;
}
#balanced-quadrilaterals .bq-ratio strong {
font-weight: 500;
white-space: nowrap;
}
@media (max-width: 640px) {
#balanced-quadrilaterals .bq-grid {
grid-template-columns: 1fr;
gap: 22px;
}
#balanced-quadrilaterals .bq-caption {
min-height: 0;
}
}
</style>
<script>
(() => {
const root = document.getElementById('balanced-quadrilaterals');
if (!root) return;
const phi = (1 + Math.sqrt(5)) / 2;
const sideNames = ['AB', 'BC', 'CD', 'DA'];
const cases = [
{
angles: [36, 72, 144, 108],
lengths: [2 * phi, 1, 1, phi * phi],
exact: ['2φ', '1', '1', 'φ²'],
color: 'var(--viz-series-1)'
},
{
angles: [36, 72, 108, 144],
lengths: [phi * phi, 1, 1, phi],
exact: ['φ²', '1', '1', 'φ'],
color: 'var(--viz-series-2)'
},
{
angles: [36, 144, 72, 108],
lengths: [phi, 1, 1, 2],
exact: ['φ', '1', '1', '2'],
color: 'var(--viz-series-3)'
}
];
const svgNS = 'http://www.w3.org/2000/svg';
let labelMode = 'ratio';
function svgElement(name, attributes = {}) {
const element = document.createElementNS(svgNS, name);
Object.entries(attributes).forEach(([key, value]) => element.setAttribute(key, value));
return element;
}
function degreesToRadians(value) {
return value * Math.PI / 180;
}
function constructVertices(item) {
const directions = [
0,
180 - item.angles[1],
360 - item.angles[1] - item.angles[2],
180 + item.angles[0]
];
const vertices = [{ x: 0, y: 0 }];
for (let index = 0; index < 3; index += 1) {
const direction = degreesToRadians(directions[index]);
const previous = vertices[vertices.length - 1];
vertices.push({
x: previous.x + item.lengths[index] * Math.cos(direction),
y: previous.y + item.lengths[index] * Math.sin(direction)
});
}
return { vertices, directions };
}
function makeProjector(vertices) {
const xs = vertices.map((point) => point.x);
const ys = vertices.map((point) => point.y);
const minX = Math.min(...xs);
const maxX = Math.max(...xs);
const minY = Math.min(...ys);
const maxY = Math.max(...ys);
const width = Math.max(maxX - minX, 0.001);
const height = Math.max(maxY - minY, 0.001);
const scale = Math.min(194 / width, 112 / height);
const centerX = (minX + maxX) / 2;
const centerY = (minY + maxY) / 2;
return (point) => ({
x: 150 + (point.x - centerX) * scale,
y: 123 - (point.y - centerY) * scale
});
}
function sideLabel(item, index) {
if (labelMode === 'ratio') return `${sideNames[index]} ${item.exact[index]}`;
const perimeter = item.lengths.reduce((sum, value) => sum + value, 0);
return `${sideNames[index]} ${(4 * item.lengths[index] / perimeter).toFixed(3)}`;
}
function drawCase(figure, item) {
const drawing = figure.querySelector('.bq-drawing');
drawing.replaceChildren();
figure.style.setProperty('--case-color', item.color);
const { vertices, directions } = constructVertices(item);
const project = makeProjector(vertices);
const screenPoints = vertices.map(project);
const pointString = screenPoints.map((point) => `${point.x},${point.y}`).join(' ');
drawing.appendChild(svgElement('polygon', {
class: 'bq-polygon',
points: pointString
}));
screenPoints.forEach((start, index) => {
const end = screenPoints[(index + 1) % 4];
drawing.appendChild(svgElement('line', {
class: 'bq-edge',
x1: start.x,
y1: start.y,
x2: end.x,
y2: end.y
}));
const mathStart = vertices[index];
const mathEnd = vertices[(index + 1) % 4];
const dx = mathEnd.x - mathStart.x;
const dy = mathEnd.y - mathStart.y;
const magnitude = Math.hypot(dx, dy);
const outwardX = dy / magnitude;
const outwardY = -dx / magnitude;
const midpoint = {
x: (start.x + end.x) / 2,
y: (start.y + end.y) / 2
};
const label = svgElement('text', {
class: 'bq-side-label',
x: midpoint.x + outwardX * 17,
y: midpoint.y - outwardY * 17
});
label.textContent = sideLabel(item, index);
drawing.appendChild(label);
});
screenPoints.forEach((point, index) => {
const outgoingDirection = degreesToRadians(directions[index]);
const angle = degreesToRadians(item.angles[index]);
const arcRadius = item.angles[index] <= 40 ? 23 : 18;
const arcSamples = 14;
const arcPoints = [];
for (let sample = 0; sample <= arcSamples; sample += 1) {
const theta = outgoingDirection + angle * sample / arcSamples;
arcPoints.push({
x: point.x + arcRadius * Math.cos(theta),
y: point.y - arcRadius * Math.sin(theta)
});
}
const arcPath = arcPoints
.map((arcPoint, sample) => `${sample === 0 ? 'M' : 'L'} ${arcPoint.x} ${arcPoint.y}`)
.join(' ');
drawing.appendChild(svgElement('path', {
class: 'bq-angle-arc',
d: arcPath
}));
drawing.appendChild(svgElement('circle', {
class: 'bq-vertex',
cx: point.x,
cy: point.y,
r: 3
}));
const bisector = outgoingDirection + angle / 2;
const labelRadius = item.angles[index] <= 40 ? 43 : 34;
const angleLabel = svgElement('text', {
class: 'bq-angle-label',
x: point.x + labelRadius * Math.cos(bisector),
y: point.y - labelRadius * Math.sin(bisector)
});
angleLabel.textContent = `${item.angles[index]}°`;
drawing.appendChild(angleLabel);
});
}
function drawAll() {
root.querySelectorAll('.bq-case').forEach((figure) => {
const index = Number(figure.dataset.caseIndex);
drawCase(figure, cases[index]);
});
}
root.querySelectorAll('[data-label-mode]').forEach((button) => {
button.addEventListener('click', () => {
labelMode = button.dataset.labelMode;
root.querySelectorAll('[data-label-mode]').forEach((peer) => {
const isSelected = peer === button;
peer.setAttribute('aria-pressed', String(isSelected));
peer.classList.toggle('btn-primary', isSelected);
});
root.querySelector('#bq-scale-status').textContent = labelMode === 'ratio'
? 'Side labels are ratios with the shortest side equal to 1.'
: 'Side labels are normalized so that the perimeter equals 4.';
drawAll();
});
});
drawAll();
})();
</script>
<script src="https://unpkg.com/@floating-ui/core@1.7.3/dist/floating-ui.core.umd.min.js"></script>
<script src="https://unpkg.com/@floating-ui/dom@1.7.4/dist/floating-ui.dom.umd.min.js"></script>
<script>
(() => {
if (window.FloatingUIDOM == null) {
return;
}
const { autoUpdate, computePosition, flip, offset, shift, size } =
window.FloatingUIDOM;
const tooltipPlacements = new Set(["top", "right", "bottom", "left"]);
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;
const getTrigger = (target) =>
target?.nodeType === Node.ELEMENT_NODE
? target.closest("[data-tooltip]")
: null;
const containsTarget = (trigger, target) =>
target?.nodeType != null && trigger.contains(target);
const getContent = (trigger) =>
trigger.getAttribute("data-tooltip")?.trim() ?? "";
const getPlacement = (trigger) => {
const placement = trigger.getAttribute("data-tooltip-placement");
return tooltipPlacements.has(placement) ? placement : "top";
};
const getTooltip = () => {
if (tooltip == null) {
tooltip = document.createElement("div");
tooltip.id = tooltipId;
tooltip.className = "tooltip";
tooltip.setAttribute("role", "tooltip");
}
return tooltip;
};
const setDescribedBy = (trigger, described) => {
const ids = new Set(
(trigger.getAttribute("aria-describedby") ?? "")
.split(/\s+/)
.filter(Boolean),
);
if (described) {
ids.add(tooltipId);
} else {
ids.delete(tooltipId);
}
if (ids.size === 0) {
trigger.removeAttribute("aria-describedby");
} else {
trigger.setAttribute("aria-describedby", [...ids].join(" "));
}
};
const clearOpenTimeout = () => {
if (openTimeout != null) {
window.clearTimeout(openTimeout);
openTimeout = null;
}
pendingTrigger = null;
};
const closeTooltip = (activateSkipDelay = true) => {
clearOpenTimeout();
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) => {
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(
"--tooltip-available-width",
`${Math.max(0, availableWidth)}px`,
);
elements.floating.style.setProperty(
"--tooltip-available-height",
`${Math.max(0, availableHeight)}px`,
);
},
}),
],
placement: getPlacement(trigger),
strategy: "fixed",
})
.then(({ x, y }) => {
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 = "visible";
})
.catch(() => {
if (activeTrigger === trigger && request === positionRequest) {
closeTooltip(false);
}
});
};
const openTooltip = (trigger) => {
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 = "hidden";
floating.style.transform = "translate(0, 0)";
document.body.appendChild(floating);
activeTrigger = trigger;
setDescribedBy(trigger, true);
autoUpdateCleanup = autoUpdate(trigger, floating, () => {
updatePosition(trigger, floating);
});
};
const requestOpen = (trigger, immediate = false) => {
if (
getContent(trigger).length === 0 ||
activeTrigger === trigger ||
pendingTrigger === trigger
) {
return;
}
clearOpenTimeout();
pendingTrigger = trigger;
const delay =
immediate || activeTrigger != null || Date.now() < skipDelayUntil
? 0
: hoverDelay;
if (delay === 0) {
openTooltip(trigger);
return;
}
openTimeout = window.setTimeout(() => {
openTimeout = null;
pendingTrigger = null;
openTooltip(trigger);
}, delay);
};
const handlePointerOpen = (event) => {
if (event.pointerType === "touch") {
return;
}
const trigger = getTrigger(event.target);
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
return;
}
requestOpen(trigger);
};
const handleLeave = (event) => {
const trigger = getTrigger(event.target);
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
return;
}
if (activeTrigger === trigger || pendingTrigger === trigger) {
closeTooltip();
}
};
const handleFocusIn = (event) => {
const trigger = getTrigger(event.target);
if (trigger?.matches(":focus-visible")) {
requestOpen(trigger, true);
}
};
const handleKeyDown = (event) => {
if (event.key === "Escape") {
closeTooltip();
}
};
const handleContextMenu = (event) => {
const trigger = getTrigger(event.target);
if (activeTrigger === trigger || pendingTrigger === trigger) {
closeTooltip();
}
};
const destroy = () => {
closeTooltip(false);
document.removeEventListener("pointerover", handlePointerOpen);
document.removeEventListener("pointerout", handleLeave);
document.removeEventListener("focusin", handleFocusIn);
document.removeEventListener("focusout", handleLeave);
document.removeEventListener("keydown", handleKeyDown);
document.removeEventListener("contextmenu", handleContextMenu);
tooltip = null;
};
document.addEventListener("pointerover", handlePointerOpen);
document.addEventListener("pointerout", handleLeave);
document.addEventListener("focusin", handleFocusIn);
document.addEventListener("focusout", handleLeave);
document.addEventListener("keydown", handleKeyDown);
document.addEventListener("contextmenu", handleContextMenu);
window.addEventListener("pagehide", destroy, { once: true });
})();
</script>
<script id="codex-visualization-lucide" async src="https://unpkg.com/lucide@1.17.0/dist/umd/lucide.js"></script>
<script>
(() => {
const initialize = () => {
globalThis.lucide?.createIcons({ attrs: { width: 16, height: 16 } });
};
if (globalThis.lucide != null) {
initialize();
return;
}
document
.getElementById("codex-visualization-lucide")
?.addEventListener("load", initialize, { once: true });
})();
</script>
</body>
</html>
"></iframe>
</body>
</html>