@dstackai/sqircle 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/style.css CHANGED
@@ -1,2 +1,2 @@
1
- .squircle-scene{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;background:0 0;width:100%;height:auto;display:block;overflow:visible}.squircle-scene.sq-theme-dark{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}.sq-layer{cursor:pointer}.sq-layer.is-selected{outline:none}.sq-variant{transition:opacity var(--sq-transition-ms,.22s) ease}.sq-hover{opacity:0;pointer-events:none}.sq-layer.sq-has-hover:hover>.sq-base{opacity:0}.sq-layer.sq-has-hover:hover>.sq-hover{opacity:1}.sq-face{pointer-events:all;shape-rendering:geometricprecision;stroke-linejoin:round}.sq-hidden{fill:none;pointer-events:none;stroke-dasharray:8 8;stroke-linecap:round;stroke-linejoin:round}.sq-inlay{fill:none;pointer-events:none;shape-rendering:geometricprecision;stroke-dasharray:9 8;stroke-linejoin:round}.sq-label{clip-rule:evenodd;fill-rule:evenodd;paint-order:stroke;pointer-events:none;shape-rendering:geometricprecision;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;transition:fill .25s,stroke .25s,stroke-width .25s,opacity .25s}:root{color:#111821;background:0 0;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}*{box-sizing:border-box}body{background:0 0;margin:0}button,input,select{font:inherit}.squircle-editor{--editor-text:#111821;--editor-muted:#697484;--editor-soft:#f6f9fc;--editor-panel:#ffffffdb;--editor-panel-soft:#ffffffb8;--editor-panel-muted:#ffffff75;--editor-line:#dfe7f1;--editor-line-strong:#dbe5ef;--editor-control-bg:#fff;--editor-control-text:#17202b;--editor-active-text:#073c68;--editor-accent:#09f;--editor-accent-soft:#0099ff24;--editor-danger:#9c2340;--editor-code-bg:#ffffffb8;--editor-code-text:#263140;--editor-stage-sheen:#b8e7ff2e;color:var(--editor-text);--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;grid-template-rows:auto 1fr;width:min(100%,1320px);min-height:100vh;margin:0 auto;padding:clamp(14px,3vw,32px);display:grid}.squircle-editor-dark{--editor-text:#f4f8ff;--editor-muted:#aab7c8;--editor-soft:#101925;--editor-panel:#0d141ff0;--editor-panel-soft:#151f2ddb;--editor-panel-muted:#0c131db8;--editor-line:#25364a;--editor-line-strong:#33485f;--editor-control-bg:#121d2b;--editor-control-text:#eef6ff;--editor-active-text:#e5f6ff;--editor-accent:#42b4ff;--editor-accent-soft:#42b4ff2b;--editor-danger:#ff8ca3;--editor-code-bg:#090e16d6;--editor-code-text:#d8e8ff;--editor-stage-sheen:#42b4ff17;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}.squircle-editor-topbar{justify-content:space-between;align-items:end;gap:18px;padding-bottom:14px;display:flex}.squircle-editor-topbar h1{letter-spacing:0;margin:0;font-size:clamp(28px,5vw,48px)}.squircle-editor-topbar p{color:var(--editor-muted);margin:4px 0 0;font-size:14px;font-weight:650}.topbar-actions{flex-wrap:wrap;justify-content:flex-end;gap:8px;display:flex}.squircle-editor button{border:1px solid var(--editor-line-strong);background:var(--editor-control-bg);min-height:34px;color:var(--editor-control-text);cursor:pointer;border-radius:6px;font-weight:800}.squircle-editor button:hover{border-color:color-mix(in srgb, var(--editor-accent) 52%, transparent);color:var(--editor-active-text)}.theme-switch{border:1px solid var(--editor-line);background:var(--editor-panel-soft);border-radius:8px;gap:2px;padding:3px;display:inline-flex}.theme-switch button{min-width:68px;min-height:28px;color:var(--editor-muted);background:0 0;border-color:#0000}.theme-switch button[aria-pressed=true]{border-color:color-mix(in srgb, var(--editor-accent) 48%, var(--editor-line));color:var(--editor-active-text);background:linear-gradient(180deg, color-mix(in srgb, var(--editor-control-bg) 92%, var(--editor-accent)), var(--editor-panel-soft));box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--editor-accent) 16%, transparent), 0 1px 5px #0f141a1f}.squircle-editor-workspace{border:1px solid var(--editor-line);background:linear-gradient(145deg, var(--editor-stage-sheen), transparent 48%), var(--editor-panel);border-radius:8px;grid-template-columns:minmax(220px,270px) minmax(420px,1fr) minmax(280px,350px);min-height:0;display:grid;overflow:hidden}.side-panel{min-width:0;padding:14px;overflow:auto}.side-panel:first-child{border-right:1px solid var(--editor-line)}.inspector-panel{border-left:1px solid var(--editor-line)}.panel-heading{justify-content:space-between;align-items:center;gap:10px;margin-bottom:12px;display:flex}.panel-heading h2{margin:0;font-size:15px}.panel-heading span{color:var(--editor-muted);font-size:12px;font-weight:850}.squircle-editor-layer-list{gap:8px;display:grid}.squircle-editor-layer-row{grid-template-columns:1fr auto;align-items:stretch;gap:6px;display:grid}.layer-select{text-align:left;justify-items:start;min-width:0;padding:10px;display:grid}.layer-select strong{font-size:13px}.layer-select span{color:var(--editor-muted);font-size:11px;font-weight:760}.layer-select.is-active{border-color:color-mix(in srgb, var(--editor-accent) 68%, var(--editor-line));background:linear-gradient(90deg, var(--editor-accent-soft), var(--editor-panel-soft) 48%), var(--editor-control-bg);box-shadow:0 0 0 2px color-mix(in srgb, var(--editor-accent) 12%, transparent);color:var(--editor-active-text);border-left-width:4px}.icon-text{color:var(--editor-muted);padding:0 10px;font-size:12px}.icon-text.danger{color:var(--editor-danger)}.squircle-editor-preview{grid-template-rows:minmax(260px,1fr) minmax(120px,220px);gap:12px;min-width:0;min-height:0;padding:18px;display:grid}.squircle-editor-preview .squircle-scene{place-self:center;max-width:620px}.code-panel{border:1px solid var(--editor-line);background:var(--editor-code-bg);border-radius:6px;grid-template-rows:auto 1fr;min-width:0;display:grid;overflow:hidden}.code-panel-header{border-bottom:1px solid var(--editor-line);justify-content:space-between;align-items:center;gap:12px;padding:9px 10px;display:flex}.code-panel-header h2{color:var(--editor-control-text);letter-spacing:0;margin:0;font-size:13px}.code-panel-header p{color:var(--editor-muted);margin:1px 0 0;font-size:11px;font-weight:760}.copy-code-button{place-items:center;width:34px;min-width:34px;padding:0;display:inline-grid;position:relative}.copy-code-button .copy-icon{fill:none;stroke:currentColor;stroke-width:1.8px;stroke-linecap:round;stroke-linejoin:round;width:17px;height:17px}.copy-status{border:1px solid var(--editor-line);background:var(--editor-control-bg);color:var(--editor-active-text);opacity:0;pointer-events:none;border-radius:5px;padding:3px 6px;font-size:11px;font-weight:850;transition:opacity .16s,transform .16s;position:absolute;bottom:calc(100% + 5px);right:0;transform:translateY(2px)}.copy-status:not(:empty){opacity:1;transform:translateY(0)}.code-output{min-width:0;color:var(--editor-code-text);margin:0;padding:12px;font-size:11px;line-height:1.45;overflow:auto}.code-output code{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace}.editor-section{border-top:1px solid var(--editor-line);gap:10px;padding:12px 0;display:grid}.editor-section:first-of-type{border-top:0;padding-top:0}.editor-section h3{color:var(--editor-muted);letter-spacing:0;text-transform:uppercase;margin:0;font-size:12px}.field{color:var(--editor-muted);gap:5px;font-size:12px;font-weight:800;display:grid}.field select,.field input[type=text],.field input[type=range]{width:100%}.field select,.field input[type=text]{border:1px solid var(--editor-line-strong);background:var(--editor-control-bg);min-height:34px;color:var(--editor-control-text);border-radius:6px;padding:0 8px}.toggle-field{color:var(--editor-control-text);align-items:center;gap:8px;font-size:13px;font-weight:800;display:flex}.toggle-field input{width:16px;height:16px}.empty-note{color:var(--editor-muted);margin:0;font-size:13px;font-weight:700}@media (width<=1060px){.squircle-editor-workspace{grid-template-columns:1fr}.side-panel:first-child,.inspector-panel{border:0;border-bottom:1px solid var(--editor-line)}}@media (width<=700px){.squircle-editor-topbar{flex-direction:column;align-items:stretch}.topbar-actions{justify-content:stretch}.topbar-actions button{flex:1}.squircle-editor-preview{padding:10px}}
1
+ .squircle-scene{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;background:0 0;width:100%;height:auto;display:block;overflow:visible}.squircle-scene.sq-theme-dark{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}.sq-layer{cursor:pointer}.sq-layer.is-selected{outline:none}.sq-variant{transition:opacity var(--sq-transition-ms,.22s) ease}.sq-hover{opacity:0;pointer-events:none}.sq-layer.sq-has-hover:hover>.sq-base{opacity:0}.sq-layer.sq-has-hover:hover>.sq-hover{opacity:1}.sq-face{pointer-events:all;shape-rendering:geometricprecision;stroke-linejoin:round}.sq-hidden{fill:none;pointer-events:none;stroke-dasharray:8 8;stroke-linecap:round;stroke-linejoin:round}.sq-inlay{fill:none;pointer-events:none;shape-rendering:geometricprecision;stroke-dasharray:9 8;stroke-linejoin:round}.sq-label{clip-rule:evenodd;fill-rule:evenodd;paint-order:stroke;pointer-events:none;shape-rendering:geometricprecision;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;transition:fill .25s,stroke .25s,stroke-width .25s,opacity .25s}:root{color:#111821;background:0 0;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}*{box-sizing:border-box}body{background:0 0;margin:0}button,input,select{font:inherit}.squircle-editor{--editor-text:#111821;--editor-muted:#697484;--editor-faint:#8b98aa;--editor-soft:#f6f9fc;--editor-panel:#ffffffdb;--editor-panel-soft:#ffffffb8;--editor-panel-muted:#ffffff75;--editor-line:#dfe7f1;--editor-line-strong:#dbe5ef;--editor-control-bg:#fff;--editor-control-text:#17202b;--editor-active-text:#073c68;--editor-accent:#09f;--editor-accent-2:#7c58f7;--editor-accent-soft:#0099ff24;--editor-danger:#9c2340;--editor-code-bg:#ffffffb8;--editor-code-text:#263140;--editor-stage-sheen:#b8e7ff2e;color:var(--editor-text);--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;grid-template-rows:auto minmax(0,1fr);width:min(100%,1320px);min-height:100vh;margin:0 auto;padding:clamp(14px,3vw,32px);display:grid}.squircle-editor-dark{--editor-text:#f4f8ff;--editor-muted:#aab7c8;--editor-faint:#7f8da2;--editor-soft:#101925;--editor-panel:#0d141ff0;--editor-panel-soft:#151f2ddb;--editor-panel-muted:#0c131db8;--editor-line:#25364a;--editor-line-strong:#33485f;--editor-control-bg:#121d2b;--editor-control-text:#eef6ff;--editor-active-text:#e5f6ff;--editor-accent:#42b4ff;--editor-accent-2:#a88cff;--editor-accent-soft:#42b4ff2b;--editor-danger:#ff8ca3;--editor-code-bg:#090e16d6;--editor-code-text:#d8e8ff;--editor-stage-sheen:#42b4ff17;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}.squircle-editor-topbar{justify-content:space-between;align-items:end;gap:18px;padding-bottom:14px;display:flex}.squircle-editor-topbar h1{letter-spacing:0;margin:0;font-size:clamp(28px,5vw,48px)}.squircle-editor-topbar p{color:var(--editor-muted);margin:4px 0 0;font-size:14px;font-weight:650}.topbar-actions{flex-wrap:wrap;justify-content:flex-end;gap:8px;display:flex}.squircle-editor button{border:1px solid var(--editor-line-strong);background:var(--editor-control-bg);min-height:34px;color:var(--editor-control-text);cursor:pointer;border-radius:6px;font-weight:800}.squircle-editor button:hover{border-color:color-mix(in srgb, var(--editor-accent) 52%, transparent);color:var(--editor-active-text)}.squircle-editor button svg{fill:none;stroke:currentColor;stroke-width:1.8px;stroke-linecap:round;stroke-linejoin:round;width:17px;height:17px}.icon-button{place-items:center;width:34px;min-width:34px;padding:0;display:inline-grid}.primary-action{border-color:color-mix(in srgb, var(--editor-accent) 62%, var(--editor-line-strong));background:linear-gradient(180deg, color-mix(in srgb, var(--editor-control-bg) 90%, var(--editor-accent)), var(--editor-control-bg));color:var(--editor-active-text);box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--editor-accent) 16%, transparent)}.danger{color:var(--editor-danger)}.theme-switch{border:1px solid var(--editor-line);background:var(--editor-panel-soft);border-radius:8px;gap:2px;padding:3px;display:inline-flex}.theme-switch button{min-width:68px;min-height:28px;color:var(--editor-muted);background:0 0;border-color:#0000}.theme-switch button[aria-pressed=true]{border-color:color-mix(in srgb, var(--editor-accent) 48%, var(--editor-line));color:var(--editor-active-text);background:linear-gradient(180deg, color-mix(in srgb, var(--editor-control-bg) 92%, var(--editor-accent)), var(--editor-panel-soft));box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--editor-accent) 16%, transparent), 0 1px 5px #0f141a1f}.squircle-editor-workspace{border:1px solid var(--editor-line);background:linear-gradient(145deg, var(--editor-stage-sheen), transparent 48%), var(--editor-panel);border-radius:8px;grid-template-columns:minmax(250px,300px) minmax(520px,1fr) minmax(300px,380px);min-height:0;display:grid;overflow:hidden}@media (width>=1061px){.squircle-editor{height:100vh;overflow:hidden}}.side-panel{min-width:0;padding:14px;overflow:auto}.layers-panel{align-content:start;gap:14px;display:grid}.side-panel:first-child{border-right:1px solid var(--editor-line)}.inspector-panel{border-left:1px solid var(--editor-line)}.panel-heading{justify-content:space-between;align-items:center;gap:10px;display:flex}.panel-heading h2{margin:0;font-size:15px}.panel-heading span{color:var(--editor-muted);margin-top:2px;font-size:12px;font-weight:850;display:block}.panel-actions{justify-content:flex-end;gap:6px;display:flex}.squircle-editor-layer-list{gap:9px;display:grid}.squircle-editor-layer-row{background:0 0;border:1px solid #0000;border-radius:8px;grid-template-columns:minmax(0,1fr) auto;align-items:stretch;gap:7px;padding:3px;transition:border-color .17s,background .17s,box-shadow .17s;display:grid}.layer-select{text-align:left;background:0 0;border-color:#0000;justify-items:start;gap:8px;min-width:0;padding:10px;display:grid}.layer-select strong{font-size:13px}.layer-card-topline,.layer-card-meta{align-items:center;width:100%;min-width:0;display:flex}.layer-card-topline{gap:7px}.layer-card-meta{justify-content:space-between;gap:8px}.layer-number{background:var(--editor-panel-soft);width:25px;min-width:25px;height:22px;color:var(--editor-faint);border-radius:5px;justify-content:center;align-items:center;font-size:11px;font-weight:900;display:inline-flex}.material-pill{border:1px solid var(--editor-line);color:var(--editor-muted);text-transform:uppercase;border-radius:999px;margin-left:auto;padding:3px 6px;font-size:10px;font-weight:900}.material-pill-solid{border-color:color-mix(in srgb, var(--editor-accent) 40%, var(--editor-line));color:var(--editor-active-text)}.material-pill-transparent{border-color:color-mix(in srgb, var(--editor-accent-2) 32%, var(--editor-line));color:color-mix(in srgb, var(--editor-active-text) 72%, var(--editor-accent-2))}.layer-palette-chip,.layer-feature-tags{min-width:0;color:var(--editor-muted);align-items:center;font-size:11px;font-weight:820;display:inline-flex}.layer-palette-chip{gap:6px}.layer-feature-tags{flex-wrap:wrap;justify-content:flex-end;gap:4px}.layer-feature-tags span{border:1px solid var(--editor-line);background:var(--editor-panel-muted);border-radius:999px;padding:2px 5px;font-size:10px}.layer-select span{color:var(--editor-muted)}.squircle-editor-layer-row.is-active{border-color:color-mix(in srgb, var(--editor-accent) 68%, var(--editor-line));background:linear-gradient(90deg, var(--editor-accent-soft), transparent 65%), var(--editor-panel-soft);box-shadow:0 0 0 2px color-mix(in srgb, var(--editor-accent) 13%, transparent), inset 4px 0 0 color-mix(in srgb, var(--editor-accent) 76%, var(--editor-accent-2));color:var(--editor-active-text)}.squircle-editor-layer-row.is-active .layer-number{background:color-mix(in srgb, var(--editor-accent) 18%, var(--editor-control-bg));color:var(--editor-active-text)}.layer-visibility{min-height:100%;color:var(--editor-muted);align-self:stretch}.layer-visibility[aria-pressed=false]{color:var(--editor-faint);opacity:.66}.squircle-editor-preview{grid-template-rows:auto minmax(320px,1fr) auto;gap:12px;min-width:0;min-height:0;padding:18px;display:grid}.preview-toolbar,.preview-actions{justify-content:space-between;align-items:center;gap:10px;display:flex}.preview-toolbar h2{margin:0;font-size:14px}.preview-toolbar span{color:var(--editor-muted);margin-top:2px;font-size:12px;font-weight:820;display:block}.preview-actions{justify-content:flex-end}.code-toggle-button{align-items:center;gap:7px;padding:0 10px;display:inline-flex}.code-toggle-button[aria-pressed=true]{border-color:color-mix(in srgb, var(--editor-accent) 56%, var(--editor-line));background:var(--editor-accent-soft);color:var(--editor-active-text)}.preview-stage{border:1px solid color-mix(in srgb, var(--editor-line) 74%, transparent);background:linear-gradient(180deg, color-mix(in srgb, var(--editor-panel-soft) 72%, transparent), transparent 56%), color-mix(in srgb, var(--editor-panel-muted) 84%, transparent);border-radius:8px;place-items:center;min-height:0;display:grid;overflow:hidden}.preview-stage .squircle-scene{place-self:center;max-width:660px;padding:16px}.code-panel{border:1px solid var(--editor-line);background:var(--editor-code-bg);border-radius:6px;grid-template-rows:auto;min-width:0;display:grid;overflow:hidden}.code-panel.is-open{grid-template-rows:auto minmax(120px,220px)}.code-panel-header{border-bottom:1px solid var(--editor-line);justify-content:space-between;align-items:center;gap:12px;padding:9px 10px;display:flex}.code-panel-header h2{color:var(--editor-control-text);letter-spacing:0;margin:0;font-size:13px}.code-panel-header p{color:var(--editor-muted);margin:1px 0 0;font-size:11px;font-weight:760}.copy-code-button{place-items:center;width:34px;min-width:34px;padding:0;display:inline-grid;position:relative}.copy-status{border:1px solid var(--editor-line);background:var(--editor-control-bg);color:var(--editor-active-text);opacity:0;pointer-events:none;border-radius:5px;padding:3px 6px;font-size:11px;font-weight:850;transition:opacity .16s,transform .16s;position:absolute;bottom:calc(100% + 5px);right:0;transform:translateY(2px)}.copy-status:not(:empty){opacity:1;transform:translateY(0)}.code-output{min-width:0;color:var(--editor-code-text);margin:0;padding:12px;font-size:11px;line-height:1.45;overflow:auto}.code-output code{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace}.editor-section{border-top:1px solid var(--editor-line);gap:10px;padding:12px 0;display:grid}.editor-section:first-of-type{border-top:0;padding-top:0}.editor-section h3{color:var(--editor-muted);letter-spacing:0;text-transform:uppercase;margin:0;font-size:12px}.inspector-heading{border-bottom:1px solid var(--editor-line);justify-content:space-between;align-items:start;gap:12px;padding-bottom:12px;display:flex}.inspector-heading h2{margin:1px 0 0;font-size:18px}.inspector-heading p{color:var(--editor-muted);margin:4px 0 0;font-size:12px;font-weight:760}.inspector-kicker{color:var(--editor-faint);text-transform:uppercase;font-size:11px;font-weight:900}.state-switch{border:1px solid var(--editor-line);background:var(--editor-panel-soft);border-radius:8px;grid-template-columns:repeat(2,minmax(0,1fr));gap:4px;margin:12px 0 2px;padding:4px;display:grid}.state-switch button{min-height:34px;color:var(--editor-muted);background:0 0;border-color:#0000;position:relative}.state-switch button[aria-pressed=true]{border-color:color-mix(in srgb, var(--editor-accent) 58%, var(--editor-line));background:linear-gradient(180deg, color-mix(in srgb, var(--editor-control-bg) 90%, var(--editor-accent)), var(--editor-control-bg));color:var(--editor-active-text);box-shadow:0 1px 5px #0f141a1a}.state-dot{background:var(--editor-accent);border-radius:50%;width:6px;height:6px;position:absolute;top:7px;right:9px}.editor-section-details{gap:0}.editor-section-details summary{cursor:pointer;justify-content:space-between;align-items:center;list-style:none;display:flex}.editor-section-details summary::-webkit-details-marker{display:none}.editor-section-details summary:after{content:"+";color:var(--editor-muted);font-size:16px;font-weight:850}.editor-section-details[open] summary:after{content:"-"}.editor-section-body{gap:10px;padding-top:10px;display:grid}.field{color:var(--editor-muted);gap:5px;font-size:12px;font-weight:800;display:grid}.variant-controls{gap:11px;display:grid}.feature-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:7px;display:grid}.feature-switch{text-align:left;justify-content:space-between;align-items:center;gap:8px;min-width:0;min-height:42px;padding:0 10px;display:flex}.feature-switch[aria-pressed=true]{border-color:color-mix(in srgb, var(--editor-accent) 62%, var(--editor-line));background:linear-gradient(90deg, var(--editor-accent-soft), var(--editor-control-bg) 72%), var(--editor-control-bg);color:var(--editor-active-text);box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--editor-accent) 12%, transparent)}.feature-switch-label{font-size:13px;font-weight:900}.feature-switch-state{color:var(--editor-muted);text-transform:uppercase;font-size:11px;font-weight:900}.nested-fields{border:1px solid color-mix(in srgb, var(--editor-line) 82%, transparent);background:var(--editor-panel-muted);border-radius:8px;gap:9px;padding:10px;display:grid}.field input[type=text],.field input[type=range]{width:100%}.field input[type=text]{border:1px solid var(--editor-line-strong);background:var(--editor-control-bg);min-height:34px;color:var(--editor-control-text);border-radius:6px;padding:0 8px}.segmented-field{border:1px solid var(--editor-line);background:var(--editor-panel-soft);border-radius:8px;grid-template-columns:repeat(auto-fit,minmax(68px,1fr));gap:3px;padding:3px;display:grid}.segmented-field button{min-width:0;min-height:30px;color:var(--editor-muted);background:0 0;border-color:#0000;padding:0 7px;font-size:12px}.segmented-field button[aria-pressed=true]{border-color:color-mix(in srgb, var(--editor-accent) 54%, var(--editor-line));background:linear-gradient(180deg, color-mix(in srgb, var(--editor-control-bg) 90%, var(--editor-accent)), var(--editor-control-bg));color:var(--editor-active-text);box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--editor-accent) 14%, transparent), 0 1px 4px #0f141a1a}.palette-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;display:grid}.palette-grid button{min-width:0;min-height:32px;color:var(--editor-muted);grid-template-columns:auto 1fr;align-items:center;gap:5px;padding:0 6px;font-size:12px;display:grid}.palette-grid button[aria-pressed=true]{border-color:color-mix(in srgb, var(--editor-accent) 68%, var(--editor-line));background:linear-gradient(90deg, var(--editor-accent-soft), var(--editor-control-bg) 62%), var(--editor-control-bg);color:var(--editor-active-text);box-shadow:0 0 0 2px color-mix(in srgb, var(--editor-accent) 12%, transparent)}.palette-swatch{border:1px solid var(--editor-line);border-radius:4px;width:16px;height:16px}.forced-token{border:1px dashed var(--editor-line-strong);background:var(--editor-panel-muted);min-height:32px;color:var(--editor-muted);border-radius:6px;align-items:center;padding:0 9px;font-size:12px;font-weight:850;display:flex}.empty-note{color:var(--editor-muted);align-content:start;gap:12px;margin:0;font-size:13px;font-weight:700;display:grid}.empty-note h2{color:var(--editor-text);margin:0;font-size:16px}.empty-note .primary-action{width:max-content;padding:0 12px}@media (width<=1060px){.squircle-editor-workspace{grid-template-columns:1fr}.side-panel:first-child,.inspector-panel{border:0;border-bottom:1px solid var(--editor-line)}}@media (width<=700px){.squircle-editor{max-width:100%;overflow-x:hidden}.squircle-editor-topbar{flex-direction:column;align-items:stretch}.topbar-title{min-width:0}.squircle-editor-topbar p{overflow-wrap:anywhere}.topbar-actions{justify-content:stretch}.topbar-actions button{flex:1}.squircle-editor-workspace,.side-panel,.squircle-editor-preview,.preview-stage,.code-panel{width:100%;min-width:0;max-width:100%}.squircle-editor-layer-row{grid-template-columns:minmax(0,1fr);padding-right:76px;position:relative}.layer-visibility{z-index:2;height:34px;min-height:34px;position:absolute;top:auto;bottom:9px;right:46px}.layer-select{overflow:hidden}.layer-card-meta{flex-direction:column;align-items:flex-start}.preview-toolbar{flex-direction:column;align-items:stretch}.preview-actions{justify-content:stretch}.code-toggle-button{flex:1;justify-content:center}.squircle-editor-preview{padding:10px}.preview-stage{min-height:320px}.preview-stage .squircle-scene{width:min(92%,360px);max-width:360px;padding:8px}.palette-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.feature-grid{grid-template-columns:1fr}}
2
2
  /*$vite$:1*/
package/docs/README.md CHANGED
@@ -1,26 +1,31 @@
1
1
  # Documentation Index
2
2
 
3
- This repo is primarily about the reusable Squircle React component in `src/squircle`. Static HTML pages remain important as design fixtures and regression references, but new product work should start with the component docs.
3
+ This repo is primarily about the reusable Squircle React component in `src/squircle`. The root HTML files are React-backed examples that mount `src/pages` entrypoints; legacy static snapshots are kept only in the ignored `legacy-static/` archive.
4
4
 
5
5
  ## Start Here
6
6
 
7
7
  | Need | Read |
8
8
  | --- | --- |
9
- | Use the React renderer or editor | [React Component Guide](./react/README.md) |
9
+ | Use the main React renderer | [React Component Guide](./react/README.md) |
10
+ | Use or maintain the constructor editor | [Editor Guide](./react/editor.md) |
10
11
  | Change geometry, gradients, labels, wireframes, or colors | [Design Documentation](./design/README.md) |
11
- | Maintain the static HTML fixtures | [Static Prototype Documentation](./static/README.md) |
12
+ | Maintain the local React examples | [React Example Pages](./examples/README.md) |
13
+ | Compare against old static snapshots | [Legacy Static Snapshots](./static/README.md) |
12
14
  | Check work before handoff | [Verification Checklist](./verification.md) |
13
15
 
14
16
  ## Agent Routing
15
17
 
16
- - Building an app or exporting a reusable artifact: use `src/squircle` and [react/README.md](./react/README.md).
18
+ - Building a renderer integration or reusable scene artifact: use `src/squircle` and [react/README.md](./react/README.md).
19
+ - Building or changing the constructor/editor: read [react/editor.md](./react/editor.md).
17
20
  - Adjusting the squircle's look: read [design/geometry.md](./design/geometry.md), [design/rendering.md](./design/rendering.md), and [design/colors.md](./design/colors.md).
18
- - Updating `index.html`, `demo.html`, or `constructor.html`: read the matching file in [static/](./static/README.md), then verify against the design docs.
21
+ - Updating `index.html`, `demo.html`, `constructor.html`, or `react.html`: keep the HTML shell thin, edit the matching React entrypoint in `src/pages`, then verify against [examples/README.md](./examples/README.md).
19
22
  - Adding palettes or annotations: update both the React constants and the design docs so future agents do not invent one-off colors.
20
23
 
21
24
  ## Important Boundaries
22
25
 
23
- - React component docs describe public TypeScript APIs and reusable configuration.
26
+ - React component docs describe the main renderer TypeScript API and reusable scene configuration.
27
+ - Editor docs describe the optional constructor UI and code-export workflow.
24
28
  - Design docs describe visual truth: math, gradients, label paths, wireframe paint, and state constructors.
25
- - Static docs describe the legacy/prototype HTML pages and their CSS/DOM contracts.
29
+ - Example docs describe the current React page contracts.
30
+ - Static docs describe ignored legacy snapshots only; they are not the source of truth.
26
31
  - Verification docs describe what must still be true after edits.
@@ -76,41 +76,41 @@ Variant classes may also define fallback ghost colors, but those must remain vis
76
76
 
77
77
  ## Hover Palettes
78
78
 
79
- `constructor.html` and `demo.html` can use any palette id as a layer's `hoverPalette`. The base variant keeps `.v${palette}` or inherits the selected page variant, and the hover variant receives `.v${hoverPalette}`. This means hover color changes use the same documented gradients, label contrast, and wireframe stroke colors as normal rendering.
79
+ React examples and generated code can use any palette id as a layer's hover `paletteId`. The base variant keeps its normal palette, and the hover variant receives its own `paletteId`. This means hover color changes use the same documented gradients, label contrast, and wireframe stroke colors as normal rendering.
80
80
 
81
- Do not implement hover color with filters, opacity hacks, or untracked ad hoc colors. Hover color is a normal palette swap. `hoverPalette` may be the same id as the base `palette`; that is useful when hover should change only material while preserving color. If both hover state and hover palette match the base state and palette, the result is a deliberate no-op and should still be exported exactly, but it should not render or crossfade a hover variant.
81
+ Do not implement hover color with filters, opacity hacks, or untracked ad hoc colors. Hover color is a normal palette swap. Hover `paletteId` may be the same id as the base `paletteId`; that is useful when hover should change only material while preserving color. If the resolved hover variant matches the base variant, `SquircleScene` should not render or crossfade a hover copy.
82
82
 
83
83
  ## Annotation Auto And Overrides
84
84
 
85
- Static pages and constructor layers with `gpuColor: "contrast"` and `dashColor: "contrast"` use the palette's automatic annotation colors. React configs use `textColor: "contrast"` for the same concept. Solid and transparent top-plane annotations use `--label-fill`. This applies to both the filled text path and the solid dashed `#top-inlay`, so default text + dash states keep one automatic contrast color.
85
+ React layers with `textColor: "contrast"` and `dashColor: "contrast"` use the palette's automatic annotation colors. Solid and transparent top-plane annotations use the palette `labelFill`. This applies to both filled text and solid dashed inlays, so default text + dash states keep one automatic contrast color.
86
86
 
87
87
  - Darker top gradients may use a near-white label. Current example: `15 Alpha` uses `#f7fbff`.
88
88
  - Lighter top gradients should use dark in-family annotation colors.
89
89
  - Do not add a second outline copy for automatic contrast.
90
90
 
91
- Wireframe text labels ignore `--label-fill` and use gradient wire paint:
91
+ Wireframe text labels ignore fixed label paint and use gradient wire paint:
92
92
 
93
93
  ```css
94
94
  fill: none !important;
95
- stroke: var(--top-fill);
95
+ stroke: url("#...text-wire...");
96
96
  stroke-width: var(--label-wire-width);
97
97
  ```
98
98
 
99
- `#top-inlay-wire` also uses `stroke: var(--top-fill)`.
99
+ Wireframe inlays use the top-face gradient.
100
100
 
101
- In React, `textStyle: "wireframe"` outlines the same live SVG text element used by filled mode. On solid/transparent material, outline paint comes from `textColor`; `contrast` resolves to `--label-fill`, matching filled text and solid dash. On wireframe material, outline paint comes from label-local `textWire` gradients; do not sample the full-face `--top-fill` ramp there, and do not replace the text with single-stroke lettering. The static constructor keeps `gpuStyle`, `gpuColor`, and `#gpu-wire-*` names only for its GPU example schema.
101
+ In React, `textStyle: "wireframe"` outlines the same live SVG text element used by filled mode. On solid/transparent material, outline paint comes from `textColor`; `contrast` resolves to `labelFill`, matching filled text and solid dash. On wireframe material, outline paint comes from label-local `textWire` gradients; do not sample the full-face top ramp there, and do not replace the text with single-stroke lettering.
102
102
 
103
- `constructor.html` adds explicit annotation overrides:
103
+ `SquircleVariantConfig` supports explicit annotation overrides:
104
104
 
105
105
  | Field | Values | Paint |
106
106
  | --- | --- | --- |
107
- | `gpuColor` | `contrast`, `white`, `black` | Filled or outlined GPU paint for solid/transparent material; `contrast` appears as `Auto` in the UI |
108
- | `gpuStyle` | `solid`, `wireframe` | Filled or outlined GPU label |
107
+ | `textColor` | `contrast`, `white`, `black` | Filled or outlined text paint for solid/transparent material; `contrast` appears as `Auto` in the UI |
108
+ | `textStyle` | `solid`, `wireframe` | Filled or outlined text label |
109
109
  | `dashColor` | `contrast`, `white`, `black` | Dash stroke for solid/transparent material; `contrast` appears as `Auto` in the UI |
110
110
 
111
- Use `Auto` when the annotation should follow the palette. The exported value is `contrast`. Use `white` or `black` only when the user deliberately wants fixed annotation paint. React generated code emits `textColor`; the static constructor stores `gpuColor`. On wireframe material, dash always uses `--top-fill`; text uses the text surface gradient at full opacity when `textStyle` is `solid` and the text wire gradient when `textStyle` is `wireframe`. Dash symbol selection is not configurable in the constructor; it follows the squircle material.
111
+ Use `Auto` when the annotation should follow the palette. The exported value is `contrast`. Use `white` or `black` only when the user deliberately wants fixed annotation paint. On wireframe material, dash always uses the top gradient; text uses the text surface gradient at full opacity when `textStyle` is `solid` and the text wire gradient when `textStyle` is `wireframe`.
112
112
 
113
- Legacy configs with `gpuStyle: "transparent"` normalize to `solid`; there is no third text paint control.
113
+ Deprecated `gpuColor` and `gpuStyle` aliases are accepted only for older snippets. Legacy configs with `gpuStyle: "transparent"` normalize to `solid`; there is no third text paint control.
114
114
 
115
115
  ## Edge Colors
116
116
 
@@ -89,25 +89,26 @@ Rotate the sampled array if needed so the selected front-facing indices are emit
89
89
 
90
90
  The complementary back-bottom edge is emitted as `ghost-hidden` and is only shown in wireframe mode.
91
91
 
92
- ## Generated SVG Blocks
92
+ ## Generated React Geometry
93
93
 
94
- The generator emits:
94
+ `src/squircle/geometry.ts` returns the geometry consumed by `SquircleScene`:
95
95
 
96
- - `#prism-ghost` with `ghost-hidden`, `ghost-side`, and `ghost-top`
97
- - `#prism-active` with `active-side` and `active-top`
98
- - `#top-inlay` and `#top-inlay-wire`
99
- - `#label-gpu`, documented separately in [rendering.md](./rendering.md)
96
+ - `topPoints`: lit top-face polygon.
97
+ - `wallPoints`: one continuous front side-wall polygon.
98
+ - `hiddenPoints`: back/bottom edge used only in wireframe mode.
99
+ - `inlayPoints`: top-plane dashed squircle polygon.
100
+ - `labelTransform`: `matrix(cosA, sinA, -cosA, sinA, cx, cy - h)` for live SVG text.
101
+ - `topBounds` and `sideBounds`: gradient coordinate boxes.
100
102
 
101
103
  ## Regeneration Checklist
102
104
 
103
- When changing geometry, regenerate the polygon coordinates from the formulas above and keep these in sync:
105
+ When changing geometry, update the generator and keep these in sync:
104
106
 
105
- - The generated comment in `index.html`
106
- - All SVG `viewBox` values
107
- - `.hero-squircle`, `.mini-squircle`, and `.single-squircle` aspect ratios in `public/static/styles.css`
108
- - Both generated inlay copies (`#top-inlay` and `#top-inlay-wire`)
109
- - Every `.plane-label` transform if `cosA`, `sinA`, `cx`, or `cy - h` changes
110
- - Gradient bboxes documented in [rendering.md](./rendering.md)
107
+ - `DEFAULT_GEOMETRY` in `src/squircle/geometry.ts`.
108
+ - `SquircleScene` gradient definitions in `src/squircle/SquircleScene.tsx`.
109
+ - Example dimensions in `src/pages`.
110
+ - Docs in [rendering.md](./rendering.md) and [single-squircle-states.md](./single-squircle-states.md).
111
+ - Legacy snapshots only if you intentionally refresh `legacy-static/`.
111
112
 
112
113
  For small visual changes:
113
114
 
@@ -42,16 +42,15 @@ The top face is drawn after the side wall, so it hides the back half of the extr
42
42
 
43
43
  The middle-layer dashed squircle is generated from the same superellipse at `0.6 * a` and projected at `z = h`, so it lies on the top plane. The static copies are:
44
44
 
45
- - `#top-inlay` for solid/transparent rendering
46
- - `#top-inlay-wire` for wireframe rendering
45
+ `SquircleScene` renders the dashed inlay as the same top-plane polygon in every material. Paint changes by material:
47
46
 
48
- By default, solid and transparent inlays use `stroke: var(--label-fill)`, the same contrast token as the filled text label. Wireframe inlays use `stroke: var(--top-fill)`, the same gradient token as the wireframe text outline.
47
+ By default, solid and transparent inlays use the palette label color, the same contrast token as filled text. Wireframe inlays use the top-face gradient, matching the prism wires.
49
48
 
50
- `constructor.html` can override dash color per layer. `dashColor: "contrast"` keeps the default token for the material's dash symbol, while `dashColor: "white"` and `dashColor: "black"` intentionally use fixed stroke colors. Dash symbol choice follows material: solid and transparent states use `#top-inlay`; wireframe states use `#top-inlay-wire`.
49
+ `dashColor: "contrast"` keeps the material default. `dashColor: "white"` and `dashColor: "black"` intentionally use fixed stroke colors on solid/transparent material. Wireframe material ignores fixed dash color and uses the wire gradient.
51
50
 
52
51
  ## Label Source
53
52
 
54
- React renders top-plane text as one live SVG `<text>` element, so component configs can pass arbitrary strings such as `text: "GPU"`, `text: "CUDA"`, or `text: "AI"`. The static fixtures still store the current `GPU` demo glyph as `#label-gpu` in `<defs>` because those pages are fixed examples.
53
+ React renders top-plane text as one live SVG `<text>` element, so component configs can pass arbitrary strings such as `text: "GPU"`, `text: "CUDA"`, or `text: "AI"`. `GPU` is only the example string used by the local demos.
55
54
 
56
55
  Current label parameters:
57
56
 
@@ -62,7 +61,7 @@ Current label parameters:
62
61
  - Centered bbox: `x = -63.0293..63.0293`, `y = -22.9473..22.9473`
63
62
  - Label wire stroke: `1.1`
64
63
 
65
- For static fixture glyphs, generate the path with `opentype.js@2.0.0`:
64
+ The old static snapshots used an outlined glyph path generated with `opentype.js@2.0.0`. Do not use that path technique in new component work; keep live text so arbitrary strings render correctly.
66
65
 
67
66
  ```js
68
67
  const font = opentype.parse(fs.readFileSync("/System/Library/Fonts/Supplemental/Arial.ttf").buffer);
@@ -72,11 +71,11 @@ const tx = -((box.x1 + box.x2) / 2);
72
71
  const ty = -((box.y1 + box.y2) / 2);
73
72
  ```
74
73
 
75
- Apply `tx` and `ty` to every path command coordinate before writing the static `d` attribute. Keep `fill-rule="evenodd"` and `clip-rule="evenodd"` on the path so counters, such as the hole inside `P`, remain true holes.
74
+ If a legacy snapshot ever needs refreshing, apply `tx` and `ty` to every path command coordinate before writing the static `d` attribute, and keep `fill-rule="evenodd"` plus `clip-rule="evenodd"` so counters remain true holes.
76
75
 
77
76
  ## Label Transform And Paint
78
77
 
79
- Every visible static label is a single `<use>` of `#label-gpu`, and every React label is a single `<text>` element. Both are transformed onto the top plane with:
78
+ Every React label is a single `<text>` element transformed onto the top plane with:
80
79
 
81
80
  ```text
82
81
  matrix(cosA, sinA, -cosA, sinA, cx, cy - h)
@@ -90,27 +89,27 @@ matrix(0.94, 0.342, -0.94, 0.342, 400, 145.6)
90
89
 
91
90
  The transform never changes between solid and wireframe modes; only paint changes. React and constructor defaults intentionally mirror the original generated GPU path: `textSize: 62`, `textFontFamily: "Arial, Helvetica, sans-serif"`, and `textFontWeight: 400`.
92
91
 
93
- Solid label style:
92
+ Solid text style:
94
93
 
95
- - `.plane-label` uses `fill: var(--label-fill)` and `stroke: none` when `textColor` is `contrast`.
96
- - `#top-inlay` uses `stroke: var(--label-fill)` so dashed inlays match the filled label color.
94
+ - The label uses palette label paint when `textColor` is `contrast`.
95
+ - Solid/transparent dash uses the same palette label paint by default.
97
96
  - `15 Alpha` uses `#f7fbff` because its top gradient is dark.
98
97
  - Lighter variants use dark in-family label fills.
99
98
 
100
- Wireframe label style:
99
+ Wireframe text style:
101
100
 
102
101
  - `textStyle: "wireframe"` uses the same live text element as filled mode.
103
- - It sets `fill:none !important` and uses `stroke: var(--gpu-wire-stroke)`.
104
- - On solid/transparent material, `--gpu-wire-stroke` comes from `textColor`; `contrast` resolves to `--label-fill`, matching filled text and solid dash.
105
- - On wireframe material, `--gpu-wire-stroke` resolves to the label-local `--gpu-wire-fill` gradient.
106
- - `#top-inlay-wire` also uses `stroke: var(--top-fill)` so dashed inlays match the wireframe label and prism wires.
102
+ - It sets `fill: none` and uses a stroke.
103
+ - On solid/transparent material, outline paint comes from `textColor`; `contrast` resolves to the palette label paint, matching filled text and solid dash.
104
+ - On wireframe material, outline paint resolves to the label-local `textWire` gradient.
105
+ - Wireframe dash uses the top-face gradient so dashed inlays match prism wires.
107
106
  - It must remain an outline around the font figures, not a single-stroke lettering replacement.
108
107
  - Keep the outline stroke thin relative to text size. The default `labelWire` is `1.1` at `textSize: 62`, safely below the ratio where counters start merging.
109
108
 
110
- Do not sample the full-face `--top-fill` ramp directly for the text outline on wireframe material; it is too large for the label geometry and makes the color read wrong. Use one text element for all text styles. Use `textColor` for solid/transparent outline paint and the label-local wire gradient for wireframe-material outline paint.
109
+ Do not sample the full-face top ramp directly for the text outline on wireframe material; it is too large for the label geometry and makes the color read wrong. Use one text element for all text styles. Use `textColor` for solid/transparent outline paint and the label-local wire gradient for wireframe-material outline paint.
111
110
 
112
- Constructor `gpuColor: "white"` and `gpuColor: "black"` remain in the static GPU example schema and map to React `textColor` in generated code. They override label paint only on solid/transparent material. On wireframe material, React `textStyle: "solid"` renders a fully opaque filled label using the text surface gradient, and `textStyle: "wireframe"` renders an outlined label using the text wire gradient.
111
+ Deprecated `gpuColor` aliases are accepted only for old snippets and map to React `textColor`. They override label paint only on solid/transparent material. On wireframe material, `textStyle: "solid"` renders a fully opaque filled label using the text surface gradient, and `textStyle: "wireframe"` renders an outlined label using the text wire gradient.
113
112
 
114
- Do not introduce `textStyle: "transparent"` as a third paint control. Legacy static configs with `gpuStyle: "transparent"` should normalize to `solid`.
113
+ Do not introduce `textStyle: "transparent"` as a third paint control. Legacy configs with `gpuStyle: "transparent"` should normalize to `solid`.
115
114
 
116
115
  Do not build filled letters from `<rect>`, `<line>`, or separate stem/bowl paths. Do not add a second label copy and do not replace the text with monoline lettering for outline mode.
@@ -1,121 +1,59 @@
1
1
  # Single-Squircle State Constructors
2
2
 
3
- This file documents the `Single Squircle States` drawer as reusable constructors. Use these recipes when another agent wants to compose a single squircle outside the full three-layer composition.
4
-
5
- ## Shared Setup
6
-
7
- Each single-state card uses:
8
-
9
- ```html
10
- <svg class="single-squircle" viewBox="-6 -6 812 314" aria-label="...">
11
- ...
12
- </svg>
3
+ This file describes reusable one-layer constructors for `SquircleScene`. Use these recipes when another agent wants one squircle outside a full multi-layer composition.
4
+
5
+ ## Base Layer Shape
6
+
7
+ ```ts
8
+ import type { SquircleLayerConfig } from "@dstackai/sqircle";
9
+
10
+ const layer: SquircleLayerConfig = {
11
+ id: "single",
12
+ base: {
13
+ material: "solid",
14
+ paletteId: "15",
15
+ text: "GPU",
16
+ textStyle: "solid",
17
+ dash: true
18
+ }
19
+ };
13
20
  ```
14
21
 
15
- The viewBox frames exactly one prism plus a small margin for hairline strokes. Single-state cards inherit the currently selected variant colors from `.single-drawer`, which receives the same CSS variables as `.hero-card`:
16
-
17
- - `--top-fill`
18
- - `--side-fill`
19
- - `--label-fill`
20
- - `--top-edge`
21
- - `--side-edge`
22
- - `--label-wire-width`
23
- - `--face-edge-width`
24
- - `--face-edge-opacity`
25
-
26
- The global transparent/wireframe switch does not affect this drawer. The drawer always shows all constructor states at once.
27
-
28
- ## Building Blocks
29
-
30
- | Part | SVG use | CSS class | Purpose |
31
- | --- | --- | --- | --- |
32
- | Solid prism | `<use href="#prism-active" />` | `.single-active` | Filled, selected-looking prism with top and side gradients |
33
- | Transparent prism | `<use href="#prism-ghost" />` | `.single-ghost` inside `.single-transparent` | Translucent filled prism |
34
- | Wireframe prism | `<use href="#prism-ghost" />` | `.single-ghost` inside `.single-wire` | Gradient outline with transparent faces |
35
- | Solid/transparent dash | `<use href="#top-inlay" />` | `.single-inlay` | Dashed top-plane squircle using `--label-fill` |
36
- | Wireframe dash | `<use href="#top-inlay-wire" />` | `.single-wire-inlay.wire-inlay` | Gradient dashed top-plane squircle |
37
- | Text label | `<use href="#label-gpu" />` | `.single-label.plane-label` | Compound path label on the top plane; the current static glyph spells `GPU` |
38
-
39
- The label always uses this transform:
40
-
41
- ```html
42
- transform="matrix(0.94,0.342,-0.94,0.342,400,145.6)"
43
- ```
22
+ `GPU` is only example text. Any string is valid, including `"{}"` or `"CUDA"`.
44
23
 
45
24
  ## Constructor Matrix
46
25
 
47
- | State | Card class | SVG children in order | Paint behavior |
48
- | --- | --- | --- | --- |
49
- | `Solid` | `.single-card` | `.single-active` | Filled prism using selected variant top/side gradients |
50
- | `Solid Text` | `.single-card` | `.single-active`, `.single-label.plane-label` | Solid prism plus filled contrast label |
51
- | `Solid Dash` | `.single-card` | `.single-active`, `.single-inlay` | Solid prism plus contrast dashed inlay |
52
- | `Solid Text + Dash` | `.single-card` | `.single-active`, `.single-inlay`, `.single-label.plane-label` | Solid prism plus dashed inlay and filled label |
53
- | `Transparent` | `.single-card.single-transparent` | `.single-ghost` | Translucent prism at `0.38` opacity |
54
- | `Transparent Text` | `.single-card.single-transparent` | `.single-ghost`, `.single-label.plane-label` | Translucent prism plus filled label at `0.62` opacity |
55
- | `Transparent Dash` | `.single-card.single-transparent` | `.single-ghost`, `.single-inlay` | Translucent prism plus contrast dashed inlay |
56
- | `Transparent Text + Dash` | `.single-card.single-transparent` | `.single-ghost`, `.single-inlay`, `.single-label.plane-label` | Translucent prism plus dashed inlay and filled label |
57
- | `Wireframe` | `.single-card.single-wire` | `.single-ghost` | Transparent faces, gradient wire strokes |
58
- | `Wireframe Text` | `.single-card.single-wire` | `.single-ghost`, `.single-label.plane-label` | Wireframe prism plus outlined gradient label |
59
- | `Wireframe Dash` | `.single-card.single-wire` | `.single-ghost`, `.single-wire-inlay.wire-inlay` | Wireframe prism plus gradient dashed inlay |
60
- | `Wireframe Text + Dash` | `.single-card.single-wire` | `.single-ghost`, `.single-wire-inlay.wire-inlay`, `.single-label.plane-label` | Wireframe prism plus gradient dashed inlay and gradient label |
61
-
62
- ## Copy-Paste Recipes
63
-
64
- Solid with text:
65
-
66
- ```html
67
- <svg class="single-squircle" viewBox="-6 -6 812 314" aria-label="Solid single squircle with text">
68
- <use class="single-active" href="#prism-active" />
69
- <use class="single-label plane-label" href="#label-gpu" transform="matrix(0.94,0.342,-0.94,0.342,400,145.6)" />
70
- </svg>
71
- ```
72
-
73
- Transparent with text and dash:
74
-
75
- ```html
76
- <svg class="single-squircle" viewBox="-6 -6 812 314" aria-label="Transparent single squircle with text and dashed inlay">
77
- <use class="single-ghost" href="#prism-ghost" />
78
- <use class="single-inlay" href="#top-inlay" />
79
- <use class="single-label plane-label" href="#label-gpu" transform="matrix(0.94,0.342,-0.94,0.342,400,145.6)" />
80
- </svg>
81
- ```
82
-
83
- Wireframe with text and dash:
84
-
85
- ```html
86
- <svg class="single-squircle" viewBox="-6 -6 812 314" aria-label="Wireframe single squircle with text and dashed inlay">
87
- <use class="single-ghost" href="#prism-ghost" />
88
- <use class="single-wire-inlay wire-inlay" href="#top-inlay-wire" />
89
- <use class="single-label plane-label" href="#label-gpu" transform="matrix(0.94,0.342,-0.94,0.342,400,145.6)" />
90
- </svg>
91
- ```
92
-
93
- The text + dash states are reusable variants that combine the top-plane text path with a dashed inlay.
94
-
95
- In the static single-state drawer, solid and transparent text + dash states use `--label-fill` for both the filled text path and dashed inlay. Wireframe text + dash states use `--top-fill` for both the outlined text path and dashed inlay. `constructor.html` can override GPU color, GPU style, and dash color per layer because it is the GPU example; React generated code maps those controls to `textColor`, `textStyle`, and `dashColor`.
26
+ | State | Config |
27
+ | --- | --- |
28
+ | Solid | `{ material: "solid" }` |
29
+ | Solid Text | `{ material: "solid", text: "GPU", textStyle: "solid" }` |
30
+ | Solid Text Outline | `{ material: "solid", text: "GPU", textStyle: "wireframe" }` |
31
+ | Solid Dash | `{ material: "solid", dash: true }` |
32
+ | Solid Text + Dash | `{ material: "solid", text: "GPU", textStyle: "solid", dash: true }` |
33
+ | Transparent | `{ material: "transparent" }` |
34
+ | Transparent Text | `{ material: "transparent", text: "GPU", textStyle: "solid" }` |
35
+ | Transparent Text Outline + Dash | `{ material: "transparent", text: "GPU", textStyle: "wireframe", dash: true }` |
36
+ | Wireframe | `{ material: "wireframe" }` |
37
+ | Wireframe Text Filled | `{ material: "wireframe", text: "GPU", textStyle: "solid" }` |
38
+ | Wireframe Text Outline | `{ material: "wireframe", text: "GPU", textStyle: "wireframe" }` |
39
+ | Wireframe Text Outline + Dash | `{ material: "wireframe", text: "GPU", textStyle: "wireframe", dash: true }` |
40
+
41
+ The local `index.html` example renders these through `createSingleStatePresets()` in `src/pages/exampleData.ts`.
42
+
43
+ ## Paint Rules
44
+
45
+ - Solid and transparent text + dash states use palette contrast paint by default.
46
+ - Solid/transparent `textColor` and `dashColor` may be `contrast`, `white`, or `black`.
47
+ - Wireframe dash always uses the wire gradient.
48
+ - Wireframe `textStyle: "wireframe"` uses the label-local text wire gradient.
49
+ - Wireframe `textStyle: "solid"` uses the text surface gradient at full opacity.
96
50
 
97
51
  ## Ordering Rules
98
52
 
99
- - Draw the prism first.
100
- - Draw the inlay after the prism.
101
- - Draw the label last.
102
- - Use `#top-inlay` for solid/transparent states.
103
- - Use `#top-inlay-wire` for wireframe states.
104
- - Do not use two label copies. The same `.plane-label` object changes paint through CSS.
53
+ `SquircleScene` draws a variant in this order:
105
54
 
106
- ## Color Inheritance
107
-
108
- The drawer must be a sibling after the hidden variant radios. The selected variant selectors target both `.hero-card` and `.single-drawer`, e.g.:
109
-
110
- ```css
111
- #variant-15:checked ~ .hero-card,
112
- #variant-15:checked ~ .single-drawer {
113
- --top-fill: url("#top-15");
114
- --side-fill: url("#side-15");
115
- --label-fill: #f7fbff;
116
- --top-edge: #7c5fd0;
117
- --side-edge: #2d1466;
118
- }
119
- ```
55
+ 1. Prism side/top or wireframe prism.
56
+ 2. Dashed inlay, when enabled.
57
+ 3. Live SVG text, when enabled.
120
58
 
121
- See [colors.md](./colors.md) for the full palette contract.
59
+ Do not create two text copies for filled and outline states. The same live `<text>` object changes only paint.
@@ -0,0 +1,33 @@
1
+ # React Example Pages
2
+
3
+ The root HTML files are Vite shells. They should stay small and only mount React entrypoints from `src/pages`.
4
+
5
+ | HTML | React entrypoint | Purpose |
6
+ | --- | --- | --- |
7
+ | `index.html` | `src/pages/IndexPage.tsx` | Hero scene plus selectable single-squircle state drawer |
8
+ | `demo.html` | `src/pages/DemoPage.tsx` | Selectable generated gallery of 3-layer compositions |
9
+ | `constructor.html` | `src/pages/ConstructorPage.tsx` | Full constructor UI using `SquircleEditor` |
10
+ | `react.html` | `src/pages/ConstructorPage.tsx` | Compatibility alias for the constructor |
11
+
12
+ ## Source Files
13
+
14
+ - `src/pages/exampleData.ts`: shared preset and seed-layer constructors.
15
+ - `src/pages/PageShell.tsx`: shared page chrome and theme switch.
16
+ - `src/pages/pages.css`: example-page layout styles.
17
+
18
+ The examples must consume `SquircleScene`, `SquircleEditor`, palettes, and helpers from `src/squircle`. Do not paste generated SVG polygons or duplicate renderer logic in page files.
19
+
20
+ ## Behavior
21
+
22
+ - `index.html` renders a main three-layer scene and a collapsed/openable drawer of single-squircle states. Palette buttons recolor the examples through React state.
23
+ - `demo.html` renders 96 generated composition presets. Clicking a card changes the main hero composition. Each layer hover is a state/color swap only.
24
+ - `constructor.html` renders `SquircleEditor` with three default plain wireframe layers. The Code panel exports React code using `@dstackai/sqircle` as the import path.
25
+ - `react.html` intentionally mirrors `constructor.html` for older links.
26
+
27
+ ## Rules
28
+
29
+ - Keep root HTML files as shells with one `#root` and one module script.
30
+ - Keep examples transparent-background friendly.
31
+ - Keep hover effects as opacity crossfades between base and hover variants. Do not add movement, shadows, scale, filters, or gap changes.
32
+ - Keep all squircle geometry, palette, stroke, annotation, and theme behavior in the reusable component layer.
33
+ - If a legacy static behavior is still valuable, migrate it into `src/pages` or `src/squircle`; do not edit ignored snapshots as source.