Files
past-go/design/previews/couple-journal-preview.html
T
2026-10-07 09:28:43 +08:00

2307 lines
101 KiB
HTML
Raw 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>Couple Journal</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="Couple Journal" 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;Couple Journal&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, 14px);
--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, system-ui, &quot;Segoe UI&quot;, sans-serif;
--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: 430;
--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, 10px);
--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(&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;
font-size: var(--font-size-normal);
font-weight: var(--font-weight-normal);
line-height: var(--line-height-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);
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: 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;
}
.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 &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 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=&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)
);
padding: 4px 8px;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
corner-shape: var(--corner-shape);
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;
}
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 {
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);
}
.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;
}
.btn,
.nav-link {
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: var(--corner-shape);
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: var(--cursor-interaction, pointer);
text-decoration: none;
}
.btn:not(:disabled):hover,
.nav-link:not(:disabled):not([aria-disabled=&quot;true&quot;]):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));
}
.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;
}
.viz-carousel-controls button,
.viz-carousel-picker &gt; summary {
display: flex;
min-height: 30px;
align-items: center;
justify-content: center;
gap: 4px;
padding: 0 6px;
-webkit-app-region: no-drag;
border: 0;
border-radius: 14px;
color: inherit;
background: transparent;
font: inherit;
-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;
padding: 0;
}
.viz-carousel-picker &gt; summary {
padding-inline: 12px;
}
.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 {
width: 100%;
min-height: 28px;
justify-content: flex-start;
padding: 4px 8px;
font-size: var(--font-size-small);
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);
}
.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%;
min-height: 28px;
padding: 0 8px;
outline: none;
border: 1px solid var(--input);
border-radius: var(--radius-lg);
corner-shape: var(--corner-shape);
color: var(--foreground);
background: var(--secondary);
}
.form-control::placeholder {
color: var(--muted-foreground);
}
.form-control[type=&quot;file&quot;] {
padding: 0;
overflow: hidden;
cursor: var(--cursor-interaction, pointer);
}
.form-control[type=&quot;file&quot;]::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=&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: 28px;
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;
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: var(--corner-shape);
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=&quot;checkbox&quot;] {
border-color: var(--input);
border-radius: var(--radius-sm);
corner-shape: var(--corner-shape);
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=&quot;checkbox&quot;]:checked {
border-color: var(--primary);
background-color: var(--primary);
}
.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;
}
.form-check-input[type=&quot;radio&quot;] {
width: 14px;
height: 14px;
border-radius: var(--radius-full);
}
.form-check-input[type=&quot;radio&quot;]: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: &quot;&quot;;
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=&quot;true&quot;],
[aria-selected=&quot;true&quot;],
.is-selected
) {
border-color: var(--primary);
color: var(--primary-foreground);
background: var(--primary);
}
.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,
.viz-carousel-controls :is(button, summary):focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
@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, var(--font-size-normal));
}
.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;
}
.form-range::-webkit-slider-runnable-track {
height: 44px;
}
.form-range::-moz-range-track {
height: 44px;
}
.form-range::-webkit-slider-thumb {
margin-top: 12px;
}
}
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;div id=&quot;couple-journal&quot; class=&quot;viz-dotted-background&quot;&gt;
&lt;div class=&quot;preview-controls&quot; aria-label=&quot;设计稿状态&quot;&gt;
&lt;label&gt;展示状态 &lt;select id=&quot;journal-state&quot;&gt;&lt;option value=&quot;filled&quot;&gt;有记录 · 已绑定&lt;/option&gt;&lt;option value=&quot;empty&quot;&gt;无记录 · 已绑定&lt;/option&gt;&lt;option value=&quot;unbound&quot;&gt;未绑定&lt;/option&gt;&lt;option value=&quot;denied&quot;&gt;对方关闭姨姨可见&lt;/option&gt;&lt;/select&gt;&lt;/label&gt;
&lt;span&gt;设计示例&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&quot;journal-phone&quot; aria-label=&quot;精致情侣手账设计稿&quot;&gt;
&lt;div class=&quot;phone-status&quot;&gt;&lt;span&gt;09:41&lt;/span&gt;&lt;span&gt;••• ▰&lt;/span&gt;&lt;/div&gt;
&lt;header class=&quot;phone-nav&quot;&gt;&lt;button id=&quot;journal-back&quot; aria-label=&quot;返回首页&quot; hidden&gt;‹&lt;/button&gt;&lt;strong id=&quot;journal-title&quot;&gt;我们&lt;/strong&gt;&lt;span class=&quot;capsule&quot;&gt;••• &lt;b&gt;◉&lt;/b&gt;&lt;/span&gt;&lt;/header&gt;
&lt;main id=&quot;journal-content&quot;&gt;&lt;/main&gt;
&lt;nav class=&quot;phone-tabs&quot; aria-label=&quot;小程序导航&quot;&gt;&lt;button data-page=&quot;home&quot;&gt;&lt;i data-lucide=&quot;house&quot;&gt;&lt;/i&gt;&lt;span&gt;我们&lt;/span&gt;&lt;/button&gt;&lt;button data-page=&quot;records&quot;&gt;&lt;i data-lucide=&quot;notebook-pen&quot;&gt;&lt;/i&gt;&lt;span&gt;记录&lt;/span&gt;&lt;/button&gt;&lt;button data-page=&quot;profile&quot;&gt;&lt;i data-lucide=&quot;user-round&quot;&gt;&lt;/i&gt;&lt;span&gt;我的&lt;/span&gt;&lt;/button&gt;&lt;/nav&gt;
&lt;div class=&quot;home-indicator&quot;&gt;&lt;/div&gt;
&lt;div id=&quot;journal-sheet&quot; class=&quot;sheet-mask&quot; hidden&gt;&lt;section class=&quot;journal-sheet&quot; role=&quot;dialog&quot; aria-modal=&quot;true&quot; aria-labelledby=&quot;sheet-title&quot;&gt;&lt;div class=&quot;sheet-grip&quot;&gt;&lt;/div&gt;&lt;header&gt;&lt;h3 id=&quot;sheet-title&quot;&gt;&lt;/h3&gt;&lt;button id=&quot;sheet-close&quot; aria-label=&quot;关闭&quot;&gt;×&lt;/button&gt;&lt;/header&gt;&lt;div id=&quot;sheet-content&quot;&gt;&lt;/div&gt;&lt;/section&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;style&gt;
#couple-journal {--j-bg:#faf5ee;--j-paper:#fffdf8;--j-ink:#352e2a;--j-muted:#85776d;--j-rose:#b74768;--j-soft:#f4e2e3;--j-line:#e9ddd0;--j-white:#fff;--j-green:#658776;--j-shadow:0 8px 25px #72524012;color:var(--j-ink);font-family:-apple-system,BlinkMacSystemFont,&quot;PingFang SC&quot;,sans-serif;padding:20px 10px 28px;color-scheme:light}
#couple-journal *{box-sizing:border-box}#couple-journal button,#couple-journal select{font:inherit}#couple-journal button{cursor:inherit}#couple-journal button:focus-visible,#couple-journal select:focus-visible{outline:3px solid var(--j-rose);outline-offset:3px}#couple-journal [hidden]{display:none!important}
#couple-journal .preview-controls{max-width:390px;margin:0 auto 20px;display:flex;justify-content:space-between;gap:10px;align-items:center;font-size:12px;color:var(--j-muted)}#couple-journal select{border:1px solid var(--j-line);background:var(--j-paper);color:var(--j-ink);border-radius:9px;padding:9px;max-width:230px;font-size:12px}#couple-journal .preview-controls label{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
#couple-journal .journal-phone{max-width:390px;margin:auto;background:var(--j-bg);border:1px solid var(--j-line);border-radius:32px;overflow:hidden;position:relative;box-shadow:0 20px 60px #46332318;min-height:830px}#couple-journal .phone-status{padding:19px 25px 8px;display:flex;justify-content:space-between;font-size:12px;font-weight:600}#couple-journal .phone-nav{height:54px;display:flex;align-items:center;justify-content:center;position:relative}#couple-journal .phone-nav strong{font-size:18px;letter-spacing:2px}#couple-journal .capsule{position:absolute;right:16px;font-size:18px;letter-spacing:1px;border:1px solid var(--j-line);border-radius:24px;padding:4px 10px}#couple-journal .capsule b{font-weight:400;border-left:1px solid var(--j-line);margin-left:7px;padding-left:8px}#couple-journal #journal-back{position:absolute;left:18px;background:none;border:0;font-size:29px;width:40px;height:44px;color:var(--j-ink)}
#couple-journal main{padding:20px 22px 28px;min-height:655px}#couple-journal .eyebrow{font-size:11px;letter-spacing:2px;color:var(--j-muted);text-transform:uppercase}#couple-journal .head-row{display:flex;justify-content:space-between;align-items:center;gap:12px}#couple-journal h2{font-size:21px;margin:6px 0 20px;font-weight:600;letter-spacing:1px}#couple-journal h3{font-size:16px;margin:0;font-weight:600}#couple-journal p{font-size:13px;line-height:1.8;margin:8px 0;color:var(--j-muted)}#couple-journal .quiet-link{background:none;border:0;color:var(--j-rose);font-size:12px;padding:8px 0;min-height:44px}#couple-journal .rose-button{border:0;background:var(--j-rose);color:var(--j-white);border-radius:14px;padding:12px 18px;font-size:13px;min-height:44px}#couple-journal .outline-button{background:var(--j-paper);color:var(--j-rose);border:1px solid var(--j-line);border-radius:12px;padding:11px 15px;min-height:44px;font-size:13px}
#couple-journal .space-cover{position:relative;margin:6px 0 27px;padding:25px 24px 20px;border-radius:6px 30px 6px 30px;background:var(--j-paper);box-shadow:var(--j-shadow);border:1px solid var(--j-line)}#couple-journal .space-cover:before{content:&quot;&quot;;position:absolute;left:calc(50% - 28px);top:-9px;width:56px;height:20px;background:#e9d5b7bb;transform:rotate(-5deg);clip-path:polygon(3% 0,100% 3%,97% 96%,0 100%)}#couple-journal .cover-top{display:flex;justify-content:space-between;align-items:center}#couple-journal .little-heart{color:var(--j-rose);transform:rotate(12deg)}#couple-journal .days{font-family:Georgia,serif;font-size:76px;line-height:1.08;letter-spacing:-4px;margin-top:12px;color:var(--j-rose)}#couple-journal .days span{font-family:inherit;font-size:14px;letter-spacing:0;color:var(--j-muted);margin-left:12px}#couple-journal .cover-date{font-size:11px;color:var(--j-muted);letter-spacing:1px;margin-top:9px}#couple-journal .couple-line{display:flex;gap:10px;align-items:center;border-top:1px dashed var(--j-line);padding-top:16px;margin-top:21px;font-size:12px}#couple-journal .avatar{width:33px;height:33px;border-radius:50%;background:var(--j-soft);display:grid;place-items:center;color:var(--j-rose);font-size:13px;border:3px solid var(--j-paper)}#couple-journal .avatar.second{background:#e4ebe3;color:#526b57;margin-left:-15px}#couple-journal .couple-line .distance{margin-left:auto;color:var(--j-muted);font-size:11px}
#couple-journal .memory{display:flex;align-items:center;gap:15px;margin:0 0 27px;padding:0;border:0;text-align:left;background:none;color:var(--j-ink);width:100%;min-height:60px}#couple-journal .calendar-stamp{width:46px;flex-shrink:0;border:1px solid var(--j-line);background:var(--j-paper);text-align:center;border-radius:7px;transform:rotate(-5deg)}#couple-journal .calendar-stamp small{display:block;font-size:9px;padding:4px;background:var(--j-soft);color:var(--j-rose)}#couple-journal .calendar-stamp b{display:block;font-family:Georgia,serif;font-size:24px;padding:4px;font-weight:400}#couple-journal .memory b{font-size:14px;font-weight:500}#couple-journal .memory p{margin:3px 0;font-size:11px}#couple-journal .memory .countdown{margin-left:auto;font-family:Georgia,serif;font-size:29px;color:var(--j-rose)}#couple-journal .countdown small{font-family:sans-serif;font-size:11px;margin-left:4px}
#couple-journal .shortcuts{display:flex;justify-content:space-between;margin-bottom:30px;border-top:1px solid var(--j-line);border-bottom:1px solid var(--j-line);padding:15px 0}#couple-journal .shortcuts button{background:none;border:0;color:var(--j-ink);font-size:11px;display:flex;align-items:center;gap:7px;min-height:44px;padding:0 3px}#couple-journal .shortcuts svg{width:17px;height:17px;color:var(--j-rose)}#couple-journal .section-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}#couple-journal .paper-story{position:relative;background:var(--j-paper);padding:16px;box-shadow:var(--j-shadow);border:1px solid var(--j-line);border-radius:5px;transform:rotate(-1deg);margin-bottom:21px;text-align:left;width:100%;color:var(--j-ink)}#couple-journal .paper-story time{font-family:Georgia,serif;font-size:12px;letter-spacing:1px;color:var(--j-muted)}#couple-journal .paper-story h3{margin:9px 0 8px}#couple-journal .paper-story p{font-size:12px;margin:6px 0 11px}#couple-journal .story-footer{display:flex;align-items:center;gap:7px;font-size:11px;color:var(--j-muted);padding-top:8px}
#couple-journal .landscape{height:147px;overflow:hidden;position:relative;background:linear-gradient(#e2bfac 0%,#f4d8ba 48%,#92aaa9 49%,#c4d1c7 100%);border-radius:2px;margin:8px 0}#couple-journal .landscape:before{content:&quot;&quot;;position:absolute;width:180px;height:170px;border-radius:45% 60% 0 0;background:#788e81;left:-30px;top:65px;transform:rotate(30deg)}#couple-journal .landscape:after{content:&quot;&quot;;position:absolute;width:200px;height:140px;border-radius:70% 60% 0 0;background:#556f68;right:-40px;top:85px;transform:rotate(-20deg)}#couple-journal .sun{position:absolute;width:29px;height:29px;border-radius:50%;background:#fff1d8;right:72px;top:27px;box-shadow:0 0 32px #fff1d899}#couple-journal .photo-label{position:absolute;bottom:10px;left:12px;color:#fffdf8;z-index:1;font-size:10px;letter-spacing:2px}
#couple-journal .empty-note{text-align:center;padding:35px 15px;background:var(--j-paper);border:1px dashed var(--j-line);border-radius:6px 25px 6px 25px;margin-top:18px}#couple-journal .empty-note&gt;svg{color:var(--j-rose);width:30px;height:30px;margin-bottom:15px}#couple-journal .empty-note h3{font-size:17px}#couple-journal .empty-note p{font-size:12px;margin:10px 0 20px}#couple-journal .invite{border:1px dashed var(--j-line);padding:17px;border-radius:16px;margin-bottom:22px;display:flex;align-items:center;justify-content:space-between;gap:12px}#couple-journal .invite h3{font-size:13px}#couple-journal .invite p{font-size:11px;margin:3px 0}
#couple-journal .record-filters{display:flex;gap:20px;border-bottom:1px solid var(--j-line);margin-bottom:24px}#couple-journal .record-filters button{background:none;border:0;color:var(--j-muted);padding:12px 2px;min-height:44px;font-size:13px}#couple-journal .record-filters button.active{border-bottom:2px solid var(--j-rose);color:var(--j-rose);font-weight:600}#couple-journal .month-label{font-family:Georgia,serif;font-size:28px;letter-spacing:-1px;margin:0 0 18px}#couple-journal .month-label small{font-family:sans-serif;font-size:12px;letter-spacing:0;color:var(--j-muted);margin-left:10px}#couple-journal .tools{margin-top:27px;padding-top:20px;border-top:1px solid var(--j-line)}#couple-journal .tool-list{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:15px}#couple-journal .tool-list button{background:var(--j-paper);border:1px solid var(--j-line);border-radius:12px;color:var(--j-muted);font-size:11px;padding:13px 3px;display:flex;align-items:center;gap:7px;flex-direction:column;min-height:60px}#couple-journal .tool-list svg{width:19px;height:19px;color:var(--j-rose)}
#couple-journal .period-summary{margin:3px 0 25px}#couple-journal .period-summary h2{font-size:28px;margin:8px 0;color:var(--j-rose)}#couple-journal .period-summary p{font-size:12px}#couple-journal .calendar-paper{background:var(--j-paper);border:1px solid var(--j-line);border-radius:22px;padding:18px 13px;box-shadow:var(--j-shadow)}#couple-journal .calendar-heading{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}#couple-journal .calendar-heading b{font-size:15px;font-weight:600}#couple-journal .calendar-heading button{background:none;border:0;color:var(--j-muted);width:44px;height:44px;font-size:21px}#couple-journal .calendar-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:4px 0}#couple-journal .calendar-grid small{text-align:center;font-size:10px;color:var(--j-muted);padding-bottom:8px}#couple-journal .calendar-grid button{background:none;border:0;font-size:12px;min-height:40px;color:var(--j-ink);border-radius:10px;padding:0;position:relative}#couple-journal .calendar-grid button.period-day{background:var(--j-soft);color:var(--j-rose)}#couple-journal .calendar-grid button.predicted{background:#f8eeeb;color:#956571}#couple-journal .calendar-grid button.selected{background:var(--j-rose);color:var(--j-white);box-shadow:0 3px 8px #b7476830}#couple-journal .calendar-grid button.recorded:after{content:&quot;&quot;;position:absolute;bottom:4px;left:calc(50% - 2px);width:4px;height:4px;background:currentColor;border-radius:50%}#couple-journal .calendar-legend{display:flex;justify-content:center;gap:17px;margin-top:17px;font-size:10px;color:var(--j-muted)}#couple-journal .calendar-legend span:before{content:&quot;&quot;;display:inline-block;width:7px;height:7px;margin-right:5px;background:var(--j-soft);border-radius:2px}#couple-journal .calendar-legend span+span:before{background:#f8eeeb}#couple-journal .day-details{margin-top:26px}#couple-journal .day-detail-row{display:flex;align-items:center;gap:10px;border-bottom:1px solid var(--j-line);padding:14px 0;font-size:12px}#couple-journal .day-detail-row svg{color:var(--j-rose);width:17px;height:17px}#couple-journal .day-detail-row span:last-child{margin-left:auto;color:var(--j-muted)}#couple-journal .permission-row{display:flex;align-items:center;justify-content:space-between;padding:18px 0;font-size:12px;border-top:1px solid var(--j-line);margin-top:24px}#couple-journal .permission-row p{font-size:11px;margin:3px 0}#couple-journal .toggle{border:0;width:42px;height:26px;border-radius:16px;background:var(--j-rose);padding:3px;display:flex;justify-content:flex-end;flex-shrink:0}#couple-journal .toggle.off{background:#b6a99b;justify-content:flex-start}#couple-journal .toggle:before{content:&quot;&quot;;width:20px;height:20px;background:var(--j-white);border-radius:50%}
#couple-journal .phone-tabs{display:flex;background:var(--j-paper);border-top:1px solid var(--j-line);padding:12px 22px 5px;justify-content:space-around}#couple-journal .phone-tabs button{background:none;border:0;display:flex;flex-direction:column;align-items:center;gap:5px;color:var(--j-muted);font-size:10px;min-width:66px;min-height:44px}#couple-journal .phone-tabs svg{width:20px;height:20px}#couple-journal .phone-tabs button.active{color:var(--j-rose)}#couple-journal .home-indicator{width:100px;height:4px;border-radius:4px;background:var(--j-ink);margin:11px auto 9px}#couple-journal .sheet-mask{position:absolute;inset:0;background:#352e2a55;z-index:3;display:flex;align-items:flex-end}#couple-journal .journal-sheet{width:100%;background:var(--j-paper);border-radius:25px 25px 0 0;padding:10px 24px 30px;box-shadow:0 -10px 35px #352e2a18}#couple-journal .sheet-grip{width:32px;height:4px;border-radius:3px;background:var(--j-line);margin:0 auto 20px}#couple-journal .journal-sheet header{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px}#couple-journal .journal-sheet header button{background:none;border:0;color:var(--j-muted);font-size:23px;width:44px;height:44px}#couple-journal .sheet-actions{display:grid;grid-template-columns:1fr 1fr;gap:12px}#couple-journal .sheet-actions button{display:flex;flex-direction:column;align-items:flex-start;gap:10px;background:var(--j-bg);color:var(--j-ink);border:1px solid var(--j-line);border-radius:15px;padding:17px;font-size:13px}#couple-journal .sheet-actions svg{color:var(--j-rose)}#couple-journal .sheet-field{display:block;font-size:12px;color:var(--j-muted);margin:14px 0}#couple-journal .sheet-field input,#couple-journal .sheet-field textarea{display:block;width:100%;margin-top:8px;border:1px solid var(--j-line);background:var(--j-bg);color:var(--j-ink);border-radius:11px;padding:12px;font:inherit;font-size:16px}#couple-journal .sheet-field textarea{min-height:100px;resize:vertical}
@media(max-width:360px){#couple-journal{padding:12px 0}#couple-journal main{padding:18px 17px}#couple-journal .shortcuts button{gap:4px;font-size:10px}#couple-journal .preview-controls{padding:0 5px}#couple-journal .days{font-size:68px}#couple-journal .calendar-grid button{min-height:38px}}
&lt;/style&gt;
&lt;script&gt;
(()=&gt;{
const root=document.getElementById(&#x27;couple-journal&#x27;),content=root.querySelector(&#x27;#journal-content&#x27;),picker=root.querySelector(&#x27;#journal-state&#x27;),sheet=root.querySelector(&#x27;#journal-sheet&#x27;);
const saved=window.openai?.widgetState?.modelContent||{};
const state={page:[&#x27;home&#x27;,&#x27;records&#x27;,&#x27;period&#x27;,&#x27;profile&#x27;].includes(saved.page)?saved.page:&#x27;home&#x27;,scenario:[&#x27;filled&#x27;,&#x27;empty&#x27;,&#x27;unbound&#x27;,&#x27;denied&#x27;].includes(saved.scenario)?saved.scenario:&#x27;filled&#x27;,filter:&#x27;日记&#x27;,day:5,visible:true,accent:&#x27;#b74768&#x27;,paper:true};
const icon=name=&gt;`&lt;i data-lucide=&quot;${name}&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;`;
const scene=()=&gt;&#x27;&lt;div class=&quot;landscape&quot; role=&quot;img&quot; aria-label=&quot;海边日落示意插画&quot;&gt;&lt;span class=&quot;sun&quot;&gt;&lt;/span&gt;&lt;span class=&quot;photo-label&quot;&gt;海边的傍晚&lt;/span&gt;&lt;/div&gt;&#x27;;
const story=()=&gt;`&lt;button class=&quot;paper-story&quot; data-action=&quot;story&quot;&gt;&lt;time&gt;10.04 / SUNDAY&lt;/time&gt;&lt;h3&gt;把日落装进口袋&lt;/h3&gt;&lt;p&gt;沿着海边走了很久,回头的时候,刚好看见你在笑。&lt;/p&gt;${scene()}&lt;div class=&quot;story-footer&quot;&gt;${icon(&#x27;heart&#x27;)} 小满 · 昨天 18:32&lt;/div&gt;&lt;/button&gt;`;
const empty=(title,desc,action=&#x27;留下第一篇&#x27;,key=&#x27;publish&#x27;)=&gt;`&lt;div class=&quot;empty-note&quot;&gt;${icon(&#x27;notebook-pen&#x27;)}&lt;h3&gt;${title}&lt;/h3&gt;&lt;p&gt;${desc}&lt;/p&gt;&lt;button class=&quot;rose-button&quot; data-action=&quot;${key}&quot;&gt;${action}&lt;/button&gt;&lt;/div&gt;`;
const tools=()=&gt;`&lt;div class=&quot;tools&quot;&gt;&lt;h3&gt;一起的小事&lt;/h3&gt;&lt;div class=&quot;tool-list&quot;&gt;${[[&#x27;music-2&#x27;,&#x27;歌曲&#x27;,&#x27;songs&#x27;],[&#x27;circle-check&#x27;,&#x27;打卡&#x27;,&#x27;checkin&#x27;],[&#x27;flower-2&#x27;,&#x27;姨姨&#x27;,&#x27;period&#x27;],[&#x27;paw-print&#x27;,&#x27;宠物&#x27;,&#x27;pet&#x27;]].map(([i,n,a])=&gt;`&lt;button data-action=&quot;${a}&quot;&gt;${icon(i)}${n}&lt;/button&gt;`).join(&#x27;&#x27;)}&lt;/div&gt;&lt;/div&gt;`;
function home(){const unbound=state.scenario===&#x27;unbound&#x27;;return `&lt;div class=&quot;eyebrow&quot;&gt;OUR LITTLE WORLD&lt;/div&gt;&lt;h2&gt;${unbound?&#x27;给日常留一点位置&#x27;:&#x27;平凡的日子,也值得记住&#x27;}&lt;/h2&gt;&lt;section class=&quot;space-cover&quot;&gt;&lt;div class=&quot;cover-top&quot;&gt;&lt;span class=&quot;eyebrow&quot;&gt;${unbound?&#x27;我的生活手账&#x27;:&#x27;我们在一起&#x27;}&lt;/span&gt;&lt;span class=&quot;little-heart&quot;&gt;${icon(&#x27;heart&#x27;)}&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;days&quot;&gt;${unbound?&#x27;124&#x27;:&#x27;365&#x27;}&lt;span&gt;天&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;cover-date&quot;&gt;${unbound?&#x27;注册于 2026.06.03&#x27;:&#x27;故事开始于 2025.10.05&#x27;}&lt;/div&gt;&lt;div class=&quot;couple-line&quot;&gt;&lt;span class=&quot;avatar&quot;&gt;满&lt;/span&gt;${unbound?&#x27;&#x27;: &#x27;&lt;span class=&quot;avatar second&quot;&gt;屿&lt;/span&gt;&#x27;}&lt;span&gt;${unbound?&#x27;小满&#x27;:&#x27;小满 &amp; 阿屿&#x27;}&lt;/span&gt;&lt;span class=&quot;distance&quot;&gt;${unbound?&#x27;等待另一半加入&#x27;:&#x27;把日子慢慢过成故事&#x27;}&lt;/span&gt;&lt;/div&gt;&lt;/section&gt;${unbound?&#x27;&lt;div class=&quot;invite&quot;&gt;&lt;div&gt;&lt;h3&gt;邀请另一半,共写这本手账&lt;/h3&gt;&lt;p&gt;绑定后,一起收藏日常&lt;/p&gt;&lt;/div&gt;&lt;button class=&quot;outline-button&quot; data-action=&quot;bind&quot;&gt;邀请&lt;/button&gt;&lt;/div&gt;&#x27;:&#x27;&lt;button class=&quot;memory&quot; data-action=&quot;memory&quot;&gt;&lt;span class=&quot;calendar-stamp&quot;&gt;&lt;small&gt;OCT&lt;/small&gt;&lt;b&gt;12&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;下一次值得期待&lt;/b&gt;&lt;p&gt;第一次旅行纪念日&lt;/p&gt;&lt;/span&gt;&lt;span class=&quot;countdown&quot;&gt;7&lt;small&gt;天后&lt;/small&gt;&lt;/span&gt;&lt;/button&gt;&#x27;}&lt;div class=&quot;shortcuts&quot;&gt;${[[&#x27;calendar-heart&#x27;,&#x27;记忆日&#x27;,&#x27;memory&#x27;],[&#x27;images&#x27;,&#x27;相册&#x27;,&#x27;albums&#x27;],[&#x27;notebook-pen&#x27;,&#x27;写日记&#x27;,&#x27;publish&#x27;],[&#x27;flower-2&#x27;,&#x27;姨姨&#x27;,&#x27;period&#x27;]].map(([i,n,a])=&gt;`&lt;button data-action=&quot;${a}&quot;&gt;${icon(i)}${n}&lt;/button&gt;`).join(&#x27;&#x27;)}&lt;/div&gt;&lt;div class=&quot;section-top&quot;&gt;&lt;h3&gt;最近的一页&lt;/h3&gt;&lt;button class=&quot;quiet-link&quot; data-action=&quot;records&quot;&gt;翻翻手账 →&lt;/button&gt;&lt;/div&gt;${state.scenario===&#x27;filled&#x27;||state.scenario===&#x27;denied&#x27;?story():empty(&#x27;故事,从今天开始&#x27;,&#x27;一句心情、一张照片,都可以成为第一页。&#x27;)}`;}
function records(){let body;if(state.scenario===&#x27;empty&#x27;||state.scenario===&#x27;unbound&#x27;)body=empty(&#x27;还没有&#x27;+state.filter,&#x27;慢慢积攒属于你们的生活片段。&#x27;,state.filter===&#x27;相册&#x27;?&#x27;创建相册&#x27;:state.filter===&#x27;记忆日&#x27;?&#x27;添加记忆日&#x27;:&#x27;写日记&#x27;,state.filter===&#x27;相册&#x27;?&#x27;newAlbum&#x27;:state.filter===&#x27;记忆日&#x27;?&#x27;newMemory&#x27;:&#x27;publish&#x27;);else if(state.filter===&#x27;日记&#x27;)body=&#x27;&lt;div class=&quot;month-label&quot;&gt;October&lt;small&gt;2026 · 十月&lt;/small&gt;&lt;/div&gt;&#x27;+story()+&#x27;&lt;button class=&quot;paper-story&quot; data-action=&quot;story&quot;&gt;&lt;time&gt;10.02 / FRIDAY&lt;/time&gt;&lt;h3&gt;今天你做的饭,满分&lt;/h3&gt;&lt;p&gt;没有特别的安排。两个人一起买菜、做饭,就已经是很好的周末。&lt;/p&gt;&lt;div class=&quot;story-footer&quot;&gt;&#x27;+icon(&#x27;heart&#x27;)+&#x27; 阿屿 · 10月2日&lt;/div&gt;&lt;/button&gt;&#x27;;else if(state.filter===&#x27;相册&#x27;)body=&#x27;&lt;button class=&quot;paper-story&quot; data-action=&quot;album&quot;&gt;&#x27;+scene()+&#x27;&lt;h3&gt;沿着海岸线&lt;/h3&gt;&lt;p&gt;12 张照片 · 10月4日更新&lt;/p&gt;&lt;/button&gt;&#x27;;else body=&#x27;&lt;button class=&quot;memory&quot; data-action=&quot;memory&quot;&gt;&lt;span class=&quot;calendar-stamp&quot;&gt;&lt;small&gt;OCT&lt;/small&gt;&lt;b&gt;12&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;第一次旅行&lt;/b&gt;&lt;p&gt;每一年,都想和你重游一次&lt;/p&gt;&lt;/span&gt;&lt;span class=&quot;countdown&quot;&gt;7&lt;small&gt;天后&lt;/small&gt;&lt;/span&gt;&lt;/button&gt;&#x27;;return `&lt;div class=&quot;head-row&quot;&gt;&lt;div&gt;&lt;span class=&quot;eyebrow&quot;&gt;PAGES OF US&lt;/span&gt;&lt;h2&gt;我们的生活手账&lt;/h2&gt;&lt;/div&gt;&lt;button class=&quot;rose-button&quot; data-action=&quot;publish&quot;&gt;+ 记录&lt;/button&gt;&lt;/div&gt;&lt;div class=&quot;record-filters&quot;&gt;${[&#x27;日记&#x27;,&#x27;相册&#x27;,&#x27;记忆日&#x27;].map(n=&gt;`&lt;button data-filter=&quot;${n}&quot; class=&quot;${state.filter===n?&#x27;active&#x27;:&#x27;&#x27;}&quot;&gt;${n}&lt;/button&gt;`).join(&#x27;&#x27;)}&lt;/div&gt;${body}${tools()}`;}
function permission(){return `&lt;div class=&quot;permission-row&quot;&gt;&lt;div&gt;对方可见&lt;p&gt;关闭后,对方无法查看这份生理记录&lt;/p&gt;&lt;/div&gt;&lt;button class=&quot;toggle ${state.visible?&#x27;&#x27;:&#x27;off&#x27;}&quot; role=&quot;switch&quot; aria-checked=&quot;${state.visible}&quot; aria-label=&quot;对方可见&quot; data-action=&quot;toggle&quot;&gt;&lt;/button&gt;&lt;/div&gt;`;}
function period(){if(state.scenario===&#x27;denied&#x27;)return empty(&#x27;对方暂时关闭了可见&#x27;,&#x27;这份生理记录目前不可查看,&lt;br&gt;需要对方重新开启后才能访问。&#x27;,&#x27;刷新权限&#x27;,&#x27;refresh&#x27;)+permission();if(state.scenario===&#x27;empty&#x27;||state.scenario===&#x27;unbound&#x27;)return `&lt;div class=&quot;period-summary&quot;&gt;&lt;span class=&quot;eyebrow&quot;&gt;BODY JOURNAL&lt;/span&gt;&lt;h2&gt;慢慢了解自己的节奏&lt;/h2&gt;&lt;p&gt;从记录一次生理期开始。&lt;/p&gt;&lt;/div&gt;${empty(&#x27;还没有生理期记录&#x27;,&#x27;填写最近一次生理期,开始记录身体状态。&#x27;,&#x27;设置生理期&#x27;,&#x27;settings&#x27;)}${permission()}`;const days=Array.from({length:35},(_,i)=&gt;{const d=i-3;return d&lt;1||d&gt;31?&#x27;&lt;span&gt;&lt;/span&gt;&#x27;:`&lt;button data-day=&quot;${d}&quot; aria-label=&quot;10月${d}日${d&lt;=5?&#x27; 经期&#x27;:&#x27;&#x27;}&quot; class=&quot;${d&lt;=5?&#x27;period-day&#x27;:&#x27;&#x27;} ${d&gt;=29?&#x27;predicted&#x27;:&#x27;&#x27;} ${d===state.day?&#x27;selected&#x27;:&#x27;&#x27;} ${[2,5].includes(d)?&#x27;recorded&#x27;:&#x27;&#x27;}&quot;&gt;${d}&lt;/button&gt;`}).join(&#x27;&#x27;);return `&lt;div class=&quot;period-summary&quot;&gt;&lt;div class=&quot;head-row&quot;&gt;&lt;span class=&quot;eyebrow&quot;&gt;BODY JOURNAL&lt;/span&gt;&lt;button class=&quot;quiet-link&quot; data-action=&quot;settings&quot;&gt;周期设置 ${icon(&#x27;settings-2&#x27;)}&lt;/button&gt;&lt;/div&gt;&lt;h2&gt;生理期第 5 天&lt;/h2&gt;&lt;p&gt;平均周期 28 天 · 下次预计 10月29日&lt;/p&gt;&lt;/div&gt;&lt;section class=&quot;calendar-paper&quot;&gt;&lt;div class=&quot;calendar-heading&quot;&gt;&lt;button data-action=&quot;calendarHelp&quot; aria-label=&quot;上个月&quot;&gt;‹&lt;/button&gt;&lt;b&gt;2026年 10月&lt;/b&gt;&lt;button data-action=&quot;calendarHelp&quot; aria-label=&quot;下个月&quot;&gt;›&lt;/button&gt;&lt;/div&gt;&lt;div class=&quot;calendar-grid&quot;&gt;${[&#x27;日&#x27;,&#x27;一&#x27;,&#x27;二&#x27;,&#x27;三&#x27;,&#x27;四&#x27;,&#x27;五&#x27;,&#x27;六&#x27;].map(d=&gt;&#x27;&lt;small&gt;&#x27;+d+&#x27;&lt;/small&gt;&#x27;).join(&#x27;&#x27;)}${days}&lt;/div&gt;&lt;div class=&quot;calendar-legend&quot;&gt;&lt;span&gt;经期&lt;/span&gt;&lt;span&gt;预测经期&lt;/span&gt;&lt;/div&gt;&lt;/section&gt;&lt;div class=&quot;day-details&quot;&gt;&lt;div class=&quot;section-top&quot;&gt;&lt;h3&gt;10月${state.day}日 · 当天记录&lt;/h3&gt;&lt;button class=&quot;quiet-link&quot; data-action=&quot;bodyRecord&quot;&gt;+ 添加&lt;/button&gt;&lt;/div&gt;${[2,5].includes(state.day)?`&lt;div class=&quot;day-detail-row&quot;&gt;${icon(&#x27;flower-2&#x27;)}&lt;span&gt;经量&lt;/span&gt;&lt;span&gt;少量&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;day-detail-row&quot;&gt;${icon(&#x27;smile&#x27;)}&lt;span&gt;心情&lt;/span&gt;&lt;span&gt;平静&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;day-detail-row&quot;&gt;${icon(&#x27;notebook-pen&#x27;)}&lt;span&gt;备注&lt;/span&gt;&lt;span&gt;今天状态不错&lt;/span&gt;&lt;/div&gt;`:&#x27;&lt;p&gt;这一天还没有记录,留意一下身体的感受。&lt;/p&gt;&lt;button class=&quot;outline-button&quot; data-action=&quot;bodyRecord&quot;&gt;记录当天状态&lt;/button&gt;&#x27;}&lt;/div&gt;${permission()}`;}
function profile(){return `&lt;div class=&quot;eyebrow&quot;&gt;MY CORNER&lt;/div&gt;&lt;h2&gt;我的小角落&lt;/h2&gt;&lt;div class=&quot;space-cover&quot;&gt;&lt;div class=&quot;head-row&quot;&gt;&lt;div&gt;&lt;h3&gt;小满&lt;/h3&gt;&lt;p&gt;ID 520087&lt;/p&gt;&lt;/div&gt;&lt;span class=&quot;avatar&quot;&gt;满&lt;/span&gt;&lt;/div&gt;&lt;button class=&quot;quiet-link&quot; data-action=&quot;editProfile&quot;&gt;编辑资料 →&lt;/button&gt;&lt;/div&gt;&lt;h3&gt;我们的关系&lt;/h3&gt;${state.scenario===&#x27;unbound&#x27;?&#x27;&lt;div class=&quot;invite&quot;&gt;&lt;p&gt;另一半还没有加入&lt;/p&gt;&lt;button class=&quot;outline-button&quot; data-action=&quot;bind&quot;&gt;绑定情侣&lt;/button&gt;&lt;/div&gt;&#x27;:&#x27;&lt;div class=&quot;permission-row&quot;&gt;&lt;span&gt;小满 &amp; 阿屿&lt;p&gt;在一起 365 天&lt;/p&gt;&lt;/span&gt;&lt;button class=&quot;quiet-link&quot; data-action=&quot;relationship&quot;&gt;关系管理 →&lt;/button&gt;&lt;/div&gt;&#x27;}&lt;div class=&quot;permission-row&quot;&gt;&lt;span&gt;外观与通知&lt;/span&gt;&lt;button class=&quot;quiet-link&quot; data-action=&quot;appearance&quot;&gt;设置 →&lt;/button&gt;&lt;/div&gt;`;}
function render(){root.style.setProperty(&#x27;--j-rose&#x27;,state.accent);root.querySelectorAll(&#x27;.paper-story,.space-cover&#x27;).forEach(e=&gt;e.style.transform=state.paper?&#x27;&#x27;:&#x27;none&#x27;);picker.value=state.scenario;content.innerHTML=({home,records,period,profile}[state.page])();root.querySelector(&#x27;#journal-title&#x27;).textContent={home:&#x27;我们&#x27;,records:&#x27;记录&#x27;,period:&#x27;姨姨&#x27;,profile:&#x27;我的&#x27;}[state.page];root.querySelector(&#x27;#journal-back&#x27;).hidden=state.page!==&#x27;period&#x27;;root.querySelectorAll(&#x27;[data-page]&#x27;).forEach(b=&gt;{b.classList.toggle(&#x27;active&#x27;,b.dataset.page===(state.page===&#x27;period&#x27;?&#x27;records&#x27;:state.page));b.setAttribute(&#x27;aria-current&#x27;,b.classList.contains(&#x27;active&#x27;)?&#x27;page&#x27;:&#x27;false&#x27;)});if(!state.paper)root.querySelectorAll(&#x27;.paper-story,.space-cover&#x27;).forEach(e=&gt;e.style.transform=&#x27;none&#x27;);globalThis.lucide?.createIcons();}
function persist(){window.openai?.setWidgetState?.({modelContent:{page:state.page,scenario:state.scenario},privateContent:null})?.catch(()=&gt;{});}
function openSheet(title,body){root.querySelector(&#x27;#sheet-title&#x27;).textContent=title;root.querySelector(&#x27;#sheet-content&#x27;).innerHTML=body;sheet.hidden=false;globalThis.lucide?.createIcons();root.querySelector(&#x27;#sheet-close&#x27;).focus();}
function action(a){if([&#x27;period&#x27;,&#x27;records&#x27;].includes(a)){state.page=a;render();persist();return;}if(a===&#x27;toggle&#x27;){state.visible=!state.visible;render();return;}if(a===&#x27;publish&#x27;)openSheet(&#x27;留下一页日常&#x27;,`&lt;div class=&quot;sheet-actions&quot;&gt;&lt;button data-action=&quot;write&quot;&gt;${icon(&#x27;notebook-pen&#x27;)}写日记&lt;/button&gt;&lt;button data-action=&quot;newAlbum&quot;&gt;${icon(&#x27;images&#x27;)}建相册&lt;/button&gt;&lt;button data-action=&quot;newMemory&quot;&gt;${icon(&#x27;calendar-heart&#x27;)}记忆日&lt;/button&gt;&lt;button data-action=&quot;bodyRecord&quot;&gt;${icon(&#x27;flower-2&#x27;)}身体记录&lt;/button&gt;&lt;/div&gt;`);else if(a===&#x27;write&#x27;)openSheet(&#x27;写日记&#x27;,&#x27;&lt;label class=&quot;sheet-field&quot;&gt;标题&lt;input placeholder=&quot;给今天起个名字&quot;&gt;&lt;/label&gt;&lt;label class=&quot;sheet-field&quot;&gt;正文&lt;textarea placeholder=&quot;今天有什么想留下的?&quot;&gt;&lt;/textarea&gt;&lt;/label&gt;&lt;button class=&quot;rose-button&quot; data-action=&quot;close&quot;&gt;完成预览&lt;/button&gt;&#x27;);else if(a===&#x27;bodyRecord&#x27;)openSheet(`10月${state.day}日 · 记录身体状态`,`&lt;div class=&quot;sheet-actions&quot;&gt;${[[&#x27;flower-2&#x27;,&#x27;经量&#x27;],[&#x27;smile&#x27;,&#x27;心情&#x27;],[&#x27;thermometer&#x27;,&#x27;体温&#x27;],[&#x27;notebook-pen&#x27;,&#x27;备注&#x27;]].map(([i,n])=&gt;`&lt;button data-action=&quot;recordField&quot;&gt;${icon(i)}${n}&lt;/button&gt;`).join(&#x27;&#x27;)}&lt;/div&gt;`);else if(a===&#x27;settings&#x27;)openSheet(&#x27;周期设置&#x27;,&#x27;&lt;label class=&quot;sheet-field&quot;&gt;最近一次开始日期&lt;input type=&quot;date&quot; value=&quot;2026-10-01&quot;&gt;&lt;/label&gt;&lt;label class=&quot;sheet-field&quot;&gt;持续天数&lt;input type=&quot;number&quot; value=&quot;5&quot;&gt;&lt;/label&gt;&lt;label class=&quot;sheet-field&quot;&gt;周期长度&lt;input type=&quot;number&quot; value=&quot;28&quot;&gt;&lt;/label&gt;&lt;button class=&quot;rose-button&quot; data-action=&quot;close&quot;&gt;完成预览&lt;/button&gt;&#x27;);else if(a===&#x27;story&#x27;)openSheet(&#x27;把日落装进口袋&#x27;,scene()+&#x27;&lt;p&gt;沿着海边走了很久,回头的时候,刚好看见你在笑。&lt;/p&gt;&lt;p&gt;小满 · 2026年10月4日&lt;/p&gt;&#x27;);else if(a===&#x27;close&#x27;){sheet.hidden=true;}else{const titles={bind:&#x27;邀请另一半&#x27;,memory:&#x27;第一次旅行纪念日&#x27;,albums:&#x27;相册里的片段&#x27;,album:&#x27;沿着海岸线&#x27;,newAlbum:&#x27;创建相册&#x27;,newMemory:&#x27;添加记忆日&#x27;,refresh:&#x27;当前权限&#x27;,relationship:&#x27;关系管理&#x27;,appearance:&#x27;外观与通知&#x27;,editProfile:&#x27;编辑资料&#x27;,recordField:&#x27;当天状态&#x27;,songs:&#x27;一起听的歌&#x27;,checkin:&#x27;一起打卡&#x27;,pet:&#x27;宠物养成&#x27;,calendarHelp:&#x27;日历月份&#x27;};openSheet(titles[a]||&#x27;记录&#x27;,a===&#x27;refresh&#x27;?&#x27;&lt;p&gt;对方仍关闭了可见。你自己的可见开关不会解除对方设置的限制。&lt;/p&gt;&#x27;:a===&#x27;relationship&#x27;?&#x27;&lt;p&gt;你和阿屿已绑定。&lt;/p&gt;&lt;button class=&quot;outline-button&quot; data-action=&quot;close&quot;&gt;解绑情侣&lt;/button&gt;&#x27;:a===&#x27;bind&#x27;?&#x27;&lt;label class=&quot;sheet-field&quot;&gt;对方ID&lt;input placeholder=&quot;输入另一半的ID&quot;&gt;&lt;/label&gt;&lt;button class=&quot;rose-button&quot; data-action=&quot;close&quot;&gt;完成预览&lt;/button&gt;&#x27;:&#x27;&lt;p&gt;此处展示交互与排版,正式页面继续使用现有接口数据。&lt;/p&gt;&lt;button class=&quot;outline-button&quot; data-action=&quot;close&quot;&gt;返回&lt;/button&gt;&#x27;);}}
root.addEventListener(&#x27;click&#x27;,e=&gt;{const b=e.target.closest(&#x27;button&#x27;);if(!b)return;if(b.dataset.page){state.page=b.dataset.page;render();persist();}else if(b.dataset.filter){state.filter=b.dataset.filter;render();}else if(b.dataset.day){state.day=Number(b.dataset.day);render();}else if(b.dataset.action)action(b.dataset.action);});picker.addEventListener(&#x27;change&#x27;,()=&gt;{state.scenario=picker.value;state.visible=true;render();persist();});root.querySelector(&#x27;#journal-back&#x27;).addEventListener(&#x27;click&#x27;,()=&gt;{state.page=&#x27;home&#x27;;render();persist();});root.querySelector(&#x27;#sheet-close&#x27;).addEventListener(&#x27;click&#x27;,()=&gt;sheet.hidden=true);sheet.addEventListener(&#x27;click&#x27;,e=&gt;{if(e.target===sheet)sheet.hidden=true;});root.addEventListener(&#x27;keydown&#x27;,e=&gt;{if(e.key===&#x27;Escape&#x27;)sheet.hidden=true;});window.addEventListener(&#x27;openai:set_globals&#x27;,e=&gt;{const v=e.detail?.globals?.widgetState?.modelContent;if(v&amp;&amp;[&#x27;home&#x27;,&#x27;records&#x27;,&#x27;period&#x27;,&#x27;profile&#x27;].includes(v.page)){state.page=v.page;if([&#x27;filled&#x27;,&#x27;empty&#x27;,&#x27;unbound&#x27;,&#x27;denied&#x27;].includes(v.scenario))state.scenario=v.scenario;render();}});render();if(globalThis.Tweak){const tweak=new Tweak({container:root.querySelector(&#x27;.journal-phone&#x27;),onChange:render});tweak.addColorPicker(state,&#x27;accent&#x27;,{label:&#x27;重点色&#x27;,reference:&#x27;--j-rose&#x27;});tweak.addToggle(state,&#x27;paper&#x27;,{label:&#x27;手账纸张倾斜&#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;
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();
});
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;
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;
summary.focus();
}
});
const dismiss = (event) =&gt; {
if (!picker.contains(event.target)) {
picker.open = false;
}
};
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);
window.addEventListener(
&quot;pagehide&quot;,
() =&gt; {
document.removeEventListener(&quot;pointerdown&quot;, dismiss);
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;
}
let viewportHeight = globalThis.innerHeight;
const storageKey = `codex:visualization-widget-state-v2:${JSON.stringify([
globalThis.location.pathname,
globalThis.location.search,
])}`;
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 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 &&
height <= 10_000
) {
frame.style.height = Math.ceil(height) + "px";
}
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 (!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.
frame.srcdoc = 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>",
);
delete frame.dataset.srcdoc;
})();
</script>
</body>
</html>