@nadicodeai/design-system 0.21.0 → 0.22.0
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/AGENTS.md +1 -1
- package/DESIGN.md +5 -21
- package/README.md +1 -22
- package/dist/css/index.css +0 -543
- package/package.json +1 -2
- package/dist/css/chat-autoplay.js +0 -537
package/AGENTS.md
CHANGED
|
@@ -41,4 +41,4 @@ Handoff gate: `npm run lint:design && npm run lint && npm run build && npm run t
|
|
|
41
41
|
- Authoring is contract-first: a visual behavior comes from `DESIGN.md`, from our own shipped surfaces, or from an explicit user approval — never from another product's rendered output (ADR 0047). Screenshots verify, they never author. Ask when the contract leaves a decision open, and stop and re-read `DESIGN.md` before a second visual patch to the same component.
|
|
42
42
|
- Name everything as NadicodeAI-owned primitives. A source brand appears only in a `../docs/adr/` decision record, as the history of what once inspired a choice; never in a name, a comment, a contract, or any living design document (ADR 0047).
|
|
43
43
|
- Any change to package-consumed output ends with an explicit release decision, a version bump or a `[no-publish]` commit marker (enforced by the pre-push release gate; procedure: `../docs/internal/npm-publishing.md`).
|
|
44
|
-
- The deleted `design-canvas/` prototypes live only in git history. `src/css/motion.css` owns shared runtime motion primitives
|
|
44
|
+
- The deleted `design-canvas/` prototypes and static chat/autoplay surface live only in git history. `src/css/motion.css` owns shared runtime motion primitives; never reconstruct the prototypes or add a runtime JS surface.
|
package/DESIGN.md
CHANGED
|
@@ -482,7 +482,7 @@ Kernel compositions arrange approved primitives for a content role. They must no
|
|
|
482
482
|
Each reusable concern has one home and one authoring surface, so a member is documented and shipped in exactly one place:
|
|
483
483
|
|
|
484
484
|
- **Tokens** — exact reusable values live in this contract's `colors`, `spacing`, `rounded`, and `typography`; they export to the generated token layer (with a hand-authored local token layer beside it). No component or section may re-encode a token value.
|
|
485
|
-
- **Framework-agnostic CSS components** — the brand/display CSS components that remain in this package (
|
|
485
|
+
- **Framework-agnostic CSS components** — the brand/display CSS components that remain in this package (agent-work artifacts, the run-state `state-tag` family, the entity-status `status-tag` family, `image`, and the small label/badge/link metadata components). Their names and semantics live once in `## Components`; design-system CSS realises that catalog without a second recipe or component-token hierarchy.
|
|
486
486
|
- **Reusable compositions and sections** — patterns that arrange the page grammar and lower tiers for a content role. They ship as React shadcn compositions from `@nadicodeai/ui`, composed over the kept page grammar; they are not a pure-CSS section layer and do not add a second component catalog here.
|
|
487
487
|
- **Layout grammar** — the page-frame, row, lane, seam, stage, and marker system documented across this `## Layout` section, carried by the layout CSS layer.
|
|
488
488
|
- **Specimens** — static demonstrations of already-defined primitives. They invent no geometry, seams, heights, or responsive behavior; they only assemble shipped tiers for inspection.
|
|
@@ -563,7 +563,7 @@ On coarse or non-hover input capability, every interactive target is at least 44
|
|
|
563
563
|
Corners are restrained and assigned by material role.
|
|
564
564
|
|
|
565
565
|
- Use `{rounded.none}` for page rows, frame edges, grid cells, and structural modules.
|
|
566
|
-
- Use `{rounded.md}` (6 px) for resting content surfaces, controls, buttons, chips, status tags, tooltips,
|
|
566
|
+
- Use `{rounded.md}` (6 px) for resting content surfaces, controls, buttons, chips, status tags, tooltips, and small artifacts.
|
|
567
567
|
- Use `{rounded.2xl}` (12 px) for menus, popovers, and dialogs.
|
|
568
568
|
- Use `{rounded.3xl}` (16 px) for inset sheets and takeovers.
|
|
569
569
|
- Use `{rounded.full}` only for intrinsically circular identity/avatar chrome and compact control marks whose geometry is inherently round or pill-shaped. It never turns a content surface, button, badge, or navigation item into a pill by default.
|
|
@@ -596,15 +596,13 @@ Interactive CSS components must expose a visible `:focus-visible` state using th
|
|
|
596
596
|
|
|
597
597
|
Content surfaces display specific content types inside the grammar. They are valid when the content exists; they do not replace the page frame, row model, or seam rules.
|
|
598
598
|
|
|
599
|
-
**`image`** — editorial media mount, the single home for art-directed imagery inside the grammar. A bounded media fill (img, picture, video, or inline SVG) with an optional mono tag chip overlaid on the media and an optional body-tone caption bar below it, both typed by the generated caption utilities applied in markup. Square-cornered (`{rounded.none}`): a mounted print, not a floating card — the parent frame supplies the visual system. With `data-frame="true"` it draws exactly one hairline (its own `{colors.line}` border), so it sits inside a borderless `panel-cell` or a visual slot without repairing or stacking onto any grid seam. The aspect ratio is the per-instance `--nc-image-ratio` knob (default 4 / 3) — set the prop on the element, never re-derive geometry inline. Variant: `data-strip="true"` is the full-width divider form for thin editorial bands between page rows (thread, texture, panorama) — the ratio unlocks and the height resolves from the responsive `{spacing.h-strip-*}` tokens through the layout-layer collapsed alias. Use `image` for every illustrated image, photographic surface, or image divider
|
|
599
|
+
**`image`** — editorial media mount, the single home for art-directed imagery inside the grammar. A bounded media fill (img, picture, video, or inline SVG) with an optional mono tag chip overlaid on the media and an optional body-tone caption bar below it, both typed by the generated caption utilities applied in markup. Square-cornered (`{rounded.none}`): a mounted print, not a floating card — the parent frame supplies the visual system. With `data-frame="true"` it draws exactly one hairline (its own `{colors.line}` border), so it sits inside a borderless `panel-cell` or a visual slot without repairing or stacking onto any grid seam. The aspect ratio is the per-instance `--nc-image-ratio` knob (default 4 / 3) — set the prop on the element, never re-derive geometry inline. Variant: `data-strip="true"` is the full-width divider form for thin editorial bands between page rows (thread, texture, panorama) — the ratio unlocks and the height resolves from the responsive `{spacing.h-strip-*}` tokens through the layout-layer collapsed alias. Use `image` for every illustrated image, photographic surface, or image divider. Conversation artifacts render through assistant-ui rather than a design-system CSS family.
|
|
600
600
|
|
|
601
601
|
### Agentic Work Surfaces
|
|
602
602
|
|
|
603
|
-
Agentic work surfaces are the NadicodeAI-specific layer. They show
|
|
603
|
+
Agentic work surfaces are the NadicodeAI-specific display layer outside the conversation renderer. They show concrete work output or workflow state such as memory, state, run progress, identity, and handoffs. Conversation messages, tools, artifacts, approvals, and pending indicators belong to assistant-ui.
|
|
604
604
|
|
|
605
|
-
**`artifact-surface`** — inspectable output area. Use for proposal previews, reports, plans, spreadsheets, schedules,
|
|
606
|
-
|
|
607
|
-
**`artifact-preview`** — compact inspectable artifact card for an agent-produced document, sheet, report, or code excerpt. The header identifies the artifact kind and the body carries the preview; use `artifact-surface` for a larger work area.
|
|
605
|
+
**`artifact-surface`** — inspectable output area outside a conversation. Use for proposal previews, reports, plans, spreadsheets, schedules, run logs, and workflow maps.
|
|
608
606
|
|
|
609
607
|
Status-tag variants share the same compact sans rectangle at `{typography.caption-strong}`, `{rounded.md}` — a small subtle-tinted rectangle, never a capsule — and differentiate by tone: a paired background+text ramp step, not a border-left rail. Tone is `[data-tone]`, not BEM modifiers. Gray is the default tone; color is reserved for marking health and exceptions, so every terminal or no-action entity status (inactive, archived, rejected, canceled, expired, revoked, denied) renders gray. Subtle-only (no solid/filled variant), no icons inside the badge, no text-transform; labels render verbatim from the source's own i18n dictionary. `status-tag` is the entity-status vocabulary — agent, credential, activation, customer, invitation, subscription, and health values that live on a record — and stays a sibling of `state-tag`: the two families never blend, and `status-tag` never touches the `{colors.state-*}` tokens.
|
|
610
608
|
|
|
@@ -618,14 +616,6 @@ Status-tag variants share the same compact sans rectangle at `{typography.captio
|
|
|
618
616
|
|
|
619
617
|
**`status-tag-blue`** — an in-progress, pending, or trial entity status still moving toward a resolution.
|
|
620
618
|
|
|
621
|
-
**`chat-thread`** — conversation container showing how people instruct, correct, review, or approve agents. Static markup always renders the complete conversation. The package's optional `chat-autoplay.js` progressive enhancer activates only on `data-nc-autoplay`, reveals messages in order while keeping the newest turn visible, loops while the thread is in view, and injects its own pause/resume control. It shows the complete thread immediately when reduced motion is requested. Consumers never author the enhancer control in static markup.
|
|
622
|
-
|
|
623
|
-
**`chat-bubble-user`** — human message bubble. Dark surface, white text.
|
|
624
|
-
|
|
625
|
-
**`chat-bubble-agent`** — agent message bubble. Light surface, ink text.
|
|
626
|
-
|
|
627
|
-
**`tool-call-trace`** — compact code/trace row. Use only when tool use is relevant to the explanation.
|
|
628
|
-
|
|
629
619
|
State-tag variants share the same compact rectangular shape at `{typography.caption-mono}` and differentiate by a 3 px colored `border-left` consuming the matching `{colors.state-*}` token. Shape carries meaning; color carries variant.
|
|
630
620
|
|
|
631
621
|
**`state-tag-ready`** — workflow state tag for work that is ready or waiting to start. Uses `{colors.state-ready}` for the left edge.
|
|
@@ -640,12 +630,6 @@ State-tag variants share the same compact rectangular shape at `{typography.capt
|
|
|
640
630
|
|
|
641
631
|
**`state-row`** — full-width workflow-state divider inside an agentic work surface. Its bounded hairlines belong to the row and its dot consumes the same state token as the matching `state-tag` variant.
|
|
642
632
|
|
|
643
|
-
**`typing-dots`** — compact agent typing indicator. It uses the agent accent rail and the shared motion hooks; reduced-motion behavior comes from the package motion layer.
|
|
644
|
-
|
|
645
|
-
**`approval-gate`** — human-in-the-loop pause surface. Use when an agent action requires explicit review, correction, approval, or rejection.
|
|
646
|
-
|
|
647
|
-
**`agent-avatar`** — identity slot for an agent personality or role. The chrome is reusable; the visual asset is project-specific.
|
|
648
|
-
|
|
649
633
|
**`usage-meter`** — token, cost, latency, or run-count display.
|
|
650
634
|
|
|
651
635
|
**`run-timeline`** — vertical step thread for an agent run, handoff, or workflow execution.
|
package/README.md
CHANGED
|
@@ -84,14 +84,6 @@ Consumer overrides belong in an explicit later layer:
|
|
|
84
84
|
|
|
85
85
|
The exact layer order and ownership rules are defined in `DESIGN.md`.
|
|
86
86
|
|
|
87
|
-
### Optional chat auto-play
|
|
88
|
-
|
|
89
|
-
Static chat markup needs no JavaScript. To opt a `data-nc-autoplay` thread into
|
|
90
|
-
progressive playback, include the public
|
|
91
|
-
`@nadicodeai/design-system/css/chat-autoplay.js` export. The `chat-thread`
|
|
92
|
-
entry in [`DESIGN.md`](./DESIGN.md#agentic-work-surfaces) owns the behavior and
|
|
93
|
-
accessibility contract.
|
|
94
|
-
|
|
95
87
|
### Worked example (static HTML)
|
|
96
88
|
|
|
97
89
|
```html
|
|
@@ -109,26 +101,13 @@ accessibility contract.
|
|
|
109
101
|
<div class="nc-page-frame">
|
|
110
102
|
<section class="nc-page-row">
|
|
111
103
|
<div class="nc-row-lane nc-row-lane-active">
|
|
112
|
-
|
|
113
|
-
<div class="nc-chat-thread" data-nc-autoplay>
|
|
114
|
-
<div class="nc-chat-thread-body">
|
|
115
|
-
<div class="nc-chat-bubble-user">Which contract template should I use?</div>
|
|
116
|
-
<div class="nc-chat-bubble-agent" data-accent="green">
|
|
117
|
-
Use the approved 2026 revision in Drive · Legal · Templates. It already has the updated privacy clauses.
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
104
|
+
<div class="nc-artifact-surface">Inspectable agent output</div>
|
|
121
105
|
</div>
|
|
122
106
|
</section>
|
|
123
107
|
</div>
|
|
124
108
|
</main>
|
|
125
|
-
|
|
126
|
-
<!-- Optional: omit this script and the thread above stays a full static conversation. -->
|
|
127
|
-
<script src="https://unpkg.com/@nadicodeai/design-system/css/chat-autoplay.js"></script>
|
|
128
109
|
</body>
|
|
129
110
|
</html>
|
|
130
111
|
```
|
|
131
112
|
|
|
132
|
-
`examples/chat-showcase.html` is the live proof page for the chat CSS components and auto-play enhancer.
|
|
133
|
-
|
|
134
113
|
Use the monorepo's `brand/brand-book/` for NadicodeAI story, positioning, buyer, promise, method, use cases, technology story, trust doctrine, identity, voice, visual direction, sales behavior, language, and boundaries.
|
package/dist/css/index.css
CHANGED
|
@@ -1753,265 +1753,6 @@
|
|
|
1753
1753
|
}
|
|
1754
1754
|
}
|
|
1755
1755
|
|
|
1756
|
-
/* src/css/components/chat-thread.css */
|
|
1757
|
-
@layer components {
|
|
1758
|
-
.nc-chat-thread {
|
|
1759
|
-
display: flex;
|
|
1760
|
-
flex-direction: column;
|
|
1761
|
-
overflow: hidden;
|
|
1762
|
-
border: var(--nc-guide) solid var(--nc-line);
|
|
1763
|
-
border-radius: var(--nc-rounded-md);
|
|
1764
|
-
background: var(--nc-canvas);
|
|
1765
|
-
color: var(--nc-ink);
|
|
1766
|
-
font-family:
|
|
1767
|
-
"Geist",
|
|
1768
|
-
"Inter",
|
|
1769
|
-
system-ui,
|
|
1770
|
-
-apple-system,
|
|
1771
|
-
"Segoe UI",
|
|
1772
|
-
sans-serif;
|
|
1773
|
-
}
|
|
1774
|
-
.nc-chat-thread-header {
|
|
1775
|
-
display: flex;
|
|
1776
|
-
align-items: center;
|
|
1777
|
-
gap: 12px;
|
|
1778
|
-
padding: 14px 16px;
|
|
1779
|
-
border-bottom: var(--nc-guide) solid var(--nc-line);
|
|
1780
|
-
background: var(--nc-canvas);
|
|
1781
|
-
}
|
|
1782
|
-
.nc-chat-thread-domain {
|
|
1783
|
-
font-family:
|
|
1784
|
-
"Geist Mono",
|
|
1785
|
-
ui-monospace,
|
|
1786
|
-
SFMono-Regular,
|
|
1787
|
-
Menlo,
|
|
1788
|
-
Monaco,
|
|
1789
|
-
monospace;
|
|
1790
|
-
font-size: 11px;
|
|
1791
|
-
font-weight: 500;
|
|
1792
|
-
line-height: 16px;
|
|
1793
|
-
color: var(--nc-muted);
|
|
1794
|
-
text-transform: uppercase;
|
|
1795
|
-
letter-spacing: 0.04em;
|
|
1796
|
-
white-space: nowrap;
|
|
1797
|
-
overflow: hidden;
|
|
1798
|
-
text-overflow: ellipsis;
|
|
1799
|
-
}
|
|
1800
|
-
.nc-chat-thread-identity {
|
|
1801
|
-
min-width: 0;
|
|
1802
|
-
flex: 1;
|
|
1803
|
-
}
|
|
1804
|
-
.nc-chat-thread-name {
|
|
1805
|
-
display: flex;
|
|
1806
|
-
align-items: baseline;
|
|
1807
|
-
gap: 6px;
|
|
1808
|
-
font-size: 14px;
|
|
1809
|
-
font-weight: 500;
|
|
1810
|
-
line-height: 20px;
|
|
1811
|
-
color: var(--nc-ink);
|
|
1812
|
-
white-space: nowrap;
|
|
1813
|
-
overflow: hidden;
|
|
1814
|
-
text-overflow: ellipsis;
|
|
1815
|
-
}
|
|
1816
|
-
.nc-chat-thread-name .nc-chat-thread-role {
|
|
1817
|
-
min-width: 0;
|
|
1818
|
-
overflow: hidden;
|
|
1819
|
-
font-weight: 400;
|
|
1820
|
-
color: var(--nc-muted);
|
|
1821
|
-
text-overflow: ellipsis;
|
|
1822
|
-
white-space: nowrap;
|
|
1823
|
-
}
|
|
1824
|
-
.nc-chat-thread-workflow {
|
|
1825
|
-
flex: 0 1 auto;
|
|
1826
|
-
min-width: 0;
|
|
1827
|
-
overflow: hidden;
|
|
1828
|
-
font-family:
|
|
1829
|
-
"Geist Mono",
|
|
1830
|
-
ui-monospace,
|
|
1831
|
-
SFMono-Regular,
|
|
1832
|
-
Menlo,
|
|
1833
|
-
Monaco,
|
|
1834
|
-
monospace;
|
|
1835
|
-
font-size: 11px;
|
|
1836
|
-
line-height: 16px;
|
|
1837
|
-
color: var(--nc-body);
|
|
1838
|
-
text-transform: uppercase;
|
|
1839
|
-
letter-spacing: 0.04em;
|
|
1840
|
-
text-overflow: ellipsis;
|
|
1841
|
-
white-space: nowrap;
|
|
1842
|
-
}
|
|
1843
|
-
.nc-chat-thread-body {
|
|
1844
|
-
flex: 1;
|
|
1845
|
-
min-height: 0;
|
|
1846
|
-
overflow: hidden auto;
|
|
1847
|
-
display: flex;
|
|
1848
|
-
flex-direction: column;
|
|
1849
|
-
gap: 10px;
|
|
1850
|
-
padding: 16px 18px;
|
|
1851
|
-
background: radial-gradient(var(--nc-line) 0.8px, transparent 0.8px) 8px 8px / 12px 12px, var(--nc-canvas);
|
|
1852
|
-
}
|
|
1853
|
-
.nc-chat-pause {
|
|
1854
|
-
display: block;
|
|
1855
|
-
align-self: flex-end;
|
|
1856
|
-
margin: var(--nc-xs) var(--nc-xs) 0;
|
|
1857
|
-
padding: var(--nc-xs) var(--nc-sm);
|
|
1858
|
-
background: var(--nc-canvas);
|
|
1859
|
-
color: var(--nc-muted);
|
|
1860
|
-
border: var(--nc-guide) solid var(--nc-line);
|
|
1861
|
-
border-radius: var(--nc-rounded-xs);
|
|
1862
|
-
font-family: var(--nc-type-caption-mono-font-family);
|
|
1863
|
-
font-size: var(--nc-type-caption-mono-font-size);
|
|
1864
|
-
font-weight: var(--nc-type-caption-mono-font-weight);
|
|
1865
|
-
line-height: var(--nc-type-caption-mono-line-height);
|
|
1866
|
-
letter-spacing: var(--nc-type-caption-mono-letter-spacing);
|
|
1867
|
-
cursor: pointer;
|
|
1868
|
-
transition:
|
|
1869
|
-
background-color var(--nc-duration-confirm) var(--nc-ease-out-strong),
|
|
1870
|
-
border-color var(--nc-duration-confirm) var(--nc-ease-out-strong),
|
|
1871
|
-
color var(--nc-duration-confirm) var(--nc-ease-out-strong),
|
|
1872
|
-
opacity var(--nc-duration-confirm) var(--nc-ease-out-strong);
|
|
1873
|
-
}
|
|
1874
|
-
@media (hover: hover) and (pointer: fine) {
|
|
1875
|
-
.nc-chat-pause:hover {
|
|
1876
|
-
color: var(--nc-ink);
|
|
1877
|
-
border-color: var(--nc-body);
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
.nc-chat-pause[aria-pressed=true] {
|
|
1881
|
-
color: var(--nc-ink);
|
|
1882
|
-
border-color: var(--nc-body);
|
|
1883
|
-
}
|
|
1884
|
-
.nc-chat-pause:active {
|
|
1885
|
-
opacity: 0.9;
|
|
1886
|
-
}
|
|
1887
|
-
.nc-chat-thread-footer {
|
|
1888
|
-
display: flex;
|
|
1889
|
-
flex-direction: column;
|
|
1890
|
-
gap: 8px;
|
|
1891
|
-
padding: 10px 16px;
|
|
1892
|
-
border-top: var(--nc-guide) solid var(--nc-line);
|
|
1893
|
-
background: var(--nc-canvas);
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
/* src/css/components/chat-bubble-user.css */
|
|
1898
|
-
@layer components {
|
|
1899
|
-
.nc-chat-bubble-user {
|
|
1900
|
-
align-self: flex-end;
|
|
1901
|
-
width: fit-content;
|
|
1902
|
-
max-width: 84%;
|
|
1903
|
-
padding: 9px 13px;
|
|
1904
|
-
border: var(--nc-guide) solid var(--nc-ink);
|
|
1905
|
-
border-radius: var(--nc-rounded-md);
|
|
1906
|
-
background: var(--nc-ink);
|
|
1907
|
-
color: var(--nc-canvas);
|
|
1908
|
-
font-family:
|
|
1909
|
-
"Geist",
|
|
1910
|
-
"Inter",
|
|
1911
|
-
system-ui,
|
|
1912
|
-
-apple-system,
|
|
1913
|
-
"Segoe UI",
|
|
1914
|
-
sans-serif;
|
|
1915
|
-
font-size: 14px;
|
|
1916
|
-
line-height: 20px;
|
|
1917
|
-
white-space: pre-wrap;
|
|
1918
|
-
word-break: break-word;
|
|
1919
|
-
animation-name: nc-msg-in;
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
|
-
/* src/css/components/chat-bubble-agent.css */
|
|
1924
|
-
@layer components {
|
|
1925
|
-
.nc-chat-bubble-agent {
|
|
1926
|
-
--nc-accent: var(--nc-link);
|
|
1927
|
-
align-self: flex-start;
|
|
1928
|
-
width: fit-content;
|
|
1929
|
-
max-width: 84%;
|
|
1930
|
-
padding: 9px 13px 9px 15px;
|
|
1931
|
-
border: var(--nc-guide) solid var(--nc-line);
|
|
1932
|
-
border-radius: var(--nc-rounded-md);
|
|
1933
|
-
box-shadow: inset 3px 0 0 0 var(--nc-accent);
|
|
1934
|
-
background: var(--nc-canvas);
|
|
1935
|
-
color: var(--nc-ink);
|
|
1936
|
-
font-family:
|
|
1937
|
-
"Geist",
|
|
1938
|
-
"Inter",
|
|
1939
|
-
system-ui,
|
|
1940
|
-
-apple-system,
|
|
1941
|
-
"Segoe UI",
|
|
1942
|
-
sans-serif;
|
|
1943
|
-
font-size: 14px;
|
|
1944
|
-
line-height: 20px;
|
|
1945
|
-
white-space: pre-wrap;
|
|
1946
|
-
word-break: break-word;
|
|
1947
|
-
animation-name: nc-msg-in;
|
|
1948
|
-
}
|
|
1949
|
-
.nc-chat-bubble-agent[data-accent=green] {
|
|
1950
|
-
--nc-accent: var(--nc-link);
|
|
1951
|
-
}
|
|
1952
|
-
.nc-chat-bubble-agent[data-accent=cyan] {
|
|
1953
|
-
--nc-accent: var(--nc-info);
|
|
1954
|
-
}
|
|
1955
|
-
.nc-chat-bubble-agent[data-accent=amber] {
|
|
1956
|
-
--nc-accent: var(--nc-warning-deep);
|
|
1957
|
-
}
|
|
1958
|
-
.nc-chat-bubble-agent[data-accent=ink] {
|
|
1959
|
-
--nc-accent: var(--nc-ink);
|
|
1960
|
-
}
|
|
1961
|
-
.nc-chat-bubble-agent[data-accent=red] {
|
|
1962
|
-
--nc-accent: var(--nc-error);
|
|
1963
|
-
}
|
|
1964
|
-
.nc-chat-bubble-agent code {
|
|
1965
|
-
padding: 1px 5px;
|
|
1966
|
-
border-radius: var(--nc-rounded-xs);
|
|
1967
|
-
background: var(--nc-canvas-soft-2);
|
|
1968
|
-
color: var(--nc-ink);
|
|
1969
|
-
font-family:
|
|
1970
|
-
"Geist Mono",
|
|
1971
|
-
ui-monospace,
|
|
1972
|
-
SFMono-Regular,
|
|
1973
|
-
Menlo,
|
|
1974
|
-
Monaco,
|
|
1975
|
-
monospace;
|
|
1976
|
-
font-size: 12px;
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
|
|
1980
|
-
/* src/css/components/tool-call-trace.css */
|
|
1981
|
-
@layer components {
|
|
1982
|
-
.nc-tool-call-trace {
|
|
1983
|
-
align-self: flex-start;
|
|
1984
|
-
width: fit-content;
|
|
1985
|
-
max-width: 92%;
|
|
1986
|
-
padding: 6px 10px;
|
|
1987
|
-
border: var(--nc-guide) solid var(--nc-line);
|
|
1988
|
-
border-radius: var(--nc-rounded-sm);
|
|
1989
|
-
background: var(--nc-canvas-soft-2);
|
|
1990
|
-
color: var(--nc-body);
|
|
1991
|
-
font-family:
|
|
1992
|
-
"Geist Mono",
|
|
1993
|
-
ui-monospace,
|
|
1994
|
-
SFMono-Regular,
|
|
1995
|
-
Menlo,
|
|
1996
|
-
Monaco,
|
|
1997
|
-
monospace;
|
|
1998
|
-
font-size: 12px;
|
|
1999
|
-
line-height: 17px;
|
|
2000
|
-
animation-name: nc-msg-in;
|
|
2001
|
-
}
|
|
2002
|
-
.nc-tool-call-trace::before {
|
|
2003
|
-
content: "";
|
|
2004
|
-
display: inline-block;
|
|
2005
|
-
width: 13px;
|
|
2006
|
-
height: 13px;
|
|
2007
|
-
margin-right: 8px;
|
|
2008
|
-
vertical-align: -2px;
|
|
2009
|
-
background-color: var(--nc-muted);
|
|
2010
|
-
-webkit-mask: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-chevron-right"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="m9 18 6-6-6-6" />%0A</svg>%0A') center / contain no-repeat;
|
|
2011
|
-
mask: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-chevron-right"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="m9 18 6-6-6-6" />%0A</svg>%0A') center / contain no-repeat;
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
1756
|
/* src/css/components/memory-item.css */
|
|
2016
1757
|
@layer components {
|
|
2017
1758
|
.nc-memory-item {
|
|
@@ -2091,241 +1832,6 @@
|
|
|
2091
1832
|
}
|
|
2092
1833
|
}
|
|
2093
1834
|
|
|
2094
|
-
/* src/css/components/approval-gate.css */
|
|
2095
|
-
@layer components {
|
|
2096
|
-
.nc-approval-gate {
|
|
2097
|
-
align-self: stretch;
|
|
2098
|
-
display: grid;
|
|
2099
|
-
gap: 6px;
|
|
2100
|
-
padding: 12px;
|
|
2101
|
-
border: var(--nc-guide) solid color-mix(in srgb, var(--nc-warning-deep) 40%, var(--nc-warning-soft));
|
|
2102
|
-
border-radius: var(--nc-rounded-md);
|
|
2103
|
-
background: var(--nc-warning-soft);
|
|
2104
|
-
color: var(--nc-ink);
|
|
2105
|
-
font-family:
|
|
2106
|
-
"Geist",
|
|
2107
|
-
"Inter",
|
|
2108
|
-
system-ui,
|
|
2109
|
-
-apple-system,
|
|
2110
|
-
"Segoe UI",
|
|
2111
|
-
sans-serif;
|
|
2112
|
-
animation-name: nc-msg-in;
|
|
2113
|
-
transition:
|
|
2114
|
-
background-color var(--nc-duration-confirm) var(--nc-ease-out-strong),
|
|
2115
|
-
border-color var(--nc-duration-confirm) var(--nc-ease-out-strong),
|
|
2116
|
-
color var(--nc-duration-confirm) var(--nc-ease-out-strong);
|
|
2117
|
-
}
|
|
2118
|
-
.nc-approval-gate-label {
|
|
2119
|
-
font-family:
|
|
2120
|
-
"Geist Mono",
|
|
2121
|
-
ui-monospace,
|
|
2122
|
-
SFMono-Regular,
|
|
2123
|
-
Menlo,
|
|
2124
|
-
Monaco,
|
|
2125
|
-
monospace;
|
|
2126
|
-
font-size: 10px;
|
|
2127
|
-
font-weight: 600;
|
|
2128
|
-
line-height: 16px;
|
|
2129
|
-
color: var(--nc-warning-deep);
|
|
2130
|
-
text-transform: uppercase;
|
|
2131
|
-
letter-spacing: 0.06em;
|
|
2132
|
-
}
|
|
2133
|
-
.nc-approval-gate-title {
|
|
2134
|
-
font-size: 14px;
|
|
2135
|
-
font-weight: 500;
|
|
2136
|
-
line-height: 20px;
|
|
2137
|
-
color: var(--nc-ink);
|
|
2138
|
-
}
|
|
2139
|
-
.nc-approval-gate-copy {
|
|
2140
|
-
font-size: 13px;
|
|
2141
|
-
line-height: 18px;
|
|
2142
|
-
color: var(--nc-body);
|
|
2143
|
-
}
|
|
2144
|
-
.nc-approval-gate-actions {
|
|
2145
|
-
display: flex;
|
|
2146
|
-
flex-wrap: wrap;
|
|
2147
|
-
gap: 6px;
|
|
2148
|
-
margin-top: 2px;
|
|
2149
|
-
}
|
|
2150
|
-
.nc-approval-gate-actions button {
|
|
2151
|
-
padding: 5px 10px;
|
|
2152
|
-
border: var(--nc-guide) solid var(--nc-ink);
|
|
2153
|
-
border-radius: var(--nc-rounded-sm);
|
|
2154
|
-
background: var(--nc-ink);
|
|
2155
|
-
color: var(--nc-canvas);
|
|
2156
|
-
font-family: inherit;
|
|
2157
|
-
font-size: 12px;
|
|
2158
|
-
font-weight: 500;
|
|
2159
|
-
line-height: 16px;
|
|
2160
|
-
cursor: pointer;
|
|
2161
|
-
}
|
|
2162
|
-
.nc-approval-gate-actions button[data-variant=secondary] {
|
|
2163
|
-
border-color: var(--nc-line);
|
|
2164
|
-
background: var(--nc-canvas);
|
|
2165
|
-
color: var(--nc-ink);
|
|
2166
|
-
}
|
|
2167
|
-
.nc-approval-gate-actions button[data-variant=outline] {
|
|
2168
|
-
border-color: var(--nc-line);
|
|
2169
|
-
background: transparent;
|
|
2170
|
-
color: var(--nc-ink);
|
|
2171
|
-
}
|
|
2172
|
-
.nc-approval-gate-actions button[data-variant=destructive] {
|
|
2173
|
-
border-color: var(--nc-error);
|
|
2174
|
-
background: var(--nc-canvas);
|
|
2175
|
-
color: var(--nc-error-deep);
|
|
2176
|
-
}
|
|
2177
|
-
.nc-approval-gate-actions button::before {
|
|
2178
|
-
content: "";
|
|
2179
|
-
display: inline-block;
|
|
2180
|
-
width: 13px;
|
|
2181
|
-
height: 13px;
|
|
2182
|
-
margin-right: 5px;
|
|
2183
|
-
vertical-align: -2px;
|
|
2184
|
-
background-color: currentColor;
|
|
2185
|
-
-webkit-mask: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-check"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M20 6 9 17l-5-5" />%0A</svg>%0A') center / contain no-repeat;
|
|
2186
|
-
mask: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-check"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M20 6 9 17l-5-5" />%0A</svg>%0A') center / contain no-repeat;
|
|
2187
|
-
}
|
|
2188
|
-
.nc-approval-gate-actions button[data-variant=secondary]::before {
|
|
2189
|
-
-webkit-mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-square-pen"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />%0A <path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z" />%0A</svg>%0A');
|
|
2190
|
-
mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-square-pen"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />%0A <path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z" />%0A</svg>%0A');
|
|
2191
|
-
}
|
|
2192
|
-
.nc-approval-gate-actions button[data-approval-choice=deny]::before {
|
|
2193
|
-
-webkit-mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-ban"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <circle cx="12" cy="12" r="10" />%0A <path d="M4.929 4.929 19.07 19.071" />%0A</svg>%0A');
|
|
2194
|
-
mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-ban"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <circle cx="12" cy="12" r="10" />%0A <path d="M4.929 4.929 19.07 19.071" />%0A</svg>%0A');
|
|
2195
|
-
}
|
|
2196
|
-
.nc-approval-gate-actions button[data-approval-choice=session]::before {
|
|
2197
|
-
-webkit-mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-clock"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <circle cx="12" cy="12" r="10" />%0A <path d="M12 6v6l4 2" />%0A</svg>%0A');
|
|
2198
|
-
mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-clock"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <circle cx="12" cy="12" r="10" />%0A <path d="M12 6v6l4 2" />%0A</svg>%0A');
|
|
2199
|
-
}
|
|
2200
|
-
.nc-approval-gate-actions button[data-approval-choice=always]::before {
|
|
2201
|
-
-webkit-mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-shield"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" />%0A</svg>%0A');
|
|
2202
|
-
mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-shield"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" />%0A</svg>%0A');
|
|
2203
|
-
}
|
|
2204
|
-
.nc-approval-gate-actions button:focus-visible {
|
|
2205
|
-
outline: var(--nc-focus-ring-width) solid var(--nc-focus-ring);
|
|
2206
|
-
outline-offset: 0;
|
|
2207
|
-
}
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
/* src/css/components/artifact-preview.css */
|
|
2211
|
-
@layer components {
|
|
2212
|
-
.nc-artifact-preview {
|
|
2213
|
-
align-self: stretch;
|
|
2214
|
-
overflow: hidden;
|
|
2215
|
-
border: var(--nc-guide) solid var(--nc-line);
|
|
2216
|
-
border-radius: var(--nc-rounded-md);
|
|
2217
|
-
background: var(--nc-canvas);
|
|
2218
|
-
font-family:
|
|
2219
|
-
"Geist",
|
|
2220
|
-
"Inter",
|
|
2221
|
-
system-ui,
|
|
2222
|
-
-apple-system,
|
|
2223
|
-
"Segoe UI",
|
|
2224
|
-
sans-serif;
|
|
2225
|
-
animation-name: nc-msg-in;
|
|
2226
|
-
}
|
|
2227
|
-
.nc-artifact-preview-header {
|
|
2228
|
-
display: flex;
|
|
2229
|
-
align-items: center;
|
|
2230
|
-
gap: 8px;
|
|
2231
|
-
padding: 7px 10px;
|
|
2232
|
-
border-bottom: var(--nc-guide) solid var(--nc-line);
|
|
2233
|
-
background: var(--nc-canvas-soft);
|
|
2234
|
-
}
|
|
2235
|
-
.nc-artifact-preview-header::before {
|
|
2236
|
-
content: "";
|
|
2237
|
-
flex: none;
|
|
2238
|
-
width: 14px;
|
|
2239
|
-
height: 14px;
|
|
2240
|
-
background-color: var(--nc-muted);
|
|
2241
|
-
-webkit-mask: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-file-text"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" />%0A <path d="M14 2v5a1 1 0 0 0 1 1h5" />%0A <path d="M10 9H8" />%0A <path d="M16 13H8" />%0A <path d="M16 17H8" />%0A</svg>%0A') center / contain no-repeat;
|
|
2242
|
-
mask: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-file-text"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" />%0A <path d="M14 2v5a1 1 0 0 0 1 1h5" />%0A <path d="M10 9H8" />%0A <path d="M16 13H8" />%0A <path d="M16 17H8" />%0A</svg>%0A') center / contain no-repeat;
|
|
2243
|
-
}
|
|
2244
|
-
.nc-artifact-preview[data-kind=sheet] .nc-artifact-preview-header::before {
|
|
2245
|
-
-webkit-mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-table"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M12 3v18" />%0A <rect width="18" height="18" x="3" y="3" rx="2" />%0A <path d="M3 9h18" />%0A <path d="M3 15h18" />%0A</svg>%0A');
|
|
2246
|
-
mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-table"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M12 3v18" />%0A <rect width="18" height="18" x="3" y="3" rx="2" />%0A <path d="M3 9h18" />%0A <path d="M3 15h18" />%0A</svg>%0A');
|
|
2247
|
-
}
|
|
2248
|
-
.nc-artifact-preview[data-kind=code] .nc-artifact-preview-header::before {
|
|
2249
|
-
-webkit-mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-code"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="m16 18 6-6-6-6" />%0A <path d="m8 6-6 6 6 6" />%0A</svg>%0A');
|
|
2250
|
-
mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-code"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="m16 18 6-6-6-6" />%0A <path d="m8 6-6 6 6 6" />%0A</svg>%0A');
|
|
2251
|
-
}
|
|
2252
|
-
.nc-artifact-preview[data-kind=pdf] .nc-artifact-preview-header::before {
|
|
2253
|
-
-webkit-mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-file"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" />%0A <path d="M14 2v5a1 1 0 0 0 1 1h5" />%0A</svg>%0A');
|
|
2254
|
-
mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-file"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" />%0A <path d="M14 2v5a1 1 0 0 0 1 1h5" />%0A</svg>%0A');
|
|
2255
|
-
}
|
|
2256
|
-
.nc-artifact-preview[data-kind=report] .nc-artifact-preview-header::before {
|
|
2257
|
-
-webkit-mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-chart-column"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M3 3v16a2 2 0 0 0 2 2h16" />%0A <path d="M18 17V9" />%0A <path d="M13 17V5" />%0A <path d="M8 17v-3" />%0A</svg>%0A');
|
|
2258
|
-
mask-image: url('data:image/svg+xml,<!-- @license lucide-static v1.21.0 - ISC -->%0A<svg%0A class="lucide lucide-chart-column"%0A xmlns="http://www.w3.org/2000/svg"%0A width="24"%0A height="24"%0A viewBox="0 0 24 24"%0A fill="none"%0A stroke="currentColor"%0A stroke-width="1.6"%0A stroke-linecap="round"%0A stroke-linejoin="round"%0A>%0A <path d="M3 3v16a2 2 0 0 0 2 2h16" />%0A <path d="M18 17V9" />%0A <path d="M13 17V5" />%0A <path d="M8 17v-3" />%0A</svg>%0A');
|
|
2259
|
-
}
|
|
2260
|
-
.nc-artifact-preview-title {
|
|
2261
|
-
font-size: 13px;
|
|
2262
|
-
font-weight: 500;
|
|
2263
|
-
line-height: 18px;
|
|
2264
|
-
color: var(--nc-ink);
|
|
2265
|
-
}
|
|
2266
|
-
.nc-artifact-preview-kind {
|
|
2267
|
-
margin-left: auto;
|
|
2268
|
-
font-family:
|
|
2269
|
-
"Geist Mono",
|
|
2270
|
-
ui-monospace,
|
|
2271
|
-
SFMono-Regular,
|
|
2272
|
-
Menlo,
|
|
2273
|
-
Monaco,
|
|
2274
|
-
monospace;
|
|
2275
|
-
font-size: 10px;
|
|
2276
|
-
font-weight: 500;
|
|
2277
|
-
line-height: 16px;
|
|
2278
|
-
color: var(--nc-muted);
|
|
2279
|
-
text-transform: uppercase;
|
|
2280
|
-
letter-spacing: 0.05em;
|
|
2281
|
-
}
|
|
2282
|
-
.nc-artifact-preview-body {
|
|
2283
|
-
padding: 10px 12px;
|
|
2284
|
-
color: var(--nc-body);
|
|
2285
|
-
font-size: 13px;
|
|
2286
|
-
line-height: 18px;
|
|
2287
|
-
white-space: pre-wrap;
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
/* src/css/components/typing-dots.css */
|
|
2292
|
-
@layer components {
|
|
2293
|
-
.nc-typing-dots {
|
|
2294
|
-
--nc-accent: var(--nc-link);
|
|
2295
|
-
align-self: flex-start;
|
|
2296
|
-
display: inline-flex;
|
|
2297
|
-
gap: 4px;
|
|
2298
|
-
align-items: center;
|
|
2299
|
-
width: fit-content;
|
|
2300
|
-
padding: 10px 14px 10px 15px;
|
|
2301
|
-
border: var(--nc-guide) solid var(--nc-line);
|
|
2302
|
-
border-radius: var(--nc-rounded-md);
|
|
2303
|
-
box-shadow: inset 3px 0 0 0 var(--nc-accent);
|
|
2304
|
-
background: var(--nc-canvas);
|
|
2305
|
-
}
|
|
2306
|
-
.nc-typing-dots[data-accent=green] {
|
|
2307
|
-
--nc-accent: var(--nc-link);
|
|
2308
|
-
}
|
|
2309
|
-
.nc-typing-dots[data-accent=cyan] {
|
|
2310
|
-
--nc-accent: var(--nc-info);
|
|
2311
|
-
}
|
|
2312
|
-
.nc-typing-dots[data-accent=amber] {
|
|
2313
|
-
--nc-accent: var(--nc-warning-deep);
|
|
2314
|
-
}
|
|
2315
|
-
.nc-typing-dots[data-accent=ink] {
|
|
2316
|
-
--nc-accent: var(--nc-ink);
|
|
2317
|
-
}
|
|
2318
|
-
.nc-typing-dots[data-accent=red] {
|
|
2319
|
-
--nc-accent: var(--nc-error);
|
|
2320
|
-
}
|
|
2321
|
-
.nc-typing-dots-dot {
|
|
2322
|
-
width: 5px;
|
|
2323
|
-
height: 5px;
|
|
2324
|
-
border-radius: var(--nc-rounded-full);
|
|
2325
|
-
background: var(--nc-muted);
|
|
2326
|
-
}
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
1835
|
/* src/css/components/state-row.css */
|
|
2330
1836
|
@layer components {
|
|
2331
1837
|
.nc-state-row {
|
|
@@ -2384,55 +1890,6 @@
|
|
|
2384
1890
|
}
|
|
2385
1891
|
}
|
|
2386
1892
|
|
|
2387
|
-
/* src/css/components/agent-avatar.css */
|
|
2388
|
-
@layer components {
|
|
2389
|
-
.nc-agent-avatar {
|
|
2390
|
-
--nc-accent: var(--nc-line);
|
|
2391
|
-
display: grid;
|
|
2392
|
-
place-items: center;
|
|
2393
|
-
width: 32px;
|
|
2394
|
-
height: 32px;
|
|
2395
|
-
overflow: hidden;
|
|
2396
|
-
border: var(--nc-guide) solid var(--nc-accent);
|
|
2397
|
-
border-radius: var(--nc-rounded-full);
|
|
2398
|
-
background: var(--nc-canvas-soft);
|
|
2399
|
-
color: var(--nc-ink);
|
|
2400
|
-
font-family:
|
|
2401
|
-
"Geist Mono",
|
|
2402
|
-
ui-monospace,
|
|
2403
|
-
SFMono-Regular,
|
|
2404
|
-
Menlo,
|
|
2405
|
-
Monaco,
|
|
2406
|
-
monospace;
|
|
2407
|
-
font-size: 12px;
|
|
2408
|
-
font-weight: 600;
|
|
2409
|
-
line-height: 1;
|
|
2410
|
-
letter-spacing: 0.02em;
|
|
2411
|
-
}
|
|
2412
|
-
.nc-agent-avatar img,
|
|
2413
|
-
.nc-agent-avatar svg {
|
|
2414
|
-
display: block;
|
|
2415
|
-
width: 100%;
|
|
2416
|
-
height: 100%;
|
|
2417
|
-
object-fit: cover;
|
|
2418
|
-
}
|
|
2419
|
-
.nc-agent-avatar[data-accent=green] {
|
|
2420
|
-
--nc-accent: var(--nc-link);
|
|
2421
|
-
}
|
|
2422
|
-
.nc-agent-avatar[data-accent=cyan] {
|
|
2423
|
-
--nc-accent: var(--nc-info);
|
|
2424
|
-
}
|
|
2425
|
-
.nc-agent-avatar[data-accent=amber] {
|
|
2426
|
-
--nc-accent: var(--nc-warning-deep);
|
|
2427
|
-
}
|
|
2428
|
-
.nc-agent-avatar[data-accent=ink] {
|
|
2429
|
-
--nc-accent: var(--nc-ink);
|
|
2430
|
-
}
|
|
2431
|
-
.nc-agent-avatar[data-accent=red] {
|
|
2432
|
-
--nc-accent: var(--nc-error);
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
|
|
2436
1893
|
/* src/css/components/usage-meter.css */
|
|
2437
1894
|
@layer components {
|
|
2438
1895
|
.nc-usage-meter {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nadicodeai/design-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"./tokens/dtcg": "./dist/tokens/nadicode.dtcg.json",
|
|
13
13
|
"./tailwind": "./dist/tailwind/nadicode.tailwind.json",
|
|
14
14
|
"./css": "./dist/css/index.css",
|
|
15
|
-
"./css/chat-autoplay.js": "./dist/css/chat-autoplay.js",
|
|
16
15
|
"./icons/*": "./dist/icons/*",
|
|
17
16
|
"./favicon/*": "./dist/favicon/*",
|
|
18
17
|
"./assets/logo-mark": {
|
|
@@ -1,537 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* chat-autoplay.js — the single optional vanilla enhancer for NadicodeAI chat
|
|
3
|
-
* threads. The ONLY JavaScript in @nadicodeai/design-system.
|
|
4
|
-
*
|
|
5
|
-
* It is pure progressive enhancement, framework-free, dependency-free, with no
|
|
6
|
-
* module-loader statements and no third-party runtime. It turns an
|
|
7
|
-
* already-static `.nc-chat-thread` into an auto-playing, looping agent
|
|
8
|
-
* conversation.
|
|
9
|
-
*
|
|
10
|
-
* Runtime behavior:
|
|
11
|
-
*
|
|
12
|
-
* (a) Motion on: messages are hidden, then revealed in scripted source order by
|
|
13
|
-
* toggling the `.nc-anim-msg-in` hook (the rise-in). A typing indicator
|
|
14
|
-
* (`.nc-typing-dots`) shows between turns, the thread body auto-scrolls to
|
|
15
|
-
* the newest message, the `.nc-usage-meter-fill` is driven via `--nc-usage`,
|
|
16
|
-
* and after `LOOP_HOLD` the cycle restarts.
|
|
17
|
-
*
|
|
18
|
-
* (b) `prefers-reduced-motion: reduce`: the FULL thread is shown immediately,
|
|
19
|
-
* every message visible, NO loop, no typing animation. (We also stop if the
|
|
20
|
-
* preference changes to reduce mid-play.)
|
|
21
|
-
*
|
|
22
|
-
* (c) Script absent: the conversation renders FULLY and statically from CSS
|
|
23
|
-
* alone. The atoms set `animation-name: nc-msg-in` but, without the
|
|
24
|
-
* `.nc-anim-msg-in` hook supplying a duration, that is inert and every
|
|
25
|
-
* message is visible. This file is the ONLY thing that ever hides a message
|
|
26
|
-
* (via the per-element inline `display` it sets while playing). No hidden
|
|
27
|
-
* class is ever present in static markup, so no-JS == full static thread.
|
|
28
|
-
*
|
|
29
|
-
* (d) Playback starts on scroll-into-view via `IntersectionObserver`; it pauses
|
|
30
|
-
* (resets to the static full thread and re-arms) when scrolled fully out.
|
|
31
|
-
*
|
|
32
|
-
* Timing parity with `src/css/motion.css`: the loop reads the duration token
|
|
33
|
-
* (`--nc-duration-stagger`) from the thread's computed style and uses the
|
|
34
|
-
* per-role `defaultTiming()` rhythm below. The stagger unit comes from CSS, not
|
|
35
|
-
* a re-typed magic number; this module owns the sequencing cadence because it
|
|
36
|
-
* is runtime behavior rather than a reusable visual token.
|
|
37
|
-
*
|
|
38
|
-
* Opt-in: only `.nc-chat-thread[data-nc-autoplay]` elements are enhanced. A
|
|
39
|
-
* thread without the attribute is left fully static.
|
|
40
|
-
*
|
|
41
|
-
* Usage (no bundler assumed):
|
|
42
|
-
* <script src="…/css/chat-autoplay.js"></script>
|
|
43
|
-
* It self-initializes on DOMContentLoaded and also exposes
|
|
44
|
-
* `window.ncChatAutoplay.init(root?)` for manual / dynamic init.
|
|
45
|
-
*/
|
|
46
|
-
(function () {
|
|
47
|
-
"use strict";
|
|
48
|
-
|
|
49
|
-
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
50
|
-
|
|
51
|
-
// The CSS token owns visual stagger. These values own runtime sequencing.
|
|
52
|
-
var LOOP_HOLD = 6000; // Keep the completed state visible for most of the loop.
|
|
53
|
-
var START_DELAY = 240;
|
|
54
|
-
|
|
55
|
-
function defaultTiming(role, textLen) {
|
|
56
|
-
var len = textLen || 0;
|
|
57
|
-
switch (role) {
|
|
58
|
-
case "user":
|
|
59
|
-
return { typingMs: 0, holdMs: 1100 + len * 8 };
|
|
60
|
-
case "tool":
|
|
61
|
-
return { typingMs: 380, holdMs: 700 };
|
|
62
|
-
case "state":
|
|
63
|
-
return { typingMs: 220, holdMs: 700 };
|
|
64
|
-
case "memory":
|
|
65
|
-
return { typingMs: 380, holdMs: 900 };
|
|
66
|
-
case "handoff":
|
|
67
|
-
return { typingMs: 420, holdMs: 1300 };
|
|
68
|
-
case "approval":
|
|
69
|
-
return { typingMs: 460, holdMs: 1600 };
|
|
70
|
-
case "artifact":
|
|
71
|
-
return { typingMs: 600, holdMs: 1800 };
|
|
72
|
-
case "agent":
|
|
73
|
-
default:
|
|
74
|
-
return { typingMs: 680, holdMs: 900 + len * 12 };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Infer a message's role from its NadicodeAI atom class so authors don't have
|
|
79
|
-
// to duplicate the role in a data attribute. An explicit `data-nc-role`
|
|
80
|
-
// overrides. Falls back to "agent" (the slowest, safest cadence).
|
|
81
|
-
var ROLE_BY_CLASS = [
|
|
82
|
-
["nc-chat-bubble-user", "user"],
|
|
83
|
-
["nc-chat-bubble-agent", "agent"],
|
|
84
|
-
["nc-tool-call-trace", "tool"],
|
|
85
|
-
["nc-memory-item", "memory"],
|
|
86
|
-
["nc-handoff-banner", "handoff"],
|
|
87
|
-
["nc-approval-gate", "approval"],
|
|
88
|
-
["nc-artifact-preview", "artifact"],
|
|
89
|
-
["nc-state-row", "state"],
|
|
90
|
-
];
|
|
91
|
-
|
|
92
|
-
function roleOf(el) {
|
|
93
|
-
var explicit = el.getAttribute("data-nc-role");
|
|
94
|
-
if (explicit) return explicit;
|
|
95
|
-
for (var i = 0; i < ROLE_BY_CLASS.length; i++) {
|
|
96
|
-
if (el.classList.contains(ROLE_BY_CLASS[i][0])) return ROLE_BY_CLASS[i][1];
|
|
97
|
-
}
|
|
98
|
-
return "agent";
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function reducedMotion() {
|
|
102
|
-
return (
|
|
103
|
-
typeof window.matchMedia === "function" &&
|
|
104
|
-
window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Read a duration token (e.g. "--nc-duration-stagger") off a live element and
|
|
109
|
-
// return milliseconds. Falls back to a documented default that equals the
|
|
110
|
-
// motion.css value if the bundle is not loaded (graceful, never throws).
|
|
111
|
-
function durationToken(el, name, fallbackMs) {
|
|
112
|
-
var raw = getComputedStyle(el).getPropertyValue(name).trim();
|
|
113
|
-
if (!raw) return fallbackMs;
|
|
114
|
-
if (raw.slice(-2) === "ms") return parseFloat(raw) || fallbackMs;
|
|
115
|
-
if (raw.slice(-1) === "s") return (parseFloat(raw) || 0) * 1000 || fallbackMs;
|
|
116
|
-
var n = parseFloat(raw);
|
|
117
|
-
return isNaN(n) ? fallbackMs : n;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ── One thread's player ───────────────────────────────────────────────────
|
|
121
|
-
function ChatThreadPlayer(thread) {
|
|
122
|
-
this.thread = thread;
|
|
123
|
-
this.body = thread.querySelector(".nc-chat-thread-body");
|
|
124
|
-
// Stable, source-ordered list of message atoms inside the body. The typing
|
|
125
|
-
// indicator and any element flagged to ignore are NOT messages.
|
|
126
|
-
this.messages = this.body
|
|
127
|
-
? Array.prototype.filter.call(this.body.children, function (el) {
|
|
128
|
-
return (
|
|
129
|
-
el.nodeType === 1 &&
|
|
130
|
-
!el.classList.contains("nc-typing-dots") &&
|
|
131
|
-
el.getAttribute("data-nc-autoplay-ignore") === null
|
|
132
|
-
);
|
|
133
|
-
})
|
|
134
|
-
: [];
|
|
135
|
-
this.typing = this.body ? this.body.querySelector(".nc-typing-dots") : null;
|
|
136
|
-
this.meterFill = thread.querySelector(".nc-usage-meter-fill");
|
|
137
|
-
this.staggerMs = 160; // overwritten from the CSS token at enhance()
|
|
138
|
-
this.timers = [];
|
|
139
|
-
this.running = false;
|
|
140
|
-
this.cancelled = false;
|
|
141
|
-
// Monotonic loop epoch. `_runLoop` captures the value live at start and every
|
|
142
|
-
// guard checks it. `stop()`/`rearm()` bump it so a stale continuation — a
|
|
143
|
-
// `_sleep` timer that already fired but whose `.then` is still queued when the
|
|
144
|
-
// player is stopped and immediately re-armed on scroll-in — sees a mismatched
|
|
145
|
-
// epoch and dies permanently, instead of resuming once `cancelled` flips back
|
|
146
|
-
// to false and racing the fresh loop. Single, uninterrupted playback never
|
|
147
|
-
// changes the epoch mid-run, so the observed sequence is unchanged.
|
|
148
|
-
this.generation = 0;
|
|
149
|
-
// WCAG 2.2.2 user-pause latch. Set true when the user presses the pause button;
|
|
150
|
-
// cleared when the user presses resume. play() and the in-view observer branch
|
|
151
|
-
// both check it; a paused thread stays paused across scroll-out/in.
|
|
152
|
-
this.userPaused = false;
|
|
153
|
-
// Set to true once the sequence has reached its end at least once. Used by
|
|
154
|
-
// the IntersectionObserver entry branch: a thread that has completed is shown
|
|
155
|
-
// in its final visible state on re-entry rather than replayed from blank, so
|
|
156
|
-
// a reader who scrolls back sees the payoff frame instead of an empty/typing state.
|
|
157
|
-
this.completedOnce = false;
|
|
158
|
-
// Accent for an auto-created typing bubble, inferred from the first agent
|
|
159
|
-
// bubble's data-accent (default green, matching the prototype).
|
|
160
|
-
this.accent = "green";
|
|
161
|
-
var firstAgent = thread.querySelector(".nc-chat-bubble-agent[data-accent]");
|
|
162
|
-
if (firstAgent) this.accent = firstAgent.getAttribute("data-accent") || "green";
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
ChatThreadPlayer.prototype._clearTimers = function () {
|
|
166
|
-
for (var i = 0; i < this.timers.length; i++) clearTimeout(this.timers[i]);
|
|
167
|
-
this.timers = [];
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
ChatThreadPlayer.prototype._sleep = function (ms) {
|
|
171
|
-
var timers = this.timers;
|
|
172
|
-
return new Promise(function (resolve) {
|
|
173
|
-
var t = setTimeout(resolve, ms);
|
|
174
|
-
timers.push(t);
|
|
175
|
-
});
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// Fully static, fully visible end-state. Used for no-loop (reduced-motion),
|
|
179
|
-
// for pause-out-of-view, and as the baseline each loop resets from. Removes
|
|
180
|
-
// every trace the player added so the DOM matches the no-JS static thread.
|
|
181
|
-
ChatThreadPlayer.prototype._showAll = function () {
|
|
182
|
-
for (var i = 0; i < this.messages.length; i++) {
|
|
183
|
-
var m = this.messages[i];
|
|
184
|
-
m.style.display = "";
|
|
185
|
-
m.classList.remove("nc-anim-msg-in");
|
|
186
|
-
}
|
|
187
|
-
this._hideTyping();
|
|
188
|
-
this.thread.removeAttribute("data-nc-playing");
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
ChatThreadPlayer.prototype._hideAllMessages = function () {
|
|
192
|
-
for (var i = 0; i < this.messages.length; i++) {
|
|
193
|
-
this.messages[i].style.display = "none";
|
|
194
|
-
this.messages[i].classList.remove("nc-anim-msg-in");
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
ChatThreadPlayer.prototype._scrollToBottom = function () {
|
|
199
|
-
var body = this.body;
|
|
200
|
-
if (!body) return;
|
|
201
|
-
requestAnimationFrame(function () {
|
|
202
|
-
body.scrollTop = body.scrollHeight;
|
|
203
|
-
});
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
// Ensure a typing indicator exists; create one if the markup has none so the
|
|
207
|
-
// "typing between turns" behaviour works on any thread. Created element is
|
|
208
|
-
// tagged so _showAll can drop it.
|
|
209
|
-
ChatThreadPlayer.prototype._ensureTyping = function () {
|
|
210
|
-
if (this.typing) return this.typing;
|
|
211
|
-
if (!this.body) return null;
|
|
212
|
-
var el = document.createElement("div");
|
|
213
|
-
el.className = "nc-typing-dots";
|
|
214
|
-
el.setAttribute("data-accent", this.accent);
|
|
215
|
-
el.setAttribute("aria-hidden", "true");
|
|
216
|
-
el.setAttribute("data-nc-autoplay-created", "");
|
|
217
|
-
for (var i = 0; i < 3; i++) {
|
|
218
|
-
var dot = document.createElement("span");
|
|
219
|
-
dot.className = "nc-typing-dots-dot nc-anim-typing";
|
|
220
|
-
dot.style.setProperty("--nc-anim-delay", i * this.staggerMs + "ms");
|
|
221
|
-
el.appendChild(dot);
|
|
222
|
-
}
|
|
223
|
-
el.style.display = "none";
|
|
224
|
-
this.body.appendChild(el);
|
|
225
|
-
this.typing = el;
|
|
226
|
-
return el;
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
ChatThreadPlayer.prototype._showTyping = function () {
|
|
230
|
-
var el = this._ensureTyping();
|
|
231
|
-
if (!el) return;
|
|
232
|
-
// Move the indicator to the end of the body so it sits below revealed turns.
|
|
233
|
-
this.body.appendChild(el);
|
|
234
|
-
el.style.display = "";
|
|
235
|
-
this._scrollToBottom();
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
ChatThreadPlayer.prototype._hideTyping = function () {
|
|
239
|
-
if (this.typing) this.typing.style.display = "none";
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
ChatThreadPlayer.prototype._setMeter = function (fraction) {
|
|
243
|
-
if (!this.meterFill) return;
|
|
244
|
-
var pct = Math.max(0, Math.min(1, fraction)) * 100;
|
|
245
|
-
this.meterFill.style.setProperty("--nc-usage", pct + "%");
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
ChatThreadPlayer.prototype._reveal = function (msg, index) {
|
|
249
|
-
msg.style.display = "";
|
|
250
|
-
// Re-trigger the rise-in: the hook supplies duration/easing; the atom
|
|
251
|
-
// already carries `animation-name: nc-msg-in`. Force a reflow so re-adding
|
|
252
|
-
// the class restarts the animation on a re-loop.
|
|
253
|
-
msg.style.setProperty("--nc-anim-delay", "0ms");
|
|
254
|
-
msg.classList.remove("nc-anim-msg-in");
|
|
255
|
-
void msg.offsetWidth;
|
|
256
|
-
msg.classList.add("nc-anim-msg-in");
|
|
257
|
-
this._setMeter((index + 1) / this.messages.length);
|
|
258
|
-
this._scrollToBottom();
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
// A loop is "stale" once it is cancelled OR a newer epoch has started; either
|
|
262
|
-
// way it must stop touching the DOM. `myGen` is captured once at start so a queued
|
|
263
|
-
// continuation can detect that the player was stopped-and-re-armed underneath
|
|
264
|
-
// it (see the `generation` note in the constructor).
|
|
265
|
-
ChatThreadPlayer.prototype._runLoop = function () {
|
|
266
|
-
var myGen = this.generation;
|
|
267
|
-
function stale(player) {
|
|
268
|
-
return player.cancelled || player.generation !== myGen;
|
|
269
|
-
}
|
|
270
|
-
function loop(player) {
|
|
271
|
-
if (stale(player)) return Promise.resolve();
|
|
272
|
-
player._hideAllMessages();
|
|
273
|
-
player._hideTyping();
|
|
274
|
-
player._setMeter(0);
|
|
275
|
-
return player
|
|
276
|
-
._sleep(START_DELAY)
|
|
277
|
-
.then(function () {
|
|
278
|
-
var chain = Promise.resolve();
|
|
279
|
-
player.messages.forEach(function (msg, i) {
|
|
280
|
-
chain = chain.then(function () {
|
|
281
|
-
if (stale(player)) return;
|
|
282
|
-
var role = roleOf(msg);
|
|
283
|
-
var t = defaultTiming(role, (msg.textContent || "").trim().length);
|
|
284
|
-
var step = function () {
|
|
285
|
-
if (stale(player)) return;
|
|
286
|
-
player._hideTyping();
|
|
287
|
-
player._reveal(msg, i);
|
|
288
|
-
return player._sleep(t.holdMs);
|
|
289
|
-
};
|
|
290
|
-
if (t.typingMs > 0) {
|
|
291
|
-
player._showTyping();
|
|
292
|
-
return player._sleep(t.typingMs).then(step);
|
|
293
|
-
}
|
|
294
|
-
return step();
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
return chain;
|
|
298
|
-
})
|
|
299
|
-
.then(function () {
|
|
300
|
-
if (stale(player)) return;
|
|
301
|
-
// Mark completion before the hold so the observer entry branch can
|
|
302
|
-
// show the final state on re-entry without replaying from blank.
|
|
303
|
-
player.completedOnce = true;
|
|
304
|
-
return player._sleep(LOOP_HOLD);
|
|
305
|
-
})
|
|
306
|
-
.then(function () {
|
|
307
|
-
if (stale(player)) return;
|
|
308
|
-
return loop(player);
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
return loop(this);
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
ChatThreadPlayer.prototype.play = function () {
|
|
315
|
-
if (this.running || this.cancelled) return;
|
|
316
|
-
// WCAG 2.2.2: user-pressed pause wins over every automatic restart.
|
|
317
|
-
if (this.userPaused) return;
|
|
318
|
-
// (b) Reduced motion: show the full thread immediately, no loop, no typing.
|
|
319
|
-
if (reducedMotion()) {
|
|
320
|
-
this._showAll();
|
|
321
|
-
this._setMeter(1);
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
if (!this.messages.length) return;
|
|
325
|
-
this.running = true;
|
|
326
|
-
this.thread.setAttribute("data-nc-playing", "");
|
|
327
|
-
this._runLoop();
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
// Stop and reset to the fully-visible static thread (re-armable).
|
|
331
|
-
ChatThreadPlayer.prototype.stop = function () {
|
|
332
|
-
this.cancelled = true;
|
|
333
|
-
this.generation++; // retire the running loop's queued continuations
|
|
334
|
-
this._clearTimers();
|
|
335
|
-
this.running = false;
|
|
336
|
-
this._showAll();
|
|
337
|
-
this._setMeter(1);
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
// Re-arm a stopped player (used when the thread scrolls back into view). Bump
|
|
341
|
-
// the epoch again so a still-queued continuation from the just-stopped loop can
|
|
342
|
-
// never resume now that `cancelled` is cleared — only the loop `play()` starts
|
|
343
|
-
// next will match the current epoch.
|
|
344
|
-
ChatThreadPlayer.prototype.rearm = function () {
|
|
345
|
-
this.cancelled = false;
|
|
346
|
-
this.generation++;
|
|
347
|
-
this.running = false;
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
// Inject the enhancer-owned pause/resume button into the thread card.
|
|
351
|
-
// One button per thread, positioned top-right above the body. Uses
|
|
352
|
-
// data-nc-autoplay-name for an accessible per-thread subject, e.g.
|
|
353
|
-
// "Pause <value>"/"Resume <value>"; falls back to "Pause demonstration".
|
|
354
|
-
// Never called in reduced-motion mode (contract (b): no button for a path
|
|
355
|
-
// that plays nothing).
|
|
356
|
-
ChatThreadPlayer.prototype._injectPauseButton = function () {
|
|
357
|
-
var thread = this.thread;
|
|
358
|
-
var subject = thread.getAttribute("data-nc-autoplay-name") || "demonstration";
|
|
359
|
-
var btn = document.createElement("button");
|
|
360
|
-
btn.type = "button";
|
|
361
|
-
btn.className = "nc-chat-pause";
|
|
362
|
-
btn.setAttribute("aria-pressed", "false");
|
|
363
|
-
btn.textContent = "Pause " + subject;
|
|
364
|
-
btn.addEventListener(
|
|
365
|
-
"click",
|
|
366
|
-
function () {
|
|
367
|
-
if (this.userPaused) {
|
|
368
|
-
// Resume: clear the latch, rearm and play.
|
|
369
|
-
this.userPaused = false;
|
|
370
|
-
btn.setAttribute("aria-pressed", "false");
|
|
371
|
-
btn.textContent = "Pause " + subject;
|
|
372
|
-
this.rearm();
|
|
373
|
-
this.play();
|
|
374
|
-
} else {
|
|
375
|
-
// Pause: set the latch, stop and reveal full content.
|
|
376
|
-
this.userPaused = true;
|
|
377
|
-
btn.setAttribute("aria-pressed", "true");
|
|
378
|
-
btn.textContent = "Resume " + subject;
|
|
379
|
-
this.stop();
|
|
380
|
-
}
|
|
381
|
-
}.bind(this),
|
|
382
|
-
);
|
|
383
|
-
// Insert before the body so the button sits above the scrolling content.
|
|
384
|
-
var body = thread.querySelector(".nc-chat-thread-body");
|
|
385
|
-
if (body) {
|
|
386
|
-
thread.insertBefore(btn, body);
|
|
387
|
-
} else {
|
|
388
|
-
thread.appendChild(btn);
|
|
389
|
-
}
|
|
390
|
-
this.pauseBtn = btn;
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
// ── Initialisation ──────────────────────────────────────────────────────
|
|
394
|
-
var players = [];
|
|
395
|
-
var observer = null;
|
|
396
|
-
|
|
397
|
-
function enhance(thread) {
|
|
398
|
-
if (thread.__ncAutoplayPlayer) return thread.__ncAutoplayPlayer;
|
|
399
|
-
var player = new ChatThreadPlayer(thread);
|
|
400
|
-
// Read the stagger token off the live thread (parity with motion.css);
|
|
401
|
-
// fallback equals the motion.css default (60ms) if the bundle is absent.
|
|
402
|
-
player.staggerMs = durationToken(thread, "--nc-duration-stagger", 60);
|
|
403
|
-
thread.__ncAutoplayPlayer = player;
|
|
404
|
-
players.push(player);
|
|
405
|
-
// Inject the pause/resume button unless reduced-motion is on (that path
|
|
406
|
-
// plays nothing and must inject no button — WCAG 2.2.2 scope follows the
|
|
407
|
-
// animation).
|
|
408
|
-
if (!reducedMotion()) {
|
|
409
|
-
player._injectPauseButton();
|
|
410
|
-
}
|
|
411
|
-
return player;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function startObserving() {
|
|
415
|
-
if (typeof window.IntersectionObserver !== "function") {
|
|
416
|
-
// No IntersectionObserver: degrade by playing immediately (still respects
|
|
417
|
-
// reduced motion inside play()).
|
|
418
|
-
for (var i = 0; i < players.length; i++) players[i].play();
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
// Manual re-init (the documented `window.ncChatAutoplay.init(root)` path for
|
|
422
|
-
// dynamic content) calls this again; tear down the prior observer first so a
|
|
423
|
-
// repeat init replaces — rather than stacks — observers. A single self-init
|
|
424
|
-
// (the common path) hits this once and `observer` is null, so behavior is
|
|
425
|
-
// unchanged; only repeated manual init benefits (no leaked observers, no
|
|
426
|
-
// duplicate intersection callbacks per thread).
|
|
427
|
-
if (observer) observer.disconnect();
|
|
428
|
-
observer = new IntersectionObserver(
|
|
429
|
-
function (entries) {
|
|
430
|
-
for (var i = 0; i < entries.length; i++) {
|
|
431
|
-
var entry = entries[i];
|
|
432
|
-
var player = entry.target.__ncAutoplayPlayer;
|
|
433
|
-
if (!player) continue;
|
|
434
|
-
if (entry.isIntersecting) {
|
|
435
|
-
// (d) Start when scrolled into view — but only if the player is not
|
|
436
|
-
// already running its loop. Re-arming a mid-loop player kills and
|
|
437
|
-
// restarts it from blank (the long right thread reads as dead under
|
|
438
|
-
// scroll crossings). A player is "running" once play() sets the flag
|
|
439
|
-
// and until stop() clears it; the loop recursion holds this state.
|
|
440
|
-
// WCAG 2.2.2: do not override a user-pressed pause on scroll-in.
|
|
441
|
-
if (player.userPaused) continue;
|
|
442
|
-
if (!player.running) {
|
|
443
|
-
if (player.completedOnce) {
|
|
444
|
-
// The sequence finished at least once. Show the full final state
|
|
445
|
-
// immediately so the reader sees the payoff frame, not a blank/
|
|
446
|
-
// typing restart. The loop resumes from this state on the next
|
|
447
|
-
// natural cycle if the thread stays in view.
|
|
448
|
-
player._showAll();
|
|
449
|
-
player._setMeter(1);
|
|
450
|
-
} else {
|
|
451
|
-
player.rearm();
|
|
452
|
-
player.play();
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
} else if (entry.intersectionRatio === 0) {
|
|
456
|
-
// Stop only when the thread is FULLY out of view (ratio exactly 0).
|
|
457
|
-
// At partial visibility (e.g. ratio 0.228) do nothing — collapsing
|
|
458
|
-
// to the finished card while the thread is still on screen is the
|
|
459
|
-
// bug this guard fixes.
|
|
460
|
-
if (player.running) {
|
|
461
|
-
player.stop();
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
},
|
|
466
|
-
{ threshold: [0, 0.25] },
|
|
467
|
-
);
|
|
468
|
-
for (var j = 0; j < players.length; j++) observer.observe(players[j].thread);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// ── Enhancer-armed, run-once scroll entrance ─────────────────────────────
|
|
472
|
-
// Arms .nc-settle elements that are fully below the viewport at init.
|
|
473
|
-
// On first IntersectionObserver entry, switches data-nc-settle from "armed"
|
|
474
|
-
// to "in" (triggers the CSS nc-msg-in animation) and immediately unobserves
|
|
475
|
-
// so the rise-in fires once and is never reversed on scroll-back. Elements
|
|
476
|
-
// already visible at load are left unarmed (no flash; hero region untouched).
|
|
477
|
-
// prefers-reduced-motion: skip entirely — page fully visible without JS.
|
|
478
|
-
function initSettle(scope) {
|
|
479
|
-
if (reducedMotion()) return;
|
|
480
|
-
if (typeof window.IntersectionObserver !== "function") return;
|
|
481
|
-
var settleElements = (scope || document).querySelectorAll(".nc-settle");
|
|
482
|
-
if (!settleElements.length) return;
|
|
483
|
-
var settleObserver = new IntersectionObserver(
|
|
484
|
-
function (entries, obs) {
|
|
485
|
-
for (var i = 0; i < entries.length; i++) {
|
|
486
|
-
if (entries[i].isIntersecting) {
|
|
487
|
-
entries[i].target.setAttribute("data-nc-settle", "in");
|
|
488
|
-
obs.unobserve(entries[i].target);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
},
|
|
492
|
-
{ threshold: 0.1 },
|
|
493
|
-
);
|
|
494
|
-
for (var i = 0; i < settleElements.length; i++) {
|
|
495
|
-
var el = settleElements[i];
|
|
496
|
-
var rect = el.getBoundingClientRect();
|
|
497
|
-
// Arm only elements whose top edge is fully below the viewport.
|
|
498
|
-
if (rect.top > window.innerHeight) {
|
|
499
|
-
el.setAttribute("data-nc-settle", "armed");
|
|
500
|
-
settleObserver.observe(el);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function init(root) {
|
|
506
|
-
var scope = root || document;
|
|
507
|
-
var threads = scope.querySelectorAll(".nc-chat-thread[data-nc-autoplay]");
|
|
508
|
-
for (var i = 0; i < threads.length; i++) enhance(threads[i]);
|
|
509
|
-
initSettle(scope);
|
|
510
|
-
startObserving();
|
|
511
|
-
return players;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// Respond to a runtime change of the reduced-motion preference: collapse any
|
|
515
|
-
// running player to the static full thread.
|
|
516
|
-
if (typeof window.matchMedia === "function") {
|
|
517
|
-
var mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
518
|
-
var onChange = function () {
|
|
519
|
-
if (!mq.matches) return;
|
|
520
|
-
for (var i = 0; i < players.length; i++) {
|
|
521
|
-
if (players[i].running) players[i].stop();
|
|
522
|
-
}
|
|
523
|
-
};
|
|
524
|
-
if (typeof mq.addEventListener === "function") mq.addEventListener("change", onChange);
|
|
525
|
-
else if (typeof mq.addListener === "function") mq.addListener(onChange);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
window.ncChatAutoplay = { init: init, players: players };
|
|
529
|
-
|
|
530
|
-
if (document.readyState === "loading") {
|
|
531
|
-
document.addEventListener("DOMContentLoaded", function () {
|
|
532
|
-
init();
|
|
533
|
-
});
|
|
534
|
-
} else {
|
|
535
|
-
init();
|
|
536
|
-
}
|
|
537
|
-
})();
|