@livx.cc/bare-v3 0.1.0-alpha.52 → 0.1.0-alpha.57
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/README.md +1 -1
- package/dist/bare.css +80 -0
- package/dist/bare.min.css +1 -1
- package/dist/elements/markdown-stream.js +374 -0
- package/dist/manifest.json +24 -3
- package/dist/references/confirm.md +9 -0
- package/dist/references/markdown-stream-prompt.md +13 -0
- package/dist/references/markdown-stream.md +91 -0
- package/dist/references/segmented.md +9 -0
- package/dist/ui.js +82 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ The comparison UI needs the fixture server for example links. Model outputs repl
|
|
|
17
17
|
|
|
18
18
|
Install the published alpha with `bun add @livx.cc/bare-v3@alpha`; use `bun pm pack` for an unpublished local build. Import CSS from `@livx.cc/bare-v3/css` or `/css/min`. Put `class="b3-root"` on html and set `data-b3-theme="dark"` explicitly for dark mode. Component references are exported under `/references/*.md`; start with `/references/start.md`. Place generic link defaults in `@layer bare.reset` so they do not override button/navigation colors. Unlayered consumer CSS overrides library layers: scope custom palette colors to each theme rather than applying light-only colors unconditionally to `:root`.
|
|
19
19
|
|
|
20
|
-
`bun lab plan config.json` creates a seeded, capped development job plan. `bun lab prepare run.json` prepares one isolated workspace and manifest without invoking a model. The [visual playground](http://127.0.0.1:4313/fixtures/playground.html) demonstrates the current component contracts and shows the bare-v2 coverage audit. Composed apps include Signal (system performance), Elsewhere (local discovery/booking), Daylight (iOS/Android web studies), Relay, Gather setup and sign-in, Counter, Margin, Still, Fieldnotes, Interval, and the Forma project workspace. New components are an implemented subset, not full bare-v2 parity or a qualified release. Optional interactions are exported from `@livx.cc/bare-v3/ui`; call `mount(document)` once and use its returned cleanup on teardown. Genuinely hard interactive widgets ship separately as light-DOM custom elements — currently `@livx.cc/bare-v3/elements/image-viewer` (`<b3-image-viewer>`) and `/elements/date-picker` (`<b3-date-picker>`). Read `recipes/image-viewer.md` or `recipes/
|
|
20
|
+
`bun lab plan config.json` creates a seeded, capped development job plan. `bun lab prepare run.json` prepares one isolated workspace and manifest without invoking a model. The [visual playground](http://127.0.0.1:4313/fixtures/playground.html) demonstrates the current component contracts and shows the bare-v2 coverage audit. Composed apps include Signal (system performance), Elsewhere (local discovery/booking), Daylight (iOS/Android web studies), Relay, Gather setup and sign-in, Counter, Margin, Still, Fieldnotes, Interval, and the Forma project workspace. New components are an implemented subset, not full bare-v2 parity or a qualified release. Optional interactions are exported from `@livx.cc/bare-v3/ui`; call `mount(document)` once and use its returned cleanup on teardown. Genuinely hard interactive widgets ship separately as light-DOM custom elements — currently `@livx.cc/bare-v3/elements/image-viewer` (`<b3-image-viewer>`) and `/elements/date-picker` (`<b3-date-picker>`), plus the `/elements/markdown-stream` module (`MarkdownStream`) for streaming LLM markdown into bare-v3 components. Read `recipes/image-viewer.md`, `recipes/date-picker.md` or `recipes/markdown-stream.md` for markup and lifecycle; `fixtures/elements/markdown-stream.html` demos the stream; `bun run chat` serves it as a live chat against a real model (needs `OPENROUTER_API_KEY` or `OPENAI_API_KEY`; offline it replays canned replies). The plain/React/Vue demos in `fixtures/elements/` exercise the image viewer; run `bun scripts/verify-image-viewer.ts` through the authorized AgentX browser workflow to verify measured transforms.
|
|
21
21
|
|
|
22
22
|
Playground previews accept shareable app/width links, for example [Signal at 390px](http://127.0.0.1:4313/fixtures/playground.html?demo=metrics&width=390#demos). The controls keep these query parameters in sync; unknown values fall back to the authored defaults. This is an iframe layout preview, not mobile-device qualification.
|
|
23
23
|
|
package/dist/bare.css
CHANGED
|
@@ -511,6 +511,17 @@
|
|
|
511
511
|
display: inline-block;
|
|
512
512
|
}
|
|
513
513
|
|
|
514
|
+
[data-b3-confirm][data-b3-armed] {
|
|
515
|
+
outline: 2px solid var(--b3-color-danger) !important;
|
|
516
|
+
outline-offset: 2px !important;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.b3-button[data-b3-armed] {
|
|
520
|
+
background: var(--b3-color-danger);
|
|
521
|
+
color: var(--b3-color-canvas);
|
|
522
|
+
border-color: var(--b3-color-danger);
|
|
523
|
+
}
|
|
524
|
+
|
|
514
525
|
.b3-input:disabled {
|
|
515
526
|
opacity: .6;
|
|
516
527
|
cursor: not-allowed;
|
|
@@ -1042,6 +1053,75 @@
|
|
|
1042
1053
|
padding-block: var(--b3-space-4);
|
|
1043
1054
|
}
|
|
1044
1055
|
|
|
1056
|
+
.b3-segmented {
|
|
1057
|
+
border-radius: var(--b3-radius);
|
|
1058
|
+
background: color-mix(in srgb, var(--b3-color-text) 7%, var(--b3-color-canvas));
|
|
1059
|
+
border: 0;
|
|
1060
|
+
flex-wrap: wrap;
|
|
1061
|
+
gap: .25rem;
|
|
1062
|
+
min-inline-size: 0;
|
|
1063
|
+
margin: 0;
|
|
1064
|
+
padding: .25rem;
|
|
1065
|
+
display: flex;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
.b3-segmented > label {
|
|
1069
|
+
border-radius: calc(var(--b3-radius) - .25rem);
|
|
1070
|
+
min-block-size: 2.75rem;
|
|
1071
|
+
max-inline-size: 100%;
|
|
1072
|
+
color: var(--b3-color-muted);
|
|
1073
|
+
text-align: center;
|
|
1074
|
+
cursor: pointer;
|
|
1075
|
+
flex: 1 0 auto;
|
|
1076
|
+
justify-content: center;
|
|
1077
|
+
align-items: center;
|
|
1078
|
+
gap: .375rem;
|
|
1079
|
+
padding-inline: .875rem;
|
|
1080
|
+
font-weight: 600;
|
|
1081
|
+
display: inline-flex;
|
|
1082
|
+
position: relative;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
.b3-segmented > label > input {
|
|
1086
|
+
opacity: 0;
|
|
1087
|
+
block-size: 100%;
|
|
1088
|
+
inline-size: 100%;
|
|
1089
|
+
cursor: inherit;
|
|
1090
|
+
margin: 0;
|
|
1091
|
+
position: absolute;
|
|
1092
|
+
inset: 0;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
.b3-segmented > label:has(input:checked) {
|
|
1096
|
+
background: var(--b3-color-surface);
|
|
1097
|
+
color: var(--b3-color-text);
|
|
1098
|
+
box-shadow: 0 1px 2px #0000001f, 0 0 0 1px var(--b3-color-border);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
.b3-segmented > label:has(input:focus-visible) {
|
|
1102
|
+
box-shadow: inset 0 0 0 2px;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
@media (forced-colors: active) {
|
|
1106
|
+
.b3-segmented > label {
|
|
1107
|
+
border: 2px solid canvas;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
.b3-segmented > label:has(input:checked) {
|
|
1111
|
+
border-color: canvastext;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
.b3-segmented > label:has(input:focus-visible) {
|
|
1115
|
+
outline-offset: 1px;
|
|
1116
|
+
outline: 2px solid highlight;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
.b3-segmented > label:has(input:disabled) {
|
|
1121
|
+
opacity: .5;
|
|
1122
|
+
cursor: not-allowed;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1045
1125
|
.b3-accordion {
|
|
1046
1126
|
border-block-end: 1px solid var(--b3-color-border);
|
|
1047
1127
|
}
|
package/dist/bare.min.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@layer bare.reset{:where(.b3-root),:where(.b3-root *){box-sizing:border-box}:where(.b3-root){font-family:var(--b3-font-body);background:var(--b3-color-canvas);color:var(--b3-color-text);color-scheme:light;margin:0;font-size:1rem;line-height:1.5}:where(.b3-root>body){margin:0}:where(.b3-root) :where(button,input,select,textarea){font:inherit}:where(.b3-root) :where(h1,h2,h3,h4,h5,h6,p){margin:0}:where(.b3-root) :where(button,a,input,select,textarea,summary,[tabindex]):focus-visible{outline:none;box-shadow:inset 0 0 0 2px}@media (forced-colors:active){:where(.b3-root) :where(button,a,input,select,textarea,summary,[tabindex]):focus-visible{outline-offset:-2px;outline:1px solid highlight}}:where(.b3-root) :where(img,svg){max-inline-size:100%}@media (prefers-reduced-motion:reduce){:where(.b3-root *),:where(.b3-root *):before,:where(.b3-root *):after{transition:none!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}[data-b3-motion=reduce] *,[data-b3-motion=reduce] :before,[data-b3-motion=reduce] :after,[data-b3-motion=reduce] ::backdrop{scroll-behavior:auto!important;transition:none!important;animation:none!important}:where(.b3-root [hidden]):not([hidden=until-found]){display:none!important}:where(.b3-root) :where(input[type=checkbox],input[type=radio],input[type=range]){accent-color:var(--b3-color-accent)}}@layer bare.tokens{.b3-root{--b3-color-canvas:#f6f5f0;--b3-color-surface:#fff;--b3-color-text:#202923;--b3-color-muted:#59655d;--b3-color-border:#d5dbd3;--b3-color-accent:#245c42;--b3-color-on-accent:#fff;--b3-color-danger:#a62c35;--b3-color-focus:#276bdd;--b3-font-body:ui-sans-serif, system-ui, sans-serif;--b3-font-display:var(--b3-font-body);--b3-radius:.75rem;--b3-space:1rem;--b3-content:68rem}.b3-root[data-b3-theme=dark]{color-scheme:dark;--b3-color-canvas:#141c19;--b3-color-surface:#202c26;--b3-color-text:#edf3ec;--b3-color-muted:#a8b9ad;--b3-color-border:#415348;--b3-color-accent:#aadbb8;--b3-color-on-accent:#132b1b;--b3-color-danger:#ffadb1;--b3-color-focus:#9ac4ff}.b3-root{--b3-space-1:.25rem;--b3-space-2:.5rem;--b3-space-3:.75rem;--b3-space-4:1rem;--b3-space-6:1.5rem;--b3-space-8:2rem;--b3-space-12:3rem;--b3-radius-small:.375rem;--b3-radius-large:1.25rem;--b3-shadow:0 8px 32px #14231b14;--b3-shadow-raised:0 16px 56px #14231b24;--b3-color-success:#256443;--b3-color-warning:#805100;--b3-color-info:#275f99;--b3-duration:.16s}.b3-root[data-b3-theme=dark]{--b3-shadow:0 8px 32px #0004;--b3-shadow-raised:0 16px 56px #0007;--b3-color-success:#99d9ab;--b3-color-warning:#f2c76c;--b3-color-info:#a2cfff}}@layer bare.layout{.b3-shell{inline-size:min(100%, var(--b3-content));margin-inline:auto;padding:clamp(1rem,4vw,3rem)}.b3-stack{gap:var(--b3-space);flex-direction:column;min-inline-size:0;display:flex}.b3-stack:where(dialog:not([open])){display:none}.b3-scroll{overscroll-behavior:contain;scrollbar-gutter:stable;flex:auto;min-block-size:0;min-inline-size:0;overflow:auto}dialog.b3-stack>.b3-scroll{min-block-size:min(6rem,30dvh)}.b3-stack:has(>.b3-scroll)>:not(.b3-scroll){flex-shrink:0}.b3-cluster{align-items:center;gap:var(--b3-space);flex-wrap:wrap;min-inline-size:0;display:flex}.b3-cluster>*,.b3-split>*{min-inline-size:0}.b3-inline{vertical-align:middle;align-items:center;gap:.5em;min-inline-size:0;max-inline-size:100%;display:inline-flex}.b3-inline>*{min-inline-size:0}.b3-inline:has(>.b3-badge){flex-wrap:wrap}.b3-inline>.b3-badge{flex-shrink:0}.b3-inline:has(>.b3-badge)>:not(.b3-badge,.b3-icon,svg){flex:1 1 min-content}.b3-grid{grid-template-columns:repeat(auto-fit, minmax(min(100%, var(--b3-grid-min,17rem)), 1fr));gap:var(--b3-space);display:grid}.b3-split{justify-content:space-between;align-items:center;gap:var(--b3-space);flex-wrap:wrap;display:flex}.b3-page{flex-direction:column;min-block-size:100svh;display:flex}.b3-page-body{flex:1;min-inline-size:0}.b3-app-layout{grid-template-columns:minmax(12rem,16rem) minmax(0,1fr);min-block-size:100svh;display:grid}.b3-sidebar{padding:var(--b3-space-6);border-inline-end:1px solid var(--b3-color-border);background:var(--b3-color-surface);min-inline-size:0}.b3-center{padding:var(--b3-space-6);place-items:center;display:grid}.b3-cover{min-block-size:min(42rem,85svh);padding-block:var(--b3-space-12);flex-direction:column;justify-content:center;display:flex}.b3-reel{gap:var(--b3-space);scroll-snap-type:x proximity;padding-block-end:var(--b3-space-2);display:flex;overflow-x:auto}.b3-reel>*{scroll-snap-align:start;flex:0 0 min(85%,20rem)}@media (width<=48rem){.b3-app-layout{grid-template-columns:minmax(0,1fr)}.b3-sidebar{border-inline-end:0;border-block-end:1px solid var(--b3-color-border)}}.b3-reel:has(>.b3-badge){scrollbar-width:none;align-items:center;gap:.5rem;padding-block:.625rem}.b3-reel:has(>.b3-badge)::-webkit-scrollbar{display:none}.b3-reel>.b3-badge{flex:none}.b3-stack>.b3-reel{flex-shrink:0}@supports (animation-timeline:scroll(self inline)){.b3-reel:has(>.b3-badge){mask-image:linear-gradient(to right, #000 calc(100% - var(--b3-reel-fade)), #0000);animation:linear both b3-reel-fade scroll(self inline)}.b3-reel:has(>.b3-badge):dir(rtl){mask-image:linear-gradient(to left, #000 calc(100% - var(--b3-reel-fade)), #0000)}@keyframes b3-reel-fade{90%{--b3-reel-fade:2.5rem}to{--b3-reel-fade:0rem}}}}@layer bare.components{.b3-title{font-family:var(--b3-font-display);letter-spacing:-.04em;overflow-wrap:anywhere;font-size:clamp(2rem,7vw,4rem);line-height:1.05}.b3-heading{font-family:var(--b3-font-display);letter-spacing:-.02em;overflow-wrap:anywhere;font-size:1.375rem;line-height:1.25}.b3-muted{color:var(--b3-color-muted)}.b3-label{letter-spacing:.06em;text-transform:uppercase;font-size:.8125rem;font-weight:650}.b3-surface{background:var(--b3-color-surface);color:var(--b3-color-text);border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);padding:clamp(1rem,3vw,2rem)}.b3-button{border-radius:var(--b3-radius);background:var(--b3-color-accent);min-block-size:2.75rem;color:var(--b3-color-on-accent);cursor:pointer;border:1px solid #0000;justify-content:center;align-items:center;gap:.5rem;padding:.625rem 1rem;font-weight:650;text-decoration:none;display:inline-flex}.b3-button :where(svg){flex-shrink:0;display:block}.b3-button>:where([aria-hidden=true]):has(>svg){flex-shrink:0}.b3-button[data-b3-emphasis=quiet]{color:var(--b3-color-text);border-color:var(--b3-color-border);background:0 0}.b3-button:disabled{opacity:.5;cursor:not-allowed}.b3-field{align-content:start;gap:.375rem;min-inline-size:0;display:grid}.b3-input{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);background:var(--b3-color-surface);min-block-size:2.75rem;inline-size:100%;min-inline-size:0;color:var(--b3-color-text);padding:.625rem .75rem}.b3-error{color:var(--b3-color-danger);font-size:.875rem}select.b3-input:not([multiple]):not([size]),select.b3-input[size="1"]:not([multiple]){appearance:none;background-image:linear-gradient(45deg,#0000 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,#0000 50%);background-position:right calc(1rem + 5px) center,right 1rem center;background-repeat:no-repeat;background-size:5px 5px;padding-inline-end:2.5rem}select.b3-input:not([multiple]):not([size]):dir(rtl),select.b3-input[size="1"]:not([multiple]):dir(rtl){background-position:1rem,calc(1rem + 5px)}@media (forced-colors:active){select.b3-input:not([multiple]):not([size]),select.b3-input[size="1"]:not([multiple]){appearance:auto;background-image:none}}:where(ul,ol):has(>.b3-row){margin:0;padding-inline-start:0}.b3-row{border-block-end:1px solid var(--b3-color-border);align-items:center;gap:.75rem;padding-block:1rem;display:flex}.b3-row:where(.b3-stack){align-items:stretch;gap:var(--b3-space)}.b3-badge{overflow-wrap:anywhere;border:1px solid var(--b3-color-border);border-radius:100vmax;align-items:center;inline-size:fit-content;max-inline-size:100%;padding:.125rem .5rem;font-size:.75rem;font-weight:650;display:inline-flex}.b3-progress{block-size:.5rem;inline-size:100%;accent-color:var(--b3-color-accent)}.b3-button{transition:box-shadow var(--b3-duration), border-color var(--b3-duration)}@media (hover:hover){.b3-button:not(:disabled):hover{box-shadow:inset 0 0 0 100vmax #00000012}}.b3-button:not(:disabled):active{box-shadow:inset 0 0 0 100vmax #00000024}.b3-button[data-b3-emphasis=outline]{color:var(--b3-color-accent);background:0 0;border-color:currentColor}.b3-button[data-b3-emphasis=danger]{background:var(--b3-color-danger);color:var(--b3-color-canvas)}.b3-button[data-b3-size=small]{padding:.5rem .75rem;font-size:.875rem}.b3-button[data-b3-size=large]{min-block-size:3.25rem;padding:.875rem 1.5rem}.b3-button[aria-busy=true]{cursor:progress}.b3-icon{vertical-align:-.2em;flex:none;block-size:1.25em;inline-size:1.25em;display:inline-block}.b3-input:disabled{opacity:.6;cursor:not-allowed}.b3-input[readonly]{background:var(--b3-color-canvas)}.b3-input::placeholder{color:var(--b3-color-muted);opacity:.8}.b3-input-group{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);background:var(--b3-color-canvas);flex-wrap:wrap;align-items:stretch;min-inline-size:0;display:flex;overflow:clip}.b3-input-group>.b3-input{border:0;border-radius:0;flex:12ch}.b3-input-group:has(>.b3-button){justify-content:end}.b3-input-group:focus-within{outline:none;box-shadow:inset 0 0 0 2px}.b3-input-affix{overflow-wrap:anywhere;min-inline-size:0;max-inline-size:100%;color:var(--b3-color-muted);background:var(--b3-color-canvas);align-items:center;padding-inline:.75rem;display:flex}.b3-choice{cursor:pointer;flex-wrap:wrap;align-items:start;gap:.75rem;padding-block:.5rem;display:flex}.b3-choice>:not(input){overflow-wrap:anywhere;flex:12ch;min-inline-size:0}.b3-choice input{--b3-choice-control:1.125rem;inline-size:var(--b3-choice-control);block-size:var(--b3-choice-control);flex:none;margin:0;margin-block-start:max(0px, calc((1lh - var(--b3-choice-control)) / 2))}.b3-choice>.b3-switch{--b3-choice-control:1.625rem}.b3-switch{appearance:none;background:var(--b3-color-muted);border:2px solid var(--b3-color-muted);cursor:pointer;border-radius:2rem;position:relative;block-size:1.625rem!important;inline-size:2.75rem!important}.b3-switch:before{content:"";background:var(--b3-color-surface);border-radius:50%;block-size:1.125rem;inline-size:1.125rem;position:absolute;inset-block-start:.125rem;inset-inline-start:.125rem}.b3-switch:checked{background:var(--b3-color-accent);border-color:var(--b3-color-accent)}.b3-switch:checked:before{background:var(--b3-color-on-accent);inset-inline-start:calc(100% - 1.25rem)}.b3-switch:disabled{opacity:.5;cursor:not-allowed}.b3-input[type=number]{appearance:textfield}.b3-input[type=number]::-webkit-inner-spin-button,.b3-input[type=number]::-webkit-outer-spin-button{appearance:none;margin:0}.b3-input::-webkit-datetime-edit,.b3-input::-webkit-datetime-edit-fields-wrapper{padding-block:0}.b3-input:is([type=date],[type=time],[type=datetime-local],[type=month],[type=week]){appearance:none;max-inline-size:100%;display:block}.b3-input::-webkit-date-and-time-value{text-align:start;min-block-size:1lh}.b3-input-affix{white-space:nowrap}.b3-card-header,.b3-card-footer{justify-content:space-between;align-items:center;gap:var(--b3-space);flex-wrap:wrap;display:flex}.b3-card-header{margin-block-end:var(--b3-space-4)}.b3-card-footer{border-block-start:1px solid var(--b3-color-border);margin-block-start:var(--b3-space-4);padding-block-start:var(--b3-space-4)}.b3-surface[data-b3-elevation=raised]{box-shadow:var(--b3-shadow)}.b3-divider{border:0;border-block-start:1px solid var(--b3-color-border);inline-size:100%;margin-inline:0;margin-block:var(--b3-space)}.b3-stack>.b3-divider{margin-block:0}.b3-alert,.b3-toast{overflow-wrap:anywhere;min-inline-size:0}.b3-alert>*,.b3-toast>*{min-inline-size:0}.b3-alert pre{max-inline-size:100%;overflow-x:auto}.b3-alert{--b3-status-color:var(--b3-color-info);border:1px solid var(--b3-status-color);border-radius:var(--b3-radius-small);color:var(--b3-color-text);background:color-mix(in srgb, var(--b3-status-color) 8%, var(--b3-color-surface));border-inline-start-width:4px;align-items:start;gap:.75rem;padding:1rem;display:flex}.b3-alert[data-b3-tone=success],.b3-badge[data-b3-tone=success]{--b3-status-color:var(--b3-color-success)}.b3-alert[data-b3-tone=warning],.b3-badge[data-b3-tone=warning]{--b3-status-color:var(--b3-color-warning)}.b3-alert[data-b3-tone=danger],.b3-badge[data-b3-tone=danger]{--b3-status-color:var(--b3-color-danger)}.b3-badge[data-b3-tone]{color:var(--b3-status-color,var(--b3-color-info));background:color-mix(in srgb, currentColor 7%, transparent);border-color:currentColor}.b3-toast-region{z-index:100;pointer-events:none;gap:.75rem;inline-size:min(24rem,100% - 2rem);display:grid;position:fixed;inset-block-end:1rem;inset-inline-end:1rem}.b3-toast{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);background:var(--b3-color-surface);color:var(--b3-color-text);box-shadow:var(--b3-shadow-raised);pointer-events:auto;flex-wrap:wrap;align-items:start;gap:1rem;padding:1rem;display:flex}.b3-toast>:not(button,.b3-button){flex:12rem}.b3-toast>:is(button,.b3-button){flex:none;max-inline-size:100%}.b3-spinner{vertical-align:-.125em;border:2px solid;border-inline-end-color:#0000;border-radius:50%;flex:none;block-size:1.125em;inline-size:1.125em;animation:.8s linear infinite b3-spin;display:inline-block}@keyframes b3-spin{to{transform:rotate(360deg)}}.b3-skeleton{background:var(--b3-color-border);border-radius:var(--b3-radius-small);min-block-size:1rem;animation:1.8s ease-in-out infinite b3-pulse}@keyframes b3-pulse{50%{opacity:.45}}:is(button,a).b3-badge{min-block-size:auto;color:var(--b3-color-text);cursor:pointer;background:0 0;gap:.25rem;text-decoration:none;position:relative}:is(button,a).b3-badge:after{content:"";position:absolute;inset:-10px 0}:is(button,a).b3-badge[data-b3-tone]{color:var(--b3-status-color,var(--b3-color-info))}:is(button,a).b3-badge:is([aria-pressed=true],[aria-current]:not([aria-current=false])){color:var(--b3-color-on-accent);background:var(--b3-color-accent);border-color:var(--b3-color-accent)}.b3-avatar{background:var(--b3-color-accent);block-size:2.5rem;inline-size:2.5rem;color:var(--b3-color-on-accent);border-radius:50%;flex:none;place-items:center;font-size:.875rem;font-weight:650;display:inline-grid;overflow:hidden}.b3-avatar>img{object-fit:cover;block-size:100%;inline-size:100%}.b3-avatar-stack{padding-inline-start:.5rem;display:flex}.b3-avatar-stack>.b3-avatar{border:2px solid var(--b3-color-surface);margin-inline-start:-.5rem}.b3-kbd{border:1px solid var(--b3-color-border);background:var(--b3-color-surface);color:var(--b3-color-muted);border-block-end-width:2px;border-radius:.25rem;padding:.15rem .35rem;font:.75rem ui-monospace,monospace}.b3-table-wrap{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);position:relative;overflow-x:auto}.b3-table{border-collapse:collapse;text-align:start;font-variant-numeric:tabular-nums;inline-size:100%}.b3-table th{color:var(--b3-color-muted);background:var(--b3-color-canvas);font-size:.75rem;font-weight:650}.b3-table :is(th,td){border-block-end:1px solid var(--b3-color-border);text-align:start;vertical-align:middle;padding:.875rem 1rem}.b3-table tbody tr:last-child td{border-block-end:0}.b3-table[data-b3-density=compact] :is(th,td){padding:.5rem .75rem}.b3-table tbody tr[aria-selected=true]{background:color-mix(in srgb, var(--b3-color-accent) 10%, var(--b3-color-surface))}.b3-definition{grid-template-columns:minmax(0,1fr) minmax(0,2fr);gap:.75rem 1.5rem;margin:0;display:grid;container:b3-definition/inline-size}.b3-definition dt{overflow-wrap:anywhere;min-inline-size:0;color:var(--b3-color-muted)}.b3-definition dd{overflow-wrap:anywhere;margin:0}@container b3-definition (width<=26rem){.b3-definition>:is(dt,dd){grid-column:1/-1}.b3-definition>dt:not(:first-child){margin-block-start:.75rem}}.b3-stat{grid-template-columns:minmax(0,1fr);gap:.5rem;display:grid}.b3-stat-value{overflow-wrap:anywhere;letter-spacing:-.045em;font-variant-numeric:tabular-nums;font-size:clamp(1.5rem,5vw,3rem);font-weight:650;line-height:1.1}.b3-nav{border-block-end:1px solid var(--b3-color-border);flex-wrap:wrap;align-items:center;gap:.5rem;padding:.75rem;display:flex}.b3-nav[data-b3-orientation=vertical]{border-block-end:0;flex-direction:column;align-items:stretch}.b3-nav-link{border-radius:var(--b3-radius-small);min-block-size:2.75rem;color:var(--b3-color-muted);align-items:center;gap:.5rem;padding:.625rem .875rem;text-decoration:none;display:flex}.b3-nav-link[aria-current=page]{background:color-mix(in srgb, var(--b3-color-accent) 10%, var(--b3-color-surface));color:var(--b3-color-accent);font-weight:650}.b3-nav-link:hover{background:var(--b3-color-surface);color:var(--b3-color-text)}.b3-breadcrumbs{color:var(--b3-color-muted);flex-wrap:wrap;align-items:center;gap:.5rem;margin:0;padding:0;font-size:.875rem;list-style:none;display:flex}.b3-breadcrumbs li+li:before{content:"/";color:var(--b3-color-border);margin-inline-end:.5rem}.b3-tablist{border-block-end:1px solid var(--b3-color-border);gap:.25rem;display:flex;overflow-x:auto}.b3-tab{appearance:none;color:var(--b3-color-muted);font:inherit;white-space:normal;overflow-wrap:anywhere;cursor:pointer;background:0 0;border:0;border-block-end:2px solid #0000;flex-shrink:0;min-block-size:2.75rem;max-inline-size:100%;padding:.75rem min(1rem,4vi)}.b3-tab[aria-selected=true]{color:var(--b3-color-accent);border-block-end-color:currentColor;font-weight:650}.b3-tab:disabled{opacity:.5;cursor:not-allowed}.b3-tabpanel{padding-block:var(--b3-space-4)}.b3-accordion{border-block-end:1px solid var(--b3-color-border)}.b3-accordion>summary{cursor:pointer;padding:1rem .25rem;font-weight:650}.b3-accordion>:not(summary){padding:0 .25rem 1rem}.b3-dropdown{display:inline-block;position:relative}.b3-dropdown>summary{list-style:none}.b3-dropdown>summary::-webkit-details-marker{display:none}.b3-dropdown-panel{z-index:20;border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);background:var(--b3-color-surface);max-block-size:70svh;inline-size:max-content;min-inline-size:min(12rem,85vw);max-inline-size:min(22rem,85vw);color:var(--b3-color-text);box-shadow:var(--b3-shadow-raised);padding:.5rem;position:absolute;inset-block-start:calc(100% + .5rem);inset-inline-start:0;overflow-y:auto}.b3-dialog{overscroll-behavior:contain;border:1px solid var(--b3-color-border);border-radius:var(--b3-radius-large);background:var(--b3-color-surface);max-block-size:calc(100svh - 2rem);inline-size:min(32rem,100% - 2rem);color:var(--b3-color-text);box-shadow:var(--b3-shadow-raised);padding:min(1.5rem,5vi);overflow:auto}.b3-dialog::backdrop{background:#101b17a6}.b3-dialog[data-b3-placement=end]{border-radius:0;block-size:100svh;max-block-size:100svh;inline-size:min(28rem,100%);max-inline-size:100%;margin:0;margin-inline-start:auto}@media (prefers-reduced-motion:no-preference){.b3-dialog[open]{animation:.28s cubic-bezier(.16,1,.3,1) b3-dialog-enter}.b3-dialog[open][data-b3-placement=end]{animation-name:b3-drawer-enter}.b3-dialog::backdrop,.b3-sheet::backdrop{transition:opacity .16s ease-out}.b3-dialog[data-b3-closing]::backdrop,.b3-sheet[data-b3-closing]::backdrop{opacity:0}@keyframes b3-dialog-enter{0%{opacity:0;transform:translateY(8px)scale(.96)}}@keyframes b3-drawer-enter{0%{opacity:0;translate:var(--b3-drawer-offset,1rem)}}.b3-dialog[dir=rtl][data-b3-placement=end],[dir=rtl] .b3-dialog[data-b3-placement=end]{--b3-drawer-offset:-1rem}}.b3-dialog>form{gap:var(--b3-space);flex-direction:column;margin:0;display:flex}.b3-dialog>form>header:has(>button:last-child){grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:.75rem;display:grid}.b3-dialog>form>header:has(>button:last-child)>:not(:last-child){grid-column:1;min-inline-size:0}.b3-dialog>form>header>button:last-child:not(:first-child){grid-area:1/2}.b3-dialog>form>footer{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:.75rem;display:flex}.b3-dialog>form>footer>[data-b3-emphasis=quiet]:first-child:not(:last-child){margin-inline-end:auto}.b3-tree{border-inline-start:1px solid var(--b3-color-border);padding-inline-start:1rem;list-style:none}.b3-tree summary{cursor:pointer;padding-block:.5rem}.b3-prose{overflow-wrap:anywhere;max-inline-size:65ch;line-height:1.75}.b3-prose :is(p,ul,ol,blockquote,pre){margin-block:1rem}.b3-prose :is(h1,h2,h3,h4,h5,h6){margin-block:1.75rem .75rem;line-height:1.25}.b3-prose :is(h5,h6){font-size:1em}.b3-prose>:first-child{margin-block-start:0}.b3-prose img{block-size:auto}.b3-prose blockquote{border-inline-start:3px solid var(--b3-color-accent);color:var(--b3-color-muted);margin-inline:0;padding-inline-start:1rem}.b3-prose pre{background:var(--b3-color-surface);border:1px solid var(--b3-color-border);border-radius:var(--b3-radius-small);padding:1rem;overflow-x:auto}.b3-visually-hidden{clip-path:inset(50%);white-space:nowrap;block-size:1px;inline-size:1px;position:absolute;overflow:hidden}.b3-skip-link{z-index:1000;border-radius:var(--b3-radius-small);background:var(--b3-color-accent);max-inline-size:calc(100% - 1.5rem);color:var(--b3-color-on-accent);padding:.75rem 1rem;font-weight:650;position:fixed;inset-block-start:max(.75rem, env(safe-area-inset-top,0px));inset-inline-start:.75rem}.b3-skip-link:not(:focus){clip-path:inset(50%);white-space:nowrap;block-size:1px;inline-size:1px;padding:0;overflow:hidden}.b3-hero{grid-template-columns:repeat(2,minmax(0,1fr));align-items:center;gap:clamp(2rem,6vw,6rem);padding-block:clamp(3rem,8vw,7rem);display:grid}.b3-hero[data-b3-align=center]{text-align:center;grid-template-columns:minmax(0,1fr);justify-items:center}.b3-hero-copy{gap:1.5rem;max-inline-size:40rem;display:grid}.b3-feature{grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:center;gap:clamp(1.5rem,5vw,5rem);padding-block:clamp(2rem,6vw,5rem);display:grid}.b3-feature[data-b3-reverse=true]>:first-child{order:2}.b3-feature-icon{border-radius:var(--b3-radius);background:var(--b3-color-accent);block-size:3rem;inline-size:3rem;color:var(--b3-color-on-accent);place-items:center;display:inline-grid}.b3-pricing{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius-large);background:var(--b3-color-surface);color:var(--b3-color-text);flex-direction:column;gap:1.25rem;padding:clamp(1.25rem,3vw,2rem);display:flex}.b3-pricing[data-b3-featured=true]{border:2px solid var(--b3-color-accent);box-shadow:var(--b3-shadow)}.b3-pricing>.b3-button:last-child{margin-block-start:auto}.b3-price{font-variant-numeric:tabular-nums;flex-wrap:wrap;align-items:baseline;gap:.375rem;display:flex}.b3-price>strong{letter-spacing:-.05em;font-size:3.25rem;line-height:1.1}.b3-checklist{gap:.75rem;margin:0;padding:0;list-style:none;display:grid}.b3-checklist>li{align-items:start;gap:.625rem;display:flex}.b3-checklist>li:before{content:"✓";color:var(--b3-color-accent);font-weight:700}.b3-quote{border-inline-start:3px solid var(--b3-color-accent);margin:0;padding:1.5rem}.b3-quote blockquote{margin:0 0 1rem;font-size:1.25rem;line-height:1.5}.b3-art{isolation:isolate;border-radius:var(--b3-radius-large);background:var(--b3-color-surface);position:relative;overflow:hidden}.b3-art:before{content:"";z-index:-1;pointer-events:none;position:absolute;inset:0}.b3-art[data-b3-art=dots]:before{background-image:radial-gradient(var(--b3-color-border) 1px,transparent 1px);background-size:16px 16px}.b3-art[data-b3-art=glow]:before{background:radial-gradient(ellipse at 20% 20%,color-mix(in srgb,var(--b3-color-accent) 20%,transparent),transparent 65%)}.b3-art[data-b3-art=wash]:before{background:linear-gradient(135deg,color-mix(in srgb,var(--b3-color-accent) 14%,transparent),transparent 70%)}@media (width<=48rem){.b3-hero,.b3-feature{grid-template-columns:minmax(0,1fr)}.b3-feature[data-b3-reverse=true]>:first-child{order:initial}}.b3-reading{grid-template-columns:minmax(0,1fr) minmax(12rem,16rem);grid-template-areas:"content outline";align-items:start;gap:clamp(2rem,5vw,5rem);display:grid}.b3-reading>:not(.b3-toc){grid-area:content;min-inline-size:0}.b3-toc{max-block-size:calc(100svh - var(--b3-outline-top,1rem) - 1rem);border-inline-start:1px solid var(--b3-color-border);grid-area:outline;padding-inline-start:1rem;font-size:.875rem;position:sticky;inset-block-start:var(--b3-outline-top,1rem);overflow-y:auto}.b3-toc ol{gap:.25rem;margin:.75rem 0 0;padding:0;list-style:none;display:grid}.b3-toc ol ol{margin:.25rem 0;padding-inline-start:1rem}.b3-toc-link{border-radius:var(--b3-radius-small);color:var(--b3-color-muted);overflow-wrap:anywhere;padding:.625rem .75rem;text-decoration:none;display:block}.b3-toc-link:hover{color:var(--b3-color-text);background:var(--b3-color-surface)}.b3-toc-link[aria-current=location]{color:var(--b3-color-accent);background:color-mix(in srgb, var(--b3-color-accent) 10%, var(--b3-color-surface));font-weight:650}@media (width<=48rem){.b3-reading{grid-template-columns:minmax(0,1fr);grid-template-areas:"outline""content";gap:2rem}.b3-toc{max-block-size:none;position:static}}.b3-command{inline-size:min(38rem,100% - 2rem);padding:0}.b3-command[open]:has(>.b3-scroll){grid-template-rows:auto minmax(0,1fr);display:grid}.b3-command>.b3-scroll>.b3-command-list{max-block-size:none;overflow:visible}.b3-command-search{border-block-end:1px solid var(--b3-color-border);flex-wrap:wrap;justify-content:end;align-items:center;gap:.75rem;padding:min(1rem,4vw);display:flex}.b3-command-search>input{flex:12ch;min-inline-size:0}.b3-command-list{overscroll-behavior:contain;max-block-size:50svh;padding:.5rem;overflow-y:auto}.b3-command-group+.b3-command-group{border-block-start:1px solid var(--b3-color-border);margin-block-start:.5rem;padding-block-start:.5rem}.b3-command-group>:first-child:not([role=option]){color:var(--b3-color-muted);padding:.5rem .75rem;font-size:.75rem;display:block}.b3-command-item{border-radius:var(--b3-radius-small);cursor:pointer;justify-content:space-between;align-items:center;gap:min(1rem,2vw);min-block-size:2.75rem;padding:min(.875rem,2svh) min(.75rem,3vw);display:flex}.b3-command-item>:first-child{overflow-wrap:anywhere;min-inline-size:0}.b3-command-item[aria-selected=true]{background:var(--b3-color-accent);color:var(--b3-color-on-accent)}.b3-command-item[aria-selected=true] .b3-muted{color:inherit}.b3-command-item[aria-disabled=true]{opacity:.5;cursor:not-allowed}.b3-command-item:not([aria-disabled=true]):hover{outline:1px solid var(--b3-color-border);outline-offset:-1px}.b3-form-grid{grid-template-columns:repeat(auto-fit,minmax(min(100%,14rem),1fr));align-items:start;gap:1.25rem;display:grid}.b3-form-grid[data-b3-layout=inline]{flex-wrap:wrap;align-items:end;display:flex}.b3-form-grid[data-b3-layout=inline]>.b3-field{flex:12rem;min-inline-size:0}.b3-form-grid[data-b3-layout=inline]>.b3-button{overflow-wrap:anywhere;flex:0 auto;max-inline-size:100%}.b3-fieldset{border:0;min-inline-size:0;margin:0;padding:0}.b3-fieldset>legend{padding:0 0 .75rem;font-weight:650}.b3-help{color:var(--b3-color-muted);font-size:.875rem}.b3-help[data-b3-tone=success]{color:var(--b3-color-success)}.b3-help[data-b3-tone=warning]{color:var(--b3-color-warning)}.b3-input[data-b3-size=small]{padding:.5rem .75rem}.b3-input[data-b3-size=large]{min-block-size:3.25rem;padding:.875rem 1rem}.b3-input[data-b3-emphasis=subtle]{background-color:var(--b3-color-canvas);border-color:#0000}.b3-input[data-b3-emphasis=outline]{background-color:#0000;border-width:2px}.b3-input[data-b3-tone=success]{border-color:var(--b3-color-success)}.b3-input[data-b3-tone=warning]{border-color:var(--b3-color-warning)}.b3-input[aria-invalid=true]{border-color:var(--b3-color-danger)}textarea.b3-input{resize:vertical}select.b3-input[multiple]{min-block-size:8rem}.b3-input[type=file]{padding:.375rem}.b3-input::file-selector-button{font:inherit;border-radius:var(--b3-radius-small);background:var(--b3-color-accent);color:var(--b3-color-on-accent);cursor:pointer;border:0;margin-inline-end:.75rem;padding:.5rem .75rem}.b3-input[type=color]{inline-size:5rem;padding:.25rem}.b3-range{min-block-size:2.75rem;inline-size:100%;accent-color:var(--b3-color-accent);margin:0}.b3-choice-card{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);background:var(--b3-color-surface);color:var(--b3-color-text);cursor:pointer;align-items:start;gap:.75rem;padding:1rem;display:flex}.b3-choice-card>input{flex:none;block-size:1.125rem;inline-size:1.125rem;margin-block-start:.3rem}.b3-choice-card:has(input:checked){border-color:var(--b3-color-accent);background:color-mix(in srgb, var(--b3-color-accent) 7%, var(--b3-color-surface));box-shadow:inset 0 0 0 1px var(--b3-color-accent)}.b3-choice-card:has(input:focus-visible){outline:none;box-shadow:inset 0 0 0 2px}.b3-choice-card:has(input:disabled){opacity:.5;cursor:not-allowed}.b3-choice-card:has(input[aria-invalid=true]){border-color:var(--b3-color-danger)}.b3-range[aria-invalid=true]{accent-color:var(--b3-color-danger)}.b3-stepper{flex-wrap:wrap;gap:1rem 2rem;margin:0;padding:0;list-style:none;display:flex}.b3-step{color:var(--b3-color-muted);align-items:center;gap:.625rem;font-size:.875rem;display:flex}.b3-step-marker{border:1px solid var(--b3-color-border);font-variant-numeric:tabular-nums;border-radius:50%;flex:none;place-items:center;block-size:2rem;inline-size:2rem;display:grid}.b3-step[aria-current=step]{color:var(--b3-color-text);font-weight:650}.b3-step[aria-current=step] .b3-step-marker{background:var(--b3-color-accent);color:var(--b3-color-on-accent);border-color:var(--b3-color-accent)}.b3-step[data-b3-complete=true] .b3-step-marker{border-color:var(--b3-color-success);color:var(--b3-color-success)}.b3-conversation{gap:1.5rem;min-inline-size:0;margin:0;padding:0;list-style:none;display:grid}.b3-message{justify-self:start;gap:.375rem;min-inline-size:0;max-inline-size:min(85%,38rem);display:grid}.b3-message[data-b3-side=self]{justify-self:end}.b3-message-body{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);background:var(--b3-color-surface);color:var(--b3-color-text);white-space:pre-wrap;overflow-wrap:anywhere;padding:.875rem 1rem}.b3-message[data-b3-side=self]>.b3-message-body{background:var(--b3-color-accent);color:var(--b3-color-on-accent);border-color:#0000}.b3-message-meta{color:var(--b3-color-muted);flex-wrap:wrap;gap:.35rem .75rem;font-size:.8125rem;display:flex}.b3-message[data-b3-side=self]>.b3-message-meta{justify-content:end}.b3-mobile-app{--b3-touch:44px;min-block-size:100svh;max-inline-size:var(--b3-app-width,70rem);margin-inline:auto;padding-block-end:calc(5.5rem + env(safe-area-inset-bottom,0px))}.b3-mobile-app[data-b3-platform=android]{--b3-touch:48px}.b3-mobile-app :where(button,.b3-button,select,input:not([type=checkbox],[type=radio])){min-block-size:var(--b3-touch)}.b3-app-bar{padding:max(.75rem, env(safe-area-inset-top,0px)) 1.25rem .75rem;align-items:center;gap:.75rem;display:flex}.b3-app-bar>:last-child:not(:first-child){margin-inline-start:auto}.b3-bottom-nav{z-index:10;max-inline-size:var(--b3-app-width,70rem);padding:.5rem clamp(.5rem, 2vw, 1rem) max(.5rem, env(safe-area-inset-bottom,0px));background:var(--b3-color-surface);color:var(--b3-color-text);border-block-start:1px solid var(--b3-color-border);flex-wrap:wrap;justify-content:space-around;gap:.5rem;margin-inline:auto;display:flex;position:fixed;inset-block-end:0;inset-inline:0}.b3-bottom-link{overflow-wrap:anywhere;text-align:center;min-block-size:var(--b3-touch,44px);min-inline-size:0;color:var(--b3-color-muted);border-radius:1rem;flex-direction:column;flex:5ch;justify-content:center;align-items:center;gap:.15rem;padding:.25rem clamp(.125rem,1vw,.5rem);font-size:.75rem;text-decoration:none;display:flex}.b3-bottom-link:has(svg){justify-content:flex-start}.b3-bottom-link[aria-current=page]{color:var(--b3-color-accent);font-weight:700}[data-b3-platform=android] .b3-bottom-link[aria-current=page]{background:var(--b3-color-canvas)}.b3-mobile-list{border:1px solid var(--b3-color-border);background:var(--b3-color-surface);border-radius:1rem;margin:0;padding:0;list-style:none;overflow:hidden}.b3-mobile-list>li+li{border-block-start:1px solid var(--b3-color-border)}.b3-mobile-row{min-block-size:var(--b3-touch,44px);color:var(--b3-color-text);align-items:center;gap:1rem;padding:.875rem 1rem;text-decoration:none;display:flex}.b3-mobile-row>:last-child:not(:first-child){margin-inline-start:auto}.b3-sheet{inline-size:min(100%, var(--b3-overlay-width,32rem));max-inline-size:none;max-block-size:min(85dvh, calc(100dvh - env(safe-area-inset-top,0px) - 1rem));overscroll-behavior:contain;border:1px solid var(--b3-color-border);padding:1.5rem min(1.5rem, 5vi) max(1.5rem, env(safe-area-inset-bottom,0px));background:var(--b3-color-surface);color:var(--b3-color-text);border-radius:1.5rem 1.5rem 0 0;margin:0 auto;inset:auto 0 0;overflow-y:auto}.b3-sheet::backdrop{background:#0007}.b3-sheet [data-b3-initial-focus]:focus{box-shadow:none;outline:none}.b3-sheet-handle{min-block-size:44px;inline-size:5rem;color:var(--b3-color-muted);touch-action:none;cursor:grab;background:0 0;border:0;border-radius:1rem;flex:none;align-self:center;place-items:center;margin-block:-1rem -.5rem;padding:0;display:grid}.b3-sheet-handle:before{content:"";background:currentColor;border-radius:1rem;block-size:.25rem;inline-size:2.25rem}.b3-sheet[data-b3-dragging]{animation:none}.b3-sheet[data-b3-dragging] .b3-sheet-handle{cursor:grabbing}[data-b3-platform=android] .b3-mobile-list{border-radius:1.5rem}@media (prefers-reduced-motion:no-preference){.b3-sheet[open]{animation:.32s cubic-bezier(.16,1,.3,1) b3-sheet-enter}@keyframes b3-sheet-enter{0%{opacity:0;transform:translateY(3rem)}to{opacity:1;transform:translateY(0)}}}.b3-root:has(dialog:modal){overflow:hidden}.b3-bottom-link[aria-current=page]{background:color-mix(in srgb, var(--b3-color-accent) 12%, transparent)}.b3-bottom-link :where(svg:not([width])){flex:none;block-size:1.375rem;inline-size:1.375rem}.b3-app-frame{inline-size:100%;block-size:var(--b3-frame-height,100dvh);min-block-size:0;max-inline-size:var(--b3-app-width,70rem);padding:env(safe-area-inset-top,0px) env(safe-area-inset-right,0px) env(safe-area-inset-bottom,0px) env(safe-area-inset-left,0px);grid-template-rows:auto minmax(0,1fr) auto;margin-inline:auto;display:grid;overflow:clip}.b3-app-frame>header{grid-row:1;min-inline-size:0}.b3-app-frame>main{overscroll-behavior:contain;grid-area:2/1;min-block-size:0;min-inline-size:0;overflow:auto}.b3-app-frame>:is(nav,footer){grid-row:3;min-inline-size:0;position:relative}.b3-app-frame>.b3-toast-region{overscroll-behavior:contain;grid-area:2/1;place-self:end;min-block-size:0;max-block-size:calc(100% - 2rem);margin:1rem;position:relative;inset:auto;overflow-y:auto}.b3-app-frame>.b3-app-bar{padding-block-start:.75rem}.b3-app-frame:has(>.b3-bottom-nav,>footer .b3-bottom-nav){padding-block-end:0}.b3-app-frame .b3-bottom-nav{inline-size:100%;max-inline-size:none;padding-block-end:max(.5rem, env(safe-area-inset-bottom,0px));position:static}}@property --b3-reel-fade{syntax:"<length>";inherits:false;initial-value:2.5rem}
|
|
1
|
+
@layer bare.reset{:where(.b3-root),:where(.b3-root *){box-sizing:border-box}:where(.b3-root){font-family:var(--b3-font-body);background:var(--b3-color-canvas);color:var(--b3-color-text);color-scheme:light;margin:0;font-size:1rem;line-height:1.5}:where(.b3-root>body){margin:0}:where(.b3-root) :where(button,input,select,textarea){font:inherit}:where(.b3-root) :where(h1,h2,h3,h4,h5,h6,p){margin:0}:where(.b3-root) :where(button,a,input,select,textarea,summary,[tabindex]):focus-visible{outline:none;box-shadow:inset 0 0 0 2px}@media (forced-colors:active){:where(.b3-root) :where(button,a,input,select,textarea,summary,[tabindex]):focus-visible{outline-offset:-2px;outline:1px solid highlight}}:where(.b3-root) :where(img,svg){max-inline-size:100%}@media (prefers-reduced-motion:reduce){:where(.b3-root *),:where(.b3-root *):before,:where(.b3-root *):after{transition:none!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}[data-b3-motion=reduce] *,[data-b3-motion=reduce] :before,[data-b3-motion=reduce] :after,[data-b3-motion=reduce] ::backdrop{scroll-behavior:auto!important;transition:none!important;animation:none!important}:where(.b3-root [hidden]):not([hidden=until-found]){display:none!important}:where(.b3-root) :where(input[type=checkbox],input[type=radio],input[type=range]){accent-color:var(--b3-color-accent)}}@layer bare.tokens{.b3-root{--b3-color-canvas:#f6f5f0;--b3-color-surface:#fff;--b3-color-text:#202923;--b3-color-muted:#59655d;--b3-color-border:#d5dbd3;--b3-color-accent:#245c42;--b3-color-on-accent:#fff;--b3-color-danger:#a62c35;--b3-color-focus:#276bdd;--b3-font-body:ui-sans-serif, system-ui, sans-serif;--b3-font-display:var(--b3-font-body);--b3-radius:.75rem;--b3-space:1rem;--b3-content:68rem}.b3-root[data-b3-theme=dark]{color-scheme:dark;--b3-color-canvas:#141c19;--b3-color-surface:#202c26;--b3-color-text:#edf3ec;--b3-color-muted:#a8b9ad;--b3-color-border:#415348;--b3-color-accent:#aadbb8;--b3-color-on-accent:#132b1b;--b3-color-danger:#ffadb1;--b3-color-focus:#9ac4ff}.b3-root{--b3-space-1:.25rem;--b3-space-2:.5rem;--b3-space-3:.75rem;--b3-space-4:1rem;--b3-space-6:1.5rem;--b3-space-8:2rem;--b3-space-12:3rem;--b3-radius-small:.375rem;--b3-radius-large:1.25rem;--b3-shadow:0 8px 32px #14231b14;--b3-shadow-raised:0 16px 56px #14231b24;--b3-color-success:#256443;--b3-color-warning:#805100;--b3-color-info:#275f99;--b3-duration:.16s}.b3-root[data-b3-theme=dark]{--b3-shadow:0 8px 32px #0004;--b3-shadow-raised:0 16px 56px #0007;--b3-color-success:#99d9ab;--b3-color-warning:#f2c76c;--b3-color-info:#a2cfff}}@layer bare.layout{.b3-shell{inline-size:min(100%, var(--b3-content));margin-inline:auto;padding:clamp(1rem,4vw,3rem)}.b3-stack{gap:var(--b3-space);flex-direction:column;min-inline-size:0;display:flex}.b3-stack:where(dialog:not([open])){display:none}.b3-scroll{overscroll-behavior:contain;scrollbar-gutter:stable;flex:auto;min-block-size:0;min-inline-size:0;overflow:auto}dialog.b3-stack>.b3-scroll{min-block-size:min(6rem,30dvh)}.b3-stack:has(>.b3-scroll)>:not(.b3-scroll){flex-shrink:0}.b3-cluster{align-items:center;gap:var(--b3-space);flex-wrap:wrap;min-inline-size:0;display:flex}.b3-cluster>*,.b3-split>*{min-inline-size:0}.b3-inline{vertical-align:middle;align-items:center;gap:.5em;min-inline-size:0;max-inline-size:100%;display:inline-flex}.b3-inline>*{min-inline-size:0}.b3-inline:has(>.b3-badge){flex-wrap:wrap}.b3-inline>.b3-badge{flex-shrink:0}.b3-inline:has(>.b3-badge)>:not(.b3-badge,.b3-icon,svg){flex:1 1 min-content}.b3-grid{grid-template-columns:repeat(auto-fit, minmax(min(100%, var(--b3-grid-min,17rem)), 1fr));gap:var(--b3-space);display:grid}.b3-split{justify-content:space-between;align-items:center;gap:var(--b3-space);flex-wrap:wrap;display:flex}.b3-page{flex-direction:column;min-block-size:100svh;display:flex}.b3-page-body{flex:1;min-inline-size:0}.b3-app-layout{grid-template-columns:minmax(12rem,16rem) minmax(0,1fr);min-block-size:100svh;display:grid}.b3-sidebar{padding:var(--b3-space-6);border-inline-end:1px solid var(--b3-color-border);background:var(--b3-color-surface);min-inline-size:0}.b3-center{padding:var(--b3-space-6);place-items:center;display:grid}.b3-cover{min-block-size:min(42rem,85svh);padding-block:var(--b3-space-12);flex-direction:column;justify-content:center;display:flex}.b3-reel{gap:var(--b3-space);scroll-snap-type:x proximity;padding-block-end:var(--b3-space-2);display:flex;overflow-x:auto}.b3-reel>*{scroll-snap-align:start;flex:0 0 min(85%,20rem)}@media (width<=48rem){.b3-app-layout{grid-template-columns:minmax(0,1fr)}.b3-sidebar{border-inline-end:0;border-block-end:1px solid var(--b3-color-border)}}.b3-reel:has(>.b3-badge){scrollbar-width:none;align-items:center;gap:.5rem;padding-block:.625rem}.b3-reel:has(>.b3-badge)::-webkit-scrollbar{display:none}.b3-reel>.b3-badge{flex:none}.b3-stack>.b3-reel{flex-shrink:0}@supports (animation-timeline:scroll(self inline)){.b3-reel:has(>.b3-badge){mask-image:linear-gradient(to right, #000 calc(100% - var(--b3-reel-fade)), #0000);animation:linear both b3-reel-fade scroll(self inline)}.b3-reel:has(>.b3-badge):dir(rtl){mask-image:linear-gradient(to left, #000 calc(100% - var(--b3-reel-fade)), #0000)}@keyframes b3-reel-fade{90%{--b3-reel-fade:2.5rem}to{--b3-reel-fade:0rem}}}}@layer bare.components{.b3-title{font-family:var(--b3-font-display);letter-spacing:-.04em;overflow-wrap:anywhere;font-size:clamp(2rem,7vw,4rem);line-height:1.05}.b3-heading{font-family:var(--b3-font-display);letter-spacing:-.02em;overflow-wrap:anywhere;font-size:1.375rem;line-height:1.25}.b3-muted{color:var(--b3-color-muted)}.b3-label{letter-spacing:.06em;text-transform:uppercase;font-size:.8125rem;font-weight:650}.b3-surface{background:var(--b3-color-surface);color:var(--b3-color-text);border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);padding:clamp(1rem,3vw,2rem)}.b3-button{border-radius:var(--b3-radius);background:var(--b3-color-accent);min-block-size:2.75rem;color:var(--b3-color-on-accent);cursor:pointer;border:1px solid #0000;justify-content:center;align-items:center;gap:.5rem;padding:.625rem 1rem;font-weight:650;text-decoration:none;display:inline-flex}.b3-button :where(svg){flex-shrink:0;display:block}.b3-button>:where([aria-hidden=true]):has(>svg){flex-shrink:0}.b3-button[data-b3-emphasis=quiet]{color:var(--b3-color-text);border-color:var(--b3-color-border);background:0 0}.b3-button:disabled{opacity:.5;cursor:not-allowed}.b3-field{align-content:start;gap:.375rem;min-inline-size:0;display:grid}.b3-input{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);background:var(--b3-color-surface);min-block-size:2.75rem;inline-size:100%;min-inline-size:0;color:var(--b3-color-text);padding:.625rem .75rem}.b3-error{color:var(--b3-color-danger);font-size:.875rem}select.b3-input:not([multiple]):not([size]),select.b3-input[size="1"]:not([multiple]){appearance:none;background-image:linear-gradient(45deg,#0000 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,#0000 50%);background-position:right calc(1rem + 5px) center,right 1rem center;background-repeat:no-repeat;background-size:5px 5px;padding-inline-end:2.5rem}select.b3-input:not([multiple]):not([size]):dir(rtl),select.b3-input[size="1"]:not([multiple]):dir(rtl){background-position:1rem,calc(1rem + 5px)}@media (forced-colors:active){select.b3-input:not([multiple]):not([size]),select.b3-input[size="1"]:not([multiple]){appearance:auto;background-image:none}}:where(ul,ol):has(>.b3-row){margin:0;padding-inline-start:0}.b3-row{border-block-end:1px solid var(--b3-color-border);align-items:center;gap:.75rem;padding-block:1rem;display:flex}.b3-row:where(.b3-stack){align-items:stretch;gap:var(--b3-space)}.b3-badge{overflow-wrap:anywhere;border:1px solid var(--b3-color-border);border-radius:100vmax;align-items:center;inline-size:fit-content;max-inline-size:100%;padding:.125rem .5rem;font-size:.75rem;font-weight:650;display:inline-flex}.b3-progress{block-size:.5rem;inline-size:100%;accent-color:var(--b3-color-accent)}.b3-button{transition:box-shadow var(--b3-duration), border-color var(--b3-duration)}@media (hover:hover){.b3-button:not(:disabled):hover{box-shadow:inset 0 0 0 100vmax #00000012}}.b3-button:not(:disabled):active{box-shadow:inset 0 0 0 100vmax #00000024}.b3-button[data-b3-emphasis=outline]{color:var(--b3-color-accent);background:0 0;border-color:currentColor}.b3-button[data-b3-emphasis=danger]{background:var(--b3-color-danger);color:var(--b3-color-canvas)}.b3-button[data-b3-size=small]{padding:.5rem .75rem;font-size:.875rem}.b3-button[data-b3-size=large]{min-block-size:3.25rem;padding:.875rem 1.5rem}.b3-button[aria-busy=true]{cursor:progress}.b3-icon{vertical-align:-.2em;flex:none;block-size:1.25em;inline-size:1.25em;display:inline-block}[data-b3-confirm][data-b3-armed]{outline:2px solid var(--b3-color-danger)!important;outline-offset:2px!important}.b3-button[data-b3-armed]{background:var(--b3-color-danger);color:var(--b3-color-canvas);border-color:var(--b3-color-danger)}.b3-input:disabled{opacity:.6;cursor:not-allowed}.b3-input[readonly]{background:var(--b3-color-canvas)}.b3-input::placeholder{color:var(--b3-color-muted);opacity:.8}.b3-input-group{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);background:var(--b3-color-canvas);flex-wrap:wrap;align-items:stretch;min-inline-size:0;display:flex;overflow:clip}.b3-input-group>.b3-input{border:0;border-radius:0;flex:12ch}.b3-input-group:has(>.b3-button){justify-content:end}.b3-input-group:focus-within{outline:none;box-shadow:inset 0 0 0 2px}.b3-input-affix{overflow-wrap:anywhere;min-inline-size:0;max-inline-size:100%;color:var(--b3-color-muted);background:var(--b3-color-canvas);align-items:center;padding-inline:.75rem;display:flex}.b3-choice{cursor:pointer;flex-wrap:wrap;align-items:start;gap:.75rem;padding-block:.5rem;display:flex}.b3-choice>:not(input){overflow-wrap:anywhere;flex:12ch;min-inline-size:0}.b3-choice input{--b3-choice-control:1.125rem;inline-size:var(--b3-choice-control);block-size:var(--b3-choice-control);flex:none;margin:0;margin-block-start:max(0px, calc((1lh - var(--b3-choice-control)) / 2))}.b3-choice>.b3-switch{--b3-choice-control:1.625rem}.b3-switch{appearance:none;background:var(--b3-color-muted);border:2px solid var(--b3-color-muted);cursor:pointer;border-radius:2rem;position:relative;block-size:1.625rem!important;inline-size:2.75rem!important}.b3-switch:before{content:"";background:var(--b3-color-surface);border-radius:50%;block-size:1.125rem;inline-size:1.125rem;position:absolute;inset-block-start:.125rem;inset-inline-start:.125rem}.b3-switch:checked{background:var(--b3-color-accent);border-color:var(--b3-color-accent)}.b3-switch:checked:before{background:var(--b3-color-on-accent);inset-inline-start:calc(100% - 1.25rem)}.b3-switch:disabled{opacity:.5;cursor:not-allowed}.b3-input[type=number]{appearance:textfield}.b3-input[type=number]::-webkit-inner-spin-button,.b3-input[type=number]::-webkit-outer-spin-button{appearance:none;margin:0}.b3-input::-webkit-datetime-edit,.b3-input::-webkit-datetime-edit-fields-wrapper{padding-block:0}.b3-input:is([type=date],[type=time],[type=datetime-local],[type=month],[type=week]){appearance:none;max-inline-size:100%;display:block}.b3-input::-webkit-date-and-time-value{text-align:start;min-block-size:1lh}.b3-input-affix{white-space:nowrap}.b3-card-header,.b3-card-footer{justify-content:space-between;align-items:center;gap:var(--b3-space);flex-wrap:wrap;display:flex}.b3-card-header{margin-block-end:var(--b3-space-4)}.b3-card-footer{border-block-start:1px solid var(--b3-color-border);margin-block-start:var(--b3-space-4);padding-block-start:var(--b3-space-4)}.b3-surface[data-b3-elevation=raised]{box-shadow:var(--b3-shadow)}.b3-divider{border:0;border-block-start:1px solid var(--b3-color-border);inline-size:100%;margin-inline:0;margin-block:var(--b3-space)}.b3-stack>.b3-divider{margin-block:0}.b3-alert,.b3-toast{overflow-wrap:anywhere;min-inline-size:0}.b3-alert>*,.b3-toast>*{min-inline-size:0}.b3-alert pre{max-inline-size:100%;overflow-x:auto}.b3-alert{--b3-status-color:var(--b3-color-info);border:1px solid var(--b3-status-color);border-radius:var(--b3-radius-small);color:var(--b3-color-text);background:color-mix(in srgb, var(--b3-status-color) 8%, var(--b3-color-surface));border-inline-start-width:4px;align-items:start;gap:.75rem;padding:1rem;display:flex}.b3-alert[data-b3-tone=success],.b3-badge[data-b3-tone=success]{--b3-status-color:var(--b3-color-success)}.b3-alert[data-b3-tone=warning],.b3-badge[data-b3-tone=warning]{--b3-status-color:var(--b3-color-warning)}.b3-alert[data-b3-tone=danger],.b3-badge[data-b3-tone=danger]{--b3-status-color:var(--b3-color-danger)}.b3-badge[data-b3-tone]{color:var(--b3-status-color,var(--b3-color-info));background:color-mix(in srgb, currentColor 7%, transparent);border-color:currentColor}.b3-toast-region{z-index:100;pointer-events:none;gap:.75rem;inline-size:min(24rem,100% - 2rem);display:grid;position:fixed;inset-block-end:1rem;inset-inline-end:1rem}.b3-toast{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);background:var(--b3-color-surface);color:var(--b3-color-text);box-shadow:var(--b3-shadow-raised);pointer-events:auto;flex-wrap:wrap;align-items:start;gap:1rem;padding:1rem;display:flex}.b3-toast>:not(button,.b3-button){flex:12rem}.b3-toast>:is(button,.b3-button){flex:none;max-inline-size:100%}.b3-spinner{vertical-align:-.125em;border:2px solid;border-inline-end-color:#0000;border-radius:50%;flex:none;block-size:1.125em;inline-size:1.125em;animation:.8s linear infinite b3-spin;display:inline-block}@keyframes b3-spin{to{transform:rotate(360deg)}}.b3-skeleton{background:var(--b3-color-border);border-radius:var(--b3-radius-small);min-block-size:1rem;animation:1.8s ease-in-out infinite b3-pulse}@keyframes b3-pulse{50%{opacity:.45}}:is(button,a).b3-badge{min-block-size:auto;color:var(--b3-color-text);cursor:pointer;background:0 0;gap:.25rem;text-decoration:none;position:relative}:is(button,a).b3-badge:after{content:"";position:absolute;inset:-10px 0}:is(button,a).b3-badge[data-b3-tone]{color:var(--b3-status-color,var(--b3-color-info))}:is(button,a).b3-badge:is([aria-pressed=true],[aria-current]:not([aria-current=false])){color:var(--b3-color-on-accent);background:var(--b3-color-accent);border-color:var(--b3-color-accent)}.b3-avatar{background:var(--b3-color-accent);block-size:2.5rem;inline-size:2.5rem;color:var(--b3-color-on-accent);border-radius:50%;flex:none;place-items:center;font-size:.875rem;font-weight:650;display:inline-grid;overflow:hidden}.b3-avatar>img{object-fit:cover;block-size:100%;inline-size:100%}.b3-avatar-stack{padding-inline-start:.5rem;display:flex}.b3-avatar-stack>.b3-avatar{border:2px solid var(--b3-color-surface);margin-inline-start:-.5rem}.b3-kbd{border:1px solid var(--b3-color-border);background:var(--b3-color-surface);color:var(--b3-color-muted);border-block-end-width:2px;border-radius:.25rem;padding:.15rem .35rem;font:.75rem ui-monospace,monospace}.b3-table-wrap{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);position:relative;overflow-x:auto}.b3-table{border-collapse:collapse;text-align:start;font-variant-numeric:tabular-nums;inline-size:100%}.b3-table th{color:var(--b3-color-muted);background:var(--b3-color-canvas);font-size:.75rem;font-weight:650}.b3-table :is(th,td){border-block-end:1px solid var(--b3-color-border);text-align:start;vertical-align:middle;padding:.875rem 1rem}.b3-table tbody tr:last-child td{border-block-end:0}.b3-table[data-b3-density=compact] :is(th,td){padding:.5rem .75rem}.b3-table tbody tr[aria-selected=true]{background:color-mix(in srgb, var(--b3-color-accent) 10%, var(--b3-color-surface))}.b3-definition{grid-template-columns:minmax(0,1fr) minmax(0,2fr);gap:.75rem 1.5rem;margin:0;display:grid;container:b3-definition/inline-size}.b3-definition dt{overflow-wrap:anywhere;min-inline-size:0;color:var(--b3-color-muted)}.b3-definition dd{overflow-wrap:anywhere;margin:0}@container b3-definition (width<=26rem){.b3-definition>:is(dt,dd){grid-column:1/-1}.b3-definition>dt:not(:first-child){margin-block-start:.75rem}}.b3-stat{grid-template-columns:minmax(0,1fr);gap:.5rem;display:grid}.b3-stat-value{overflow-wrap:anywhere;letter-spacing:-.045em;font-variant-numeric:tabular-nums;font-size:clamp(1.5rem,5vw,3rem);font-weight:650;line-height:1.1}.b3-nav{border-block-end:1px solid var(--b3-color-border);flex-wrap:wrap;align-items:center;gap:.5rem;padding:.75rem;display:flex}.b3-nav[data-b3-orientation=vertical]{border-block-end:0;flex-direction:column;align-items:stretch}.b3-nav-link{border-radius:var(--b3-radius-small);min-block-size:2.75rem;color:var(--b3-color-muted);align-items:center;gap:.5rem;padding:.625rem .875rem;text-decoration:none;display:flex}.b3-nav-link[aria-current=page]{background:color-mix(in srgb, var(--b3-color-accent) 10%, var(--b3-color-surface));color:var(--b3-color-accent);font-weight:650}.b3-nav-link:hover{background:var(--b3-color-surface);color:var(--b3-color-text)}.b3-breadcrumbs{color:var(--b3-color-muted);flex-wrap:wrap;align-items:center;gap:.5rem;margin:0;padding:0;font-size:.875rem;list-style:none;display:flex}.b3-breadcrumbs li+li:before{content:"/";color:var(--b3-color-border);margin-inline-end:.5rem}.b3-tablist{border-block-end:1px solid var(--b3-color-border);gap:.25rem;display:flex;overflow-x:auto}.b3-tab{appearance:none;color:var(--b3-color-muted);font:inherit;white-space:normal;overflow-wrap:anywhere;cursor:pointer;background:0 0;border:0;border-block-end:2px solid #0000;flex-shrink:0;min-block-size:2.75rem;max-inline-size:100%;padding:.75rem min(1rem,4vi)}.b3-tab[aria-selected=true]{color:var(--b3-color-accent);border-block-end-color:currentColor;font-weight:650}.b3-tab:disabled{opacity:.5;cursor:not-allowed}.b3-tabpanel{padding-block:var(--b3-space-4)}.b3-segmented{border-radius:var(--b3-radius);background:color-mix(in srgb, var(--b3-color-text) 7%, var(--b3-color-canvas));border:0;flex-wrap:wrap;gap:.25rem;min-inline-size:0;margin:0;padding:.25rem;display:flex}.b3-segmented>label{border-radius:calc(var(--b3-radius) - .25rem);min-block-size:2.75rem;max-inline-size:100%;color:var(--b3-color-muted);text-align:center;cursor:pointer;flex:1 0 auto;justify-content:center;align-items:center;gap:.375rem;padding-inline:.875rem;font-weight:600;display:inline-flex;position:relative}.b3-segmented>label>input{opacity:0;block-size:100%;inline-size:100%;cursor:inherit;margin:0;position:absolute;inset:0}.b3-segmented>label:has(input:checked){background:var(--b3-color-surface);color:var(--b3-color-text);box-shadow:0 1px 2px #0000001f, 0 0 0 1px var(--b3-color-border)}.b3-segmented>label:has(input:focus-visible){box-shadow:inset 0 0 0 2px}@media (forced-colors:active){.b3-segmented>label{border:2px solid canvas}.b3-segmented>label:has(input:checked){border-color:canvastext}.b3-segmented>label:has(input:focus-visible){outline-offset:1px;outline:2px solid highlight}}.b3-segmented>label:has(input:disabled){opacity:.5;cursor:not-allowed}.b3-accordion{border-block-end:1px solid var(--b3-color-border)}.b3-accordion>summary{cursor:pointer;padding:1rem .25rem;font-weight:650}.b3-accordion>:not(summary){padding:0 .25rem 1rem}.b3-dropdown{display:inline-block;position:relative}.b3-dropdown>summary{list-style:none}.b3-dropdown>summary::-webkit-details-marker{display:none}.b3-dropdown-panel{z-index:20;border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);background:var(--b3-color-surface);max-block-size:70svh;inline-size:max-content;min-inline-size:min(12rem,85vw);max-inline-size:min(22rem,85vw);color:var(--b3-color-text);box-shadow:var(--b3-shadow-raised);padding:.5rem;position:absolute;inset-block-start:calc(100% + .5rem);inset-inline-start:0;overflow-y:auto}.b3-dialog{overscroll-behavior:contain;border:1px solid var(--b3-color-border);border-radius:var(--b3-radius-large);background:var(--b3-color-surface);max-block-size:calc(100svh - 2rem);inline-size:min(32rem,100% - 2rem);color:var(--b3-color-text);box-shadow:var(--b3-shadow-raised);padding:min(1.5rem,5vi);overflow:auto}.b3-dialog::backdrop{background:#101b17a6}.b3-dialog[data-b3-placement=end]{border-radius:0;block-size:100svh;max-block-size:100svh;inline-size:min(28rem,100%);max-inline-size:100%;margin:0;margin-inline-start:auto}@media (prefers-reduced-motion:no-preference){.b3-dialog[open]{animation:.28s cubic-bezier(.16,1,.3,1) b3-dialog-enter}.b3-dialog[open][data-b3-placement=end]{animation-name:b3-drawer-enter}.b3-dialog::backdrop,.b3-sheet::backdrop{transition:opacity .16s ease-out}.b3-dialog[data-b3-closing]::backdrop,.b3-sheet[data-b3-closing]::backdrop{opacity:0}@keyframes b3-dialog-enter{0%{opacity:0;transform:translateY(8px)scale(.96)}}@keyframes b3-drawer-enter{0%{opacity:0;translate:var(--b3-drawer-offset,1rem)}}.b3-dialog[dir=rtl][data-b3-placement=end],[dir=rtl] .b3-dialog[data-b3-placement=end]{--b3-drawer-offset:-1rem}}.b3-dialog>form{gap:var(--b3-space);flex-direction:column;margin:0;display:flex}.b3-dialog>form>header:has(>button:last-child){grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:.75rem;display:grid}.b3-dialog>form>header:has(>button:last-child)>:not(:last-child){grid-column:1;min-inline-size:0}.b3-dialog>form>header>button:last-child:not(:first-child){grid-area:1/2}.b3-dialog>form>footer{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:.75rem;display:flex}.b3-dialog>form>footer>[data-b3-emphasis=quiet]:first-child:not(:last-child){margin-inline-end:auto}.b3-tree{border-inline-start:1px solid var(--b3-color-border);padding-inline-start:1rem;list-style:none}.b3-tree summary{cursor:pointer;padding-block:.5rem}.b3-prose{overflow-wrap:anywhere;max-inline-size:65ch;line-height:1.75}.b3-prose :is(p,ul,ol,blockquote,pre){margin-block:1rem}.b3-prose :is(h1,h2,h3,h4,h5,h6){margin-block:1.75rem .75rem;line-height:1.25}.b3-prose :is(h5,h6){font-size:1em}.b3-prose>:first-child{margin-block-start:0}.b3-prose img{block-size:auto}.b3-prose blockquote{border-inline-start:3px solid var(--b3-color-accent);color:var(--b3-color-muted);margin-inline:0;padding-inline-start:1rem}.b3-prose pre{background:var(--b3-color-surface);border:1px solid var(--b3-color-border);border-radius:var(--b3-radius-small);padding:1rem;overflow-x:auto}.b3-visually-hidden{clip-path:inset(50%);white-space:nowrap;block-size:1px;inline-size:1px;position:absolute;overflow:hidden}.b3-skip-link{z-index:1000;border-radius:var(--b3-radius-small);background:var(--b3-color-accent);max-inline-size:calc(100% - 1.5rem);color:var(--b3-color-on-accent);padding:.75rem 1rem;font-weight:650;position:fixed;inset-block-start:max(.75rem, env(safe-area-inset-top,0px));inset-inline-start:.75rem}.b3-skip-link:not(:focus){clip-path:inset(50%);white-space:nowrap;block-size:1px;inline-size:1px;padding:0;overflow:hidden}.b3-hero{grid-template-columns:repeat(2,minmax(0,1fr));align-items:center;gap:clamp(2rem,6vw,6rem);padding-block:clamp(3rem,8vw,7rem);display:grid}.b3-hero[data-b3-align=center]{text-align:center;grid-template-columns:minmax(0,1fr);justify-items:center}.b3-hero-copy{gap:1.5rem;max-inline-size:40rem;display:grid}.b3-feature{grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:center;gap:clamp(1.5rem,5vw,5rem);padding-block:clamp(2rem,6vw,5rem);display:grid}.b3-feature[data-b3-reverse=true]>:first-child{order:2}.b3-feature-icon{border-radius:var(--b3-radius);background:var(--b3-color-accent);block-size:3rem;inline-size:3rem;color:var(--b3-color-on-accent);place-items:center;display:inline-grid}.b3-pricing{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius-large);background:var(--b3-color-surface);color:var(--b3-color-text);flex-direction:column;gap:1.25rem;padding:clamp(1.25rem,3vw,2rem);display:flex}.b3-pricing[data-b3-featured=true]{border:2px solid var(--b3-color-accent);box-shadow:var(--b3-shadow)}.b3-pricing>.b3-button:last-child{margin-block-start:auto}.b3-price{font-variant-numeric:tabular-nums;flex-wrap:wrap;align-items:baseline;gap:.375rem;display:flex}.b3-price>strong{letter-spacing:-.05em;font-size:3.25rem;line-height:1.1}.b3-checklist{gap:.75rem;margin:0;padding:0;list-style:none;display:grid}.b3-checklist>li{align-items:start;gap:.625rem;display:flex}.b3-checklist>li:before{content:"✓";color:var(--b3-color-accent);font-weight:700}.b3-quote{border-inline-start:3px solid var(--b3-color-accent);margin:0;padding:1.5rem}.b3-quote blockquote{margin:0 0 1rem;font-size:1.25rem;line-height:1.5}.b3-art{isolation:isolate;border-radius:var(--b3-radius-large);background:var(--b3-color-surface);position:relative;overflow:hidden}.b3-art:before{content:"";z-index:-1;pointer-events:none;position:absolute;inset:0}.b3-art[data-b3-art=dots]:before{background-image:radial-gradient(var(--b3-color-border) 1px,transparent 1px);background-size:16px 16px}.b3-art[data-b3-art=glow]:before{background:radial-gradient(ellipse at 20% 20%,color-mix(in srgb,var(--b3-color-accent) 20%,transparent),transparent 65%)}.b3-art[data-b3-art=wash]:before{background:linear-gradient(135deg,color-mix(in srgb,var(--b3-color-accent) 14%,transparent),transparent 70%)}@media (width<=48rem){.b3-hero,.b3-feature{grid-template-columns:minmax(0,1fr)}.b3-feature[data-b3-reverse=true]>:first-child{order:initial}}.b3-reading{grid-template-columns:minmax(0,1fr) minmax(12rem,16rem);grid-template-areas:"content outline";align-items:start;gap:clamp(2rem,5vw,5rem);display:grid}.b3-reading>:not(.b3-toc){grid-area:content;min-inline-size:0}.b3-toc{max-block-size:calc(100svh - var(--b3-outline-top,1rem) - 1rem);border-inline-start:1px solid var(--b3-color-border);grid-area:outline;padding-inline-start:1rem;font-size:.875rem;position:sticky;inset-block-start:var(--b3-outline-top,1rem);overflow-y:auto}.b3-toc ol{gap:.25rem;margin:.75rem 0 0;padding:0;list-style:none;display:grid}.b3-toc ol ol{margin:.25rem 0;padding-inline-start:1rem}.b3-toc-link{border-radius:var(--b3-radius-small);color:var(--b3-color-muted);overflow-wrap:anywhere;padding:.625rem .75rem;text-decoration:none;display:block}.b3-toc-link:hover{color:var(--b3-color-text);background:var(--b3-color-surface)}.b3-toc-link[aria-current=location]{color:var(--b3-color-accent);background:color-mix(in srgb, var(--b3-color-accent) 10%, var(--b3-color-surface));font-weight:650}@media (width<=48rem){.b3-reading{grid-template-columns:minmax(0,1fr);grid-template-areas:"outline""content";gap:2rem}.b3-toc{max-block-size:none;position:static}}.b3-command{inline-size:min(38rem,100% - 2rem);padding:0}.b3-command[open]:has(>.b3-scroll){grid-template-rows:auto minmax(0,1fr);display:grid}.b3-command>.b3-scroll>.b3-command-list{max-block-size:none;overflow:visible}.b3-command-search{border-block-end:1px solid var(--b3-color-border);flex-wrap:wrap;justify-content:end;align-items:center;gap:.75rem;padding:min(1rem,4vw);display:flex}.b3-command-search>input{flex:12ch;min-inline-size:0}.b3-command-list{overscroll-behavior:contain;max-block-size:50svh;padding:.5rem;overflow-y:auto}.b3-command-group+.b3-command-group{border-block-start:1px solid var(--b3-color-border);margin-block-start:.5rem;padding-block-start:.5rem}.b3-command-group>:first-child:not([role=option]){color:var(--b3-color-muted);padding:.5rem .75rem;font-size:.75rem;display:block}.b3-command-item{border-radius:var(--b3-radius-small);cursor:pointer;justify-content:space-between;align-items:center;gap:min(1rem,2vw);min-block-size:2.75rem;padding:min(.875rem,2svh) min(.75rem,3vw);display:flex}.b3-command-item>:first-child{overflow-wrap:anywhere;min-inline-size:0}.b3-command-item[aria-selected=true]{background:var(--b3-color-accent);color:var(--b3-color-on-accent)}.b3-command-item[aria-selected=true] .b3-muted{color:inherit}.b3-command-item[aria-disabled=true]{opacity:.5;cursor:not-allowed}.b3-command-item:not([aria-disabled=true]):hover{outline:1px solid var(--b3-color-border);outline-offset:-1px}.b3-form-grid{grid-template-columns:repeat(auto-fit,minmax(min(100%,14rem),1fr));align-items:start;gap:1.25rem;display:grid}.b3-form-grid[data-b3-layout=inline]{flex-wrap:wrap;align-items:end;display:flex}.b3-form-grid[data-b3-layout=inline]>.b3-field{flex:12rem;min-inline-size:0}.b3-form-grid[data-b3-layout=inline]>.b3-button{overflow-wrap:anywhere;flex:0 auto;max-inline-size:100%}.b3-fieldset{border:0;min-inline-size:0;margin:0;padding:0}.b3-fieldset>legend{padding:0 0 .75rem;font-weight:650}.b3-help{color:var(--b3-color-muted);font-size:.875rem}.b3-help[data-b3-tone=success]{color:var(--b3-color-success)}.b3-help[data-b3-tone=warning]{color:var(--b3-color-warning)}.b3-input[data-b3-size=small]{padding:.5rem .75rem}.b3-input[data-b3-size=large]{min-block-size:3.25rem;padding:.875rem 1rem}.b3-input[data-b3-emphasis=subtle]{background-color:var(--b3-color-canvas);border-color:#0000}.b3-input[data-b3-emphasis=outline]{background-color:#0000;border-width:2px}.b3-input[data-b3-tone=success]{border-color:var(--b3-color-success)}.b3-input[data-b3-tone=warning]{border-color:var(--b3-color-warning)}.b3-input[aria-invalid=true]{border-color:var(--b3-color-danger)}textarea.b3-input{resize:vertical}select.b3-input[multiple]{min-block-size:8rem}.b3-input[type=file]{padding:.375rem}.b3-input::file-selector-button{font:inherit;border-radius:var(--b3-radius-small);background:var(--b3-color-accent);color:var(--b3-color-on-accent);cursor:pointer;border:0;margin-inline-end:.75rem;padding:.5rem .75rem}.b3-input[type=color]{inline-size:5rem;padding:.25rem}.b3-range{min-block-size:2.75rem;inline-size:100%;accent-color:var(--b3-color-accent);margin:0}.b3-choice-card{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);background:var(--b3-color-surface);color:var(--b3-color-text);cursor:pointer;align-items:start;gap:.75rem;padding:1rem;display:flex}.b3-choice-card>input{flex:none;block-size:1.125rem;inline-size:1.125rem;margin-block-start:.3rem}.b3-choice-card:has(input:checked){border-color:var(--b3-color-accent);background:color-mix(in srgb, var(--b3-color-accent) 7%, var(--b3-color-surface));box-shadow:inset 0 0 0 1px var(--b3-color-accent)}.b3-choice-card:has(input:focus-visible){outline:none;box-shadow:inset 0 0 0 2px}.b3-choice-card:has(input:disabled){opacity:.5;cursor:not-allowed}.b3-choice-card:has(input[aria-invalid=true]){border-color:var(--b3-color-danger)}.b3-range[aria-invalid=true]{accent-color:var(--b3-color-danger)}.b3-stepper{flex-wrap:wrap;gap:1rem 2rem;margin:0;padding:0;list-style:none;display:flex}.b3-step{color:var(--b3-color-muted);align-items:center;gap:.625rem;font-size:.875rem;display:flex}.b3-step-marker{border:1px solid var(--b3-color-border);font-variant-numeric:tabular-nums;border-radius:50%;flex:none;place-items:center;block-size:2rem;inline-size:2rem;display:grid}.b3-step[aria-current=step]{color:var(--b3-color-text);font-weight:650}.b3-step[aria-current=step] .b3-step-marker{background:var(--b3-color-accent);color:var(--b3-color-on-accent);border-color:var(--b3-color-accent)}.b3-step[data-b3-complete=true] .b3-step-marker{border-color:var(--b3-color-success);color:var(--b3-color-success)}.b3-conversation{gap:1.5rem;min-inline-size:0;margin:0;padding:0;list-style:none;display:grid}.b3-message{justify-self:start;gap:.375rem;min-inline-size:0;max-inline-size:min(85%,38rem);display:grid}.b3-message[data-b3-side=self]{justify-self:end}.b3-message-body{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);background:var(--b3-color-surface);color:var(--b3-color-text);white-space:pre-wrap;overflow-wrap:anywhere;padding:.875rem 1rem}.b3-message[data-b3-side=self]>.b3-message-body{background:var(--b3-color-accent);color:var(--b3-color-on-accent);border-color:#0000}.b3-message-meta{color:var(--b3-color-muted);flex-wrap:wrap;gap:.35rem .75rem;font-size:.8125rem;display:flex}.b3-message[data-b3-side=self]>.b3-message-meta{justify-content:end}.b3-mobile-app{--b3-touch:44px;min-block-size:100svh;max-inline-size:var(--b3-app-width,70rem);margin-inline:auto;padding-block-end:calc(5.5rem + env(safe-area-inset-bottom,0px))}.b3-mobile-app[data-b3-platform=android]{--b3-touch:48px}.b3-mobile-app :where(button,.b3-button,select,input:not([type=checkbox],[type=radio])){min-block-size:var(--b3-touch)}.b3-app-bar{padding:max(.75rem, env(safe-area-inset-top,0px)) 1.25rem .75rem;align-items:center;gap:.75rem;display:flex}.b3-app-bar>:last-child:not(:first-child){margin-inline-start:auto}.b3-bottom-nav{z-index:10;max-inline-size:var(--b3-app-width,70rem);padding:.5rem clamp(.5rem, 2vw, 1rem) max(.5rem, env(safe-area-inset-bottom,0px));background:var(--b3-color-surface);color:var(--b3-color-text);border-block-start:1px solid var(--b3-color-border);flex-wrap:wrap;justify-content:space-around;gap:.5rem;margin-inline:auto;display:flex;position:fixed;inset-block-end:0;inset-inline:0}.b3-bottom-link{overflow-wrap:anywhere;text-align:center;min-block-size:var(--b3-touch,44px);min-inline-size:0;color:var(--b3-color-muted);border-radius:1rem;flex-direction:column;flex:5ch;justify-content:center;align-items:center;gap:.15rem;padding:.25rem clamp(.125rem,1vw,.5rem);font-size:.75rem;text-decoration:none;display:flex}.b3-bottom-link:has(svg){justify-content:flex-start}.b3-bottom-link[aria-current=page]{color:var(--b3-color-accent);font-weight:700}[data-b3-platform=android] .b3-bottom-link[aria-current=page]{background:var(--b3-color-canvas)}.b3-mobile-list{border:1px solid var(--b3-color-border);background:var(--b3-color-surface);border-radius:1rem;margin:0;padding:0;list-style:none;overflow:hidden}.b3-mobile-list>li+li{border-block-start:1px solid var(--b3-color-border)}.b3-mobile-row{min-block-size:var(--b3-touch,44px);color:var(--b3-color-text);align-items:center;gap:1rem;padding:.875rem 1rem;text-decoration:none;display:flex}.b3-mobile-row>:last-child:not(:first-child){margin-inline-start:auto}.b3-sheet{inline-size:min(100%, var(--b3-overlay-width,32rem));max-inline-size:none;max-block-size:min(85dvh, calc(100dvh - env(safe-area-inset-top,0px) - 1rem));overscroll-behavior:contain;border:1px solid var(--b3-color-border);padding:1.5rem min(1.5rem, 5vi) max(1.5rem, env(safe-area-inset-bottom,0px));background:var(--b3-color-surface);color:var(--b3-color-text);border-radius:1.5rem 1.5rem 0 0;margin:0 auto;inset:auto 0 0;overflow-y:auto}.b3-sheet::backdrop{background:#0007}.b3-sheet [data-b3-initial-focus]:focus{box-shadow:none;outline:none}.b3-sheet-handle{min-block-size:44px;inline-size:5rem;color:var(--b3-color-muted);touch-action:none;cursor:grab;background:0 0;border:0;border-radius:1rem;flex:none;align-self:center;place-items:center;margin-block:-1rem -.5rem;padding:0;display:grid}.b3-sheet-handle:before{content:"";background:currentColor;border-radius:1rem;block-size:.25rem;inline-size:2.25rem}.b3-sheet[data-b3-dragging]{animation:none}.b3-sheet[data-b3-dragging] .b3-sheet-handle{cursor:grabbing}[data-b3-platform=android] .b3-mobile-list{border-radius:1.5rem}@media (prefers-reduced-motion:no-preference){.b3-sheet[open]{animation:.32s cubic-bezier(.16,1,.3,1) b3-sheet-enter}@keyframes b3-sheet-enter{0%{opacity:0;transform:translateY(3rem)}to{opacity:1;transform:translateY(0)}}}.b3-root:has(dialog:modal){overflow:hidden}.b3-bottom-link[aria-current=page]{background:color-mix(in srgb, var(--b3-color-accent) 12%, transparent)}.b3-bottom-link :where(svg:not([width])){flex:none;block-size:1.375rem;inline-size:1.375rem}.b3-app-frame{inline-size:100%;block-size:var(--b3-frame-height,100dvh);min-block-size:0;max-inline-size:var(--b3-app-width,70rem);padding:env(safe-area-inset-top,0px) env(safe-area-inset-right,0px) env(safe-area-inset-bottom,0px) env(safe-area-inset-left,0px);grid-template-rows:auto minmax(0,1fr) auto;margin-inline:auto;display:grid;overflow:clip}.b3-app-frame>header{grid-row:1;min-inline-size:0}.b3-app-frame>main{overscroll-behavior:contain;grid-area:2/1;min-block-size:0;min-inline-size:0;overflow:auto}.b3-app-frame>:is(nav,footer){grid-row:3;min-inline-size:0;position:relative}.b3-app-frame>.b3-toast-region{overscroll-behavior:contain;grid-area:2/1;place-self:end;min-block-size:0;max-block-size:calc(100% - 2rem);margin:1rem;position:relative;inset:auto;overflow-y:auto}.b3-app-frame>.b3-app-bar{padding-block-start:.75rem}.b3-app-frame:has(>.b3-bottom-nav,>footer .b3-bottom-nav){padding-block-end:0}.b3-app-frame .b3-bottom-nav{inline-size:100%;max-inline-size:none;padding-block-end:max(.5rem, env(safe-area-inset-bottom,0px));position:static}}@property --b3-reel-fade{syntax:"<length>";inherits:false;initial-value:2.5rem}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
// src/elements/markdown-stream.js
|
|
2
|
+
var esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
3
|
+
var parseAttrs = (s) => Object.fromEntries([...(s || "").matchAll(/(\w+)=(?:"([^"]*)"|(\S+))/g)].map((m) => [m[1], m[2] ?? m[3]]));
|
|
4
|
+
var isNum = (s) => /^[-+$€£]?[\d,.]+%?$/.test(String(s).trim());
|
|
5
|
+
var toneAttr = (tone) => tone ? ` data-b3-tone="${esc(tone)}"` : "";
|
|
6
|
+
var directives = {
|
|
7
|
+
callout(t, ctx) {
|
|
8
|
+
const title = t.attrs.title ? `<strong>${esc(t.attrs.title)}</strong>` : "";
|
|
9
|
+
return `<div class="b3-alert"${toneAttr(t.attrs.tone)}><div>${title}${ctx.parse(t.tokens)}</div></div>`;
|
|
10
|
+
},
|
|
11
|
+
stats(t, ctx) {
|
|
12
|
+
const inline = ctx?.inline ?? esc;
|
|
13
|
+
const cards = t.body.split(`
|
|
14
|
+
`).filter((l) => l.trim()).map((line) => {
|
|
15
|
+
const [label, value, note = ""] = line.replace(/^\s*\|/, "").replace(/\|\s*$/, "").split("|").map((s) => s.trim());
|
|
16
|
+
const tone = /^[↑+]/.test(note) ? "success" : /^[↓-]/.test(note) ? "danger" : "";
|
|
17
|
+
return `<div class="b3-stat b3-surface"><span class="b3-label">${inline(label)}</span><strong class="b3-stat-value">${value ? inline(value.replace(/^\*\*(.*)\*\*$/, "$1")) : "…"}</strong>${note ? `<span class="b3-badge"${toneAttr(tone)}>${inline(note)}</span>` : ""}</div>`;
|
|
18
|
+
});
|
|
19
|
+
return `<div class="b3-md-stats">${cards.join("")}</div>`;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var fences = {
|
|
23
|
+
chart(text) {
|
|
24
|
+
const rows = text.split(`
|
|
25
|
+
`).map((l) => l.split(":")).filter((p) => p.length > 1 && !isNaN(parseFloat(p[1])));
|
|
26
|
+
const max = Math.max(...rows.map((r) => parseFloat(r[1])), 1);
|
|
27
|
+
return `<figure class="b3-md-bars b3-surface">${rows.map(([k, v]) => `<div class="b3-md-bar"><span>${esc(k.trim())}</span><i style="inline-size:${Math.max(parseFloat(v), 0) / max * 100}%"></i><b>${esc(v.trim())}</b></div>`).join("")}</figure>`;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
directives.callout.doc = '`:::callout{tone=info|success|warning|danger title="Short title"}` newline, markdown body, newline `:::` — a highlighted note, tip, warning or recommendation.';
|
|
31
|
+
directives.stats.doc = "`:::stats` newline, one card per line `Label | Value | note`, newline `:::` — headline numbers; a note starting with ↑ or + shows as good, ↓ or - as bad.";
|
|
32
|
+
fences.chart.doc = "```chart fence, one bar per line `Label: number` (plain number, no units) — a horizontal bar chart for comparing a few values.";
|
|
33
|
+
function promptGuide(options = {}) {
|
|
34
|
+
const all = { ...directives, ...options.directives, ...fences, ...options.fences };
|
|
35
|
+
const tones = Object.entries(options.tones ?? new MarkdownStreamOptions().tones).reduce((by, [word, tone]) => ((by[tone] ??= []).push(word), by), {});
|
|
36
|
+
return [
|
|
37
|
+
"Your replies are Markdown rendered live into UI components. Use GitHub-flavoured Markdown (headings, **bold**, lists, links, `inline code`, fenced code with a language, > quotes) plus:",
|
|
38
|
+
`- Tables: GFM pipe tables with a header row and a \`| --- |\` delimiter row. Never add a Status column the data does not already have. Only when a row really has one of these states (e.g. an invoice that is paid), write the plain word alone in the cell and it is shown as a coloured badge automatically (no images, emoji or badge URLs): ${Object.entries(tones).map(([tone, words]) => `${words.join(", ")} (${tone})`).join("; ")}. Numeric columns align right.`,
|
|
39
|
+
"- Task lists: `- [x] done` and `- [ ] to do`.",
|
|
40
|
+
...Object.values(all).filter((render) => render?.doc).map((render) => `- ${render.doc}`),
|
|
41
|
+
"Use a component only when it makes the answer clearer; plain prose is fine. Put every block on its own lines with a blank line around it, always close ::: and ``` blocks, and never write raw HTML."
|
|
42
|
+
].join(`
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
var codeBlock = (text, lang) => `<div class="b3-md-code"><header><span class="b3-muted">${esc(lang || "text")}</span><button type="button" class="b3-button" data-b3-emphasis="quiet" data-b3-md-copy>Copy</button></header><pre><code>${esc(text)}</code></pre></div>`;
|
|
46
|
+
function settle(buf) {
|
|
47
|
+
let s = buf;
|
|
48
|
+
const lines = s.split(`
|
|
49
|
+
`);
|
|
50
|
+
const tail = [];
|
|
51
|
+
while (lines.length && /^\s*\|/.test(lines.at(-1)))
|
|
52
|
+
tail.unshift(lines.pop());
|
|
53
|
+
const complete = buf.endsWith(`
|
|
54
|
+
`) ? tail : tail.slice(0, -1);
|
|
55
|
+
let pendingTable = false;
|
|
56
|
+
if (tail.length && !complete.some((l) => /^\s*\|?\s*:?-{2,}/.test(l))) {
|
|
57
|
+
pendingTable = true;
|
|
58
|
+
s = lines.join(`
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
s = s.replace(/(^|\n) {0,3}(`{3}[^\n]*|~{3}[^\n]*|:{3}[^\n]*|`{1,2}|~{1,2}|:{1,2})$/, "$1");
|
|
62
|
+
if ((s.match(/^ {0,3}(`{3}|~{3})/gm) || []).length % 2)
|
|
63
|
+
return { src: s, pendingTable };
|
|
64
|
+
s = s.replace(/\[([^\]\n]*)\]?\([^)\n]*$/, "$1").replace(/\[([^\]\n]*)$/, "$1").replace(/(\*{1,3}|_{1,3}|~{1,2}|`)$/, "");
|
|
65
|
+
const fenceEnd = [...s.matchAll(/^ {0,3}(`{3}|~{3})[^\n]*\n?/gm)].at(-1);
|
|
66
|
+
const para = s.slice(Math.max(s.lastIndexOf(`
|
|
67
|
+
|
|
68
|
+
`) + 1, fenceEnd ? fenceEnd.index + fenceEnd[0].length : 0));
|
|
69
|
+
let close = "";
|
|
70
|
+
for (const m of ["`", "~~"])
|
|
71
|
+
if (para.split(m).length % 2 === 0)
|
|
72
|
+
close = m + close;
|
|
73
|
+
close += openEmphasis(para);
|
|
74
|
+
return { src: close ? s.trimEnd() + close : s, pendingTable };
|
|
75
|
+
}
|
|
76
|
+
function openEmphasis(para) {
|
|
77
|
+
const bare = para.replace(/`[^`\n]*`?/g, "").replace(/\\[*_]/g, "").replace(/\]\([^)\n]*\)?|https?:\/\/\S*/g, "]");
|
|
78
|
+
const stack = [];
|
|
79
|
+
for (const { 0: run, index } of bare.matchAll(/\*+|_+/g)) {
|
|
80
|
+
const before = bare[index - 1] ?? " ", after = bare[index + run.length] ?? " ";
|
|
81
|
+
let n = run.length;
|
|
82
|
+
if (/\S/.test(before) && (run[0] === "*" || !/\w/.test(after))) {
|
|
83
|
+
while (n && stack.at(-1)?.[0] === run[0]) {
|
|
84
|
+
const top = stack.pop();
|
|
85
|
+
const k = Math.min(n, top.length);
|
|
86
|
+
n -= k;
|
|
87
|
+
if (top.length > k)
|
|
88
|
+
stack.push(top.slice(k));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const opens = /[\p{L}\p{N}]/u.test(after) || run.length > 1 && /\s/.test(before) && /^[^\s\w*_-]\S*$/.test(bare.slice(index + run.length));
|
|
92
|
+
if (n && opens && !(/\w/.test(before) && (run[0] === "_" || n === 1)))
|
|
93
|
+
stack.push(run.slice(0, n));
|
|
94
|
+
}
|
|
95
|
+
return stack.reverse().join("");
|
|
96
|
+
}
|
|
97
|
+
var ALERTS = { note: "info", info: "info", tip: "success", important: "info", warning: "warning", caution: "danger", danger: "danger" };
|
|
98
|
+
var STYLE_ID = "b3-markdown-stream-style";
|
|
99
|
+
var STYLE = `
|
|
100
|
+
.b3-md-appear{animation:b3-md-in var(--b3-duration,.2s) ease-out;}
|
|
101
|
+
@keyframes b3-md-in{from{opacity:0;transform:translateY(3px);}}
|
|
102
|
+
.b3-md-caret{display:inline-block;inline-size:.5em;block-size:1em;vertical-align:-.15em;margin-inline-start:2px;background:currentColor;animation:b3-md-blink 1s steps(2) infinite;}
|
|
103
|
+
@keyframes b3-md-blink{50%{opacity:0;}}
|
|
104
|
+
.b3-md-code{border:1px solid var(--b3-color-border);border-radius:var(--b3-radius);overflow:hidden;}
|
|
105
|
+
.b3-md-code>header{display:flex;justify-content:space-between;align-items:center;gap:.5rem;padding:.25rem .5rem .25rem .75rem;font-size:.8rem;background:var(--b3-color-surface);border-block-end:1px solid var(--b3-color-border);}
|
|
106
|
+
.b3-md-code>pre{margin:0;padding:.75rem;overflow-x:auto;}
|
|
107
|
+
.b3-md-bars{display:grid;gap:.4rem;margin-inline:0;padding:var(--b3-space,1rem);}
|
|
108
|
+
.b3-md-bar{display:grid;grid-template-columns:minmax(0,7rem) 1fr 3rem;gap:.5rem;align-items:center;font-size:.9rem;}
|
|
109
|
+
.b3-md-bar>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
110
|
+
.b3-md-bar>i{display:block;block-size:.7rem;border-radius:999px;background:var(--b3-color-accent);transition:inline-size var(--b3-duration,.2s);}
|
|
111
|
+
.b3-md-bar>b{text-align:end;font-variant-numeric:tabular-nums;}
|
|
112
|
+
.b3-md-stats{display:grid;gap:.75rem;grid-template-columns:repeat(auto-fit,minmax(min(10rem,100%),1fr));}
|
|
113
|
+
.b3-md-tasks{list-style:none;padding-inline-start:0;}
|
|
114
|
+
.b3-md-tasks input{margin-inline-end:.5rem;vertical-align:-.1em;}
|
|
115
|
+
.b3-md-tasks>li>input+p{display:inline;}
|
|
116
|
+
.b3-md-num{text-align:end;font-variant-numeric:tabular-nums;}
|
|
117
|
+
.b3-md-pending{display:grid;gap:.5rem;}
|
|
118
|
+
.b3-md-pending>.b3-skeleton{block-size:1.5rem;}
|
|
119
|
+
.b3-md-pending>.b3-skeleton+.b3-skeleton{inline-size:80%;}
|
|
120
|
+
@media (prefers-reduced-motion:reduce){.b3-md-appear,.b3-md-caret{animation:none;}}
|
|
121
|
+
`;
|
|
122
|
+
function ensureStyle(document2) {
|
|
123
|
+
if (!document2?.head || document2.getElementById(STYLE_ID))
|
|
124
|
+
return;
|
|
125
|
+
const style = document2.createElement("style");
|
|
126
|
+
style.id = STYLE_ID;
|
|
127
|
+
style.textContent = STYLE;
|
|
128
|
+
document2.head.append(style);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
class MarkdownStreamOptions {
|
|
132
|
+
Marked = null;
|
|
133
|
+
sanitize = null;
|
|
134
|
+
directives = directives;
|
|
135
|
+
fences = fences;
|
|
136
|
+
code = codeBlock;
|
|
137
|
+
tones = { paid: "success", done: "success", ok: "success", active: "success", pending: "warning", overdue: "danger", failed: "danger", blocked: "danger", draft: "info" };
|
|
138
|
+
html = "escape";
|
|
139
|
+
markedOptions = {};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
class MarkdownStream {
|
|
143
|
+
el = null;
|
|
144
|
+
buf = "";
|
|
145
|
+
nodes = [];
|
|
146
|
+
ended = false;
|
|
147
|
+
#md;
|
|
148
|
+
#onClick = (event) => this.#copy(event);
|
|
149
|
+
constructor(options) {
|
|
150
|
+
this.options = { ...new MarkdownStreamOptions, ...options };
|
|
151
|
+
this.options.directives = { ...directives, ...options?.directives };
|
|
152
|
+
this.options.fences = { ...fences, ...options?.fences };
|
|
153
|
+
const { Marked, sanitize } = this.options;
|
|
154
|
+
if (typeof Marked !== "function")
|
|
155
|
+
throw new TypeError('MarkdownStream: options.Marked (from "marked") is required');
|
|
156
|
+
if (typeof sanitize !== "function")
|
|
157
|
+
throw new TypeError("MarkdownStream: options.sanitize is required (e.g. html => DOMPurify.sanitize(html))");
|
|
158
|
+
this.#md = this.#createParser();
|
|
159
|
+
}
|
|
160
|
+
attach(el) {
|
|
161
|
+
this.detach();
|
|
162
|
+
this.el = el;
|
|
163
|
+
this.buf = "";
|
|
164
|
+
this.nodes = [];
|
|
165
|
+
this.ended = false;
|
|
166
|
+
ensureStyle(el.ownerDocument);
|
|
167
|
+
el.addEventListener("click", this.#onClick);
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
detach() {
|
|
171
|
+
this.el?.removeEventListener("click", this.#onClick);
|
|
172
|
+
this.el = null;
|
|
173
|
+
}
|
|
174
|
+
push(chunk) {
|
|
175
|
+
if (this.ended) {
|
|
176
|
+
console.warn("MarkdownStream: push() after end() ignored; call attach(el) to start a new stream");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.buf += chunk;
|
|
180
|
+
this.render(true);
|
|
181
|
+
}
|
|
182
|
+
end() {
|
|
183
|
+
this.ended = true;
|
|
184
|
+
this.render(false);
|
|
185
|
+
}
|
|
186
|
+
toHtml(src) {
|
|
187
|
+
return this.options.sanitize(this.#md.parse(src));
|
|
188
|
+
}
|
|
189
|
+
render(streaming) {
|
|
190
|
+
const { el } = this;
|
|
191
|
+
if (!el)
|
|
192
|
+
throw new Error("MarkdownStream: call attach(el) before push()");
|
|
193
|
+
const doc = el.ownerDocument;
|
|
194
|
+
const { src, pendingTable } = streaming ? settle(this.buf) : { src: this.buf, pendingTable: false };
|
|
195
|
+
const tokens = this.#md.lexer(src).filter((t) => t.type !== "space");
|
|
196
|
+
tokens.forEach((tok, i) => {
|
|
197
|
+
const prev = this.nodes[i];
|
|
198
|
+
if (prev && prev.raw === tok.raw)
|
|
199
|
+
return;
|
|
200
|
+
const holder = doc.createElement("div");
|
|
201
|
+
holder.innerHTML = this.options.sanitize(this.#md.parser([tok]));
|
|
202
|
+
const only = holder.childElementCount === 1 && [...holder.childNodes].every((n) => n.nodeType === 1 || !n.textContent.trim()) ? holder.firstElementChild : null;
|
|
203
|
+
const node = only ?? holder;
|
|
204
|
+
if (!prev)
|
|
205
|
+
node.classList.add("b3-md-appear");
|
|
206
|
+
prev ? prev.el.replaceWith(node) : el.append(node);
|
|
207
|
+
this.nodes[i] = { raw: tok.raw, el: node };
|
|
208
|
+
});
|
|
209
|
+
this.nodes.splice(tokens.length).forEach((n) => n.el.remove());
|
|
210
|
+
el.querySelector(":scope .b3-md-caret, :scope > .b3-md-pending")?.remove();
|
|
211
|
+
if (pendingTable)
|
|
212
|
+
el.insertAdjacentHTML("beforeend", '<div class="b3-md-pending" aria-hidden="true"><div class="b3-skeleton"></div><div class="b3-skeleton"></div></div>');
|
|
213
|
+
else if (streaming) {
|
|
214
|
+
const last = this.nodes.at(-1)?.el;
|
|
215
|
+
const at = last?.matches("p, li, td, th, pre") ? last : last?.querySelector("p:last-of-type, li:last-child, td:last-child") || last || el;
|
|
216
|
+
at.insertAdjacentHTML("beforeend", '<span class="b3-md-caret" aria-hidden="true"></span>');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
#copy(event) {
|
|
220
|
+
const button = event.target.closest?.("[data-b3-md-copy]");
|
|
221
|
+
if (!button || !this.el?.contains(button))
|
|
222
|
+
return;
|
|
223
|
+
const text = button.closest(".b3-md-code")?.querySelector("code")?.textContent ?? "";
|
|
224
|
+
const show = (label) => {
|
|
225
|
+
button.textContent = label;
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
button.textContent = "Copy";
|
|
228
|
+
}, 1200);
|
|
229
|
+
};
|
|
230
|
+
const fail = (error) => {
|
|
231
|
+
console.warn("MarkdownStream: copy failed", error);
|
|
232
|
+
show("Copy failed");
|
|
233
|
+
};
|
|
234
|
+
const clip = this.el.ownerDocument.defaultView?.navigator?.clipboard;
|
|
235
|
+
if (clip?.writeText)
|
|
236
|
+
clip.writeText(text).then(() => show("Copied"), fail);
|
|
237
|
+
else
|
|
238
|
+
fail(new Error("navigator.clipboard is unavailable (insecure context?)"));
|
|
239
|
+
}
|
|
240
|
+
#createParser() {
|
|
241
|
+
const opts = this.options;
|
|
242
|
+
const directive = {
|
|
243
|
+
name: "directive",
|
|
244
|
+
level: "block",
|
|
245
|
+
start: (src) => src.match(/^:::/m)?.index,
|
|
246
|
+
tokenizer(src) {
|
|
247
|
+
const m = /^:::(\w+)(?:\{([^}\n]*)\})?[^\n]*(?:\n|$)/.exec(src);
|
|
248
|
+
if (!m)
|
|
249
|
+
return;
|
|
250
|
+
const lines = src.slice(m[0].length).split(`
|
|
251
|
+
`);
|
|
252
|
+
let fence = null, end = lines.length;
|
|
253
|
+
for (let i = 0;i < lines.length; i++) {
|
|
254
|
+
const f = /^ {0,3}(`{3,}|~{3,})/.exec(lines[i])?.[1];
|
|
255
|
+
if (fence) {
|
|
256
|
+
if (f && f[0] === fence[0] && f.length >= fence.length && !lines[i].trim().slice(f.length))
|
|
257
|
+
fence = null;
|
|
258
|
+
} else if (f)
|
|
259
|
+
fence = f;
|
|
260
|
+
else if (/^:::[^\S\n]*$/.test(lines[i])) {
|
|
261
|
+
end = i;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const body = lines.slice(0, end).join(`
|
|
266
|
+
`).replace(/\n$/, "");
|
|
267
|
+
const closed = end < lines.length;
|
|
268
|
+
const raw = m[0] + lines.slice(0, closed ? end + 1 : end).join(`
|
|
269
|
+
`) + (closed && end + 1 < lines.length ? `
|
|
270
|
+
` : "");
|
|
271
|
+
return { type: "directive", raw, name: m[1], attrs: parseAttrs(m[2]), body, tokens: this.lexer.blockTokens(body, []) };
|
|
272
|
+
},
|
|
273
|
+
renderer(t) {
|
|
274
|
+
const render = opts.directives[t.name] ?? opts.directives.callout;
|
|
275
|
+
return render(t, { parse: (tokens) => this.parser.parse(tokens), parseInline: (tokens) => this.parser.parseInline(tokens), inline: (text) => md.parseInline(text), esc });
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
const md = new opts.Marked({ gfm: true, extensions: [directive] });
|
|
279
|
+
md.use({
|
|
280
|
+
renderer: {
|
|
281
|
+
blockquote(t) {
|
|
282
|
+
const m = /^\s*(?:\[!(\w+)\][^\S\n]*\n?|\*\*(\w+):?\*\*:?[^\S\n]*)/.exec(t.text);
|
|
283
|
+
const kind = (m?.[1] ?? m?.[2])?.toLowerCase();
|
|
284
|
+
if (!Object.hasOwn(ALERTS, kind))
|
|
285
|
+
return false;
|
|
286
|
+
const title = kind[0].toUpperCase() + kind.slice(1);
|
|
287
|
+
return opts.directives.callout({ attrs: { tone: ALERTS[kind], title }, tokens: md.lexer(t.text.slice(m[0].length)) }, { parse: (tokens) => this.parser.parse(tokens), parseInline: (tokens) => this.parser.parseInline(tokens), inline: (text) => md.parseInline(text), esc });
|
|
288
|
+
},
|
|
289
|
+
table(t) {
|
|
290
|
+
const numeric = t.header.map((_, i) => t.rows.length > 0 && t.rows.every((r) => isNum(r[i]?.text || "0")));
|
|
291
|
+
const cell = (c, i, tag) => {
|
|
292
|
+
const html = this.parser.parseInline(c.tokens);
|
|
293
|
+
const tone = tag === "td" && opts.tones[c.text.trim().toLowerCase()];
|
|
294
|
+
const body = tone ? `<span class="b3-badge" data-b3-tone="${esc(tone)}">${html}</span>` : html;
|
|
295
|
+
return `<${tag}${tag === "th" ? ' scope="col"' : ""}${numeric[i] ? ' class="b3-md-num"' : ""}>${body}</${tag}>`;
|
|
296
|
+
};
|
|
297
|
+
return `<div class="b3-table-wrap" tabindex="0" role="region" aria-label="Table"><table class="b3-table"><thead><tr>${t.header.map((c, i) => cell(c, i, "th")).join("")}</tr></thead><tbody>${t.rows.map((r) => `<tr>${r.map((c, i) => cell(c, i, "td")).join("")}</tr>`).join("")}</tbody></table></div>`;
|
|
298
|
+
},
|
|
299
|
+
code({ text, lang }) {
|
|
300
|
+
const name = (lang || "").trim().split(/\s/)[0];
|
|
301
|
+
const fence = Object.hasOwn(opts.fences, name) ? opts.fences[name] : null;
|
|
302
|
+
return fence ? fence(text, { esc, lang: name }) : opts.code(text, name);
|
|
303
|
+
},
|
|
304
|
+
html(t) {
|
|
305
|
+
return opts.html === "render" ? false : t.block ? `<p>${esc(t.text.trim())}</p>` : esc(t.text);
|
|
306
|
+
},
|
|
307
|
+
list(t) {
|
|
308
|
+
if (!t.items.some((i) => i.task))
|
|
309
|
+
return false;
|
|
310
|
+
return `<ul class="b3-md-tasks">${t.items.map((i) => {
|
|
311
|
+
const tokens = i.tokens.filter((k) => k.type !== "checkbox");
|
|
312
|
+
const head = tokens.find((k) => k.type === "text" || k.type === "paragraph");
|
|
313
|
+
if (head?.tokens)
|
|
314
|
+
head.tokens = head.tokens.filter((k) => k.type !== "checkbox");
|
|
315
|
+
const body = tokens.map((k) => k.type === "text" && !i.loose ? this.parser.parseInline(k.tokens ?? [k]) : this.parser.parse([k])).join("");
|
|
316
|
+
return `<li><input type="checkbox" disabled${i.checked ? " checked" : ""}>${body}</li>`;
|
|
317
|
+
}).join("")}</ul>`;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
if (opts.markedOptions && Object.keys(opts.markedOptions).length)
|
|
322
|
+
md.use(opts.markedOptions);
|
|
323
|
+
return md;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function stickToBottom(scroller = document.scrollingElement, { threshold = 80 } = {}) {
|
|
327
|
+
const doc = scroller.ownerDocument ?? scroller;
|
|
328
|
+
const win = doc.defaultView;
|
|
329
|
+
const target = scroller === doc.scrollingElement ? win : scroller;
|
|
330
|
+
let pinned = true, lastTop = scroller.scrollTop;
|
|
331
|
+
const measure = () => {
|
|
332
|
+
const top = scroller.scrollTop;
|
|
333
|
+
if (scroller.scrollHeight - top - scroller.clientHeight >= threshold)
|
|
334
|
+
pinned = false;
|
|
335
|
+
else if (top >= lastTop)
|
|
336
|
+
pinned = true;
|
|
337
|
+
lastTop = top;
|
|
338
|
+
};
|
|
339
|
+
const up = (e) => {
|
|
340
|
+
if (e.target?.closest?.('input, textarea, select, [contenteditable]:not([contenteditable="false"])'))
|
|
341
|
+
return;
|
|
342
|
+
if (scroller.scrollTop > 0 && (e.deltaY < 0 || /^(ArrowUp|PageUp|Home)$/.test(e.key)))
|
|
343
|
+
pinned = false;
|
|
344
|
+
};
|
|
345
|
+
const on = [["scroll", measure], ["wheel", up], ["keydown", up]];
|
|
346
|
+
on.forEach(([type, f]) => target.addEventListener(type, f, { passive: true }));
|
|
347
|
+
return {
|
|
348
|
+
get pinned() {
|
|
349
|
+
return pinned;
|
|
350
|
+
},
|
|
351
|
+
stick(force = false) {
|
|
352
|
+
if (force || pinned) {
|
|
353
|
+
scroller.scrollTop = scroller.scrollHeight;
|
|
354
|
+
pinned = true;
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
destroy() {
|
|
358
|
+
on.forEach(([type, f]) => target.removeEventListener(type, f));
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
export {
|
|
363
|
+
MarkdownStream,
|
|
364
|
+
MarkdownStreamOptions,
|
|
365
|
+
codeBlock,
|
|
366
|
+
directives,
|
|
367
|
+
esc,
|
|
368
|
+
fences,
|
|
369
|
+
isNum,
|
|
370
|
+
parseAttrs,
|
|
371
|
+
promptGuide,
|
|
372
|
+
settle,
|
|
373
|
+
stickToBottom
|
|
374
|
+
};
|
package/dist/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.0-alpha.
|
|
3
|
-
"cssSha256": "
|
|
2
|
+
"version": "0.1.0-alpha.57",
|
|
3
|
+
"cssSha256": "5b3fec9da13833361cfa59510dd9bcf25719455f1b20cf6f7692d58a45303fe7",
|
|
4
4
|
"classes": [
|
|
5
5
|
"b3-accordion",
|
|
6
6
|
"b3-alert",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"b3-root",
|
|
75
75
|
"b3-row",
|
|
76
76
|
"b3-scroll",
|
|
77
|
+
"b3-segmented",
|
|
77
78
|
"b3-sheet",
|
|
78
79
|
"b3-sheet-handle",
|
|
79
80
|
"b3-shell",
|
|
@@ -286,6 +287,22 @@
|
|
|
286
287
|
"guidance": "Optional mount() wires [role=tablist][data-b3-tabs]. Use native buttons with role=tab, unique id, aria-controls, aria-selected and matching role=tabpanel with aria-labelledby. Inactive panels start hidden. Arrow keys, Home/End and RTL direction select and focus tabs; disabled tabs are skipped. Native Tab exits the tablist. Tabs remain in a horizontal scroller; long labels wrap within its width, and keyboard selection immediately reveals the active tab without a smooth-scroll delay.",
|
|
287
288
|
"example": "<div class=\"b3-tablist\" role=\"tablist\" aria-label=\"Project\" data-b3-tabs><button class=\"b3-tab\" id=\"tab-a\" role=\"tab\" aria-controls=\"panel-a\" aria-selected=\"true\">Summary</button><button class=\"b3-tab\" id=\"tab-b\" role=\"tab\" aria-controls=\"panel-b\" aria-selected=\"false\" tabindex=\"-1\">Activity</button></div><section class=\"b3-tabpanel\" id=\"panel-a\" role=\"tabpanel\" aria-labelledby=\"tab-a\">Summary content</section><section class=\"b3-tabpanel\" id=\"panel-b\" role=\"tabpanel\" aria-labelledby=\"tab-b\" hidden>Activity content</section>"
|
|
288
289
|
},
|
|
290
|
+
{
|
|
291
|
+
"id": "segmented",
|
|
292
|
+
"title": "Segmented choice",
|
|
293
|
+
"classes": [
|
|
294
|
+
"b3-segmented"
|
|
295
|
+
],
|
|
296
|
+
"guidance": "One choice among 2-5 peers that filters or switches the current view (Day/Week, All/Open/Done). It is a radio group, not tabs: use tabs only when each option owns a role=tabpanel. Author native radios sharing one name, each inside its label; wrap them in div.b3-segmented role=radiogroup with aria-label (or fieldset.b3-segmented with a b3-visually-hidden legend). React to the change event and read the checked value; arrow keys, focus, form values and reset stay native. Each option is at least 44px tall; the checked option lifts onto the surface. Options that do not fit wrap onto another row at full height rather than squeezing or hiding; past five options use a select. Keep labels short; an icon beside the text is fine.",
|
|
297
|
+
"example": "<div class=\"b3-segmented\" role=\"radiogroup\" aria-label=\"Range\"><label><input type=\"radio\" name=\"range\" value=\"day\" checked>Day</label><label><input type=\"radio\" name=\"range\" value=\"week\">Week</label><label><input type=\"radio\" name=\"range\" value=\"month\">Month</label></div>"
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"id": "confirm",
|
|
301
|
+
"title": "Confirm a destructive action",
|
|
302
|
+
"classes": [],
|
|
303
|
+
"guidance": "window.confirm/alert/prompt are unavailable in sandboxed frames and must not guard anything. Put data-b3-confirm on the button that deletes, resets, discards or removes. The first press arms it: the label swaps to the attribute value (e.g. data-b3-confirm=\"Tap again to delete\"), a danger ring appears and a live region announces it. A second press within 4 seconds runs the click; timeout, Escape, blur or pressing elsewhere disarms and restores the original content. mount() intercepts the arming press in the capture phase, so write the ordinary click handler (direct or delegated) with no confirmation logic: it only runs on the confirming press. Only click is guarded: a pointerdown, touchstart or mousedown handler runs on the first press and bypasses it. A repeat within 350ms or a held key is ignored, so a double tap never confirms. Any re-render or change of the item cancels the arm and the next press only re-arms, so never re-render the list on a timer while the user can act on it. An empty value keeps the content (use it on icon-only buttons with an aria-label). Forms and links are also held until confirmed. Use a dialog instead when the user must read consequences or choose between outcomes.",
|
|
304
|
+
"example": "<button class=\"b3-button\" data-b3-emphasis=\"quiet\" type=\"button\" data-b3-confirm=\"Tap again to delete\" data-id=\"42\">Delete</button>"
|
|
305
|
+
},
|
|
289
306
|
{
|
|
290
307
|
"id": "disclosure",
|
|
291
308
|
"title": "Accordion, dropdown and hierarchy",
|
|
@@ -438,6 +455,8 @@
|
|
|
438
455
|
"data-display.md",
|
|
439
456
|
"navigation.md",
|
|
440
457
|
"tabs.md",
|
|
458
|
+
"segmented.md",
|
|
459
|
+
"confirm.md",
|
|
441
460
|
"disclosure.md",
|
|
442
461
|
"dialog.md",
|
|
443
462
|
"content.md",
|
|
@@ -454,8 +473,10 @@
|
|
|
454
473
|
"editable-filtered-rows.md",
|
|
455
474
|
"icons.md",
|
|
456
475
|
"image-viewer.md",
|
|
476
|
+
"markdown-stream.md",
|
|
457
477
|
"motion.md",
|
|
458
478
|
"scroll-entry.md",
|
|
459
|
-
"start.md"
|
|
479
|
+
"start.md",
|
|
480
|
+
"markdown-stream-prompt.md"
|
|
460
481
|
]
|
|
461
482
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Confirm a destructive action
|
|
2
|
+
|
|
3
|
+
window.confirm/alert/prompt are unavailable in sandboxed frames and must not guard anything. Put data-b3-confirm on the button that deletes, resets, discards or removes. The first press arms it: the label swaps to the attribute value (e.g. data-b3-confirm="Tap again to delete"), a danger ring appears and a live region announces it. A second press within 4 seconds runs the click; timeout, Escape, blur or pressing elsewhere disarms and restores the original content. mount() intercepts the arming press in the capture phase, so write the ordinary click handler (direct or delegated) with no confirmation logic: it only runs on the confirming press. Only click is guarded: a pointerdown, touchstart or mousedown handler runs on the first press and bypasses it. A repeat within 350ms or a held key is ignored, so a double tap never confirms. Any re-render or change of the item cancels the arm and the next press only re-arms, so never re-render the list on a timer while the user can act on it. An empty value keeps the content (use it on icon-only buttons with an aria-label). Forms and links are also held until confirmed. Use a dialog instead when the user must read consequences or choose between outcomes.
|
|
4
|
+
|
|
5
|
+
Classes:
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<button class="b3-button" data-b3-emphasis="quiet" type="button" data-b3-confirm="Tap again to delete" data-id="42">Delete</button>
|
|
9
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Markdown stream: model prompt
|
|
2
|
+
|
|
3
|
+
System-prompt snippet from `promptGuide()` in `@livx.cc/bare-v3/elements/markdown-stream`.
|
|
4
|
+
|
|
5
|
+
~~~~text
|
|
6
|
+
Your replies are Markdown rendered live into UI components. Use GitHub-flavoured Markdown (headings, **bold**, lists, links, `inline code`, fenced code with a language, > quotes) plus:
|
|
7
|
+
- Tables: GFM pipe tables with a header row and a `| --- |` delimiter row. Never add a Status column the data does not already have. Only when a row really has one of these states (e.g. an invoice that is paid), write the plain word alone in the cell and it is shown as a coloured badge automatically (no images, emoji or badge URLs): paid, done, ok, active (success); pending (warning); overdue, failed, blocked (danger); draft (info). Numeric columns align right.
|
|
8
|
+
- Task lists: `- [x] done` and `- [ ] to do`.
|
|
9
|
+
- `:::callout{tone=info|success|warning|danger title="Short title"}` newline, markdown body, newline `:::` — a highlighted note, tip, warning or recommendation.
|
|
10
|
+
- `:::stats` newline, one card per line `Label | Value | note`, newline `:::` — headline numbers; a note starting with ↑ or + shows as good, ↓ or - as bad.
|
|
11
|
+
- ```chart fence, one bar per line `Label: number` (plain number, no units) — a horizontal bar chart for comparing a few values.
|
|
12
|
+
Use a component only when it makes the answer clearer; plain prose is fine. Put every block on its own lines with a blank line around it, always close ::: and ``` blocks, and never write raw HTML.
|
|
13
|
+
~~~~
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Streaming markdown
|
|
2
|
+
|
|
3
|
+
`MarkdownStream` renders an LLM's streaming markdown into bare-v3 components. Finished blocks are
|
|
4
|
+
frozen (their DOM is never replaced, so selection and copy buttons stay put), only the tail
|
|
5
|
+
re-renders, and half-written syntax never shows raw. Opt-in; not part of `ui.js`.
|
|
6
|
+
|
|
7
|
+
It bundles no parser or sanitizer: pass marked's `Marked` class and a sanitize function.
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { Marked } from 'marked';
|
|
11
|
+
import DOMPurify from 'dompurify';
|
|
12
|
+
import { MarkdownStream, stickToBottom } from '@livx.cc/bare-v3/elements/markdown-stream';
|
|
13
|
+
|
|
14
|
+
const view = new MarkdownStream({ Marked, sanitize: (html) => DOMPurify.sanitize(html) });
|
|
15
|
+
const scroll = stickToBottom(); // pins while streaming; the reader scrolling up un-pins
|
|
16
|
+
view.attach(document.querySelector('article.b3-prose'));
|
|
17
|
+
for await (const chunk of res.body.pipeThrough(new TextDecoderStream())) { view.push(chunk); scroll.stick(); }
|
|
18
|
+
view.end();
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## What renders
|
|
22
|
+
|
|
23
|
+
Any markdown-speaking model works with no prompt: standard markdown is upgraded on its own. The `:::` directives and `chart` fence are extras a model only writes once taught (see `promptGuide` below).
|
|
24
|
+
|
|
25
|
+
| Markdown | Renders as |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| GFM table | `b3-table` in `b3-table-wrap`; status words (paid, pending, overdue…) become toned `b3-badge`s; all-numeric columns align end |
|
|
28
|
+
| `:::callout{tone=warning title="…"}` … `:::` | `b3-alert` with tone and title; body is markdown |
|
|
29
|
+
| `:::stats` lines `Label \| Value \| ↑ note` `:::` | `b3-stat` cards; `↑`/`+` notes success, `↓`/`-` danger |
|
|
30
|
+
| ```` ```chart ```` lines `Label: number` | horizontal bars scaled to the max |
|
|
31
|
+
| ```` ```lang ```` | code block with language label and Copy button |
|
|
32
|
+
| `- [x] task` | list with disabled checkboxes |
|
|
33
|
+
| `> [!NOTE]` / `> [!WARNING]` (GitHub alerts) or `> **Note:** …` | `b3-alert`; note/important → info, tip → success, warning → warning, caution/danger → danger |
|
|
34
|
+
|
|
35
|
+
## Options
|
|
36
|
+
|
|
37
|
+
`Marked`, `sanitize` (both required), `directives` (name → `(token, ctx) => html`; unknown names
|
|
38
|
+
fall back to `callout`), `fences` (lang → `(text, ctx) => html`), `code` (`(text, lang) => html`),
|
|
39
|
+
`tones` (cell text → tone), `markedOptions` (extra `marked.use()` input), `html` (`'escape'` default |
|
|
40
|
+
`'render'`). Maps merge over the built-ins, so adding one renderer keeps the rest. Everything a renderer
|
|
41
|
+
returns is sanitized.
|
|
42
|
+
|
|
43
|
+
- **Raw HTML is shown as text by default**: `Use <Button variant="x">` or `a <div> … </div>` in model
|
|
44
|
+
output (coding agents write JSX) renders as literal tags, not live DOM. `html: 'render'` passes it
|
|
45
|
+
through `sanitize` instead, for hosts whose content is trusted HTML.
|
|
46
|
+
- **Footnotes and other GFM extras** are not built in; add a marked extension via `markedOptions`, e.g.
|
|
47
|
+
`markedOptions: markedFootnote()` (from `marked-footnote`) for `Note[^1]` / `[^1]: x`.
|
|
48
|
+
|
|
49
|
+
Methods: `attach(el)`, `push(chunk)`, `end()`, `toHtml(src)`, `detach()`. `end()` finalises the
|
|
50
|
+
stream: a later `push()` is ignored with a console warning; call `attach(el)` to start a new one. `settle(buffer)`, `promptGuide(options)` and
|
|
51
|
+
`stickToBottom(scroller)` are exported on their own.
|
|
52
|
+
|
|
53
|
+
## Agent / LLM integration
|
|
54
|
+
|
|
55
|
+
**System prompt (optional).** Skip it and plain markdown still renders fully; add it to unlock callouts, stats and charts. `promptGuide(options?)` returns a short snippet teaching a model the vocabulary
|
|
56
|
+
above. It is generated from the same `directives`/`fences`/`tones` the stream uses, so pass the same
|
|
57
|
+
options: a custom renderer contributes a line by carrying a `.doc` string. The default text ships as
|
|
58
|
+
`references/markdown-stream-prompt.md`.
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
import { promptGuide } from '@livx.cc/bare-v3/elements/markdown-stream';
|
|
62
|
+
steps.doc = '`:::steps` … `:::` — a numbered walkthrough';
|
|
63
|
+
const system = promptGuide({ directives: { steps } }) + '\nBe helpful and concise.';
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Server contract.** `POST /api/chat` with `{ messages: [{ role, content }] }` answers `text/plain`
|
|
67
|
+
chunks of the reply: parse the provider's SSE and forward only the text deltas. Abort the upstream
|
|
68
|
+
request when the client disconnects. `scripts/chat-server.ts` is a working example (`bun run chat`;
|
|
69
|
+
OpenRouter or OpenAI via plain HTTP; `CHAT_MODEL`, `CHAT_MAX_TOKENS` and `CHAT_PORT` override).
|
|
70
|
+
|
|
71
|
+
**Client loop.**
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
const history = [];
|
|
75
|
+
async function ask(text, signal) {
|
|
76
|
+
history.push({ role: 'user', content: text });
|
|
77
|
+
const view = new MarkdownStream({ Marked, sanitize: (html) => DOMPurify.sanitize(html) }).attach(newArticle());
|
|
78
|
+
try {
|
|
79
|
+
const res = await fetch('/api/chat', { method: 'POST', signal, headers: { 'content-type': 'application/json' }, body: JSON.stringify({ messages: history }) });
|
|
80
|
+
if (!res.ok) throw new Error(await res.text());
|
|
81
|
+
for await (const chunk of res.body.pipeThrough(new TextDecoderStream())) { view.push(chunk); scroll.stick(); }
|
|
82
|
+
} finally { view.end(); history.push({ role: 'assistant', content: view.buf }); }
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- **History is raw markdown** (`view.buf`), never rendered HTML; a stopped reply keeps its partial text.
|
|
87
|
+
- **Always sanitize**: model output is untrusted input, and renderers' HTML goes through `sanitize` too.
|
|
88
|
+
- Stop = `AbortController.abort()`; the fetch rejects and the server aborts the provider call.
|
|
89
|
+
|
|
90
|
+
`fixtures/elements/markdown-stream.html` is the full chat (composer, Stop, multi-turn). Under
|
|
91
|
+
`bun run chat` it talks to the model; under any plain static server it falls back to canned replies.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Segmented choice
|
|
2
|
+
|
|
3
|
+
One choice among 2-5 peers that filters or switches the current view (Day/Week, All/Open/Done). It is a radio group, not tabs: use tabs only when each option owns a role=tabpanel. Author native radios sharing one name, each inside its label; wrap them in div.b3-segmented role=radiogroup with aria-label (or fieldset.b3-segmented with a b3-visually-hidden legend). React to the change event and read the checked value; arrow keys, focus, form values and reset stay native. Each option is at least 44px tall; the checked option lifts onto the surface. Options that do not fit wrap onto another row at full height rather than squeezing or hiding; past five options use a select. Keep labels short; an icon beside the text is fine.
|
|
4
|
+
|
|
5
|
+
Classes: b3-segmented
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<div class="b3-segmented" role="radiogroup" aria-label="Range"><label><input type="radio" name="range" value="day" checked>Day</label><label><input type="radio" name="range" value="week">Week</label><label><input type="radio" name="range" value="month">Month</label></div>
|
|
9
|
+
```
|
package/dist/ui.js
CHANGED
|
@@ -748,6 +748,87 @@ function toasts({ root, document: document2, find, options }) {
|
|
|
748
748
|
}, options);
|
|
749
749
|
}
|
|
750
750
|
|
|
751
|
+
// src/interactions/confirm.js
|
|
752
|
+
var WINDOW = 4000;
|
|
753
|
+
var SETTLE = 350;
|
|
754
|
+
var ITEM = "li, tr, article, [data-id], .b3-row, .b3-card";
|
|
755
|
+
function confirms({ root, document: document2, options }) {
|
|
756
|
+
const view = document2.defaultView;
|
|
757
|
+
let armed = null;
|
|
758
|
+
let item = null;
|
|
759
|
+
let armedAt = 0;
|
|
760
|
+
let saved = null;
|
|
761
|
+
let timer = 0;
|
|
762
|
+
let status = null;
|
|
763
|
+
const observer = new view.MutationObserver((records) => {
|
|
764
|
+
if (!armed.isConnected || records.some(({ target }) => item.contains(target) && !status.contains(target)))
|
|
765
|
+
disarm();
|
|
766
|
+
});
|
|
767
|
+
function disarm() {
|
|
768
|
+
if (!armed)
|
|
769
|
+
return;
|
|
770
|
+
view.clearTimeout(timer);
|
|
771
|
+
observer.disconnect();
|
|
772
|
+
const label = armed.dataset.b3Confirm;
|
|
773
|
+
if (saved && label && armed.textContent === label)
|
|
774
|
+
armed.replaceChildren(...saved);
|
|
775
|
+
armed.removeAttribute("data-b3-armed");
|
|
776
|
+
armed = item = saved = null;
|
|
777
|
+
status.textContent = "";
|
|
778
|
+
}
|
|
779
|
+
function arm(button) {
|
|
780
|
+
if (!status) {
|
|
781
|
+
status = document2.createElement("span");
|
|
782
|
+
status.className = "b3-visually-hidden";
|
|
783
|
+
status.setAttribute("role", "status");
|
|
784
|
+
document2.body.append(status);
|
|
785
|
+
}
|
|
786
|
+
armed = button;
|
|
787
|
+
armedAt = view.performance.now();
|
|
788
|
+
item = button.parentElement.closest(ITEM) || button.parentElement;
|
|
789
|
+
const label = button.dataset.b3Confirm;
|
|
790
|
+
if (label) {
|
|
791
|
+
saved = [...button.childNodes];
|
|
792
|
+
button.replaceChildren(label);
|
|
793
|
+
}
|
|
794
|
+
button.setAttribute("data-b3-armed", "");
|
|
795
|
+
observer.observe(root, { childList: true, subtree: true });
|
|
796
|
+
observer.observe(item, { childList: true, subtree: true, attributes: true, characterData: true });
|
|
797
|
+
timer = view.setTimeout(disarm, WINDOW);
|
|
798
|
+
view.requestAnimationFrame(() => {
|
|
799
|
+
if (armed === button)
|
|
800
|
+
status.textContent = label || "Press again to confirm";
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
root.addEventListener("click", (event) => {
|
|
804
|
+
const button = event.target.closest?.("[data-b3-confirm]");
|
|
805
|
+
if (armed && button !== armed)
|
|
806
|
+
disarm();
|
|
807
|
+
if (!button || button.disabled || !root.contains(button))
|
|
808
|
+
return;
|
|
809
|
+
if (button === armed && view.performance.now() - armedAt >= SETTLE)
|
|
810
|
+
return disarm();
|
|
811
|
+
event.preventDefault();
|
|
812
|
+
event.stopImmediatePropagation();
|
|
813
|
+
if (button !== armed)
|
|
814
|
+
arm(button);
|
|
815
|
+
}, { ...options, capture: true });
|
|
816
|
+
root.addEventListener("focusout", ({ target }) => {
|
|
817
|
+
if (target === armed)
|
|
818
|
+
view.queueMicrotask(() => target === armed && disarm());
|
|
819
|
+
}, options);
|
|
820
|
+
root.addEventListener("keydown", (event) => {
|
|
821
|
+
if (event.key === "Escape")
|
|
822
|
+
disarm();
|
|
823
|
+
else if (event.repeat && (event.key === "Enter" || event.key === " ") && event.target.closest?.("[data-b3-confirm]"))
|
|
824
|
+
event.preventDefault();
|
|
825
|
+
}, { ...options, capture: true });
|
|
826
|
+
options.signal.addEventListener("abort", () => {
|
|
827
|
+
disarm();
|
|
828
|
+
status?.remove();
|
|
829
|
+
}, { once: true });
|
|
830
|
+
}
|
|
831
|
+
|
|
751
832
|
// src/ui.js
|
|
752
833
|
function mount(root = document) {
|
|
753
834
|
const controller = new AbortController;
|
|
@@ -763,6 +844,7 @@ function mount(root = document) {
|
|
|
763
844
|
disclosures(context);
|
|
764
845
|
dialogs(context);
|
|
765
846
|
toasts(context);
|
|
847
|
+
confirms(context);
|
|
766
848
|
outline(context);
|
|
767
849
|
commands(context);
|
|
768
850
|
return () => controller.abort();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livx.cc/bare-v3",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.57",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
"./references/*": "./dist/references/*",
|
|
14
14
|
"./ui": "./dist/ui.js",
|
|
15
15
|
"./elements/image-viewer": "./dist/elements/image-viewer.js",
|
|
16
|
-
"./elements/date-picker": "./dist/elements/date-picker.js"
|
|
16
|
+
"./elements/date-picker": "./dist/elements/date-picker.js",
|
|
17
|
+
"./elements/markdown-stream": "./dist/elements/markdown-stream.js"
|
|
17
18
|
},
|
|
18
19
|
"scripts": {
|
|
19
20
|
"build": "bun scripts/build.ts",
|
|
20
21
|
"test": "bun test",
|
|
21
22
|
"lab": "bun lab/cli.ts",
|
|
23
|
+
"chat": "bun scripts/chat-server.ts",
|
|
22
24
|
"check:package": "bun scripts/check-package.ts",
|
|
23
25
|
"typecheck": "tsc"
|
|
24
26
|
},
|
|
@@ -31,8 +33,10 @@
|
|
|
31
33
|
"ai.libx.js": "0.18.29",
|
|
32
34
|
"basecoat-css": "1.0.2",
|
|
33
35
|
"daisyui": "5.7.28",
|
|
36
|
+
"happy-dom": "20.14.5",
|
|
34
37
|
"lightningcss": "1.33.0",
|
|
35
38
|
"lucide-static": "1.31.0",
|
|
39
|
+
"marked": "18.0.14",
|
|
36
40
|
"pug": "3.0.4",
|
|
37
41
|
"tailwindcss": "4.3.3",
|
|
38
42
|
"typescript": "7.0.2"
|