diff --git a/.architecture/ARCHITECTURE.md b/.architecture/ARCHITECTURE.md new file mode 100644 index 0000000..695924d --- /dev/null +++ b/.architecture/ARCHITECTURE.md @@ -0,0 +1,244 @@ +# SynthOS / SynthTabs Architecture + +## Overview + +SynthOS is an AI-powered local web application builder. Users create fully functional +HTML/CSS/JS apps through conversational chat — no manual coding required. **SynthTabs** +is a fork that adds Microsoft Teams integration via the **Customizer** extensibility +layer. + +Installed via `npm install -g synthos`, run with `synthos start`. Node.js v20+. + +--- + +## Tech Stack + +| Layer | Technology | +|------------- |-------------------------------------------------| +| Language | TypeScript (strict, ESNext target) | +| Server | Express.js | +| DOM Engine | cheerio (server-side HTML manipulation) | +| AI Providers | Anthropic SDK, OpenAI SDK, FireworksAI (OpenAI-compat) | +| Agent Protocols | A2A SDK, OpenClaw (WebSocket) | +| CLI | yargs | +| Tests | Mocha + ts-mocha, nyc (coverage) | + +--- + +## Folder Structure + +``` +synthtabs/ +├── bin/ CLI entry (synthos.js) +├── src/ TypeScript source +│ ├── index.ts Public exports +│ ├── init.ts Config creation, first-run setup +│ ├── files.ts File I/O helpers +│ ├── pages.ts Page CRUD, metadata, versioning +│ ├── scripts.ts Shell script execution +│ ├── settings.ts Settings persistence (v2) +│ ├── themes.ts Theme loading & parsing +│ ├── migrations.ts Page version upgrades (v1 → v2) +│ ├── synthos-cli.ts CLI command parser +│ ├── customizer/ +│ │ ├── Customizer.ts Extensibility framework +│ │ └── index.ts Default Customizer instance +│ ├── service/ +│ │ ├── server.ts Express app assembly +│ │ ├── usePageRoutes.ts GET/POST /:page (serve + transform) +│ │ ├── useApiRoutes.ts /api/pages, /api/themes, /api/settings, etc. +│ │ ├── useConnectorRoutes.ts /api/connectors (OAuth2, proxy) +│ │ ├── useAgentRoutes.ts /api/agents (A2A + OpenClaw streaming) +│ │ ├── useDataRoutes.ts /api/data/:page/:table (JSON CRUD) +│ │ ├── transformPage.ts Core page transformation pipeline +│ │ ├── createCompletePrompt.ts Provider-specific prompt routing +│ │ ├── modelInstructions.ts Per-model prompt tuning +│ │ ├── generateImage.ts DALL-E 3 image generation +│ │ ├── requiresSettings.ts Middleware: settings guard +│ │ └── debugLog.ts Colored debug logging +│ ├── models/ LLM provider implementations +│ │ ├── anthropic.ts Claude (Opus/Sonnet/Haiku) +│ │ ├── openai.ts GPT (5.2/5-mini/5-nano) +│ │ └── fireworksai.ts FireworksAI (GLM-5) +│ ├── agents/ Agent protocol implementations +│ │ ├── a2aProvider.ts A2A HTTP protocol +│ │ └── openclawProvider.ts OpenClaw WebSocket + SSH tunnels +│ └── connectors/ Connector registry loader +├── default-pages/ Starter HTML templates (copied on init) +├── default-scripts/ OS-specific shell script templates +├── default-themes/ Theme CSS + JSON (Nebula Dusk/Dawn) +├── page-scripts/ Versioned page scripts (page-v2.js, helpers-v2.js) +├── required-pages/ System pages (builder, settings, pages, scripts, apis) +├── service-connectors/ 28+ connector JSON definitions +├── migration-rules/ Markdown rules for page upgrades +├── tests/ Unit tests +├── teams-default-pages/ [TEAMS] Custom page templates +├── teams-default-scripts/ [TEAMS] Custom shell scripts +├── teams-default-themes/ [TEAMS] Custom themes +├── teams-page-scripts/ [TEAMS] Custom versioned page scripts +├── teams-required-pages/ [TEAMS] Custom system pages +├── teams-service-connectors/ [TEAMS] Custom connectors +└── .synthos/ USER DATA (created at runtime, git-ignored) + ├── settings.json API keys, model config, features + ├── pages// page.html + page.json + data tables + ├── themes/ Local theme copies + └── scripts/ User shell scripts +``` + +--- + +## Core Loop: Page Transformation + +This is the heart of SynthOS — every page edit goes through this pipeline: + +``` +User types message in chat panel + │ + ▼ +POST /:page (usePageRoutes.ts) + │ + ▼ +transformPage() (transformPage.ts) + 1. Assign data-node-id to every element (cheerio) + 2. Build LLM prompt with: + - Annotated HTML + - Theme info (CSS variables, colors) + - Enabled connectors + hints + - Enabled agents + capabilities + - Available scripts + - Custom transform instructions (from Customizer) + 3. Route to provider (Anthropic / OpenAI / FireworksAI) + 4. LLM returns JSON array of change operations: + { op: "update"|"delete"|"insert", nodeId, html, parentId } + 5. Apply changes via cheerio + 6. Strip data-node-id attributes + │ + ▼ +Save updated HTML → Serve to browser +``` + +On error (bad JSON, missing nodes), the original page is returned unchanged +with an injected ` - - \ No newline at end of file diff --git a/.saved-pages/game_factory/page.json b/.saved-pages/game_factory/page.json deleted file mode 100644 index 49e67be..0000000 --- a/.saved-pages/game_factory/page.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "title": "Game Factory", - "categories": [ - "Builder" - ], - "pinned": false, - "createdDate": "2026-02-16T07:11:49.649Z", - "lastModified": "2026-02-16T07:11:49.649Z", - "pageVersion": 1, - "mode": "unlocked" -} \ No newline at end of file diff --git a/.saved-pages/my_notes/notes/63b6fb56-3ea2-4eeb-a50a-5255261ad3e0.json b/.saved-pages/my_notes/notes/63b6fb56-3ea2-4eeb-a50a-5255261ad3e0.json deleted file mode 100644 index 21d63e0..0000000 --- a/.saved-pages/my_notes/notes/63b6fb56-3ea2-4eeb-a50a-5255261ad3e0.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "63b6fb56-3ea2-4eeb-a50a-5255261ad3e0", - "title": "SynthOS Tasks", - "content": "* [x] Create the new My Notes app\n* [ ] Rewrite the transform prompt that Claude screwed up\n* [ ] Minimize all of the default and required pages\n* [ ] " -} \ No newline at end of file diff --git a/.saved-pages/my_notes/notes/b1e32533-ccce-42a0-ae1c-e8647f322a4b.json b/.saved-pages/my_notes/notes/b1e32533-ccce-42a0-ae1c-e8647f322a4b.json deleted file mode 100644 index 837a73d..0000000 --- a/.saved-pages/my_notes/notes/b1e32533-ccce-42a0-ae1c-e8647f322a4b.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "b1e32533-ccce-42a0-ae1c-e8647f322a4b", - "title": "My First Note", - "content": "# Things I need to do" -} \ No newline at end of file diff --git a/.saved-pages/my_notes/page.html b/.saved-pages/my_notes/page.html deleted file mode 100644 index bc32888..0000000 --- a/.saved-pages/my_notes/page.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - SynthOS - Notes - - - - - - - - - - -
-
SynthOS
-

SynthOS: Welcome to My Notes — a simple note-taking app! Your notes are listed in the sidebar on the left. Click any note to view or edit it, or tap "+ New Note" to create a fresh one. Each note has a rich text editor with formatting tools, and you can save or delete notes using the buttons below the editor. Use the search box to filter notes by title or content. What would you like to do?

- -
- - -
-
-

My Notes

📝

Welcome to Notes

Select a note from the sidebar to view or edit it, or click "+ New Note" to create a new one.

- - - - - - - \ No newline at end of file diff --git a/.saved-pages/my_notes/page.json b/.saved-pages/my_notes/page.json deleted file mode 100644 index 6e8a33d..0000000 --- a/.saved-pages/my_notes/page.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "title": "My Notes", - "categories": [ - "Tools" - ], - "pinned": false, - "createdDate": "2026-02-15T00:34:24.996Z", - "lastModified": "2026-02-15T00:40:19.428Z", - "pageVersion": 2, - "mode": "locked" -} \ No newline at end of file diff --git a/.saved-pages/oregon_trail1/page.html b/.saved-pages/oregon_trail1/page.html deleted file mode 100644 index 4270ee8..0000000 --- a/.saved-pages/oregon_trail1/page.html +++ /dev/null @@ -1,521 +0,0 @@ - - - - SynthOS - - - - - - - - - -
-
SynthOS
-
-
SynthOS: What can I create for you? Ask "what can you do?" or "how does this work?" to learn more. Remember to save often!
- -

User: can you build a map of the united states circa mid 1800's

SynthOS: I've built an interactive map of the United States circa 1850! It shows the states and territories as they existed around that time, including key acquisitions like the Louisiana Purchase (1803), the Oregon Territory, the Mexican Cession (1848), and the Texas Annexation (1845). Hover over any region to see details. The map uses a vintage parchment style to give it a period-appropriate feel.

User: now can you plot out the path of the oregon trail?

SynthOS: Done! I've plotted the Oregon Trail on the map as a dashed red-brown line tracing the historic route from Independence, Missouri to Oregon City, Oregon. Key landmarks and stops along the trail are marked with dots and labels, including Fort Kearny, Chimney Rock, Fort Laramie, Independence Rock, South Pass, Fort Hall, Fort Boise, The Dalles, and Oregon City. Hover over any waypoint to see details about that stop on the trail.

- -
- - -
-
-
-
United States of America — Circa 1850
-
- -
-
-
Legend
-
Original 13 States
-
Early Statehood (1790s–1820s)
-
Louisiana Purchase (1803)
-
Oregon Territory
-
Texas Annexation (1845)
-
Mexican Cession (1848)
-
Territories
-
Oregon Trail
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/.saved-pages/oregon_trail1/page.json b/.saved-pages/oregon_trail1/page.json deleted file mode 100644 index fc043ab..0000000 --- a/.saved-pages/oregon_trail1/page.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title": "Oregon Trail - Initial", - "categories": [ - "Educational" - ], - "pinned": false, - "showInAll": true, - "createdDate": "2026-02-16T13:28:00.654Z", - "lastModified": "2026-02-16T13:28:00.654Z", - "pageVersion": 2, - "mode": "unlocked" -} \ No newline at end of file diff --git a/.saved-pages/pixels_photo_search/page.html b/.saved-pages/pixels_photo_search/page.html deleted file mode 100644 index 0ee05da..0000000 --- a/.saved-pages/pixels_photo_search/page.html +++ /dev/null @@ -1,784 +0,0 @@ - - - - SynthOS - - - - - - - - - -
-
SynthOS
-
-
SynthOS: What can I create for you? Ask "what can you do?" or "how does this work?" to learn more. Remember to save often!
- -

User: Create a stock photography search app using Pexels

SynthOS: I've built a stock photography search app powered by Pexels! You can search for any topic, browse results in a beautiful masonry grid, filter by orientation and color, and click any photo to see it full-size with download options and photographer credits. Photos provided by Pexels.

- -
- - -
-
-
- - - - - - - - \ No newline at end of file diff --git a/.saved-pages/pixels_photo_search/page.json b/.saved-pages/pixels_photo_search/page.json deleted file mode 100644 index 75b8dc0..0000000 --- a/.saved-pages/pixels_photo_search/page.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title": "Pexels Photo Search", - "categories": [ - "Tools" - ], - "pinned": false, - "showInAll": true, - "createdDate": "2026-02-16T12:25:06.710Z", - "lastModified": "2026-02-16T18:22:22.434Z", - "pageVersion": 2, - "mode": "unlocked" -} \ No newline at end of file diff --git a/Microsoft-Teams-UI-Kit.md b/Microsoft-Teams-UI-Kit.md new file mode 100644 index 0000000..03b7842 --- /dev/null +++ b/Microsoft-Teams-UI-Kit.md @@ -0,0 +1,2866 @@ +\## Commit: Component Inventory + Full Light/Dark Themes (up through Tooltip) + + + +Below is the consolidated \*\*component inventory (HTML)\*\* plus the \*\*single token-based CSS system\*\* and \*\*complete Light/Dark theme blocks\*\* that cover everything added so far: Buttons, Alert, Breadcrumb, Card, Carousel, Checkbox, Coachmark, Menu, Dialog, Dropdown, Group list, Link, \*\*Input\*\*, \*\*Key value pair\*\*, \*\*Paragraph\*\*, \*\*Picker\*\*, \*\*Pivot\*\*, \*\*Progress indicator\*\*, \*\*Radio (final)\*\*, \*\*Scrollbar\*\*, \*\*Search box\*\*, \*\*Side panel\*\*, \*\*Status label\*\*, \*\*Toast\*\*, \*\*Toggle (final)\*\*, \*\*Tooltip\*\*. + + + +--- + + + +\# Component inventory (HTML) + + + +\## Button + + + +```html + + + + + + + + + + + + + + + +
+ +  + +  + +
+ +``` + + + +\## Alert + + + +```html + +
+ +  + +  Message goes here + +  + +  + +
+ +``` + + + +\## Breadcrumb + + + +```html + + + +``` + + + +\## Card + + + +```html + +
+ + 
+ + 
+ + 
+ + 
+ + 
Fluent base card
+ + 
Subtitle
+ + 
+ +  + + 
+ + 
+ +
+ +``` + + + +\## Carousel + + + +```html + + + +``` + + + +\## Checkbox + + + +```html + + + +``` + + + +\## Coachmark + + + +```html + + + +``` + + + +\## Contextual menu (Menu) + + + +```html + + + +``` + + + +\## Dialog + + + +```html + + + +``` + + + +\## Dropdown (button + menu) + + + +```html + +
+ +  + +  + +  + +
+ + + +``` + + + +\## Group list (disclosure header) + + + +```html + +
+ +  + +  + +
+ +``` + + + +\## Hyperlink + + + +```html + +Hyperlink + +A very long hyperlink that should truncate + +Disabled + +``` + + + +--- + + + +\## Input + + + +```html + +
+ +  + + + + 
+ +  + + 
+ + + + 
Optional hint
+ +
+ + + +
+ + 
+ +  Prefix + +  + + 
+ +
+ + + +
+ +  + + 
+ +  + +  + + 
+ +  + +
+ +``` + + + +\## Key value pair + + + +```html + +
+ + 
+ + 
Label
+ + 
+ + 
Key
+ + 
Value
+ + 
+ + 
+ + + + 
+ + 
Toggle switch
+ + 
+ + 
Key
+ +  + + 
+ + 
+ + + + 
+ + 
Input box
+ + 
+ + 
Key
+ + 
+ + 
+ + 
+ + + + 
+ + 
Radios
+ + 
+ + 
Key
+ + 
+ +  + +  + + 
+ + 
+ + 
+ +
+ +``` + + + +\## Paragraph + + + +```html + +
+ + 

Headline example

+ + 

Description example…

+ + 

Subheadline here

+ + 

Lorem ipsum…

+ + 

Description example…

+ +
+ +``` + + + +\## Picker (generic + date/time) + + + +```html + +
+ +  + + + + 
+ + 
+ +  + + 
+ + + + 
+ +  + + 
+ + 
+ +
+ + + +
+ + 
+ + 
+ + 
+ +  + + 
Jan 2021
+ +  + + 
+ + 
+ +  + +  + + 
+ + 
+ +
+ + + +
+ + 
+ + 
+ + 
+ +  + +  + + 
+ + 
+ +
+ +``` + + + +\## Pivot + + + +```html + + + +``` + + + +\## Progress indicator + + + +```html + +
+ +  + +
+ + + +
+ + 
+ + 
Label
+ + 
Description
+ + 
+ + 
+ + 
+ + 
+ +
+ +``` + + + +\## Radio (final) + + + +```html + +
+ +  + +  + +
+ +``` + + + +\## Scroll region (styled scrollbars) + + + +```html + +
Very long content…
+ +``` + + + +\## Search box + + + +```html + + + +``` + + + +\## Side panel + + + +```html + + + +``` + + + +\## Status label + + + +```html + + + +  + +  Critical + + + + + + + +  + +  Warning + + + +``` + + + +\## Toast + + + +```html + +
+ + 
+ + 
+ +  + + 
+ + 
ContosoBot
+ + 
Lorem ipsum…
+ + 
+ +  + + 
+ + 
+ + 
Lorem ipsum…
+ + 
+ + 
+ +  + +  + + 
+ + 
+ +
+ +``` + + + +\## Toggle (final) + + + +```html + + + +``` + + + +\## Tooltip + + + +```html + + + + + +``` + + + +--- + + + +\# Single stylesheet (Base tokens + component CSS) + + + +```css + +/\* ===== Base Tokens (theme sets values) ===== \*/ + +:root{ + +  --font:"Segoe UI",system-ui,-apple-system,sans-serif; + +  --type-xs: 12px/16px var(--font); + +  --type-sm: 13px/20px var(--font); + +  --type-md: 16px/22px var(--font); + +  --type-lg: 18px/24px var(--font); + + + +  --bg: ; + +  --surface: ; + +  --surface-2: ; + +  --surface-hover: ; + +  --surface-pressed: ; + +  --surface-disabled: ; + + + +  --text: ; + +  --text-muted: ; + +  --text-disabled: ; + + + +  --border: ; + +  --border-strong: ; + + + +  --accent: ; + +  --accent-hover: ; + +  --accent-pressed: ; + +  --accent-contrast: ; + + + +  --danger: ; + +  --danger-2: ; + + + +  --focus-ring: ; + + + +  --shadow-1: ; + +  --shadow-2: ; + + + +  --scrim: ; + +} + + + +/\* ===== Primitives ===== \*/ + +.mt-link{ color:var(--accent); text-decoration:none; cursor:pointer; font:inherit; } + +.mt-link:hover{ color:var(--accent-hover); text-decoration:underline; } + +.mt-link:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; border-radius:2px; } + +.mt-link\[aria-disabled="true"]{ color:var(--text-disabled); pointer-events:none; cursor:default; text-decoration:none; } + +.mt-link--truncate{ display:inline-block; max-width:100%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } + + + +/\* ===== Button ===== \*/ + +.mt-btn{ + +  display:inline-flex; align-items:center; justify-content:center; gap:6px; + +  border-radius:4px; border:1px solid var(--btn-border); + +  background:var(--btn-bg); color:var(--btn-fg); + +  font:600 var(--type-sm); + +  height:32px; padding:0 12px; + +  cursor:pointer; user-select:none; + +} + +.mt-btn\[data-size="sm"]{ height:24px; padding:0 8px; font:600 var(--type-xs); } + +.mt-btn:hover:not(:disabled){ background:var(--btn-hover-bg); } + +.mt-btn:active:not(:disabled){ background:var(--btn-pressed-bg); } + +.mt-btn:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-btn:disabled{ background:var(--surface-disabled); color:var(--text-disabled); border-color:var(--surface-disabled); cursor:not-allowed; } + +.mt-btn\[data-icon-only]{ width:32px; padding:0; } + +.mt-btn\[data-icon-only]\[data-size="sm"]{ width:24px; } + +.mt-btn-group{ display:inline-flex; } + +.mt-btn--split{ width:32px; padding:0; } + + + +.mt-btn\[data-style="accent"]{ + +  --btn-bg:var(--accent); --btn-fg:var(--accent-contrast); --btn-border:var(--accent); + +  --btn-hover-bg:var(--accent-hover); --btn-pressed-bg:var(--accent-pressed); + +} + +.mt-btn\[data-style="neutral"]{ + +  --btn-bg:var(--surface); --btn-fg:var(--text); --btn-border:var(--border); + +  --btn-hover-bg:var(--surface-hover); --btn-pressed-bg:var(--surface-pressed); + +} + +.mt-btn\[data-style="outline"]{ + +  --btn-bg:transparent; --btn-fg:var(--text); --btn-border:var(--border-strong); + +  --btn-hover-bg:var(--surface-hover); --btn-pressed-bg:var(--surface-pressed); + +} + +.mt-btn\[data-style="ghost"]{ + +  --btn-bg:transparent; --btn-fg:var(--text); --btn-border:transparent; + +  --btn-hover-bg:var(--surface-hover); --btn-pressed-bg:var(--surface-pressed); + +} + + + +/\* ===== Alert ===== \*/ + +.mt-alert{ + +  display:flex; align-items:center; gap:10px; + +  min-height:32px; padding:6px 10px; + +  border:1px solid var(--alert-border); + +  border-radius:4px; + +  background:var(--alert-bg); + +  color:var(--alert-fg); + +} + +.mt-alert\_\_icon{ width:14px; height:14px; border-radius:999px; background:var(--alert-icon); } + +.mt-alert\_\_message{ font:var(--type-xs); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; } + +.mt-alert\_\_action{ + +  height:22px; padding:0 10px; border-radius:3px; + +  border:1px solid var(--border); background:var(--surface); color:var(--text); + +  font:600 11px/20px var(--font); + +} + +.mt-alert\_\_dismiss{ + +  width:22px; height:22px; display:grid; place-items:center; + +  border:0; background:transparent; color:var(--text-muted); border-radius:3px; + +} + +.mt-alert\_\_action:focus-visible,.mt-alert\_\_dismiss:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + + + +.mt-alert\[data-variant="error"] { --alert-bg:var(--alert-error-bg); --alert-border:var(--alert-error-border); --alert-fg:var(--alert-error-fg); --alert-icon:var(--alert-error-icon); } + +.mt-alert\[data-variant="warning"]{ --alert-bg:var(--alert-warn-bg); --alert-border:var(--alert-warn-border); --alert-fg:var(--alert-warn-fg); --alert-icon:var(--alert-warn-icon); } + +.mt-alert\[data-variant="success"]{ --alert-bg:var(--alert-ok-bg); --alert-border:var(--alert-ok-border); --alert-fg:var(--alert-ok-fg); --alert-icon:var(--alert-ok-icon); } + +.mt-alert\[data-variant="info"] { --alert-bg:var(--alert-info-bg); --alert-border:var(--alert-info-border); --alert-fg:var(--alert-info-fg); --alert-icon:var(--alert-info-icon); } + + + +/\* ===== Breadcrumb ===== \*/ + +.mt-breadcrumb{ font:var(--type-sm); } + +.mt-breadcrumb\_\_list{ display:flex; align-items:center; gap:6px; list-style:none; margin:0; padding:0; } + +.mt-breadcrumb\_\_sep{ color:var(--text-disabled); user-select:none; } + +.mt-breadcrumb\_\_current{ color:var(--text); font-weight:600; } + + + +/\* ===== Card ===== \*/ + +.mt-card{ + +  position:relative; display:flex; flex-direction:column; overflow:hidden; + +  border-radius:6px; border:1px solid var(--border); + +  background:var(--surface); color:var(--text); + +  box-shadow:var(--shadow-1); + +} + +.mt-card:hover{ box-shadow:var(--shadow-2); } + +.mt-card:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-card\[data-size="xs"]{ width:160px; } .mt-card\[data-size="sm"]{ width:220px; } + +.mt-card\[data-size="md"]{ width:280px; } .mt-card\[data-size="lg"]{ width:360px; } + +.mt-card\_\_media{ aspect-ratio:16/9; background:var(--surface-2); } + +.mt-card\_\_media img{ width:100%; height:100%; object-fit:cover; display:block; } + +.mt-card\_\_body{ padding:10px 12px; } + +.mt-card\_\_header{ display:flex; gap:8px; align-items:flex-start; } + +.mt-card\_\_title{ font:600 var(--type-sm); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } + +.mt-card\_\_subtitle{ font:var(--type-xs); color:var(--text-muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } + +.mt-card\_\_menu{ width:28px; height:28px; border:0; background:transparent; color:var(--text-muted); border-radius:4px; cursor:pointer; } + +.mt-card\_\_menu:hover{ background:var(--surface-hover); color:var(--text); } + +.mt-card\_\_menu:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-card\[data-selected="true"]{ border-color:var(--accent); } + +.mt-card\[data-selected="true"]::after{ + +  content:"✓"; position:absolute; top:8px; right:8px; + +  width:18px; height:18px; border-radius:999px; display:grid; place-items:center; + +  background:var(--accent); color:var(--accent-contrast); font:700 12px/1 var(--font); + +} + +.mt-card\[data-disabled="true"]{ background:var(--surface-disabled); color:var(--text-disabled); box-shadow:none; opacity:.7; pointer-events:none; } + + + +/\* ===== Carousel ===== \*/ + +.mt-carousel\_\_viewport{ position:relative; overflow:hidden; display:flex; justify-content:center; align-items:center; } + +.mt-carousel\_\_track{ display:flex; transition:transform 240ms ease; } + +.mt-carousel\_\_slide{ min-width:100%; display:flex; justify-content:center; padding:20px 0; } + +.mt-carousel\_\_content{ + +  width:70%; max-width:600px; aspect-ratio:16/9; + +  border-radius:6px; border:1px solid var(--border); + +  background:var(--surface); box-shadow:var(--shadow-1); + +  display:grid; place-items:center; + +} + +.mt-carousel\_\_nav{ + +  position:absolute; top:50%; transform:translateY(-50%); + +  width:36px; height:36px; border-radius:999px; border:0; + +  background:transparent; color:var(--text-muted); cursor:pointer; + +} + +.mt-carousel\_\_nav--prev{ left:12px; } .mt-carousel\_\_nav--next{ right:12px; } + +.mt-carousel\_\_nav:hover{ background:var(--surface-hover); color:var(--text); } + +.mt-carousel\_\_nav:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-carousel\_\_dots{ display:flex; justify-content:center; gap:6px; margin-top:8px; } + +.mt-carousel\_\_dot{ width:6px; height:6px; border-radius:999px; border:0; background:var(--text-disabled); cursor:pointer; } + +.mt-carousel\_\_dot\[aria-selected="true"]{ background:var(--accent); transform:scale(1.2); } + +.mt-carousel\_\_dot:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + + + +/\* ===== Checkbox ===== \*/ + +.mt-checkbox{ display:inline-flex; align-items:center; gap:8px; cursor:pointer; font:var(--type-sm); color:var(--text); } + +.mt-checkbox\_\_input{ position:absolute; opacity:0; pointer-events:none; } + +.mt-checkbox\_\_control{ + +  width:16px; height:16px; display:grid; place-items:center; + +  border-radius:3px; border:1px solid var(--border-strong); background:transparent; + +} + +.mt-checkbox\_\_icon{ width:12px; height:12px; color:var(--accent-contrast); opacity:0; transform:scale(.85); transition:120ms ease; } + +.mt-checkbox:hover .mt-checkbox\_\_control{ border-color:var(--text); } + +.mt-checkbox\_\_input:checked + .mt-checkbox\_\_control{ background:var(--accent); border-color:var(--accent); } + +.mt-checkbox\_\_input:checked + .mt-checkbox\_\_control .mt-checkbox\_\_icon{ opacity:1; transform:scale(1); } + +.mt-checkbox\_\_input:focus-visible + .mt-checkbox\_\_control{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-checkbox\_\_input:disabled + .mt-checkbox\_\_control{ background:var(--surface-disabled); border-color:var(--border); } + +.mt-checkbox\_\_input:disabled ~ .mt-checkbox\_\_label{ color:var(--text-disabled); cursor:not-allowed; } + + + +/\* ===== Menu ===== \*/ + +.mt-menu{ + +  min-width:180px; max-width:280px; padding:4px; + +  border-radius:6px; border:1px solid var(--menu-border); + +  background:var(--menu-bg); box-shadow:var(--menu-shadow); + +} + +.mt-menu\_\_item{ + +  width:100%; height:32px; + +  display:grid; grid-template-columns:20px 1fr auto auto; align-items:center; gap:8px; + +  padding:0 8px; border:0; border-radius:4px; background:transparent; + +  color:var(--text); font:var(--type-sm); text-align:left; cursor:pointer; + +} + +.mt-menu\_\_icon{ color:var(--text-muted); } + +.mt-menu\_\_shortcut,.mt-menu\_\_submenu{ color:var(--text-muted); font:var(--type-xs); } + +.mt-menu\_\_item:hover{ background:var(--surface-hover); } + +.mt-menu\_\_item:active{ background:var(--surface-pressed); } + +.mt-menu\_\_item:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-menu\_\_divider{ height:1px; background:var(--border); margin:4px 6px; } + +.mt-menu\_\_section{ padding:6px 10px 4px; font:700 11px/14px var(--font); color:var(--text-muted); } + +.mt-menu\_\_item\[aria-disabled="true"]{ color:var(--text-disabled); cursor:not-allowed; } + +.mt-menu\_\_item\[aria-disabled="true"]:hover{ background:transparent; } + + + +/\* ===== Dialog ===== \*/ + +.mt-dialog{ position:fixed; inset:0; z-index:1100; } + +.mt-dialog\_\_scrim{ position:absolute; inset:0; background:var(--scrim); } + +.mt-dialog\_\_panel{ + +  position:relative; margin:10vh auto 0; + +  border-radius:8px; border:1px solid var(--border); + +  background:var(--surface); box-shadow:0 20px 50px rgba(0,0,0,.35); + +  display:flex; flex-direction:column; + +} + +.mt-dialog\_\_panel\[data-size="sm"]{ width:480px; } + +.mt-dialog\_\_panel\[data-size="md"]{ width:600px; } + +.mt-dialog\_\_panel\[data-size="lg"]{ width:680px; } + +@media (max-width:720px){ .mt-dialog\_\_panel{ width:calc(100% - 32px); } } + +.mt-dialog\_\_header{ padding:20px 24px 0; } + +.mt-dialog\_\_title{ font:700 var(--type-lg); margin:0; color:var(--text); } + +.mt-dialog\_\_body{ padding:16px 24px; font:var(--type-sm); color:var(--text); } + +.mt-dialog\_\_error{ padding:0 24px 8px; font:var(--type-xs); color:var(--danger); } + +.mt-dialog\_\_footer{ padding:16px 24px 20px; display:flex; justify-content:flex-end; gap:8px; } + + + +/\* ===== Dropdown ===== \*/ + +.mt-field{ display:flex; flex-direction:column; gap:4px; } + +.mt-field\_\_label{ font:700 var(--type-xs); color:var(--text-muted); } + +.mt-field\_\_error{ font:var(--type-xs); color:var(--danger); } + +.mt-field\_\_hint{ font:var(--type-xs); color:var(--text-muted); } + + + +.mt-dropdown{ + +  height:32px; padding:0 12px; display:flex; align-items:center; justify-content:space-between; gap:8px; + +  border-radius:4px; border:1px solid var(--border); + +  background:var(--surface); color:var(--text); + +  font:var(--type-sm); cursor:pointer; + +} + +.mt-dropdown:hover{ border-color:var(--text); } + +.mt-dropdown:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-dropdown\[aria-expanded="true"]{ border-color:var(--accent); box-shadow:inset 0 -2px 0 var(--accent); } + +.mt-field\[data-state="error"] .mt-dropdown{ border-color:var(--danger); } + + + +/\* ===== Group list ===== \*/ + +.mt-group\_\_header{ + +  width:100%; display:flex; align-items:center; gap:6px; + +  padding:6px 8px; border:0; background:transparent; + +  font:700 var(--type-sm); color:var(--text); + +  cursor:pointer; text-align:left; + +} + +.mt-group\_\_header:hover:not(:disabled){ background:var(--surface-hover); } + +.mt-group\_\_header:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-group\_\_chevron{ color:var(--text-muted); transition:transform 120ms ease; } + +.mt-group\_\_header\[aria-expanded="true"] .mt-group\_\_chevron{ transform:rotate(90deg); } + +.mt-group\_\_header:disabled{ color:var(--text-disabled); cursor:not-allowed; } + +.mt-group\_\_header:disabled .mt-group\_\_chevron{ color:var(--text-disabled); } + +.mt-group\_\_content{ padding-left:20px; } + + + +/\* ===== Coachmark ===== \*/ + +.mt-coachmark{ position:fixed; inset:0; z-index:1000; } + +.mt-coachmark\_\_scrim{ position:absolute; inset:0; background:var(--scrim); } + +.mt-coachmark\_\_panel{ + +  position:absolute; + +  min-width:260px; max-width:420px; + +  border-radius:8px; border:1px solid var(--border); + +  background:var(--surface); box-shadow:var(--shadow-2); + +  padding:16px; + +} + +.mt-coachmark\_\_title{ font:700 var(--type-md); margin:0 0 8px; color:var(--text); } + +.mt-coachmark\_\_body{ font:var(--type-sm); color:var(--text-muted); } + +.mt-coachmark\_\_footer{ display:flex; justify-content:flex-end; gap:8px; margin-top:12px; } + +.mt-coachmark\_\_close{ + +  position:absolute; top:8px; right:8px; + +  width:28px; height:28px; border:0; background:transparent; + +  color:var(--text-muted); border-radius:4px; cursor:pointer; + +} + +.mt-coachmark\_\_close:hover{ background:var(--surface-hover); color:var(--text); } + +.mt-coachmark\_\_close:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + + + +/\* ===== Input ===== \*/ + +.mt-input{ + +  height:32px; padding:0 12px; + +  display:flex; align-items:center; gap:8px; + +  border-radius:4px; + +  border:1px solid var(--input-border); + +  background:var(--input-bg); + +  color:var(--input-fg); + +} + +.mt-input\[data-size="sm"]{ height:24px; padding:0 8px; } + +.mt-input:hover{ border-color:var(--input-border-hover); } + + + +.mt-input\_\_control{ + +  width:100%; + +  border:0; + +  outline:none; + +  background:transparent; + +  color:inherit; + +  font:var(--type-sm); + +  min-width:0; + +} + +.mt-input\[data-size="sm"] .mt-input\_\_control{ font:var(--type-xs); } + +.mt-input\_\_control::placeholder{ color:var(--input-placeholder); } + + + +.mt-input:focus-within{ + +  border-color:var(--input-border-focus); + +  box-shadow: inset 0 -2px 0 var(--input-underline-focus); + +} + +.mt-input\_\_control:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; border-radius:2px; } + + + +.mt-input\[data-disabled="true"]{ background:var(--surface-disabled); border-color:var(--border); color:var(--text-disabled); } + +.mt-input\_\_control:disabled{ cursor:not-allowed; } + + + +.mt-input\_\_prefix{ display:inline-flex; align-items:center; justify-content:center; flex:0 0 auto; } + +.mt-input\_\_prefix--chip{ + +  height:20px; padding:0 6px; border-radius:3px; + +  background:var(--input-prefix-bg); color:var(--input-prefix-fg); + +  font:600 11px/20px var(--font); + +} + +.mt-input\_\_prefix--icon{ width:16px; height:16px; border-radius:2px; background:var(--input-icon-bg); } + + + +.mt-input\_\_end{ + +  width:22px; height:22px; display:grid; place-items:center; + +  border:0; border-radius:3px; + +  background:transparent; color:var(--text-muted); + +  cursor:pointer; + +} + +.mt-input\_\_end:hover{ background:var(--surface-hover); color:var(--text); } + +.mt-input\_\_end:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + + + +.mt-field\[data-state="error"] .mt-input{ + +  border-color:var(--danger); + +  box-shadow: inset 0 -2px 0 var(--danger); + +} + + + +/\* ===== Key value pair ===== \*/ + +.mt-kvp{ display:flex; flex-direction:column; gap:18px; } + +.mt-kvp\_\_row{ display:grid; grid-template-columns:160px 1fr; column-gap:28px; align-items:start; } + +.mt-kvp\_\_key{ + +  font:700 var(--type-xs); color:var(--text-muted); + +  text-transform:uppercase; letter-spacing:.02em; + +} + +.mt-kvp\_\_value{ display:flex; flex-direction:column; gap:6px; } + +.mt-kvp\_\_text-title{ font:600 var(--type-xs); color:var(--text); } + +.mt-kvp\_\_text-sub{ font:var(--type-xs); color:var(--text-muted); } + + + +/\* ===== Paragraph ===== \*/ + +.mt-paragraph{ max-width:520px; color:var(--text); } + +.mt-paragraph\_\_headline{ margin:0 0 10px; font:700 var(--type-lg); color:var(--text); } + +.mt-paragraph\_\_desc{ margin:0 0 18px; font:var(--type-xs); color:var(--text-muted); } + +.mt-paragraph\_\_subhead{ margin:0 0 8px; font:700 var(--type-sm); color:var(--text); } + +.mt-paragraph\_\_body{ margin:0 0 14px; font:var(--type-xs); color:var(--text-muted); } + +.mt-paragraph\_\_meta{ margin:0; font:var(--type-xs); color:var(--text-muted); } + + + +/\* ===== Picker ===== \*/ + +.mt-picker{ position:relative; } + +.mt-picker\_\_panel{ + +  position:absolute; top:calc(100% + 4px); left:0; width:100%; + +  max-height:280px; overflow:auto; + +  border-radius:6px; border:1px solid var(--picker-border); + +  background:var(--picker-bg); box-shadow:var(--picker-shadow); + +  padding:4px; display:none; z-index:100; + +} + +.mt-picker\[data-open="true"] .mt-picker\_\_panel{ display:block; } + +.mt-picker\_\_option{ + +  width:100%; height:40px; + +  display:flex; align-items:center; gap:10px; + +  padding:0 10px; border:0; border-radius:4px; + +  background:transparent; color:var(--text); + +  font:var(--type-sm); text-align:left; cursor:pointer; + +} + +.mt-picker\_\_option:hover{ background:var(--surface-hover); } + +.mt-picker\_\_option\[data-selected="true"]{ background:var(--surface-pressed); } + +.mt-picker\_\_option-label{ font-weight:600; } + +.mt-picker\_\_option-meta{ font:var(--type-xs); color:var(--text-muted); } + + + +.mt-avatar{ + +  width:24px; height:24px; border-radius:999px; + +  background:var(--accent); color:var(--accent-contrast); + +  font:600 11px/24px var(--font); text-align:center; + +} + +.mt-picker\_\_file-icon{ width:20px; height:20px; border-radius:3px; background:var(--accent); } + + + +/\* Date \*/ + +.mt-date\_\_header{ display:flex; justify-content:space-between; align-items:center; padding:6px 8px; font:600 var(--type-sm); } + +.mt-date\_\_nav{ + +  width:28px; height:28px; border:0; border-radius:4px; + +  background:transparent; color:var(--text-muted); cursor:pointer; + +} + +.mt-date\_\_nav:hover{ background:var(--surface-hover); } + +.mt-date\_\_grid{ display:grid; grid-template-columns:repeat(7,1fr); gap:4px; padding:8px; } + +.mt-date\_\_cell{ + +  height:32px; border:0; border-radius:4px; + +  background:transparent; color:var(--text); + +  font:var(--type-xs); cursor:pointer; + +} + +.mt-date\_\_cell:hover{ background:var(--surface-hover); } + +.mt-date\_\_cell\[data-selected="true"]{ background:var(--accent); color:var(--accent-contrast); } + + + +/\* Time \*/ + +.mt-time\_\_row{ display:flex; gap:8px; padding:8px; } + +.mt-time\_\_input{ + +  flex:1; height:32px; border-radius:4px; + +  border:1px solid var(--border); background:var(--surface); + +  color:var(--text); padding:0 8px; font:var(--type-sm); + +} + +.mt-time\_\_input:focus{ outline:2px solid var(--focus-ring); outline-offset:2px; } + + + +/\* ===== Pivot ===== \*/ + +.mt-pivot\_\_list{ display:flex; align-items:flex-end; gap:24px; } + +.mt-pivot\_\_tab{ + +  position:relative; height:32px; padding:0; + +  border:0; background:transparent; cursor:pointer; + +  font:600 var(--type-sm); + +  color:var(--pivot-fg); + +} + +.mt-pivot\_\_tab::after{ + +  content:""; position:absolute; left:0; right:0; bottom:-2px; + +  height:2px; border-radius:2px; background:transparent; transition:120ms ease; + +} + +.mt-pivot\_\_tab:hover::after{ background:var(--pivot-underline-hover); } + +.mt-pivot\_\_tab\[aria-selected="true"]{ color:var(--pivot-active-fg); } + +.mt-pivot\_\_tab\[aria-selected="true"]::after{ background:var(--pivot-underline-active); } + +.mt-pivot\_\_tab:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:4px; border-radius:4px; } + + + +/\* ===== Progress ===== \*/ + +.mt-progress{ display:flex; align-items:center; gap:14px; color:var(--text); } + +.mt-progress\_\_spinner{ + +  width:16px; height:16px; border-radius:999px; + +  border:2px solid var(--progress-spinner-track); + +  border-top-color: var(--progress-spinner-head); + +  box-sizing:border-box; + +  animation: mt-spin 900ms linear infinite; + +} + +@keyframes mt-spin{ to{ transform:rotate(360deg); } } + + + +.mt-progress\[data-variant="determinate"]{ align-items:flex-start; gap:16px; } + +.mt-progress\_\_text{ min-width:120px; } + +.mt-progress\_\_label{ font:600 var(--type-xs); color:var(--text); margin:0 0 2px; } + +.mt-progress\_\_desc{ font:var(--type-xs); color:var(--text-muted); margin:0; } + +.mt-progress\_\_bar{ + +  flex:1; min-width:220px; height:2px; border-radius:999px; + +  background:var(--progress-track); overflow:hidden; margin-top:8px; + +} + +.mt-progress\_\_fill{ height:100%; background:var(--progress-fill); border-radius:999px; transition:width 200ms ease; } + +@media (prefers-reduced-motion: reduce){ + +  .mt-progress\_\_spinner{ animation:none; } + +  .mt-progress\_\_fill{ transition:none; } + +} + + + +/\* ===== Radio (final) ===== \*/ + +.mt-radio-group{ display:flex; flex-direction:column; gap:8px; } + +.mt-radio{ + +  display:inline-flex; align-items:center; gap:8px; + +  cursor:pointer; font:var(--type-sm); color:var(--text); position:relative; + +} + +.mt-radio\_\_input{ position:absolute; opacity:0; pointer-events:none; } + +.mt-radio\_\_control{ + +  width:16px; height:16px; border-radius:999px; + +  border:1px solid var(--radio-border); background:transparent; + +  position:relative; transition:120ms ease; + +} + +.mt-radio\_\_control::after{ + +  content:""; width:8px; height:8px; border-radius:999px; + +  background:var(--radio-dot); + +  position:absolute; top:50%; left:50%; + +  transform:translate(-50%,-50%) scale(.6); + +  opacity:0; transition:120ms ease; + +} + +.mt-radio:hover .mt-radio\_\_control{ border-color:var(--radio-border-hover); } + +.mt-radio\_\_input:checked + .mt-radio\_\_control{ border-color:var(--accent); } + +.mt-radio\_\_input:checked + .mt-radio\_\_control::after{ opacity:1; transform:translate(-50%,-50%) scale(1); } + +.mt-radio\_\_input:focus-visible + .mt-radio\_\_control{ outline:2px solid var(--focus-ring); outline-offset:3px; } + +.mt-radio\_\_input:disabled + .mt-radio\_\_control{ border-color:var(--radio-disabled-border); background:var(--radio-disabled-bg); } + +.mt-radio\_\_input:disabled + .mt-radio\_\_control::after{ background:var(--radio-disabled-dot); } + +.mt-radio\_\_input:disabled ~ .mt-radio\_\_label{ color:var(--text-disabled); cursor:not-allowed; } + + + +/\* ===== Scroll region + scrollbars ===== \*/ + +.mt-scroll{ + +  max-height:120px; overflow:auto; + +  padding:10px 12px; + +  border:1px solid var(--border); + +  border-radius:4px; + +  background:var(--surface); + +  color:var(--text); + +  font:var(--type-xs); + +  box-shadow: inset -10px 0 10px -12px var(--scroll-shadow); + +  scrollbar-width: thin; + +  scrollbar-color: var(--scroll-thumb) var(--scroll-track); + +} + +.mt-scroll::-webkit-scrollbar{ width:10px; height:10px; } + +.mt-scroll::-webkit-scrollbar-track{ background:var(--scroll-track); border-radius:999px; } + +.mt-scroll::-webkit-scrollbar-thumb{ + +  background:var(--scroll-thumb); border-radius:999px; + +  border:3px solid var(--scroll-track); + +} + +.mt-scroll::-webkit-scrollbar-thumb:hover{ background:var(--scroll-thumb-hover); } + +.mt-scroll::-webkit-scrollbar-thumb:active{ background:var(--scroll-thumb-active); } + +.mt-scroll::-webkit-scrollbar-corner{ background:transparent; } + + + +/\* ===== Search box ===== \*/ + +.mt-search\_\_box{ + +  height:32px; padding:0 10px; + +  display:flex; align-items:center; gap:8px; + +  border-radius:4px; + +  border:1px solid transparent; + +  background:var(--search-bg); + +  color:var(--text); + +} + +.mt-search\_\_icon{ width:16px; height:16px; border-radius:2px; background:var(--search-icon); flex:0 0 auto; } + +.mt-search\_\_input{ + +  width:100%; border:0; outline:none; background:transparent; + +  color:inherit; font:var(--type-sm); min-width:0; + +} + +.mt-search\_\_input::placeholder{ color:var(--search-placeholder); } + +.mt-search\_\_box:focus-within{ box-shadow: inset 0 -2px 0 var(--search-underline); } + +.mt-search\_\_input:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; border-radius:2px; } + +.mt-search\_\_clear{ + +  width:22px; height:22px; display:grid; place-items:center; + +  border:0; border-radius:3px; + +  background:transparent; color:var(--text-muted); + +  cursor:pointer; flex:0 0 auto; + +} + +.mt-search\_\_clear:hover{ background:var(--surface-hover); color:var(--text); } + +.mt-search\_\_clear:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + + + +/\* ===== Side panel ===== \*/ + +.mt-sidepanel{ position:fixed; inset:0; z-index:1200; pointer-events:none; } + +.mt-sidepanel\_\_scrim{ position:absolute; inset:0; background:var(--scrim); opacity:0; transition:opacity 180ms ease; } + +.mt-sidepanel\_\_panel{ + +  position:absolute; top:0; right:0; height:100%; + +  display:flex; flex-direction:column; + +  background:var(--surface); + +  border-left:1px solid var(--border); + +  box-shadow:var(--shadow-2); + +  transform:translateX(100%); + +  transition:transform 220ms ease; + +  pointer-events:auto; + +} + +.mt-sidepanel\_\_panel\[data-size="sm"]{ width:320px; } + +.mt-sidepanel\_\_panel\[data-size="md"]{ width:400px; } + +.mt-sidepanel\_\_panel\[data-size="lg"]{ width:480px; } + +.mt-sidepanel\[data-open="true"]{ pointer-events:auto; } + +.mt-sidepanel\[data-open="true"] .mt-sidepanel\_\_scrim{ opacity:1; } + +.mt-sidepanel\[data-open="true"] .mt-sidepanel\_\_panel{ transform:translateX(0); } + + + +.mt-sidepanel\_\_header{ + +  height:56px; padding:0 16px; + +  display:flex; align-items:center; justify-content:space-between; + +  border-bottom:1px solid var(--border); + +} + +.mt-sidepanel\_\_title{ margin:0; font:700 var(--type-md); color:var(--text); } + +.mt-sidepanel\_\_close{ + +  width:32px; height:32px; border:0; border-radius:4px; + +  background:transparent; color:var(--text-muted); cursor:pointer; + +} + +.mt-sidepanel\_\_close:hover{ background:var(--surface-hover); color:var(--text); } + +.mt-sidepanel\_\_close:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + + + +.mt-sidepanel\_\_body{ flex:1; overflow:auto; padding:16px; color:var(--text); } + +.mt-sidepanel\_\_footer{ + +  padding:12px 16px; border-top:1px solid var(--border); + +  display:flex; justify-content:flex-end; gap:8px; + +  background:var(--surface); + +} + + + +/\* ===== Status label ===== \*/ + +.mt-status{ display:inline-flex; align-items:center; gap:6px; font:600 var(--type-xs); color:var(--status-fg); } + +.mt-status\_\_icon{ + +  width:14px; height:14px; display:grid; place-items:center; + +  font:700 10px/1 var(--font); color:currentColor; + +} + +.mt-status{ --status-fg: var(--status-color); } + +.mt-status\[data-variant="critical"]{ + +  height:18px; padding:0 8px; border-radius:3px; + +  background:var(--status-critical-bg); color:var(--status-critical-fg); + +} + +.mt-status\[data-variant="critical"] .mt-status\_\_icon{ color:var(--status-critical-fg); } + +.mt-status\[data-variant="error"]{ --status-color: var(--status-error); } + +.mt-status\[data-variant="warning"]{ --status-color: var(--status-warning); } + +.mt-status\[data-variant="success"]{ --status-color: var(--status-success); } + +.mt-status\[data-variant="info"]{ --status-color: var(--status-info); } + + + +/\* ===== Toast ===== \*/ + +.mt-toast-stack{ + +  position:fixed; bottom:16px; right:16px; + +  display:flex; flex-direction:column; gap:12px; + +  z-index:1300; + +} + +.mt-toast{ + +  width:320px; border-radius:6px; + +  background:var(--toast-bg); color:var(--text); + +  border:1px solid var(--toast-border); + +  box-shadow:var(--shadow-2); + +  display:flex; flex-direction:column; overflow:hidden; + +  animation: mt-toast-in 180ms ease; + +} + +@keyframes mt-toast-in{ from{ transform:translateY(8px); opacity:0; } to{ transform:translateY(0); opacity:1; } } + +.mt-toast\_\_header{ display:flex; align-items:flex-start; gap:10px; padding:12px 12px 8px; } + +.mt-toast\_\_avatar{ width:28px; height:28px; border-radius:999px; background:var(--accent); flex:0 0 auto; } + +.mt-toast\_\_meta{ flex:1; min-width:0; } + +.mt-toast\_\_title{ font:600 var(--type-sm); color:var(--text); } + +.mt-toast\_\_subtitle{ font:var(--type-xs); color:var(--text-muted); } + +.mt-toast\_\_close{ + +  width:28px; height:28px; border:0; background:transparent; + +  border-radius:4px; color:var(--text-muted); cursor:pointer; + +} + +.mt-toast\_\_close:hover{ background:var(--surface-hover); color:var(--text); } + +.mt-toast\_\_close:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-toast\_\_body{ padding:0 12px 10px; } + +.mt-toast\_\_message{ font:var(--type-xs); color:var(--text-muted); } + +.mt-toast\_\_reply{ display:flex; align-items:center; gap:6px; padding:8px 10px 10px; border-top:1px solid var(--border); } + +.mt-toast\_\_input{ + +  flex:1; height:28px; border-radius:4px; + +  border:1px solid var(--border); background:var(--surface); + +  color:var(--text); padding:0 8px; font:var(--type-xs); + +} + +.mt-toast\_\_input:focus{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-toast\_\_send{ + +  width:28px; height:28px; border:0; border-radius:4px; + +  background:var(--accent); color:var(--accent-contrast); cursor:pointer; + +} + +.mt-toast\_\_send:hover{ background:var(--accent-hover); } + +.mt-toast\[data-variant="accent"]{ background:var(--accent); color:var(--accent-contrast); border-color:var(--accent); } + +.mt-toast\[data-variant="accent"] .mt-toast\_\_subtitle, + +.mt-toast\[data-variant="accent"] .mt-toast\_\_message{ color:rgba(255,255,255,.9); } + +.mt-toast\[data-variant="accent"] .mt-toast\_\_close{ color:rgba(255,255,255,.8); } + + + +/\* ===== Toggle (final) ===== \*/ + +.mt-toggle{ display:inline-flex; align-items:center; gap:8px; cursor:pointer; user-select:none; color:var(--text); } + +.mt-toggle\_\_input{ position:absolute; opacity:0; pointer-events:none; } + +.mt-toggle\_\_track{ + +  width:28px; height:16px; border-radius:999px; + +  background:var(--toggle-track-off); + +  border:1px solid var(--toggle-border-off); + +  position:relative; transition:120ms ease; box-sizing:border-box; + +} + +.mt-toggle\_\_thumb{ + +  width:12px; height:12px; border-radius:999px; + +  background:var(--toggle-thumb); + +  position:absolute; top:1px; left:1px; + +  transition:120ms ease; + +} + +.mt-toggle\_\_input:checked + .mt-toggle\_\_track{ + +  background:var(--toggle-track-on); + +  border-color:var(--toggle-border-on); + +} + +.mt-toggle\_\_input:checked + .mt-toggle\_\_track .mt-toggle\_\_thumb{ transform:translateX(12px); } + + + +.mt-toggle:hover .mt-toggle\_\_track{ background:var(--toggle-track-off-hover); border-color:var(--toggle-border-off-hover); } + +.mt-toggle:hover .mt-toggle\_\_input:checked + .mt-toggle\_\_track{ background:var(--toggle-track-on-hover); border-color:var(--toggle-border-on-hover); } + +.mt-toggle:active .mt-toggle\_\_track{ background:var(--toggle-track-off-pressed); border-color:var(--toggle-border-off-pressed); } + +.mt-toggle:active .mt-toggle\_\_input:checked + .mt-toggle\_\_track{ background:var(--toggle-track-on-pressed); border-color:var(--toggle-border-on-pressed); } + +.mt-toggle\_\_input:focus-visible + .mt-toggle\_\_track{ outline:2px solid var(--focus-ring); outline-offset:3px; } + +.mt-toggle\_\_input:disabled + .mt-toggle\_\_track{ background:var(--toggle-track-disabled); border-color:var(--toggle-border-disabled); } + +.mt-toggle\_\_input:disabled + .mt-toggle\_\_track .mt-toggle\_\_thumb{ background:var(--toggle-thumb-disabled); } + +.mt-toggle\_\_input:disabled ~ .mt-toggle\_\_label{ color:var(--text-disabled); } + + + +/\* ===== Tooltip ===== \*/ + +.mt-tooltip{ + +  position:absolute; z-index:1400; + +  max-width:220px; + +  padding:6px 8px; + +  border-radius:4px; + +  font:var(--type-xs); + +  white-space:nowrap; + +  background:var(--tooltip-bg); + +  color:var(--tooltip-fg); + +  box-shadow:var(--tooltip-shadow); + +  opacity:0; + +  transform:translateY(2px); + +  pointer-events:none; + +  transition:opacity 120ms ease, transform 120ms ease; + +} + +.mt-tooltip\[data-open="true"]{ opacity:1; transform:translateY(0); } + +.mt-tooltip::after{ + +  content:""; + +  position:absolute; + +  width:8px; height:8px; + +  background:var(--tooltip-bg); + +  transform:rotate(45deg); + +} + +.mt-tooltip\[data-placement="top"]::after{ bottom:-4px; left:50%; transform:translateX(-50%) rotate(45deg); } + +.mt-tooltip\[data-placement="bottom"]::after{ top:-4px; left:50%; transform:translateX(-50%) rotate(45deg); } + +.mt-tooltip\[data-placement="left"]::after{ right:-4px; top:50%; transform:translateY(-50%) rotate(45deg); } + +.mt-tooltip\[data-placement="right"]::after{ left:-4px; top:50%; transform:translateY(-50%) rotate(45deg); } + +``` + + + +--- + + + +\# Theme definitions (Light + Dark) + + + +\## Light theme + + + +```css + +:root\[data-theme="light"]{ + +  --bg:#ffffff; + + + +  --surface:#ffffff; + +  --surface-2:#F7F7F7; + +  --surface-hover:#F3F2F1; + +  --surface-pressed:#EDEBE9; + +  --surface-disabled:#F3F2F1; + + + +  --text:#252423; + +  --text-muted:#605E5C; + +  --text-disabled:#A19F9D; + + + +  --border:#E1DFDD; + +  --border-strong:#C8C6C4; + + + +  --accent:#6264A7; + +  --accent-hover:#585A96; + +  --accent-pressed:#4F5187; + +  --accent-contrast:#ffffff; + + + +  --danger:#D13438; + +  --danger-2:#D13438; + + + +  --focus-ring:#6264A7; + + + +  --shadow-1:0 1px 2px rgba(0,0,0,0.08); + +  --shadow-2:0 6px 18px rgba(0,0,0,0.18); + + + +  --scrim:rgba(0,0,0,0.5); + + + +  /\* menu \*/ + +  --menu-bg:#ffffff; + +  --menu-border:#E1DFDD; + +  --menu-shadow:0 6px 18px rgba(0,0,0,0.18); + + + +  /\* alerts \*/ + +  --alert-error-bg:#FCF4F6; --alert-error-border:#F3D6DC; --alert-error-fg:#A4262C; --alert-error-icon:#A4262C; + +  --alert-warn-bg:#FBF6D9; --alert-warn-border:#F2E2A5; --alert-warn-fg:#8A6A00; --alert-warn-icon:#8A6A00; + +  --alert-ok-bg:#E7F2DA; --alert-ok-border:#CDE6B3; --alert-ok-fg:#107C10; --alert-ok-icon:#107C10; + +  --alert-info-bg:#F5F5F5; --alert-info-border:#E1DFDD; --alert-info-fg:#252423; --alert-info-icon:#605E5C; + + + +  /\* input \*/ + +  --input-bg:#ffffff; + +  --input-fg:var(--text); + +  --input-border:var(--border); + +  --input-border-hover:var(--border-strong); + +  --input-border-focus:var(--accent); + +  --input-underline-focus:var(--accent); + +  --input-placeholder:var(--text-disabled); + +  --input-prefix-bg:var(--accent); + +  --input-prefix-fg:var(--accent-contrast); + +  --input-icon-bg:#605E5C; + + + +  /\* picker \*/ + +  --picker-bg:#ffffff; + +  --picker-border:var(--border); + +  --picker-shadow:0 8px 24px rgba(0,0,0,0.18); + + + +  /\* pivot \*/ + +  --pivot-fg:var(--text-muted); + +  --pivot-active-fg:var(--accent); + +  --pivot-underline-active:var(--accent); + +  --pivot-underline-hover:#C8C6C4; + + + +  /\* progress \*/ + +  --progress-track:#E1DFDD; + +  --progress-fill:var(--accent); + +  --progress-spinner-track:#E1DFDD; + +  --progress-spinner-head:var(--accent); + + + +  /\* radio \*/ + +  --radio-border:#C8C6C4; + +  --radio-border-hover:#605E5C; + +  --radio-dot:var(--accent); + +  --radio-disabled-border:#E1DFDD; + +  --radio-disabled-bg:#F3F2F1; + +  --radio-disabled-dot:#C8C6C4; + + + +  /\* scrollbars \*/ + +  --scroll-track:transparent; + +  --scroll-thumb:#C8C6C4; + +  --scroll-thumb-hover:#A19F9D; + +  --scroll-thumb-active:#8A8886; + +  --scroll-shadow:rgba(0,0,0,0.18); + + + +  /\* search \*/ + +  --search-bg:#F3F2F1; + +  --search-icon:#605E5C; + +  --search-placeholder:#A19F9D; + +  --search-underline:var(--accent); + + + +  /\* status \*/ + +  --status-critical-bg:#C4314B; + +  --status-critical-fg:#ffffff; + +  --status-error:#C4314B; + +  --status-warning:#986F0B; + +  --status-success:#107C10; + +  --status-info:var(--text-muted); + + + +  /\* toast \*/ + +  --toast-bg:#ffffff; + +  --toast-border:var(--border); + + + +  /\* toggle \*/ + +  --toggle-track-off:#FFFFFF; + +  --toggle-border-off:#8A8886; + +  --toggle-thumb:#8A8886; + +  --toggle-track-off-hover:#FFFFFF; + +  --toggle-border-off-hover:#605E5C; + +  --toggle-track-off-pressed:#FFFFFF; + +  --toggle-border-off-pressed:#252423; + + + +  --toggle-track-on:var(--accent); + +  --toggle-border-on:var(--accent); + +  --toggle-track-on-hover:var(--accent-hover); + +  --toggle-border-on-hover:var(--accent-hover); + +  --toggle-track-on-pressed:var(--accent-pressed); + +  --toggle-border-on-pressed:var(--accent-pressed); + + + +  --toggle-track-disabled:#F3F2F1; + +  --toggle-border-disabled:#E1DFDD; + +  --toggle-thumb-disabled:#C8C6C4; + + + +  /\* tooltip \*/ + +  --tooltip-bg:#323130; + +  --tooltip-fg:#ffffff; + +  --tooltip-shadow:0 6px 16px rgba(0,0,0,0.25); + +} + +``` + + + +\## Dark theme + + + +```css + +:root\[data-theme="dark"]{ + +  --bg:#1B1B1B; + + + +  --surface:#2B2B2B; + +  --surface-2:#242424; + +  --surface-hover:#3A3A3A; + +  --surface-pressed:#444444; + +  --surface-disabled:#3A3A3A; + + + +  --text:#ffffff; + +  --text-muted:#C8C8C8; + +  --text-disabled:#8A8886; + + + +  --border:#3F3F3F; + +  --border-strong:#5A5A5A; + + + +  --accent:#7B83EB; + +  --accent-hover:#A6ABFF; + +  --accent-pressed:#4F52B2; + +  --accent-contrast:#ffffff; + + + +  --danger:#F1707B; + +  --danger-2:#F1707B; + + + +  --focus-ring:#7B83EB; + + + +  --shadow-1:0 1px 2px rgba(0,0,0,0.35); + +  --shadow-2:0 10px 28px rgba(0,0,0,0.6); + + + +  --scrim:rgba(0,0,0,0.75); + + + +  /\* menu \*/ + +  --menu-bg:#2B2B2B; + +  --menu-border:#3F3F3F; + +  --menu-shadow:0 10px 28px rgba(0,0,0,0.6); + + + +  /\* alerts \*/ + +  --alert-error-bg:#3E1F25; --alert-error-border:#6B2B34; --alert-error-fg:#F1707B; --alert-error-icon:#F1707B; + +  --alert-warn-bg:#463100; --alert-warn-border:#7A5A00; --alert-warn-fg:#FFD86A; --alert-warn-icon:#FFD86A; + +  --alert-ok-bg:#0D2E0D; --alert-ok-border:#1E5A1E; --alert-ok-fg:#7FE07F; --alert-ok-icon:#7FE07F; + +  --alert-info-bg:#1F1F1F; --alert-info-border:#3F3F3F; --alert-info-fg:#ffffff; --alert-info-icon:#C8C8C8; + + + +  /\* input \*/ + +  --input-bg:var(--surface); + +  --input-fg:var(--text); + +  --input-border:var(--border); + +  --input-border-hover:var(--border-strong); + +  --input-border-focus:var(--accent); + +  --input-underline-focus:var(--accent); + +  --input-placeholder:var(--text-disabled); + +  --input-prefix-bg:var(--accent); + +  --input-prefix-fg:var(--accent-contrast); + +  --input-icon-bg:#C8C8C8; + + + +  /\* picker \*/ + +  --picker-bg:var(--surface); + +  --picker-border:var(--border); + +  --picker-shadow:0 12px 30px rgba(0,0,0,0.6); + + + +  /\* pivot \*/ + +  --pivot-fg:var(--text-muted); + +  --pivot-active-fg:var(--accent); + +  --pivot-underline-active:var(--accent); + +  --pivot-underline-hover:#8A8886; + + + +  /\* progress \*/ + +  --progress-track:#5A5A5A; + +  --progress-fill:var(--accent); + +  --progress-spinner-track:#5A5A5A; + +  --progress-spinner-head:#ffffff; + + + +  /\* radio \*/ + +  --radio-border:#8A8886; + +  --radio-border-hover:#C8C8C8; + +  --radio-dot:var(--accent); + +  --radio-disabled-border:#3F3F3F; + +  --radio-disabled-bg:#2B2B2B; + +  --radio-disabled-dot:#5A5A5A; + + + +  /\* scrollbars \*/ + +  --scroll-track:transparent; + +  --scroll-thumb:#5A5A5A; + +  --scroll-thumb-hover:#8A8886; + +  --scroll-thumb-active:#C8C8C8; + +  --scroll-shadow:rgba(0,0,0,0.55); + + + +  /\* search \*/ + +  --search-bg:#2B2B2B; + +  --search-icon:#C8C8C8; + +  --search-placeholder:#8A8886; + +  --search-underline:var(--accent); + + + +  /\* status \*/ + +  --status-critical-bg:#C4314B; + +  --status-critical-fg:#ffffff; + +  --status-error:#F1707B; + +  --status-warning:#FFD86A; + +  --status-success:#7FE07F; + +  --status-info:var(--text-muted); + + + +  /\* toast \*/ + +  --toast-bg:#2B2B2B; + +  --toast-border:var(--border); + + + +  /\* toggle \*/ + +  --toggle-track-off:#1F1F1F; + +  --toggle-border-off:#8A8886; + +  --toggle-thumb:#8A8886; + +  --toggle-track-off-hover:#1F1F1F; + +  --toggle-border-off-hover:#C8C8C8; + +  --toggle-track-off-pressed:#1F1F1F; + +  --toggle-border-off-pressed:#FFFFFF; + + + +  --toggle-track-on:var(--accent); + +  --toggle-border-on:var(--accent); + +  --toggle-track-on-hover:#A6ABFF; + +  --toggle-border-on-hover:#A6ABFF; + +  --toggle-track-on-pressed:var(--accent-pressed); + +  --toggle-border-on-pressed:var(--accent-pressed); + + + +  --toggle-track-disabled:#2B2B2B; + +  --toggle-border-disabled:#3F3F3F; + +  --toggle-thumb-disabled:#5A5A5A; + + + +  /\* tooltip \*/ + +  --tooltip-bg:#3B3A39; + +  --tooltip-fg:#ffffff; + +  --tooltip-shadow:0 8px 20px rgba(0,0,0,0.6); + +} + +``` + + + +--- + + + +\## Usage + + + +```html + +… + + + +… + +``` + + + diff --git a/SYNC_FORK.md b/SYNC_FORK.md new file mode 100644 index 0000000..0631bbc --- /dev/null +++ b/SYNC_FORK.md @@ -0,0 +1,127 @@ +# Syncing SynthTabs with Upstream SynthOS + +## Upstream Remote + +``` +upstream = ../synthos (local path) +``` + +If the git remote `upstream` is not configured yet, add it: + +```bash +git remote add upstream ../synthos +``` + +## Sync Procedure + +1. **Fetch upstream changes** + ```bash + git fetch upstream main + ``` + +2. **Create a sync branch** + ```bash + git checkout -b sync/upstream-YYYY-MM-DD + ``` + +3. **Merge upstream into sync branch** + ```bash + git merge upstream/main --no-commit + ``` + +4. **Apply conflict-resolution rules** (see below), then commit and PR. + +--- + +## Conflict-Resolution Rules + +### ALWAYS KEEP OURS (never accept upstream) + +| File / Pattern | Reason | +|----------------|--------| +| `README.md` | SynthTabs has its own README | +| `package.json` — `name` | Must stay `"synthtabs"` | +| `package.json` — `version` | We maintain our own version | +| `package.json` — `description` | Teams-specific description | +| `package.json` — `keywords` | Teams-specific keywords | +| `package.json` — `bugs` | Points to our repo | +| `package.json` — `repository` | Points to our repo | +| `package.json` — `bin` | Must stay `"./bin/synthtabs.js"` (our CLI) | +| `package.json` — `files` | Includes `teams-*` folders and `teams-tests` | +| `package.json` — `scripts.start` | Must use our CLI entry point | +| `package.json` — `scripts.test:mocha` | Must include both `tests/**/*.spec.ts` and `teams-tests/**/*.spec.ts` | +| `SHARED-MEMORIES.md` | We may have our own project context | +| `.github/` | Our own CI/CD config | +| `.claude/` | Our own Claude Code settings | +| `.experts/` | Our own expert knowledge base | + +### ALWAYS ACCEPT UPSTREAM + +| File / Pattern | Reason | +|----------------|--------| +| `src/**/*.ts` | Core logic — we extend via Customizer, not by editing source | +| `required-pages/` | System pages come from upstream | +| `default-pages/` | Starter templates come from upstream | +| `default-scripts/` | OS script templates come from upstream | +| `default-themes/` | Base themes come from upstream | +| `page-scripts/` | Versioned page scripts come from upstream | +| `service-connectors/` | Base connector definitions come from upstream | +| `migration-rules/` | Page migration rules come from upstream | +| `tests/` | Upstream test coverage — run alongside our `teams-tests/` | + +### MERGE CAREFULLY (manual review required) + +| File / Pattern | How to merge | +|----------------|-------------| +| `package.json` — `dependencies` | Accept new/updated deps from upstream. Keep any Teams-specific deps we added. | +| `package.json` — `devDependencies` | Same — accept upstream updates, keep our additions. | +| `package.json` — `scripts` (non-start) | Accept upstream changes to `build`, `test`, etc. Keep our custom scripts. | +| `tsconfig.json` | Accept upstream compiler changes. Keep any overrides we need. | +| `bin/` | Upstream may update `synthos.js`. Our `synthtabs.js` wrapper is separate — keep both. | +| `src/synthos-cli.ts` → `src/synthtabs-cli.ts` | When upstream changes `synthos-cli.ts`, manually migrate those changes into `synthtabs-cli.ts`. Our CLI mirrors upstream but uses our branding (`synthtabs` script name, `SynthTabs` messaging) and passes our `customizer` instance. | + +### NEVER TOUCH (ours only, upstream doesn't have these) + +| File / Pattern | Reason | +|----------------|--------| +| `teams-default-pages/` | Teams-specific content | +| `teams-default-scripts/` | Teams-specific content | +| `teams-default-themes/` | Teams-specific content | +| `teams-page-scripts/` | Teams-specific content | +| `teams-required-pages/` | Teams-specific content | +| `teams-service-connectors/` | Teams-specific content | +| `teams-tests/` | Our own unit and integration tests | +| `.architecture/` | Our architecture docs | +| `SYNC_FORK.md` | This file | + +--- + +## Post-Merge Checklist + +- [ ] `package.json` — name is still `"synthtabs"`, version is ours, bin points to our CLI +- [ ] `npm install` succeeds (new upstream deps are picked up) +- [ ] `npm run build` succeeds +- [ ] `npm test` passes (runs both upstream `tests/` and our `teams-tests/`) +- [ ] Our `teams-*` folders are untouched +- [ ] `README.md` is still our Teams-specific readme +- [ ] No upstream-only files leaked into our `files` list that shouldn't be there + +--- + +## Quick Reference: Manual package.json Merge + +When `package.json` conflicts, use this approach: + +1. Accept the upstream version first +2. Restore our overrides: + - `name`: `"synthtabs"` + - `version`: our current version + - `description`: `"Dynamic vibe coded Tab Apps for Microsoft Teams."` + - `bin`: `"./bin/synthtabs.js"` + - `bugs.url`: our GitHub issues URL + - `repository.url`: our GitHub repo URL + - `keywords`: include `"teams"` + - `files`: include `teams-*` folders and `teams-tests` + - `scripts.start`: `"node ./bin/synthtabs.js start"` + - `scripts.test:mocha`: `"nyc ts-mocha tests/**/*.spec.ts teams-tests/**/*.spec.ts"` +3. Add back any Teams-specific dependencies we have diff --git a/UPSTREAM_CHANGES.md b/UPSTREAM_CHANGES.md new file mode 100644 index 0000000..e821970 --- /dev/null +++ b/UPSTREAM_CHANGES.md @@ -0,0 +1,71 @@ +# Upstream Changes to Apply to SynthOS + +## page-v2.js: Fix initial focus race with chat-collapsed check + +The initial `chatInput.focus()` runs before the chat-collapsed localStorage check, +so if the panel was previously collapsed, the input gets focused then immediately +blurred when the panel slides off-screen. The browser moves focus to the body/viewer. + +### Fix + +In `page-scripts/page-v2.js`: + +1. **Remove** the early focus call (around line 100-102): + +```diff +- // 1. Initial focus +- var chatInput = document.getElementById('chatInput'); +- if (chatInput) chatInput.focus(); ++ var chatInput = document.getElementById('chatInput'); +``` + +2. **Add** focus at the very end of the outer IIFE, just before `})();`: + +```diff + })(); ++ ++ // Initial focus — run after all setup (including chat-collapsed check) ++ if (chatInput && !document.body.classList.contains('chat-collapsed')) { ++ chatInput.focus(); ++ } + })(); +``` + +## REQUIRED_PAGES should be derived from the Customizer's requiredPagesFolder + +`REQUIRED_PAGES` in `src/pages.ts` is a hardcoded array (`['builder', 'pages', 'settings', ...]`). +This should instead be dynamically read from the files in the Customizer's `requiredPagesFolder` +directory (strip `.html` extensions to get the page names). Forks that add or rename required pages +(e.g. SynthTabs renames `pages.html` → `tabs.html`) currently have to patch this array, which is +fragile and causes merge conflicts on sync. + +### Suggested approach + +Replace the static constant with a function that scans the folder at startup: + +```ts +// src/pages.ts +export function getRequiredPages(requiredPagesFolder: string): string[] { + // list *.html files in the folder and strip the extension +} +``` + +Cache the result in `SynthOSConfig` so it's computed once during init and passed through +to `useApiRoutes` / `usePageRoutes`. + +## Customizer: add `tabsListRoute` getter + +Added a `tabsListRoute` getter to `Customizer` (default `'/pages'`) so forks can override the +route that the outdated-page redirect targets. SynthTabs overrides this to `'/tabs'`. + +**Files changed:** +- `src/customizer/Customizer.ts` — new getter `tabsListRoute` returning `'/pages'` + +## usePageRoutes: use Customizer for outdated-page redirect + +The outdated-page redirect in `usePageRoutes.ts` was hardcoded to `res.redirect('/pages')`. +Changed to `res.redirect(customizer?.tabsListRoute ?? '/pages')` so forks can control +the redirect destination. + +**Files changed:** +- `src/service/usePageRoutes.ts` — line ~122, redirect now uses `customizer.tabsListRoute` diff --git a/bin/synthtabs.js b/bin/synthtabs.js new file mode 100644 index 0000000..b311880 --- /dev/null +++ b/bin/synthtabs.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node +var app = require('../dist/synthtabs-cli.js'); +app.run(); diff --git a/package-lock.json b/package-lock.json index 2d43db5..c5f8875 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "synthos", - "version": "0.7.2", + "name": "synthtabs", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "synthos", - "version": "0.7.2", + "name": "synthtabs", + "version": "0.1.0", "license": "MIT", "dependencies": { "@a2a-js/sdk": "^0.3.10", @@ -20,7 +20,7 @@ "yargs": "^17.7.2" }, "bin": { - "synthos": "bin/synthos.js" + "synthtabs": "bin/synthtabs.js" }, "devDependencies": { "@types/adm-zip": "^0.5.7", diff --git a/package.json b/package.json index 0d5b14d..f11988c 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,24 @@ { - "name": "synthos", + "name": "synthtabs", "author": "Steven Ickman", - "description": "The Hyper-Personalized App Builder That Turns Everyone Into a Programmer.", - "version": "0.8.0", + "description": "Dynamic vibe coded Tab Apps for Microsoft Teams.", + "version": "0.1.0", "license": "MIT", "keywords": [ "synthos", - "ai" + "ai", + "teams" ], "bugs": { - "url": "https://github.com/Stevenic/synthos/issues" + "url": "https://github.com/Stevenic-microsoft/synthtabs/issues" }, "repository": { "type": "git", - "url": "https://github.com/Stevenic/synthos.git" + "url": "https://github.com/Stevenic-microsoft/synthtabs.git" }, "main": "./dist/index.js", "types": "./dist/index.d.ts", - "bin": "./bin/synthos.js", + "bin": "./bin/synthtabs.js", "dependencies": { "@a2a-js/sdk": "^0.3.10", "@anthropic-ai/sdk": "^0.75.0", @@ -47,21 +48,21 @@ "scripts": { "build": "tsc -b", "clean": "rimraf dist tsconfig.tsbuildinfo node_modules", - "start": "node ./bin/synthos.js", + "start": "node ./bin/synthtabs.js start", "test": "npm run build && npm run test:mocha", - "test:mocha": "nyc ts-mocha tests/**/*.spec.ts" + "test:mocha": "nyc ts-mocha tests/**/*.spec.ts teams-tests/**/*.spec.ts" }, "files": [ "dist", "bin", "src", "tests", - "default-pages", - "default-scripts", - "default-themes", - "page-scripts", - "required-pages", - "service-connectors", - "images" + "teams-default-pages", + "teams-default-scripts", + "teams-default-themes", + "teams-page-scripts", + "teams-required-pages", + "teams-service-connectors", + "teams-tests" ] } diff --git a/src/customizer/Customizer.ts b/src/customizer/Customizer.ts index a51293d..471c991 100644 --- a/src/customizer/Customizer.ts +++ b/src/customizer/Customizer.ts @@ -50,6 +50,12 @@ export class Customizer { return path.join(__dirname, '../../service-connectors'); } + /** Route path for the "browse all pages/tabs" listing page. + * Override in a derived class to change the redirect target for outdated pages. */ + get tabsListRoute(): string { + return '/pages'; + } + // --- Feature group control --- // Built-in groups: 'pages', 'api', 'connectors', 'agents', // 'data', 'brainstorm', 'search', 'scripts' diff --git a/src/customizer/TeamsCustomizer.ts b/src/customizer/TeamsCustomizer.ts new file mode 100644 index 0000000..252f563 --- /dev/null +++ b/src/customizer/TeamsCustomizer.ts @@ -0,0 +1,377 @@ +import path from 'path'; +import { Customizer } from './Customizer'; + +/** + * Teams-specific customizer that overrides folder paths and the local + * data-folder name for the SynthTabs fork. + */ +export class TeamsCustomizer extends Customizer { + constructor() { + super(); + this.addTransformInstructions(transformInstructions); + } + + get localFolder(): string { + return '.synthtabs'; + } + + get requiredPagesFolder(): string { + return path.join(__dirname, '../../teams-required-pages'); + } + + get defaultPagesFolder(): string { + return path.join(__dirname, '../../teams-default-pages'); + } + + get defaultThemesFolder(): string { + return path.join(__dirname, '../../teams-default-themes'); + } + + get pageScriptsFolder(): string { + return path.join(__dirname, '../../teams-page-scripts'); + } + + get serviceConnectorsFolder(): string { + return path.join(__dirname, '../../teams-service-connectors'); + } + + get tabsListRoute(): string { + return '/tabs'; + } +} + +const transformInstructions = `The viewer panel automatically fills the full available space. App content stretches to fill the full viewer width and height so do not add the "full-viewer" class to the viewer-panel element. +Chat panel collapse/expand: The chat panel uses a header bar (.chat-panel-header) with a close button (.chat-panel-close) and a collapsed rail (.chat-rail) instead of the floating .chat-toggle button. Do NOT create or reference .chat-toggle elements. The page script handles close/expand behaviour and localStorage persistence automatically. +Prefer using the below when designing pages: + + + +## Button + + + + + +
+ + +
+ +## Alert +
+ + Message goes here + + +
+ +## Breadcrumb + + +## Card +
+
+
+
+
+
Fluent base card
+
Subtitle
+
+ +
+
+
+ +## Carousel + + +## Checkbox + + +## Coachmark + + +## Contextual Menu + + +## Dialog + + +## Dropdown +
+ + + +
+ +## Group List +
+ + +
+ +## Hyperlink +Hyperlink +A very long hyperlink that should truncate +Disabled + +## Input +
+ +
+ +
+
Optional hint
+
+
+
+ Prefix + +
+
+
+ +
+ + +
+ +
+ +## Key Value Pair +
+
+
Label
+
+
Key
+
Value
+
+
+
+ +## Paragraph +
+

Headline example

+

Description example…

+

Subheadline here

+

Lorem ipsum…

+

Description example…

+
+ +## Picker +
+ +
+
+ +
+
+ +
+
+
+ +## Date Picker +
+
+
+
+ +
Jan 2021
+ +
+
+ + +
+
+
+ +## Time Picker +
+
+
+
+ + +
+
+
+ +## Pivot + + +## Progress Indicator +
+ +
+
+
+
Label
+
Description
+
+
+
+
+
+ +## Radio +
+ + +
+ +## Scroll Region +
Very long content…
+ +## Search Box + + +## Side Panel + + +## Status Label + + + Critical + + + + Warning + + +## Toast +
+
+
+ +
+
ContosoBot
+
Lorem ipsum…
+
+ +
+
+
Lorem ipsum…
+
+
+ + +
+
+
+ +## Toggle + + +## Tooltip + + +`; \ No newline at end of file diff --git a/src/customizer/index.ts b/src/customizer/index.ts index d2670b7..a26264b 100644 --- a/src/customizer/index.ts +++ b/src/customizer/index.ts @@ -1,6 +1,6 @@ export { Customizer, RouteInstaller } from './Customizer'; -import { Customizer } from './Customizer'; +export { TeamsCustomizer } from './TeamsCustomizer'; +import { TeamsCustomizer } from './TeamsCustomizer'; -// Default instance — enables everything, uses base folders. -// Fork developers: replace this with your derived class instance. -export const customizer = new Customizer(); +// Default instance — uses Teams-specific folders and .synthtabs local folder. +export const customizer = new TeamsCustomizer(); diff --git a/src/pages.ts b/src/pages.ts index 81634e6..d4f2aaa 100644 --- a/src/pages.ts +++ b/src/pages.ts @@ -4,7 +4,7 @@ import path from 'path'; // Page State Cache const _pages: { [name: string]: string } = {}; -export const REQUIRED_PAGES = ['builder', 'pages', 'settings', 'apis', 'scripts']; +export const REQUIRED_PAGES = ['builder', 'pages', 'tabs', 'settings', 'apis', 'scripts']; export const PAGE_VERSION = 2; diff --git a/src/service/usePageRoutes.ts b/src/service/usePageRoutes.ts index e0e9b22..e8b62d2 100644 --- a/src/service/usePageRoutes.ts +++ b/src/service/usePageRoutes.ts @@ -117,9 +117,9 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize const metadata = await loadPageMetadata(config.pagesFolder, page, config.requiredPagesFolder); const pageVersion = metadata?.pageVersion ?? 0; - // Block outdated pages (redirect to /pages so user sees upgrade UI) + // Block outdated pages (redirect to tabs list so user sees upgrade UI) if (pageVersion < PAGE_VERSION && !REQUIRED_PAGES.includes(page)) { - res.redirect('/pages'); + res.redirect(customizer?.tabsListRoute ?? '/pages'); return; } diff --git a/src/synthtabs-cli.ts b/src/synthtabs-cli.ts new file mode 100644 index 0000000..c557c09 --- /dev/null +++ b/src/synthtabs-cli.ts @@ -0,0 +1,49 @@ +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import { server } from "./service"; +import { createConfig, init } from "./init"; +import { customizer } from "./customizer"; + +const dynamicImport = new Function('specifier', `return import(specifier)`); + +export async function run() { + await yargs(hideBin(process.argv)) + .scriptName('synthtabs') + .command('start', `Starts the SynthTabs server.`, (yargs) => { + return yargs + .option('port', { + describe: `The port number to use.`, + type: 'number', + default: 4242 + }) + .option('pages', { + describe: `Include default pages when initializing a new .synthtabs folder.`, + type: 'boolean', + default: true + }) + .option('debug', { + describe: `Log all server requests with timing and page update summaries.`, + type: 'boolean', + default: false + }) + .option('debug-page-updates', { + describe: `Log model input/output for page transformations to the console.`, + type: 'boolean', + default: false + }) + .demandOption([]); + }, async (args) => { + const config = createConfig(customizer.localFolder, { debug: args.debug, debugPageUpdates: args.debugPageUpdates }, customizer); + await init(config, args.pages); + await server(config, customizer).listen(args.port, async () => { + console.log(`SynthTabs server is running on http://localhost:${args.port}`); + + // Open using default browser + const open = await dynamicImport('open'); + open.default(`http://localhost:${args.port}`); + }); + }) + .help() + .demandCommand() + .parseAsync(); +} diff --git a/teams-default-pages/.gitkeep b/teams-default-pages/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/teams-default-scripts/.gitkeep b/teams-default-scripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/teams-default-scripts/android.terminal.json b/teams-default-scripts/android.terminal.json new file mode 100644 index 0000000..7cb7acd --- /dev/null +++ b/teams-default-scripts/android.terminal.json @@ -0,0 +1,7 @@ +{ + "id": "android-terminal", + "type": "command", + "command": "{{command}}", + "description": "Executes a Android based terminal command in the current working directory", + "variables": "{{command}}" +} \ No newline at end of file diff --git a/teams-default-scripts/linux-terminal.json b/teams-default-scripts/linux-terminal.json new file mode 100644 index 0000000..d702c50 --- /dev/null +++ b/teams-default-scripts/linux-terminal.json @@ -0,0 +1,7 @@ +{ + "id": "linux-terminal", + "type": "command", + "command": "{{command}}", + "description": "Executes a Linux based terminal command in the current working directory", + "variables": "{{command}}" +} \ No newline at end of file diff --git a/teams-default-scripts/mac-terminal.json b/teams-default-scripts/mac-terminal.json new file mode 100644 index 0000000..6047be6 --- /dev/null +++ b/teams-default-scripts/mac-terminal.json @@ -0,0 +1,7 @@ +{ + "id": "mac-terminal", + "type": "command", + "command": "{{command}}", + "description": "Executes a MacOS based terminal command in the current working directory", + "variables": "{{command}}" +} \ No newline at end of file diff --git a/teams-default-scripts/windows-terminal.json b/teams-default-scripts/windows-terminal.json new file mode 100644 index 0000000..42a8bf4 --- /dev/null +++ b/teams-default-scripts/windows-terminal.json @@ -0,0 +1,7 @@ +{ + "id": "windows-terminal", + "type": "command", + "command": "{{command}}", + "description": "Executes a windows based terminal command in the current working directory", + "variables": "{{command}}" +} \ No newline at end of file diff --git a/teams-default-themes/.gitkeep b/teams-default-themes/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/teams-default-themes/teams-dark.json b/teams-default-themes/teams-dark.json new file mode 100644 index 0000000..01360f8 --- /dev/null +++ b/teams-default-themes/teams-dark.json @@ -0,0 +1,134 @@ +{ + "mode": "dark", + "colors": { + "bg-primary": "#292929", + "bg-secondary": "#1F1F1F", + "bg-tertiary": "#333333", + "accent-primary": "#4F52B2", + "accent-secondary": "#5B5FC7", + "accent-tertiary": "#444791", + "accent-glow": "rgba(79,82,178,0.2)", + "text-primary": "#ffffff", + "text-secondary": "#C8C8C8", + "border-color": "#3D3D3D", + "header-min-height": "58px", + "header-padding-vertical": "14px", + "header-padding-horizontal": "20px", + "header-line-height": "1.25", + + "surface": "#292929", + "surface-2": "#1F1F1F", + "surface-hover": "#3D3D3D", + "surface-pressed": "#444444", + "surface-disabled": "#3D3D3D", + + "text-disabled": "#8A8886", + "border-strong": "#5A5A5A", + + "accent-contrast": "#ffffff", + "accent-hover": "#5B5FC7", + "accent-pressed": "#444791", + + "danger": "#F1707B", + "danger-2": "#F1707B", + "focus-ring": "#4F52B2", + + "shadow-1": "0 1px 2px rgba(0,0,0,0.35)", + "shadow-2": "0 10px 28px rgba(0,0,0,0.6)", + "scrim": "rgba(0,0,0,0.75)", + + "menu-bg": "#333333", + "menu-border": "#3D3D3D", + "menu-shadow": "0 10px 28px rgba(0,0,0,0.6)", + + "alert-error-bg": "#3E1F25", + "alert-error-border": "#6B2B34", + "alert-error-fg": "#F1707B", + "alert-error-icon": "#F1707B", + "alert-warn-bg": "#463100", + "alert-warn-border": "#7A5A00", + "alert-warn-fg": "#FFD86A", + "alert-warn-icon": "#FFD86A", + "alert-ok-bg": "#0D2E0D", + "alert-ok-border": "#1E5A1E", + "alert-ok-fg": "#7FE07F", + "alert-ok-icon": "#7FE07F", + "alert-info-bg": "#1F1F1F", + "alert-info-border": "#3D3D3D", + "alert-info-fg": "#ffffff", + "alert-info-icon": "#C8C8C8", + + "input-bg": "#1F1F1F", + "input-border": "#3D3D3D", + "input-border-hover": "#5A5A5A", + "input-border-focus": "#4F52B2", + "input-underline-focus": "#4F52B2", + "input-placeholder": "#8A8886", + "input-prefix-bg": "#4F52B2", + "input-prefix-fg": "#ffffff", + "input-icon-bg": "#C8C8C8", + + "picker-bg": "#333333", + "picker-border": "#3D3D3D", + "picker-shadow": "0 12px 30px rgba(0,0,0,0.6)", + + "pivot-fg": "#C8C8C8", + "pivot-active-fg": "#4F52B2", + "pivot-underline-active": "#4F52B2", + "pivot-underline-hover": "#8A8886", + + "progress-track": "#5A5A5A", + "progress-fill": "#4F52B2", + "progress-spinner-track": "#5A5A5A", + "progress-spinner-head": "#ffffff", + + "radio-border": "#8A8886", + "radio-border-hover": "#C8C8C8", + "radio-dot": "#4F52B2", + "radio-disabled-border": "#3D3D3D", + "radio-disabled-bg": "#1F1F1F", + "radio-disabled-dot": "#5A5A5A", + + "scroll-track": "transparent", + "scroll-thumb": "#5A5A5A", + "scroll-thumb-hover": "#8A8886", + "scroll-thumb-active": "#C8C8C8", + "scroll-shadow": "rgba(0,0,0,0.55)", + + "search-bg": "#1F1F1F", + "search-icon": "#C8C8C8", + "search-placeholder": "#8A8886", + "search-underline": "#4F52B2", + + "status-critical-bg": "#C4314B", + "status-critical-fg": "#ffffff", + "status-error": "#F1707B", + "status-warning": "#FFD86A", + "status-success": "#7FE07F", + "status-info": "#C8C8C8", + + "toast-bg": "#333333", + "toast-border": "#3D3D3D", + + "toggle-track-off": "#1F1F1F", + "toggle-border-off": "#8A8886", + "toggle-thumb": "#8A8886", + "toggle-track-off-hover": "#1F1F1F", + "toggle-border-off-hover": "#C8C8C8", + "toggle-track-off-pressed": "#1F1F1F", + "toggle-border-off-pressed": "#FFFFFF", + "toggle-track-on": "#4F52B2", + "toggle-border-on": "#4F52B2", + "toggle-track-on-hover": "#5B5FC7", + "toggle-border-on-hover": "#5B5FC7", + "toggle-track-on-pressed": "#444791", + "toggle-border-on-pressed": "#444791", + "toggle-track-disabled": "#1F1F1F", + "toggle-border-disabled": "#3D3D3D", + "toggle-thumb-disabled": "#5A5A5A", + + "tooltip-bg": "#3B3A39", + "tooltip-fg": "#ffffff", + "tooltip-shadow": "0 8px 20px rgba(0,0,0,0.6)" + } +} diff --git a/teams-default-themes/teams-dark.v1.css b/teams-default-themes/teams-dark.v1.css new file mode 100644 index 0000000..588fc23 --- /dev/null +++ b/teams-default-themes/teams-dark.v1.css @@ -0,0 +1,1422 @@ +:root { + /* ---- Core palette ---- */ + --bg-primary: #292929; + --bg-secondary: #1F1F1F; + --bg-tertiary: #333333; + --accent-primary: #4F52B2; + --accent-secondary: #5B5FC7; + --accent-tertiary: #444791; + --accent-contrast: #ffffff; + --accent-hover: #5B5FC7; + --accent-pressed: #444791; + --accent-glow: rgba(79,82,178,0.2); + --text-primary: #ffffff; + --text-secondary: #C8C8C8; + --text-disabled: #8A8886; + --border-color: #3D3D3D; + --border-strong: #5A5A5A; + --danger: #F1707B; + --focus-ring: #4F52B2; + --shadow-sm: 0 1px 2px rgba(0,0,0,0.35); + --shadow-lg: 0 10px 28px rgba(0,0,0,0.6); + --scrim: rgba(0,0,0,0.75); + + /* ---- Typography ---- */ + --font: "Segoe UI", system-ui, -apple-system, sans-serif; + --type-xs: 12px/16px var(--font); + --type-sm: 13px/20px var(--font); + --type-md: 16px/22px var(--font); + --type-lg: 18px/24px var(--font); + + /* ---- Shell layout ---- */ + --header-min-height: 58px; + --header-padding-vertical: 14px; + --header-padding-horizontal: 20px; + --header-line-height: 1.25; + + /* ---- Menu ---- */ + --menu-bg: #333333; + --menu-border: #3D3D3D; + --menu-shadow: 0 10px 28px rgba(0,0,0,0.6); + + /* ---- Alert ---- */ + --alert-error-bg: #3E1F25; --alert-error-border: #6B2B34; --alert-error-fg: #F1707B; --alert-error-icon: #F1707B; + --alert-warn-bg: #463100; --alert-warn-border: #7A5A00; --alert-warn-fg: #FFD86A; --alert-warn-icon: #FFD86A; + --alert-ok-bg: #0D2E0D; --alert-ok-border: #1E5A1E; --alert-ok-fg: #7FE07F; --alert-ok-icon: #7FE07F; + --alert-info-bg: #1F1F1F; --alert-info-border: #3D3D3D; --alert-info-fg: #ffffff; --alert-info-icon: #C8C8C8; + + /* ---- Input ---- */ + --input-bg: #1F1F1F; + --input-fg: var(--text-primary); + --input-border: var(--border-color); + --input-border-hover: var(--border-strong); + --input-border-focus: var(--accent-primary); + --input-underline-focus: var(--accent-primary); + --input-placeholder: var(--text-disabled); + --input-prefix-bg: var(--accent-primary); + --input-prefix-fg: var(--accent-contrast); + --input-icon-bg: #C8C8C8; + + /* ---- Picker ---- */ + --picker-bg: #333333; + --picker-border: var(--border-color); + --picker-shadow: 0 12px 30px rgba(0,0,0,0.6); + + /* ---- Pivot ---- */ + --pivot-fg: var(--text-secondary); + --pivot-active-fg: var(--accent-primary); + --pivot-underline-active: var(--accent-primary); + --pivot-underline-hover: #8A8886; + + /* ---- Progress ---- */ + --progress-track: #5A5A5A; + --progress-fill: var(--accent-primary); + --progress-spinner-track: #5A5A5A; + --progress-spinner-head: #ffffff; + + /* ---- Radio ---- */ + --radio-border: #8A8886; + --radio-border-hover: #C8C8C8; + --radio-dot: var(--accent-primary); + --radio-disabled-border: #3D3D3D; + --radio-disabled-bg: #1F1F1F; + --radio-disabled-dot: #5A5A5A; + + /* ---- Scroll ---- */ + --scroll-track: transparent; + --scroll-thumb: #5A5A5A; + --scroll-thumb-hover: #8A8886; + --scroll-thumb-active: #C8C8C8; + --scroll-shadow: rgba(0,0,0,0.55); + + /* ---- Search ---- */ + --search-bg: #1F1F1F; + --search-icon: #C8C8C8; + --search-placeholder: #8A8886; + --search-underline: var(--accent-primary); + + /* ---- Status ---- */ + --status-critical-bg: #C4314B; + --status-critical-fg: #ffffff; + --status-error: #F1707B; + --status-warning: #FFD86A; + --status-success: #7FE07F; + --status-info: var(--text-secondary); + + /* ---- Toast ---- */ + --toast-bg: #333333; + --toast-border: var(--border-color); + + /* ---- Toggle ---- */ + --toggle-track-off: #1F1F1F; + --toggle-border-off: #8A8886; + --toggle-thumb: #8A8886; + --toggle-track-off-hover: #1F1F1F; + --toggle-border-off-hover: #C8C8C8; + --toggle-track-off-pressed: #1F1F1F; + --toggle-border-off-pressed: #FFFFFF; + --toggle-track-on: var(--accent-primary); + --toggle-border-on: var(--accent-primary); + --toggle-track-on-hover: var(--accent-secondary); + --toggle-border-on-hover: var(--accent-secondary); + --toggle-track-on-pressed: var(--accent-tertiary); + --toggle-border-on-pressed: var(--accent-tertiary); + --toggle-track-disabled: #1F1F1F; + --toggle-border-disabled: #3D3D3D; + --toggle-thumb-disabled: #5A5A5A; + + /* ---- Tooltip ---- */ + --tooltip-bg: #3B3A39; + --tooltip-fg: #ffffff; + --tooltip-shadow: 0 8px 20px rgba(0,0,0,0.6); +} + +/* ================================================================ + SHELL STYLES + ================================================================ */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: var(--font); + background: var(--bg-primary); + color: var(--text-primary); + height: 100vh; + display: flex; +} + +.chat-panel { + width: 30%; + background: var(--bg-secondary); + box-shadow: -1px 0 2px rgba(0,0,0,0.35); + padding: 20px; + display: flex; + flex-direction: column; + border-left: 1px solid var(--border-color); + position: relative; +} + +.chat-messages { + flex-grow: 1; + overflow-y: auto; + padding: 15px; + margin-top: 15px; + background: var(--bg-tertiary); + border-radius: 6px; + border: 1px solid var(--border-color); +} + +.chat-message { + margin-bottom: 15px; + padding: 12px 15px; + background: var(--bg-secondary); + border-radius: 6px; + border: 1px solid var(--border-color); +} + +.chat-message p { + margin-bottom: 5px; + line-height: 1.5; +} + +.chat-message p strong { + font-weight: 600; + color: var(--accent-secondary); +} + +.chat-message p code { + background: var(--accent-glow); + padding: 2px 6px; + border-radius: 4px; + font-family: 'Courier New', Courier, monospace; + color: var(--accent-secondary); + border: 1px solid var(--border-color); +} + +.link-group { + display: flex; + justify-content: space-between; + margin: 15px 0; + padding: 10px; + background: var(--bg-tertiary); + border-radius: 6px; + border: 1px solid var(--border-color); +} + +.link-group a { + font-size: 14px; + color: var(--accent-primary); + text-decoration: none; + padding: 8px 15px; + border-radius: 4px; + transition: all 0.2s ease; + border: 1px solid transparent; +} + +.link-group a:hover { + background: var(--accent-glow); + border-color: var(--border-color); + color: var(--accent-secondary); +} + +form { + display: flex; + flex-direction: row; + width: 100%; + gap: 10px; + align-items: center; +} + +.chat-input { + padding: 14px 18px; + border: 1px solid var(--border-color); + border-radius: 4px; + flex-grow: 1; + font-size: 14px; + background: var(--bg-tertiary); + color: var(--text-primary); + transition: border-color 0.2s ease; +} + +.chat-input:focus { + outline: none; + box-shadow: inset 0 -2px 0 var(--accent-primary); +} + +.chat-input::placeholder { + color: var(--text-disabled); +} + +.chat-submit { + padding: 14px 20px; + border: none; + border-radius: 4px; + font-size: 14px; + background: var(--accent-primary); + color: #fff; + cursor: pointer; + transition: background 0.2s ease; + font-weight: 600; + letter-spacing: 1px; + white-space: nowrap; +} + +.chat-submit:hover { + background: var(--accent-secondary); +} + +.chat-submit:active { + background: var(--accent-tertiary); +} + +.viewer-panel { + flex: 1; + min-width: 0; + padding: 0; + background: var(--bg-tertiary); + display: flex; + flex-direction: column; + position: relative; + overflow: hidden; + outline: none; +} + +.viewer-panel > div { + flex: 1; + min-height: 0; +} + +.loading-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(27,27,27,0.9); + justify-content: center; + align-items: center; + z-index: 1000; +} + +.spinner { + width: 50px; + height: 50px; + border: 4px solid var(--border-color); + border-top-color: var(--accent-primary); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background: var(--scroll-track); + border-radius: 10px; +} + +::-webkit-scrollbar-thumb { + background: var(--scroll-thumb); + border-radius: 10px; + border: 3px solid var(--bg-primary); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--scroll-thumb-hover); +} + +::-webkit-scrollbar-corner { + background: transparent; +} + +* { + scrollbar-width: thin; + scrollbar-color: var(--scroll-thumb) transparent; +} + +/* ---- Chat Panel Header ---- */ +.chat-panel-header { + display: flex; + align-items: center; + justify-content: space-between; + margin: -20px -20px 0; + padding: 12px 16px; + border-bottom: 1px solid var(--border-color); + background: var(--bg-tertiary); +} + +.chat-panel-header__title { + font: 600 var(--type-sm); + color: var(--text-primary); +} + +.chat-panel-close { + width: 28px; + height: 28px; + display: grid; + place-items: center; + border: 0; + border-radius: 4px; + background: transparent; + color: var(--text-secondary); + cursor: pointer; + font-size: 16px; + line-height: 1; +} + +.chat-panel-close:hover { + background: var(--bg-secondary); + color: var(--text-primary); +} + +/* ---- Chat Rail ---- */ +.chat-rail { + width: 28px; + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + background: var(--bg-secondary); + border-left: 1px solid var(--border-color); + cursor: pointer; + user-select: none; + flex-shrink: 0; +} + +.chat-rail:hover { + background: #3D3D3D; +} + +.chat-rail-label { + writing-mode: vertical-rl; + transform: rotate(180deg); + font: 600 var(--type-xs); + color: var(--text-secondary); + letter-spacing: 0.5px; + white-space: nowrap; +} + +/* ---- Chat Collapsed State ---- */ +body.chat-collapsed .chat-panel { + display: none; +} + +body.chat-collapsed .chat-rail { + display: flex; +} + +/* ---- Modal ---- */ +.modal-overlay { + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + background: var(--scrim); + display: none; + align-items: center; + justify-content: center; + z-index: 2000; + backdrop-filter: blur(4px); +} + +.modal-overlay.show { display: flex; } + +.modal-content { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; + width: 90%; + max-width: 450px; + box-shadow: var(--shadow-lg); + overflow: hidden; +} + +.modal-header { + background: var(--accent-primary); + color: white; + padding: 16px 20px; + font-size: 18px; + font-weight: 700; +} + +.modal-body { padding: 20px; } + +.modal-footer { + padding: 16px 20px; + border-top: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; +} + +.modal-footer-right { display: flex; gap: 10px; margin-left: auto; } + +/* ---- Form elements ---- */ +.form-group { margin-bottom: 16px; } +.form-group:last-child { margin-bottom: 0; } + +.form-label { + display: block; + font-size: 13px; + color: var(--text-secondary); + margin-bottom: 6px; + font-weight: 600; +} + +.form-input { + width: 100%; + padding: 10px 14px; + border: 1px solid var(--border-color); + border-radius: 4px; + background: var(--bg-tertiary); + color: var(--text-primary); + font-size: 14px; + outline: none; + transition: border-color 0.2s ease; + box-sizing: border-box; +} + +.form-input:focus { outline: none; box-shadow: inset 0 -2px 0 var(--accent-primary); } +.form-input:read-only { opacity: 0.7; cursor: not-allowed; } +.form-input::placeholder { color: var(--text-disabled); } + +.checkbox-label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; +} + +.checkbox-label input[type="checkbox"] { + width: 18px; + height: 18px; + accent-color: var(--accent-primary); + cursor: pointer; +} + +.checkbox-label span { font-size: 14px; color: var(--text-primary); } + +/* ---- Modal buttons ---- */ +.modal-btn { + padding: 10px 20px; + border-radius: 4px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s ease; + border: none; +} + +.modal-btn-primary { + background: var(--accent-primary); + color: white; +} + +.modal-btn-primary:hover { + background: var(--accent-secondary); +} + +.modal-btn-secondary { + background: var(--bg-tertiary); + color: var(--text-primary); + border: 1px solid var(--border-strong); +} + +.modal-btn-secondary:hover { + background: #3D3D3D; +} + +.modal-btn-danger { background: var(--danger); color: white; } + +.modal-btn-danger:hover { + background: #D13438; +} + +/* ---- Brainstorm icon button (inside input) ---- */ +.chat-input-wrapper { + position: relative; + flex: 1; + display: flex; + align-items: center; +} + +.chat-input-wrapper .chat-input { + width: 100%; + padding-right: 36px; +} + +.brainstorm-icon-btn { + position: absolute; + right: 6px; + top: 50%; + transform: translateY(-50%); + background: transparent; + border: none; + color: var(--text-secondary); + cursor: pointer; + padding: 4px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: color 0.2s, background 0.2s; +} + +.brainstorm-icon-btn:hover { + color: var(--accent-primary); + background: var(--accent-glow); +} + +/* ---- Brainstorm modal (full-window variant) ---- */ +.brainstorm-modal .modal-content { + max-width: none; + width: calc(100% - 80px); + height: calc(100% - 80px); + display: flex; + flex-direction: column; +} + +.brainstorm-modal .modal-header { + display: flex; + justify-content: space-between; + align-items: center; + flex-shrink: 0; +} + +.brainstorm-close-btn { + background: none; + border: none; + color: white; + font-size: 24px; + cursor: pointer; + padding: 0 4px; + line-height: 1; + opacity: 0.7; + transition: opacity 0.2s; +} + +.brainstorm-close-btn:hover { opacity: 1; } + +.brainstorm-messages { + flex: 1; + overflow-y: auto; + scrollbar-gutter: stable; + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.brainstorm-message { + padding: 12px 16px; + border-radius: 6px; + font-size: 14px; + line-height: 1.5; + max-width: 85%; + word-wrap: break-word; +} + +.brainstorm-user { + background: var(--accent-primary); + color: white; + align-self: flex-end; +} + +.brainstorm-assistant { + background: var(--bg-tertiary); + color: var(--text-primary); + border: 1px solid var(--border-color); + align-self: flex-start; +} + +.brainstorm-input-row { + display: flex; + gap: 8px; + padding: 16px 20px; + border-top: 1px solid var(--border-color); + flex-shrink: 0; +} + +.brainstorm-input { + flex: 1; + padding: 10px 14px; + border: 1px solid var(--border-color); + border-radius: 4px; + background: var(--bg-tertiary); + color: var(--text-primary); + font-size: 14px; + outline: none; + transition: border-color 0.2s; +} + +.brainstorm-input:focus { outline: none; box-shadow: inset 0 -2px 0 var(--accent-primary); } +.brainstorm-input::placeholder { color: var(--text-disabled); } + +.brainstorm-send-btn { + padding: 10px 20px; + border: none; + border-radius: 4px; + background: var(--accent-primary); + color: white; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; +} + +.brainstorm-send-btn:hover { + background: var(--accent-secondary); +} + +.brainstorm-send-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.brainstorm-assistant p { margin: 4px 0; } +.brainstorm-assistant pre { background: var(--bg-primary); padding: 10px; border-radius: 4px; overflow-x: auto; margin: 8px 0; } +.brainstorm-assistant code { font-size: 13px; } +.brainstorm-assistant ul, .brainstorm-assistant ol { margin: 4px 0; padding-left: 20px; } + +.brainstorm-build-row { + display: flex; + justify-content: flex-end; + margin-top: 10px; +} + +.brainstorm-build-btn { + padding: 6px 16px; + font-size: 12px; + font-weight: 600; + border: none; + border-radius: 4px; + background: var(--accent-primary); + color: white; + cursor: pointer; + transition: background 0.2s; +} + +.brainstorm-build-btn:hover { + background: var(--accent-secondary); +} + +.brainstorm-suggestions { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 10px; +} + +.brainstorm-suggestion-chip { + padding: 5px 12px; + font-size: 12px; + border: 1px solid var(--border-color); + border-radius: 4px; + background: var(--bg-primary); + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s; + line-height: 1.4; +} + +.brainstorm-suggestion-chip:hover { + border-color: var(--accent-primary); + color: var(--accent-primary); + background: var(--accent-glow); +} + +.brainstorm-suggestion-chip:disabled { + opacity: 0.4; + cursor: default; + pointer-events: none; +} + +.brainstorm-thinking { + align-self: flex-start; + padding: 12px 16px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + color: var(--text-secondary); + font-size: 14px; + font-style: italic; +} + +/* ---- Save Line ---- */ +.save-line { + display: flex; + align-items: center; + gap: 10px; + margin: 8px 0 15px; + font-size: 11px; + color: var(--text-secondary); + opacity: 0.7; +} + +.save-line::before, +.save-line::after { + content: ''; + flex: 1; + height: 1px; + background: var(--border-color); +} + +.save-line-label { + white-space: nowrap; + letter-spacing: 0.5px; +} + +/* ---- Shared page rules ---- */ +#loadingOverlay { position: absolute; } +.chat-submit:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } +.chat-input:disabled { opacity: 0.5; cursor: not-allowed; } + + +/* ================================================================ + TEAMS UI KIT — COMPONENT STYLES + ================================================================ */ + +/* ===== Hyperlink ===== */ +.mt-link{ color:var(--accent-primary); text-decoration:none; cursor:pointer; font:inherit; } +.mt-link:hover{ color:var(--accent-secondary); text-decoration:underline; } +.mt-link:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; border-radius:2px; } +.mt-link[aria-disabled="true"]{ color:var(--text-disabled); pointer-events:none; cursor:default; text-decoration:none; } +.mt-link--truncate{ display:inline-block; max-width:100%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } + +/* ===== Button ===== */ +.mt-btn{ + display:inline-flex; align-items:center; justify-content:center; gap:6px; + border-radius:4px; border:1px solid var(--btn-border); + background:var(--btn-bg); color:var(--btn-fg); + font:600 var(--type-sm); + height:32px; padding:0 12px; + cursor:pointer; user-select:none; +} +.mt-btn[data-size="sm"]{ height:24px; padding:0 8px; font:600 var(--type-xs); } +.mt-btn:hover:not(:disabled){ background:var(--btn-hover-bg); } +.mt-btn:active:not(:disabled){ background:var(--btn-pressed-bg); } +.mt-btn:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-btn:disabled{ background:var(--bg-tertiary); color:var(--text-disabled); border-color:var(--bg-tertiary); cursor:not-allowed; } +.mt-btn[data-icon-only]{ width:32px; padding:0; } +.mt-btn[data-icon-only][data-size="sm"]{ width:24px; } +.mt-btn-group{ display:inline-flex; } +.mt-btn--split{ width:32px; padding:0; } + +.mt-btn[data-style="accent"]{ + --btn-bg:var(--accent-primary); --btn-fg:var(--accent-contrast); --btn-border:var(--accent-primary); + --btn-hover-bg:var(--accent-secondary); --btn-pressed-bg:var(--accent-tertiary); +} +.mt-btn[data-style="neutral"]{ + --btn-bg:var(--bg-secondary); --btn-fg:var(--text-primary); --btn-border:var(--border-color); + --btn-hover-bg:var(--bg-tertiary); --btn-pressed-bg:var(--bg-tertiary); +} +.mt-btn[data-style="outline"]{ + --btn-bg:transparent; --btn-fg:var(--text-primary); --btn-border:var(--border-strong); + --btn-hover-bg:var(--bg-tertiary); --btn-pressed-bg:var(--bg-tertiary); +} +.mt-btn[data-style="ghost"]{ + --btn-bg:transparent; --btn-fg:var(--text-primary); --btn-border:transparent; + --btn-hover-bg:var(--bg-tertiary); --btn-pressed-bg:var(--bg-tertiary); +} + +/* ===== Alert ===== */ +.mt-alert{ + display:flex; align-items:center; gap:10px; + min-height:32px; padding:6px 10px; + border:1px solid var(--alert-border); + border-radius:4px; + background:var(--alert-bg); + color:var(--alert-fg); +} +.mt-alert__icon{ width:14px; height:14px; border-radius:999px; background:var(--alert-icon); } +.mt-alert__message{ font:var(--type-xs); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; } +.mt-alert__action{ + height:22px; padding:0 10px; border-radius:3px; + border:1px solid var(--border-color); background:var(--bg-secondary); color:var(--text-primary); + font:600 11px/20px var(--font); +} +.mt-alert__dismiss{ + width:22px; height:22px; display:grid; place-items:center; + border:0; background:transparent; color:var(--text-secondary); border-radius:3px; +} +.mt-alert__action:focus-visible,.mt-alert__dismiss:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-alert[data-variant="error"] { --alert-bg:var(--alert-error-bg); --alert-border:var(--alert-error-border); --alert-fg:var(--alert-error-fg); --alert-icon:var(--alert-error-icon); } +.mt-alert[data-variant="warning"]{ --alert-bg:var(--alert-warn-bg); --alert-border:var(--alert-warn-border); --alert-fg:var(--alert-warn-fg); --alert-icon:var(--alert-warn-icon); } +.mt-alert[data-variant="success"]{ --alert-bg:var(--alert-ok-bg); --alert-border:var(--alert-ok-border); --alert-fg:var(--alert-ok-fg); --alert-icon:var(--alert-ok-icon); } +.mt-alert[data-variant="info"] { --alert-bg:var(--alert-info-bg); --alert-border:var(--alert-info-border); --alert-fg:var(--alert-info-fg); --alert-icon:var(--alert-info-icon); } + +/* ===== Breadcrumb ===== */ +.mt-breadcrumb{ font:var(--type-sm); } +.mt-breadcrumb__list{ display:flex; align-items:center; gap:6px; list-style:none; margin:0; padding:0; } +.mt-breadcrumb__sep{ color:var(--text-disabled); user-select:none; } +.mt-breadcrumb__current{ color:var(--text-primary); font-weight:600; } + +/* ===== Card ===== */ +.mt-card{ + position:relative; display:flex; flex-direction:column; overflow:hidden; + border-radius:6px; border:1px solid var(--border-color); + background:var(--bg-primary); color:var(--text-primary); + box-shadow:0 2px 4px #1D1D1D; transition:background 0.2s, box-shadow 0.2s; +} +.mt-card:hover{ background:#3D3D3D; box-shadow:0 4px 8px #1D1D1D; } +.mt-card:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-card[data-size="xs"]{ width:160px; } .mt-card[data-size="sm"]{ width:220px; } +.mt-card[data-size="md"]{ width:280px; } .mt-card[data-size="lg"]{ width:360px; } +.mt-card__media{ aspect-ratio:16/9; background:var(--bg-tertiary); } +.mt-card__media img{ width:100%; height:100%; object-fit:cover; display:block; } +.mt-card__body{ padding:10px 12px; } +.mt-card__header{ display:flex; gap:8px; align-items:flex-start; } +.mt-card__title{ font:600 var(--type-sm); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } +.mt-card__subtitle{ font:var(--type-xs); color:var(--text-secondary); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } +.mt-card__menu{ width:28px; height:28px; border:0; background:transparent; color:var(--text-secondary); border-radius:4px; cursor:pointer; } +.mt-card__menu:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-card__menu:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-card[data-selected="true"]{ border-color:var(--accent-primary); } +.mt-card[data-selected="true"]::after{ + content:"✓"; position:absolute; top:8px; right:8px; + width:18px; height:18px; border-radius:999px; display:grid; place-items:center; + background:var(--accent-primary); color:var(--accent-contrast); font:700 12px/1 var(--font); +} +.mt-card[data-disabled="true"]{ background:var(--bg-tertiary); color:var(--text-disabled); box-shadow:none; opacity:.7; pointer-events:none; } + +/* ===== Carousel ===== */ +.mt-carousel__viewport{ position:relative; overflow:hidden; display:flex; justify-content:center; align-items:center; } +.mt-carousel__track{ display:flex; transition:transform 240ms ease; } +.mt-carousel__slide{ min-width:100%; display:flex; justify-content:center; padding:20px 0; } +.mt-carousel__content{ + width:70%; max-width:600px; aspect-ratio:16/9; + border-radius:6px; border:1px solid var(--border-color); + background:var(--bg-secondary); box-shadow:var(--shadow-sm); + display:grid; place-items:center; +} +.mt-carousel__nav{ + position:absolute; top:50%; transform:translateY(-50%); + width:36px; height:36px; border-radius:999px; border:0; + background:transparent; color:var(--text-secondary); cursor:pointer; +} +.mt-carousel__nav--prev{ left:12px; } .mt-carousel__nav--next{ right:12px; } +.mt-carousel__nav:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-carousel__nav:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-carousel__dots{ display:flex; justify-content:center; gap:6px; margin-top:8px; } +.mt-carousel__dot{ width:6px; height:6px; border-radius:999px; border:0; background:var(--text-disabled); cursor:pointer; } +.mt-carousel__dot[aria-selected="true"]{ background:var(--accent-primary); transform:scale(1.2); } +.mt-carousel__dot:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +/* ===== Checkbox ===== */ +.mt-checkbox{ display:inline-flex; align-items:center; gap:8px; cursor:pointer; font:var(--type-sm); color:var(--text-primary); } +.mt-checkbox__input{ position:absolute; opacity:0; pointer-events:none; } +.mt-checkbox__control{ + width:16px; height:16px; display:grid; place-items:center; + border-radius:3px; border:1px solid var(--border-strong); background:transparent; +} +.mt-checkbox__icon{ width:12px; height:12px; color:var(--accent-contrast); opacity:0; transform:scale(.85); transition:120ms ease; } +.mt-checkbox:hover .mt-checkbox__control{ border-color:var(--text-primary); } +.mt-checkbox__input:checked + .mt-checkbox__control{ background:var(--accent-primary); border-color:var(--accent-primary); } +.mt-checkbox__input:checked + .mt-checkbox__control .mt-checkbox__icon{ opacity:1; transform:scale(1); } +.mt-checkbox__input:focus-visible + .mt-checkbox__control{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-checkbox__input:disabled + .mt-checkbox__control{ background:var(--bg-tertiary); border-color:var(--border-color); } +.mt-checkbox__input:disabled ~ .mt-checkbox__label{ color:var(--text-disabled); cursor:not-allowed; } + +/* ===== Menu ===== */ +.mt-menu{ + min-width:180px; max-width:280px; padding:4px; + border-radius:6px; border:1px solid var(--menu-border); + background:var(--menu-bg); box-shadow:var(--menu-shadow); +} +.mt-menu__item{ + width:100%; height:32px; + display:grid; grid-template-columns:20px 1fr auto auto; align-items:center; gap:8px; + padding:0 8px; border:0; border-radius:4px; background:transparent; + color:var(--text-primary); font:var(--type-sm); text-align:left; cursor:pointer; +} +.mt-menu__icon{ color:var(--text-secondary); } +.mt-menu__shortcut,.mt-menu__submenu{ color:var(--text-secondary); font:var(--type-xs); } +.mt-menu__item:hover{ background:var(--bg-tertiary); } +.mt-menu__item:active{ background:var(--bg-tertiary); } +.mt-menu__item:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-menu__divider{ height:1px; background:var(--border-color); margin:4px 6px; } +.mt-menu__section{ padding:6px 10px 4px; font:700 11px/14px var(--font); color:var(--text-secondary); } +.mt-menu__item[aria-disabled="true"]{ color:var(--text-disabled); cursor:not-allowed; } +.mt-menu__item[aria-disabled="true"]:hover{ background:transparent; } + +/* ===== Dialog ===== */ +.mt-dialog{ position:fixed; inset:0; z-index:1100; } +.mt-dialog__scrim{ position:absolute; inset:0; background:var(--scrim); } +.mt-dialog__panel{ + position:relative; margin:10vh auto 0; + border-radius:8px; border:1px solid var(--border-color); + background:var(--bg-secondary); box-shadow:0 20px 50px rgba(0,0,0,.5); + display:flex; flex-direction:column; +} +.mt-dialog__panel[data-size="sm"]{ width:480px; } +.mt-dialog__panel[data-size="md"]{ width:600px; } +.mt-dialog__panel[data-size="lg"]{ width:680px; } +@media (max-width:720px){ .mt-dialog__panel{ width:calc(100% - 32px); } } +.mt-dialog__header{ padding:20px 24px 0; } +.mt-dialog__title{ font:700 var(--type-lg); margin:0; color:var(--text-primary); } +.mt-dialog__body{ padding:16px 24px; font:var(--type-sm); color:var(--text-primary); } +.mt-dialog__error{ padding:0 24px 8px; font:var(--type-xs); color:var(--danger); } +.mt-dialog__footer{ padding:16px 24px 20px; display:flex; justify-content:flex-end; gap:8px; } + +/* ===== Dropdown / Field ===== */ +.mt-field{ display:flex; flex-direction:column; gap:4px; } +.mt-field__label{ font:700 var(--type-xs); color:var(--text-secondary); } +.mt-field__error{ font:var(--type-xs); color:var(--danger); } +.mt-field__hint{ font:var(--type-xs); color:var(--text-secondary); } + +.mt-dropdown{ + height:32px; padding:0 12px; display:flex; align-items:center; justify-content:space-between; gap:8px; + border-radius:4px; border:1px solid var(--border-color); + background:var(--bg-secondary); color:var(--text-primary); + font:var(--type-sm); cursor:pointer; +} +.mt-dropdown:hover{ border-color:var(--text-primary); } +.mt-dropdown:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-dropdown[aria-expanded="true"]{ border-color:var(--accent-primary); box-shadow:inset 0 -2px 0 var(--accent-primary); } +.mt-field[data-state="error"] .mt-dropdown{ border-color:var(--danger); } + +/* ===== Group list ===== */ +.mt-group__header{ + width:100%; display:flex; align-items:center; gap:6px; + padding:6px 8px; border:0; background:transparent; + font:700 var(--type-sm); color:var(--text-primary); + cursor:pointer; text-align:left; +} +.mt-group__header:hover:not(:disabled){ background:var(--bg-tertiary); } +.mt-group__header:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-group__chevron{ color:var(--text-secondary); transition:transform 120ms ease; } +.mt-group__header[aria-expanded="true"] .mt-group__chevron{ transform:rotate(90deg); } +.mt-group__header:disabled{ color:var(--text-disabled); cursor:not-allowed; } +.mt-group__header:disabled .mt-group__chevron{ color:var(--text-disabled); } +.mt-group__content{ padding-left:20px; } + +/* ===== Coachmark ===== */ +.mt-coachmark{ position:fixed; inset:0; z-index:1000; } +.mt-coachmark__scrim{ position:absolute; inset:0; background:var(--scrim); } +.mt-coachmark__panel{ + position:absolute; + min-width:260px; max-width:420px; + border-radius:8px; border:1px solid var(--border-color); + background:var(--bg-secondary); box-shadow:var(--shadow-lg); + padding:16px; +} +.mt-coachmark__title{ font:700 var(--type-md); margin:0 0 8px; color:var(--text-primary); } +.mt-coachmark__body{ font:var(--type-sm); color:var(--text-secondary); } +.mt-coachmark__footer{ display:flex; justify-content:flex-end; gap:8px; margin-top:12px; } +.mt-coachmark__close{ + position:absolute; top:8px; right:8px; + width:28px; height:28px; border:0; background:transparent; + color:var(--text-secondary); border-radius:4px; cursor:pointer; +} +.mt-coachmark__close:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-coachmark__close:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +/* ===== Native select ===== */ +select{ cursor:pointer; appearance:none; background-repeat:no-repeat; background-position:right 12px center; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23E0E0E0' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); } +select option{ background:#292929; color:var(--text-primary); padding:8px; } +select option:checked{ background:#3D3D3D; } + +/* ===== Input ===== */ +.mt-input{ + height:32px; padding:0 12px; + display:flex; align-items:center; gap:8px; + border-radius:4px; + border:1px solid var(--input-border); + background:var(--input-bg); + color:var(--input-fg); +} +.mt-input[data-size="sm"]{ height:24px; padding:0 8px; } +.mt-input:hover{ border-color:var(--input-border-hover); } +.mt-input__control{ + width:100%; border:0; outline:none; background:transparent; + color:inherit; font:var(--type-sm); min-width:0; +} +.mt-input[data-size="sm"] .mt-input__control{ font:var(--type-xs); } +.mt-input__control::placeholder{ color:var(--input-placeholder); } +.mt-input:focus-within{ + box-shadow: inset 0 -2px 0 var(--input-border-focus); +} +.mt-input__control:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; border-radius:2px; } +.mt-input[data-disabled="true"]{ background:var(--bg-tertiary); border-color:var(--border-color); color:var(--text-disabled); } +.mt-input__control:disabled{ cursor:not-allowed; } +.mt-input__prefix{ display:inline-flex; align-items:center; justify-content:center; flex:0 0 auto; } +.mt-input__prefix--chip{ + height:20px; padding:0 6px; border-radius:3px; + background:var(--input-prefix-bg); color:var(--input-prefix-fg); + font:600 11px/20px var(--font); +} +.mt-input__prefix--icon{ width:16px; height:16px; border-radius:2px; background:var(--input-icon-bg); } +.mt-input__end{ + width:22px; height:22px; display:grid; place-items:center; + border:0; border-radius:3px; + background:transparent; color:var(--text-secondary); + cursor:pointer; +} +.mt-input__end:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-input__end:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-field[data-state="error"] .mt-input{ + border-color:var(--danger); + box-shadow: inset 0 -2px 0 var(--danger); +} + +/* ===== Key value pair ===== */ +.mt-kvp{ display:flex; flex-direction:column; gap:18px; } +.mt-kvp__row{ display:grid; grid-template-columns:160px 1fr; column-gap:28px; align-items:start; } +.mt-kvp__key{ + font:700 var(--type-xs); color:var(--text-secondary); + text-transform:uppercase; letter-spacing:.02em; +} +.mt-kvp__value{ display:flex; flex-direction:column; gap:6px; } +.mt-kvp__text-title{ font:600 var(--type-xs); color:var(--text-primary); } +.mt-kvp__text-sub{ font:var(--type-xs); color:var(--text-secondary); } + +/* ===== Paragraph ===== */ +.mt-paragraph{ max-width:520px; color:var(--text-primary); } +.mt-paragraph__headline{ margin:0 0 10px; font:700 var(--type-lg); color:var(--text-primary); } +.mt-paragraph__desc{ margin:0 0 18px; font:var(--type-xs); color:var(--text-secondary); } +.mt-paragraph__subhead{ margin:0 0 8px; font:700 var(--type-sm); color:var(--text-primary); } +.mt-paragraph__body{ margin:0 0 14px; font:var(--type-xs); color:var(--text-secondary); } +.mt-paragraph__meta{ margin:0; font:var(--type-xs); color:var(--text-secondary); } + +/* ===== Picker ===== */ +.mt-picker{ position:relative; } +.mt-picker__panel{ + position:absolute; top:calc(100% + 4px); left:0; width:100%; + max-height:280px; overflow:auto; + border-radius:6px; border:1px solid var(--picker-border); + background:var(--picker-bg); box-shadow:var(--picker-shadow); + padding:4px; display:none; z-index:100; +} +.mt-picker[data-open="true"] .mt-picker__panel{ display:block; } +.mt-picker__option{ + width:100%; height:40px; + display:flex; align-items:center; gap:10px; + padding:0 10px; border:0; border-radius:4px; + background:transparent; color:var(--text-primary); + font:var(--type-sm); text-align:left; cursor:pointer; +} +.mt-picker__option:hover{ background:var(--bg-tertiary); } +.mt-picker__option[data-selected="true"]{ background:var(--bg-tertiary); } +.mt-picker__option-label{ font-weight:600; } +.mt-picker__option-meta{ font:var(--type-xs); color:var(--text-secondary); } + +.mt-avatar{ + width:24px; height:24px; border-radius:999px; + background:var(--accent-primary); color:var(--accent-contrast); + font:600 11px/24px var(--font); text-align:center; +} +.mt-picker__file-icon{ width:20px; height:20px; border-radius:3px; background:var(--accent-primary); } + +/* Date */ +.mt-date__header{ display:flex; justify-content:space-between; align-items:center; padding:6px 8px; font:600 var(--type-sm); } +.mt-date__nav{ + width:28px; height:28px; border:0; border-radius:4px; + background:transparent; color:var(--text-secondary); cursor:pointer; +} +.mt-date__nav:hover{ background:var(--bg-tertiary); } +.mt-date__grid{ display:grid; grid-template-columns:repeat(7,1fr); gap:4px; padding:8px; } +.mt-date__cell{ + height:32px; border:0; border-radius:4px; + background:transparent; color:var(--text-primary); + font:var(--type-xs); cursor:pointer; +} +.mt-date__cell:hover{ background:var(--bg-tertiary); } +.mt-date__cell[data-selected="true"]{ background:var(--accent-primary); color:var(--accent-contrast); } + +/* Time */ +.mt-time__row{ display:flex; gap:8px; padding:8px; } +.mt-time__input{ + flex:1; height:32px; border-radius:4px; + border:1px solid var(--border-color); background:var(--bg-secondary); + color:var(--text-primary); padding:0 8px; font:var(--type-sm); +} +.mt-time__input:focus{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +/* ===== Pivot ===== */ +.mt-pivot__list{ display:flex; align-items:flex-end; gap:24px; } +.mt-pivot__tab{ + position:relative; height:32px; padding:0; + border:0; background:transparent; cursor:pointer; + font:600 var(--type-sm); + color:var(--pivot-fg); +} +.mt-pivot__tab::after{ + content:""; position:absolute; left:0; right:0; bottom:-2px; + height:2px; border-radius:2px; background:transparent; transition:120ms ease; +} +.mt-pivot__tab:hover::after{ background:var(--pivot-underline-hover); } +.mt-pivot__tab[aria-selected="true"]{ color:var(--pivot-active-fg); } +.mt-pivot__tab[aria-selected="true"]::after{ background:var(--pivot-underline-active); } +.mt-pivot__tab:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:4px; border-radius:4px; } + +/* ===== Progress ===== */ +.mt-progress{ display:flex; align-items:center; gap:14px; color:var(--text-primary); } +.mt-progress__spinner{ + width:16px; height:16px; border-radius:999px; + border:2px solid var(--progress-spinner-track); + border-top-color: var(--progress-spinner-head); + box-sizing:border-box; + animation: mt-spin 900ms linear infinite; +} +@keyframes mt-spin{ to{ transform:rotate(360deg); } } + +.mt-progress[data-variant="determinate"]{ align-items:flex-start; gap:16px; } +.mt-progress__text{ min-width:120px; } +.mt-progress__label{ font:600 var(--type-xs); color:var(--text-primary); margin:0 0 2px; } +.mt-progress__desc{ font:var(--type-xs); color:var(--text-secondary); margin:0; } +.mt-progress__bar{ + flex:1; min-width:220px; height:2px; border-radius:999px; + background:var(--progress-track); overflow:hidden; margin-top:8px; +} +.mt-progress__fill{ height:100%; background:var(--progress-fill); border-radius:999px; transition:width 200ms ease; } +@media (prefers-reduced-motion: reduce){ + .mt-progress__spinner{ animation:none; } + .mt-progress__fill{ transition:none; } +} + +/* ===== Radio ===== */ +.mt-radio-group{ display:flex; flex-direction:column; gap:8px; } +.mt-radio{ + display:inline-flex; align-items:center; gap:8px; + cursor:pointer; font:var(--type-sm); color:var(--text-primary); position:relative; +} +.mt-radio__input{ position:absolute; opacity:0; pointer-events:none; } +.mt-radio__control{ + width:16px; height:16px; border-radius:999px; + border:1px solid var(--radio-border); background:transparent; + position:relative; transition:120ms ease; +} +.mt-radio__control::after{ + content:""; width:8px; height:8px; border-radius:999px; + background:var(--radio-dot); + position:absolute; top:50%; left:50%; + transform:translate(-50%,-50%) scale(.6); + opacity:0; transition:120ms ease; +} +.mt-radio:hover .mt-radio__control{ border-color:var(--radio-border-hover); } +.mt-radio__input:checked + .mt-radio__control{ border-color:var(--accent-primary); } +.mt-radio__input:checked + .mt-radio__control::after{ opacity:1; transform:translate(-50%,-50%) scale(1); } +.mt-radio__input:focus-visible + .mt-radio__control{ outline:2px solid var(--focus-ring); outline-offset:3px; } +.mt-radio__input:disabled + .mt-radio__control{ border-color:var(--radio-disabled-border); background:var(--radio-disabled-bg); } +.mt-radio__input:disabled + .mt-radio__control::after{ background:var(--radio-disabled-dot); } +.mt-radio__input:disabled ~ .mt-radio__label{ color:var(--text-disabled); cursor:not-allowed; } + +/* ===== Scroll region ===== */ +.mt-scroll{ + max-height:120px; overflow:auto; + padding:10px 12px; + border:1px solid var(--border-color); + border-radius:4px; + background:var(--bg-secondary); + color:var(--text-primary); + font:var(--type-xs); + box-shadow: inset -10px 0 10px -12px var(--scroll-shadow); + scrollbar-width: thin; + scrollbar-color: var(--scroll-thumb) var(--scroll-track); +} +.mt-scroll::-webkit-scrollbar{ width:10px; height:10px; } +.mt-scroll::-webkit-scrollbar-track{ background:var(--scroll-track); border-radius:999px; } +.mt-scroll::-webkit-scrollbar-thumb{ + background:var(--scroll-thumb); border-radius:999px; + border:3px solid var(--scroll-track); +} +.mt-scroll::-webkit-scrollbar-thumb:hover{ background:var(--scroll-thumb-hover); } +.mt-scroll::-webkit-scrollbar-thumb:active{ background:var(--scroll-thumb-active); } +.mt-scroll::-webkit-scrollbar-corner{ background:transparent; } + +/* ===== Search box ===== */ +.mt-search__box{ + height:32px; padding:0 10px; + display:flex; align-items:center; gap:8px; + border-radius:4px; + border:1px solid transparent; + background:var(--search-bg); + color:var(--text-primary); +} +.mt-search__icon{ width:16px; height:16px; border-radius:2px; background:var(--search-icon); flex:0 0 auto; } +.mt-search__input{ + width:100%; border:0; outline:none; background:transparent; + color:inherit; font:var(--type-sm); min-width:0; +} +.mt-search__input::placeholder{ color:var(--search-placeholder); } +.mt-search__box:focus-within{ box-shadow: inset 0 -2px 0 var(--search-underline); } +.mt-search__input:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; border-radius:2px; } +.mt-search__clear{ + width:22px; height:22px; display:grid; place-items:center; + border:0; border-radius:3px; + background:transparent; color:var(--text-secondary); + cursor:pointer; flex:0 0 auto; +} +.mt-search__clear:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-search__clear:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +/* ===== Side panel ===== */ +.mt-sidepanel{ position:fixed; inset:0; z-index:1200; pointer-events:none; } +.mt-sidepanel__scrim{ position:absolute; inset:0; background:var(--scrim); opacity:0; transition:opacity 180ms ease; } +.mt-sidepanel__panel{ + position:absolute; top:0; right:0; height:100%; + display:flex; flex-direction:column; + background:var(--bg-secondary); + border-left:1px solid var(--border-color); + box-shadow:var(--shadow-lg); + transform:translateX(100%); + transition:transform 220ms ease; + pointer-events:auto; +} +.mt-sidepanel__panel[data-size="sm"]{ width:320px; } +.mt-sidepanel__panel[data-size="md"]{ width:400px; } +.mt-sidepanel__panel[data-size="lg"]{ width:480px; } +.mt-sidepanel[data-open="true"]{ pointer-events:auto; } +.mt-sidepanel[data-open="true"] .mt-sidepanel__scrim{ opacity:1; } +.mt-sidepanel[data-open="true"] .mt-sidepanel__panel{ transform:translateX(0); } + +.mt-sidepanel__header{ + height:56px; padding:0 16px; + display:flex; align-items:center; justify-content:space-between; + border-bottom:1px solid var(--border-color); +} +.mt-sidepanel__title{ margin:0; font:700 var(--type-md); color:var(--text-primary); } +.mt-sidepanel__close{ + width:32px; height:32px; border:0; border-radius:4px; + background:transparent; color:var(--text-secondary); cursor:pointer; +} +.mt-sidepanel__close:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-sidepanel__close:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-sidepanel__body{ flex:1; overflow:auto; padding:16px; color:var(--text-primary); } +.mt-sidepanel__footer{ + padding:12px 16px; border-top:1px solid var(--border-color); + display:flex; justify-content:flex-end; gap:8px; + background:var(--bg-secondary); +} + +/* ===== Status label ===== */ +.mt-status{ display:inline-flex; align-items:center; gap:6px; font:600 var(--type-xs); color:var(--status-color); } +.mt-status__icon{ + width:14px; height:14px; display:grid; place-items:center; + font:700 10px/1 var(--font); color:currentColor; +} +.mt-status[data-variant="critical"]{ + height:18px; padding:0 8px; border-radius:3px; + background:var(--status-critical-bg); color:var(--status-critical-fg); +} +.mt-status[data-variant="critical"] .mt-status__icon{ color:var(--status-critical-fg); } +.mt-status[data-variant="error"]{ --status-color: var(--status-error); } +.mt-status[data-variant="warning"]{ --status-color: var(--status-warning); } +.mt-status[data-variant="success"]{ --status-color: var(--status-success); } +.mt-status[data-variant="info"]{ --status-color: var(--status-info); } + +/* ===== Toast ===== */ +.mt-toast-stack{ + position:fixed; bottom:16px; right:16px; + display:flex; flex-direction:column; gap:12px; + z-index:1300; +} +.mt-toast{ + width:320px; border-radius:6px; + background:var(--toast-bg); color:var(--text-primary); + border:1px solid var(--toast-border); + box-shadow:var(--shadow-lg); + display:flex; flex-direction:column; overflow:hidden; + animation: mt-toast-in 180ms ease; +} +@keyframes mt-toast-in{ from{ transform:translateY(8px); opacity:0; } to{ transform:translateY(0); opacity:1; } } +.mt-toast__header{ display:flex; align-items:flex-start; gap:10px; padding:12px 12px 8px; } +.mt-toast__avatar{ width:28px; height:28px; border-radius:999px; background:var(--accent-primary); flex:0 0 auto; } +.mt-toast__meta{ flex:1; min-width:0; } +.mt-toast__title{ font:600 var(--type-sm); color:var(--text-primary); } +.mt-toast__subtitle{ font:var(--type-xs); color:var(--text-secondary); } +.mt-toast__close{ + width:28px; height:28px; border:0; background:transparent; + border-radius:4px; color:var(--text-secondary); cursor:pointer; +} +.mt-toast__close:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-toast__close:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-toast__body{ padding:0 12px 10px; } +.mt-toast__message{ font:var(--type-xs); color:var(--text-secondary); } +.mt-toast__reply{ display:flex; align-items:center; gap:6px; padding:8px 10px 10px; border-top:1px solid var(--border-color); } +.mt-toast__input{ + flex:1; height:28px; border-radius:4px; + border:1px solid var(--border-color); background:var(--bg-secondary); + color:var(--text-primary); padding:0 8px; font:var(--type-xs); +} +.mt-toast__input:focus{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-toast__send{ + width:28px; height:28px; border:0; border-radius:4px; + background:var(--accent-primary); color:var(--accent-contrast); cursor:pointer; +} +.mt-toast__send:hover{ background:var(--accent-secondary); } +.mt-toast[data-variant="accent"]{ background:var(--accent-primary); color:var(--accent-contrast); border-color:var(--accent-primary); } +.mt-toast[data-variant="accent"] .mt-toast__subtitle, +.mt-toast[data-variant="accent"] .mt-toast__message{ color:rgba(255,255,255,.9); } +.mt-toast[data-variant="accent"] .mt-toast__close{ color:rgba(255,255,255,.8); } + +/* ===== Toggle ===== */ +.mt-toggle{ display:inline-flex; align-items:center; gap:8px; cursor:pointer; user-select:none; color:var(--text-primary); } +.mt-toggle__input{ position:absolute; opacity:0; pointer-events:none; } +.mt-toggle__track{ + width:28px; height:16px; border-radius:999px; + background:var(--toggle-track-off); + border:1px solid var(--toggle-border-off); + position:relative; transition:120ms ease; box-sizing:border-box; +} +.mt-toggle__thumb{ + width:12px; height:12px; border-radius:999px; + background:var(--toggle-thumb); + position:absolute; top:1px; left:1px; + transition:120ms ease; +} +.mt-toggle__input:checked + .mt-toggle__track{ + background:var(--toggle-track-on); + border-color:var(--toggle-border-on); +} +.mt-toggle__input:checked + .mt-toggle__track .mt-toggle__thumb{ transform:translateX(12px); } + +.mt-toggle:hover .mt-toggle__track{ background:var(--toggle-track-off-hover); border-color:var(--toggle-border-off-hover); } +.mt-toggle:hover .mt-toggle__input:checked + .mt-toggle__track{ background:var(--toggle-track-on-hover); border-color:var(--toggle-border-on-hover); } +.mt-toggle:active .mt-toggle__track{ background:var(--toggle-track-off-pressed); border-color:var(--toggle-border-off-pressed); } +.mt-toggle:active .mt-toggle__input:checked + .mt-toggle__track{ background:var(--toggle-track-on-pressed); border-color:var(--toggle-border-on-pressed); } +.mt-toggle__input:focus-visible + .mt-toggle__track{ outline:2px solid var(--focus-ring); outline-offset:3px; } +.mt-toggle__input:disabled + .mt-toggle__track{ background:var(--toggle-track-disabled); border-color:var(--toggle-border-disabled); } +.mt-toggle__input:disabled + .mt-toggle__track .mt-toggle__thumb{ background:var(--toggle-thumb-disabled); } +.mt-toggle__input:disabled ~ .mt-toggle__label{ color:var(--text-disabled); } + +/* ===== Tooltip ===== */ +.mt-tooltip{ + position:absolute; z-index:1400; + max-width:220px; + padding:6px 8px; + border-radius:4px; + font:var(--type-xs); + white-space:nowrap; + background:var(--tooltip-bg); + color:var(--tooltip-fg); + box-shadow:var(--tooltip-shadow); + opacity:0; + transform:translateY(2px); + pointer-events:none; + transition:opacity 120ms ease, transform 120ms ease; +} +.mt-tooltip[data-open="true"]{ opacity:1; transform:translateY(0); } +.mt-tooltip::after{ + content:""; + position:absolute; + width:8px; height:8px; + background:var(--tooltip-bg); + transform:rotate(45deg); +} +.mt-tooltip[data-placement="top"]::after{ bottom:-4px; left:50%; transform:translateX(-50%) rotate(45deg); } +.mt-tooltip[data-placement="bottom"]::after{ top:-4px; left:50%; transform:translateX(-50%) rotate(45deg); } +.mt-tooltip[data-placement="left"]::after{ right:-4px; top:50%; transform:translateY(-50%) rotate(45deg); } +.mt-tooltip[data-placement="right"]::after{ left:-4px; top:50%; transform:translateY(-50%) rotate(45deg); } diff --git a/teams-default-themes/teams-light.json b/teams-default-themes/teams-light.json new file mode 100644 index 0000000..bd43496 --- /dev/null +++ b/teams-default-themes/teams-light.json @@ -0,0 +1,134 @@ +{ + "mode": "light", + "colors": { + "bg-primary": "#ffffff", + "bg-secondary": "#F7F7F7", + "bg-tertiary": "#F3F2F1", + "accent-primary": "#5B5FC7", + "accent-secondary": "#4F52B2", + "accent-tertiary": "#444791", + "accent-glow": "rgba(91,95,199,0.15)", + "text-primary": "#252423", + "text-secondary": "#605E5C", + "border-color": "#D1D1D1", + "header-min-height": "58px", + "header-padding-vertical": "14px", + "header-padding-horizontal": "20px", + "header-line-height": "1.25", + + "surface": "#ffffff", + "surface-2": "#F7F7F7", + "surface-hover": "#F5F5F5", + "surface-pressed": "#EDEBE9", + "surface-disabled": "#F3F2F1", + + "text-disabled": "#A19F9D", + "border-strong": "#C8C6C4", + + "accent-contrast": "#ffffff", + "accent-hover": "#4F52B2", + "accent-pressed": "#444791", + + "danger": "#D13438", + "danger-2": "#D13438", + "focus-ring": "#5B5FC7", + + "shadow-1": "0 1px 2px rgba(0,0,0,0.08)", + "shadow-2": "0 6px 18px rgba(0,0,0,0.18)", + "scrim": "rgba(0,0,0,0.5)", + + "menu-bg": "#ffffff", + "menu-border": "#D1D1D1", + "menu-shadow": "0 6px 18px rgba(0,0,0,0.18)", + + "alert-error-bg": "#FCF4F6", + "alert-error-border": "#F3D6DC", + "alert-error-fg": "#A4262C", + "alert-error-icon": "#A4262C", + "alert-warn-bg": "#FBF6D9", + "alert-warn-border": "#F2E2A5", + "alert-warn-fg": "#8A6A00", + "alert-warn-icon": "#8A6A00", + "alert-ok-bg": "#E7F2DA", + "alert-ok-border": "#CDE6B3", + "alert-ok-fg": "#107C10", + "alert-ok-icon": "#107C10", + "alert-info-bg": "#F5F5F5", + "alert-info-border": "#D1D1D1", + "alert-info-fg": "#252423", + "alert-info-icon": "#605E5C", + + "input-bg": "#F5F5F5", + "input-border": "#D1D1D1", + "input-border-hover": "#C8C6C4", + "input-border-focus": "#5B5FC7", + "input-underline-focus": "#5B5FC7", + "input-placeholder": "#A19F9D", + "input-prefix-bg": "#5B5FC7", + "input-prefix-fg": "#ffffff", + "input-icon-bg": "#605E5C", + + "picker-bg": "#ffffff", + "picker-border": "#D1D1D1", + "picker-shadow": "0 8px 24px rgba(0,0,0,0.18)", + + "pivot-fg": "#605E5C", + "pivot-active-fg": "#5B5FC7", + "pivot-underline-active": "#5B5FC7", + "pivot-underline-hover": "#C8C6C4", + + "progress-track": "#D1D1D1", + "progress-fill": "#5B5FC7", + "progress-spinner-track": "#D1D1D1", + "progress-spinner-head": "#5B5FC7", + + "radio-border": "#C8C6C4", + "radio-border-hover": "#605E5C", + "radio-dot": "#5B5FC7", + "radio-disabled-border": "#D1D1D1", + "radio-disabled-bg": "#F3F2F1", + "radio-disabled-dot": "#C8C6C4", + + "scroll-track": "transparent", + "scroll-thumb": "#C8C6C4", + "scroll-thumb-hover": "#A19F9D", + "scroll-thumb-active": "#8A8886", + "scroll-shadow": "rgba(0,0,0,0.18)", + + "search-bg": "#F3F2F1", + "search-icon": "#605E5C", + "search-placeholder": "#A19F9D", + "search-underline": "#5B5FC7", + + "status-critical-bg": "#C4314B", + "status-critical-fg": "#ffffff", + "status-error": "#C4314B", + "status-warning": "#986F0B", + "status-success": "#107C10", + "status-info": "#605E5C", + + "toast-bg": "#ffffff", + "toast-border": "#D1D1D1", + + "toggle-track-off": "#FFFFFF", + "toggle-border-off": "#8A8886", + "toggle-thumb": "#8A8886", + "toggle-track-off-hover": "#FFFFFF", + "toggle-border-off-hover": "#605E5C", + "toggle-track-off-pressed": "#FFFFFF", + "toggle-border-off-pressed": "#252423", + "toggle-track-on": "#5B5FC7", + "toggle-border-on": "#5B5FC7", + "toggle-track-on-hover": "#4F52B2", + "toggle-border-on-hover": "#4F52B2", + "toggle-track-on-pressed": "#444791", + "toggle-border-on-pressed": "#444791", + "toggle-track-disabled": "#F3F2F1", + "toggle-border-disabled": "#D1D1D1", + "toggle-thumb-disabled": "#C8C6C4", + + "tooltip-bg": "#323130", + "tooltip-fg": "#ffffff", + "tooltip-shadow": "0 6px 16px rgba(0,0,0,0.25)" + } +} diff --git a/teams-default-themes/teams-light.v1.css b/teams-default-themes/teams-light.v1.css new file mode 100644 index 0000000..d6776ab --- /dev/null +++ b/teams-default-themes/teams-light.v1.css @@ -0,0 +1,1423 @@ +:root { + /* ---- Core palette ---- */ + --bg-primary: #ffffff; + --bg-secondary: #F7F7F7; + --bg-tertiary: #F3F2F1; + --accent-primary: #5B5FC7; + --accent-secondary: #4F52B2; + --accent-tertiary: #444791; + --accent-contrast: #ffffff; + --accent-hover: #4F52B2; + --accent-pressed: #444791; + --accent-glow: rgba(91,95,199,0.15); + --text-primary: #252423; + --text-secondary: #605E5C; + --text-disabled: #A19F9D; + --border-color: #D1D1D1; + --border-strong: #C8C6C4; + --surface-hover: #F5F5F5; + --danger: #D13438; + --focus-ring: #5B5FC7; + --shadow-sm: 0 1px 2px rgba(0,0,0,0.08); + --shadow-lg: 0 6px 18px rgba(0,0,0,0.18); + --scrim: rgba(0,0,0,0.5); + + /* ---- Typography ---- */ + --font: "Segoe UI", system-ui, -apple-system, sans-serif; + --type-xs: 12px/16px var(--font); + --type-sm: 13px/20px var(--font); + --type-md: 16px/22px var(--font); + --type-lg: 18px/24px var(--font); + + /* ---- Shell layout ---- */ + --header-min-height: 58px; + --header-padding-vertical: 14px; + --header-padding-horizontal: 20px; + --header-line-height: 1.25; + + /* ---- Menu ---- */ + --menu-bg: #ffffff; + --menu-border: #D1D1D1; + --menu-shadow: 0 6px 18px rgba(0,0,0,0.18); + + /* ---- Alert ---- */ + --alert-error-bg: #FCF4F6; --alert-error-border: #F3D6DC; --alert-error-fg: #A4262C; --alert-error-icon: #A4262C; + --alert-warn-bg: #FBF6D9; --alert-warn-border: #F2E2A5; --alert-warn-fg: #8A6A00; --alert-warn-icon: #8A6A00; + --alert-ok-bg: #E7F2DA; --alert-ok-border: #CDE6B3; --alert-ok-fg: #107C10; --alert-ok-icon: #107C10; + --alert-info-bg: #F5F5F5; --alert-info-border: #D1D1D1; --alert-info-fg: #252423; --alert-info-icon: #605E5C; + + /* ---- Input ---- */ + --input-bg: #F5F5F5; + --input-fg: var(--text-primary); + --input-border: var(--border-color); + --input-border-hover: var(--border-strong); + --input-border-focus: var(--accent-primary); + --input-underline-focus: var(--accent-primary); + --input-placeholder: var(--text-disabled); + --input-prefix-bg: var(--accent-primary); + --input-prefix-fg: var(--accent-contrast); + --input-icon-bg: #605E5C; + + /* ---- Picker ---- */ + --picker-bg: #ffffff; + --picker-border: var(--border-color); + --picker-shadow: 0 8px 24px rgba(0,0,0,0.18); + + /* ---- Pivot ---- */ + --pivot-fg: var(--text-secondary); + --pivot-active-fg: var(--accent-primary); + --pivot-underline-active: var(--accent-primary); + --pivot-underline-hover: #C8C6C4; + + /* ---- Progress ---- */ + --progress-track: #D1D1D1; + --progress-fill: var(--accent-primary); + --progress-spinner-track: #D1D1D1; + --progress-spinner-head: var(--accent-primary); + + /* ---- Radio ---- */ + --radio-border: #C8C6C4; + --radio-border-hover: #605E5C; + --radio-dot: var(--accent-primary); + --radio-disabled-border: #D1D1D1; + --radio-disabled-bg: #F3F2F1; + --radio-disabled-dot: #C8C6C4; + + /* ---- Scroll ---- */ + --scroll-track: transparent; + --scroll-thumb: #C8C6C4; + --scroll-thumb-hover: #A19F9D; + --scroll-thumb-active: #8A8886; + --scroll-shadow: rgba(0,0,0,0.18); + + /* ---- Search ---- */ + --search-bg: #F3F2F1; + --search-icon: #605E5C; + --search-placeholder: #A19F9D; + --search-underline: var(--accent-primary); + + /* ---- Status ---- */ + --status-critical-bg: #C4314B; + --status-critical-fg: #ffffff; + --status-error: #C4314B; + --status-warning: #986F0B; + --status-success: #107C10; + --status-info: var(--text-secondary); + + /* ---- Toast ---- */ + --toast-bg: #ffffff; + --toast-border: var(--border-color); + + /* ---- Toggle ---- */ + --toggle-track-off: #FFFFFF; + --toggle-border-off: #8A8886; + --toggle-thumb: #8A8886; + --toggle-track-off-hover: #FFFFFF; + --toggle-border-off-hover: #605E5C; + --toggle-track-off-pressed: #FFFFFF; + --toggle-border-off-pressed: #252423; + --toggle-track-on: var(--accent-primary); + --toggle-border-on: var(--accent-primary); + --toggle-track-on-hover: var(--accent-secondary); + --toggle-border-on-hover: var(--accent-secondary); + --toggle-track-on-pressed: var(--accent-tertiary); + --toggle-border-on-pressed: var(--accent-tertiary); + --toggle-track-disabled: #F3F2F1; + --toggle-border-disabled: #D1D1D1; + --toggle-thumb-disabled: #C8C6C4; + + /* ---- Tooltip ---- */ + --tooltip-bg: #323130; + --tooltip-fg: #ffffff; + --tooltip-shadow: 0 6px 16px rgba(0,0,0,0.25); +} + +/* ================================================================ + SHELL STYLES + ================================================================ */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: var(--font); + background: var(--bg-primary); + color: var(--text-primary); + height: 100vh; + display: flex; +} + +.chat-panel { + width: 30%; + background: var(--bg-secondary); + box-shadow: -1px 0 2px rgba(0,0,0,0.08); + padding: 20px; + display: flex; + flex-direction: column; + border-left: 1px solid var(--border-color); + position: relative; +} + +.chat-messages { + flex-grow: 1; + overflow-y: auto; + padding: 15px; + margin-top: 15px; + background: var(--bg-primary); + border-radius: 6px; + border: 1px solid var(--border-color); +} + +.chat-message { + margin-bottom: 15px; + padding: 12px 15px; + background: var(--bg-tertiary); + border-radius: 6px; + border: 1px solid var(--border-color); +} + +.chat-message p { + margin-bottom: 5px; + line-height: 1.5; +} + +.chat-message p strong { + font-weight: 600; + color: var(--accent-primary); +} + +.chat-message p code { + background: var(--accent-glow); + padding: 2px 6px; + border-radius: 4px; + font-family: 'Courier New', Courier, monospace; + color: var(--accent-primary); + border: 1px solid var(--border-color); +} + +.link-group { + display: flex; + justify-content: space-between; + margin: 15px 0; + padding: 10px; + background: var(--bg-primary); + border-radius: 6px; + border: 1px solid var(--border-color); +} + +.link-group a { + font-size: 14px; + color: var(--accent-primary); + text-decoration: none; + padding: 8px 15px; + border-radius: 4px; + transition: all 0.2s ease; + border: 1px solid transparent; +} + +.link-group a:hover { + background: var(--accent-glow); + border-color: var(--border-color); + color: var(--accent-secondary); +} + +form { + display: flex; + flex-direction: row; + width: 100%; + gap: 10px; + align-items: center; +} + +.chat-input { + padding: 14px 18px; + border: 1px solid var(--border-color); + border-radius: 4px; + flex-grow: 1; + font-size: 14px; + background: var(--bg-primary); + color: var(--text-primary); + transition: border-color 0.2s ease; +} + +.chat-input:focus { + outline: none; + box-shadow: inset 0 -2px 0 var(--accent-primary); +} + +.chat-input::placeholder { + color: var(--text-disabled); +} + +.chat-submit { + padding: 14px 20px; + border: none; + border-radius: 4px; + font-size: 14px; + background: var(--accent-primary); + color: #fff; + cursor: pointer; + transition: background 0.2s ease; + font-weight: 600; + letter-spacing: 1px; + white-space: nowrap; +} + +.chat-submit:hover { + background: var(--accent-secondary); +} + +.chat-submit:active { + background: var(--accent-tertiary); +} + +.viewer-panel { + flex: 1; + min-width: 0; + padding: 0; + background: var(--bg-tertiary); + display: flex; + flex-direction: column; + position: relative; + overflow: hidden; + outline: none; +} + +.viewer-panel > div { + flex: 1; + min-height: 0; +} + +.loading-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(255,255,255,0.9); + justify-content: center; + align-items: center; + z-index: 1000; +} + +.spinner { + width: 50px; + height: 50px; + border: 4px solid var(--border-color); + border-top-color: var(--accent-primary); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background: var(--scroll-track); + border-radius: 10px; +} + +::-webkit-scrollbar-thumb { + background: var(--scroll-thumb); + border-radius: 10px; + border: 3px solid var(--bg-primary); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--scroll-thumb-hover); +} + +::-webkit-scrollbar-corner { + background: transparent; +} + +* { + scrollbar-width: thin; + scrollbar-color: var(--scroll-thumb) transparent; +} + +/* ---- Chat Panel Header ---- */ +.chat-panel-header { + display: flex; + align-items: center; + justify-content: space-between; + margin: -20px -20px 0; + padding: 12px 16px; + border-bottom: 1px solid var(--border-color); + background: var(--bg-tertiary); +} + +.chat-panel-header__title { + font: 600 var(--type-sm); + color: var(--text-primary); +} + +.chat-panel-close { + width: 28px; + height: 28px; + display: grid; + place-items: center; + border: 0; + border-radius: 4px; + background: transparent; + color: var(--text-secondary); + cursor: pointer; + font-size: 16px; + line-height: 1; +} + +.chat-panel-close:hover { + background: var(--bg-primary); + color: var(--text-primary); +} + +/* ---- Chat Rail ---- */ +.chat-rail { + width: 28px; + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + background: var(--bg-secondary); + border-left: 1px solid var(--border-color); + cursor: pointer; + user-select: none; + flex-shrink: 0; +} + +.chat-rail:hover { + background: var(--surface-hover); +} + +.chat-rail-label { + writing-mode: vertical-rl; + transform: rotate(180deg); + font: 600 var(--type-xs); + color: var(--text-secondary); + letter-spacing: 0.5px; + white-space: nowrap; +} + +/* ---- Chat Collapsed State ---- */ +body.chat-collapsed .chat-panel { + display: none; +} + +body.chat-collapsed .chat-rail { + display: flex; +} + +/* ---- Modal ---- */ +.modal-overlay { + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + background: var(--scrim); + display: none; + align-items: center; + justify-content: center; + z-index: 2000; + backdrop-filter: blur(4px); +} + +.modal-overlay.show { display: flex; } + +.modal-content { + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 8px; + width: 90%; + max-width: 450px; + box-shadow: var(--shadow-lg); + overflow: hidden; +} + +.modal-header { + background: var(--accent-primary); + color: white; + padding: 16px 20px; + font-size: 18px; + font-weight: 700; +} + +.modal-body { padding: 20px; } + +.modal-footer { + padding: 16px 20px; + border-top: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; +} + +.modal-footer-right { display: flex; gap: 10px; margin-left: auto; } + +/* ---- Form elements ---- */ +.form-group { margin-bottom: 16px; } +.form-group:last-child { margin-bottom: 0; } + +.form-label { + display: block; + font-size: 13px; + color: var(--text-secondary); + margin-bottom: 6px; + font-weight: 600; +} + +.form-input { + width: 100%; + padding: 10px 14px; + border: 1px solid var(--border-color); + border-radius: 4px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 14px; + outline: none; + transition: border-color 0.2s ease; + box-sizing: border-box; +} + +.form-input:focus { outline: none; box-shadow: inset 0 -2px 0 var(--accent-primary); } +.form-input:read-only { opacity: 0.7; cursor: not-allowed; } +.form-input::placeholder { color: var(--text-disabled); } + +.checkbox-label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; +} + +.checkbox-label input[type="checkbox"] { + width: 18px; + height: 18px; + accent-color: var(--accent-primary); + cursor: pointer; +} + +.checkbox-label span { font-size: 14px; color: var(--text-primary); } + +/* ---- Modal buttons ---- */ +.modal-btn { + padding: 10px 20px; + border-radius: 4px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s ease; + border: none; +} + +.modal-btn-primary { + background: var(--accent-primary); + color: white; +} + +.modal-btn-primary:hover { + background: var(--accent-secondary); +} + +.modal-btn-secondary { + background: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border-strong); +} + +.modal-btn-secondary:hover { + background: var(--bg-tertiary); +} + +.modal-btn-danger { background: var(--danger); color: white; } + +.modal-btn-danger:hover { + background: #A4262C; +} + +/* ---- Brainstorm icon button (inside input) ---- */ +.chat-input-wrapper { + position: relative; + flex: 1; + display: flex; + align-items: center; +} + +.chat-input-wrapper .chat-input { + width: 100%; + padding-right: 36px; +} + +.brainstorm-icon-btn { + position: absolute; + right: 6px; + top: 50%; + transform: translateY(-50%); + background: transparent; + border: none; + color: var(--text-secondary); + cursor: pointer; + padding: 4px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: color 0.2s, background 0.2s; +} + +.brainstorm-icon-btn:hover { + color: var(--accent-primary); + background: var(--accent-glow); +} + +/* ---- Brainstorm modal (full-window variant) ---- */ +.brainstorm-modal .modal-content { + max-width: none; + width: calc(100% - 80px); + height: calc(100% - 80px); + display: flex; + flex-direction: column; +} + +.brainstorm-modal .modal-header { + display: flex; + justify-content: space-between; + align-items: center; + flex-shrink: 0; +} + +.brainstorm-close-btn { + background: none; + border: none; + color: white; + font-size: 24px; + cursor: pointer; + padding: 0 4px; + line-height: 1; + opacity: 0.7; + transition: opacity 0.2s; +} + +.brainstorm-close-btn:hover { opacity: 1; } + +.brainstorm-messages { + flex: 1; + overflow-y: auto; + scrollbar-gutter: stable; + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.brainstorm-message { + padding: 12px 16px; + border-radius: 6px; + font-size: 14px; + line-height: 1.5; + max-width: 85%; + word-wrap: break-word; +} + +.brainstorm-user { + background: var(--accent-primary); + color: white; + align-self: flex-end; +} + +.brainstorm-assistant { + background: var(--bg-tertiary); + color: var(--text-primary); + border: 1px solid var(--border-color); + align-self: flex-start; +} + +.brainstorm-input-row { + display: flex; + gap: 8px; + padding: 16px 20px; + border-top: 1px solid var(--border-color); + flex-shrink: 0; +} + +.brainstorm-input { + flex: 1; + padding: 10px 14px; + border: 1px solid var(--border-color); + border-radius: 4px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 14px; + outline: none; + transition: border-color 0.2s; +} + +.brainstorm-input:focus { outline: none; box-shadow: inset 0 -2px 0 var(--accent-primary); } +.brainstorm-input::placeholder { color: var(--text-disabled); } + +.brainstorm-send-btn { + padding: 10px 20px; + border: none; + border-radius: 4px; + background: var(--accent-primary); + color: white; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; +} + +.brainstorm-send-btn:hover { + background: var(--accent-secondary); +} + +.brainstorm-send-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.brainstorm-assistant p { margin: 4px 0; } +.brainstorm-assistant pre { background: var(--bg-secondary); padding: 10px; border-radius: 4px; overflow-x: auto; margin: 8px 0; } +.brainstorm-assistant code { font-size: 13px; } +.brainstorm-assistant ul, .brainstorm-assistant ol { margin: 4px 0; padding-left: 20px; } + +.brainstorm-build-row { + display: flex; + justify-content: flex-end; + margin-top: 10px; +} + +.brainstorm-build-btn { + padding: 6px 16px; + font-size: 12px; + font-weight: 600; + border: none; + border-radius: 4px; + background: var(--accent-primary); + color: white; + cursor: pointer; + transition: background 0.2s; +} + +.brainstorm-build-btn:hover { + background: var(--accent-secondary); +} + +.brainstorm-suggestions { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 10px; +} + +.brainstorm-suggestion-chip { + padding: 5px 12px; + font-size: 12px; + border: 1px solid var(--border-color); + border-radius: 4px; + background: var(--bg-primary); + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s; + line-height: 1.4; +} + +.brainstorm-suggestion-chip:hover { + border-color: var(--accent-primary); + color: var(--accent-primary); + background: var(--accent-glow); +} + +.brainstorm-suggestion-chip:disabled { + opacity: 0.4; + cursor: default; + pointer-events: none; +} + +.brainstorm-thinking { + align-self: flex-start; + padding: 12px 16px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + color: var(--text-secondary); + font-size: 14px; + font-style: italic; +} + +/* ---- Save Line ---- */ +.save-line { + display: flex; + align-items: center; + gap: 10px; + margin: 8px 0 15px; + font-size: 11px; + color: var(--text-secondary); + opacity: 0.7; +} + +.save-line::before, +.save-line::after { + content: ''; + flex: 1; + height: 1px; + background: var(--border-color); +} + +.save-line-label { + white-space: nowrap; + letter-spacing: 0.5px; +} + +/* ---- Shared page rules ---- */ +#loadingOverlay { position: absolute; } +.chat-submit:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } +.chat-input:disabled { opacity: 0.5; cursor: not-allowed; } + + +/* ================================================================ + TEAMS UI KIT — COMPONENT STYLES + ================================================================ */ + +/* ===== Hyperlink ===== */ +.mt-link{ color:var(--accent-primary); text-decoration:none; cursor:pointer; font:inherit; } +.mt-link:hover{ color:var(--accent-secondary); text-decoration:underline; } +.mt-link:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; border-radius:2px; } +.mt-link[aria-disabled="true"]{ color:var(--text-disabled); pointer-events:none; cursor:default; text-decoration:none; } +.mt-link--truncate{ display:inline-block; max-width:100%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } + +/* ===== Button ===== */ +.mt-btn{ + display:inline-flex; align-items:center; justify-content:center; gap:6px; + border-radius:4px; border:1px solid var(--btn-border); + background:var(--btn-bg); color:var(--btn-fg); + font:600 var(--type-sm); + height:32px; padding:0 12px; + cursor:pointer; user-select:none; +} +.mt-btn[data-size="sm"]{ height:24px; padding:0 8px; font:600 var(--type-xs); } +.mt-btn:hover:not(:disabled){ background:var(--btn-hover-bg); } +.mt-btn:active:not(:disabled){ background:var(--btn-pressed-bg); } +.mt-btn:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-btn:disabled{ background:var(--bg-tertiary); color:var(--text-disabled); border-color:var(--bg-tertiary); cursor:not-allowed; } +.mt-btn[data-icon-only]{ width:32px; padding:0; } +.mt-btn[data-icon-only][data-size="sm"]{ width:24px; } +.mt-btn-group{ display:inline-flex; } +.mt-btn--split{ width:32px; padding:0; } + +.mt-btn[data-style="accent"]{ + --btn-bg:var(--accent-primary); --btn-fg:var(--accent-contrast); --btn-border:var(--accent-primary); + --btn-hover-bg:var(--accent-secondary); --btn-pressed-bg:var(--accent-tertiary); +} +.mt-btn[data-style="neutral"]{ + --btn-bg:var(--bg-primary); --btn-fg:var(--text-primary); --btn-border:var(--border-color); + --btn-hover-bg:var(--surface-hover); --btn-pressed-bg:var(--surface-hover); +} +.mt-btn[data-style="outline"]{ + --btn-bg:transparent; --btn-fg:var(--text-primary); --btn-border:var(--border-strong); + --btn-hover-bg:var(--bg-tertiary); --btn-pressed-bg:var(--bg-tertiary); +} +.mt-btn[data-style="ghost"]{ + --btn-bg:transparent; --btn-fg:var(--text-primary); --btn-border:transparent; + --btn-hover-bg:var(--bg-tertiary); --btn-pressed-bg:var(--bg-tertiary); +} + +/* ===== Alert ===== */ +.mt-alert{ + display:flex; align-items:center; gap:10px; + min-height:32px; padding:6px 10px; + border:1px solid var(--alert-border); + border-radius:4px; + background:var(--alert-bg); + color:var(--alert-fg); +} +.mt-alert__icon{ width:14px; height:14px; border-radius:999px; background:var(--alert-icon); } +.mt-alert__message{ font:var(--type-xs); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; } +.mt-alert__action{ + height:22px; padding:0 10px; border-radius:3px; + border:1px solid var(--border-color); background:var(--bg-primary); color:var(--text-primary); + font:600 11px/20px var(--font); +} +.mt-alert__dismiss{ + width:22px; height:22px; display:grid; place-items:center; + border:0; background:transparent; color:var(--text-secondary); border-radius:3px; +} +.mt-alert__action:focus-visible,.mt-alert__dismiss:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-alert[data-variant="error"] { --alert-bg:var(--alert-error-bg); --alert-border:var(--alert-error-border); --alert-fg:var(--alert-error-fg); --alert-icon:var(--alert-error-icon); } +.mt-alert[data-variant="warning"]{ --alert-bg:var(--alert-warn-bg); --alert-border:var(--alert-warn-border); --alert-fg:var(--alert-warn-fg); --alert-icon:var(--alert-warn-icon); } +.mt-alert[data-variant="success"]{ --alert-bg:var(--alert-ok-bg); --alert-border:var(--alert-ok-border); --alert-fg:var(--alert-ok-fg); --alert-icon:var(--alert-ok-icon); } +.mt-alert[data-variant="info"] { --alert-bg:var(--alert-info-bg); --alert-border:var(--alert-info-border); --alert-fg:var(--alert-info-fg); --alert-icon:var(--alert-info-icon); } + +/* ===== Breadcrumb ===== */ +.mt-breadcrumb{ font:var(--type-sm); } +.mt-breadcrumb__list{ display:flex; align-items:center; gap:6px; list-style:none; margin:0; padding:0; } +.mt-breadcrumb__sep{ color:var(--text-disabled); user-select:none; } +.mt-breadcrumb__current{ color:var(--text-primary); font-weight:600; } + +/* ===== Card ===== */ +.mt-card{ + position:relative; display:flex; flex-direction:column; overflow:hidden; + border-radius:6px; border:1px solid #EFEFEF; + background:var(--bg-primary); color:var(--text-primary); + box-shadow:0 2px 4px #E1E1E1; transition:background 0.2s, box-shadow 0.2s; +} +.mt-card:hover{ background:var(--surface-hover); box-shadow:0 4px 8px #E1E1E1; } +.mt-card:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-card[data-size="xs"]{ width:160px; } .mt-card[data-size="sm"]{ width:220px; } +.mt-card[data-size="md"]{ width:280px; } .mt-card[data-size="lg"]{ width:360px; } +.mt-card__media{ aspect-ratio:16/9; background:var(--bg-secondary); } +.mt-card__media img{ width:100%; height:100%; object-fit:cover; display:block; } +.mt-card__body{ padding:10px 12px; } +.mt-card__header{ display:flex; gap:8px; align-items:flex-start; } +.mt-card__title{ font:600 var(--type-sm); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } +.mt-card__subtitle{ font:var(--type-xs); color:var(--text-secondary); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } +.mt-card__menu{ width:28px; height:28px; border:0; background:transparent; color:var(--text-secondary); border-radius:4px; cursor:pointer; } +.mt-card__menu:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-card__menu:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-card[data-selected="true"]{ border-color:var(--accent-primary); } +.mt-card[data-selected="true"]::after{ + content:"✓"; position:absolute; top:8px; right:8px; + width:18px; height:18px; border-radius:999px; display:grid; place-items:center; + background:var(--accent-primary); color:var(--accent-contrast); font:700 12px/1 var(--font); +} +.mt-card[data-disabled="true"]{ background:var(--bg-tertiary); color:var(--text-disabled); box-shadow:none; opacity:.7; pointer-events:none; } + +/* ===== Carousel ===== */ +.mt-carousel__viewport{ position:relative; overflow:hidden; display:flex; justify-content:center; align-items:center; } +.mt-carousel__track{ display:flex; transition:transform 240ms ease; } +.mt-carousel__slide{ min-width:100%; display:flex; justify-content:center; padding:20px 0; } +.mt-carousel__content{ + width:70%; max-width:600px; aspect-ratio:16/9; + border-radius:6px; border:1px solid var(--border-color); + background:var(--bg-primary); box-shadow:var(--shadow-sm); + display:grid; place-items:center; +} +.mt-carousel__nav{ + position:absolute; top:50%; transform:translateY(-50%); + width:36px; height:36px; border-radius:999px; border:0; + background:transparent; color:var(--text-secondary); cursor:pointer; +} +.mt-carousel__nav--prev{ left:12px; } .mt-carousel__nav--next{ right:12px; } +.mt-carousel__nav:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-carousel__nav:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-carousel__dots{ display:flex; justify-content:center; gap:6px; margin-top:8px; } +.mt-carousel__dot{ width:6px; height:6px; border-radius:999px; border:0; background:var(--text-disabled); cursor:pointer; } +.mt-carousel__dot[aria-selected="true"]{ background:var(--accent-primary); transform:scale(1.2); } +.mt-carousel__dot:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +/* ===== Checkbox ===== */ +.mt-checkbox{ display:inline-flex; align-items:center; gap:8px; cursor:pointer; font:var(--type-sm); color:var(--text-primary); } +.mt-checkbox__input{ position:absolute; opacity:0; pointer-events:none; } +.mt-checkbox__control{ + width:16px; height:16px; display:grid; place-items:center; + border-radius:3px; border:1px solid var(--border-strong); background:transparent; +} +.mt-checkbox__icon{ width:12px; height:12px; color:var(--accent-contrast); opacity:0; transform:scale(.85); transition:120ms ease; } +.mt-checkbox:hover .mt-checkbox__control{ border-color:var(--text-primary); } +.mt-checkbox__input:checked + .mt-checkbox__control{ background:var(--accent-primary); border-color:var(--accent-primary); } +.mt-checkbox__input:checked + .mt-checkbox__control .mt-checkbox__icon{ opacity:1; transform:scale(1); } +.mt-checkbox__input:focus-visible + .mt-checkbox__control{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-checkbox__input:disabled + .mt-checkbox__control{ background:var(--bg-tertiary); border-color:var(--border-color); } +.mt-checkbox__input:disabled ~ .mt-checkbox__label{ color:var(--text-disabled); cursor:not-allowed; } + +/* ===== Menu ===== */ +.mt-menu{ + min-width:180px; max-width:280px; padding:4px; + border-radius:6px; border:1px solid var(--menu-border); + background:var(--menu-bg); box-shadow:var(--menu-shadow); +} +.mt-menu__item{ + width:100%; height:32px; + display:grid; grid-template-columns:20px 1fr auto auto; align-items:center; gap:8px; + padding:0 8px; border:0; border-radius:4px; background:transparent; + color:var(--text-primary); font:var(--type-sm); text-align:left; cursor:pointer; +} +.mt-menu__icon{ color:var(--text-secondary); } +.mt-menu__shortcut,.mt-menu__submenu{ color:var(--text-secondary); font:var(--type-xs); } +.mt-menu__item:hover{ background:var(--bg-tertiary); } +.mt-menu__item:active{ background:var(--bg-tertiary); } +.mt-menu__item:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-menu__divider{ height:1px; background:var(--border-color); margin:4px 6px; } +.mt-menu__section{ padding:6px 10px 4px; font:700 11px/14px var(--font); color:var(--text-secondary); } +.mt-menu__item[aria-disabled="true"]{ color:var(--text-disabled); cursor:not-allowed; } +.mt-menu__item[aria-disabled="true"]:hover{ background:transparent; } + +/* ===== Dialog ===== */ +.mt-dialog{ position:fixed; inset:0; z-index:1100; } +.mt-dialog__scrim{ position:absolute; inset:0; background:var(--scrim); } +.mt-dialog__panel{ + position:relative; margin:10vh auto 0; + border-radius:8px; border:1px solid var(--border-color); + background:var(--bg-primary); box-shadow:0 20px 50px rgba(0,0,0,.35); + display:flex; flex-direction:column; +} +.mt-dialog__panel[data-size="sm"]{ width:480px; } +.mt-dialog__panel[data-size="md"]{ width:600px; } +.mt-dialog__panel[data-size="lg"]{ width:680px; } +@media (max-width:720px){ .mt-dialog__panel{ width:calc(100% - 32px); } } +.mt-dialog__header{ padding:20px 24px 0; } +.mt-dialog__title{ font:700 var(--type-lg); margin:0; color:var(--text-primary); } +.mt-dialog__body{ padding:16px 24px; font:var(--type-sm); color:var(--text-primary); } +.mt-dialog__error{ padding:0 24px 8px; font:var(--type-xs); color:var(--danger); } +.mt-dialog__footer{ padding:16px 24px 20px; display:flex; justify-content:flex-end; gap:8px; } + +/* ===== Dropdown / Field ===== */ +.mt-field{ display:flex; flex-direction:column; gap:4px; } +.mt-field__label{ font:700 var(--type-xs); color:var(--text-secondary); } +.mt-field__error{ font:var(--type-xs); color:var(--danger); } +.mt-field__hint{ font:var(--type-xs); color:var(--text-secondary); } + +.mt-dropdown{ + height:32px; padding:0 12px; display:flex; align-items:center; justify-content:space-between; gap:8px; + border-radius:4px; border:1px solid var(--border-color); + background:var(--bg-primary); color:var(--text-primary); + font:var(--type-sm); cursor:pointer; +} +.mt-dropdown:hover{ border-color:var(--text-primary); } +.mt-dropdown:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-dropdown[aria-expanded="true"]{ border-color:var(--accent-primary); box-shadow:inset 0 -2px 0 var(--accent-primary); } +.mt-field[data-state="error"] .mt-dropdown{ border-color:var(--danger); } + +/* ===== Group list ===== */ +.mt-group__header{ + width:100%; display:flex; align-items:center; gap:6px; + padding:6px 8px; border:0; background:transparent; + font:700 var(--type-sm); color:var(--text-primary); + cursor:pointer; text-align:left; +} +.mt-group__header:hover:not(:disabled){ background:var(--bg-tertiary); } +.mt-group__header:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-group__chevron{ color:var(--text-secondary); transition:transform 120ms ease; } +.mt-group__header[aria-expanded="true"] .mt-group__chevron{ transform:rotate(90deg); } +.mt-group__header:disabled{ color:var(--text-disabled); cursor:not-allowed; } +.mt-group__header:disabled .mt-group__chevron{ color:var(--text-disabled); } +.mt-group__content{ padding-left:20px; } + +/* ===== Coachmark ===== */ +.mt-coachmark{ position:fixed; inset:0; z-index:1000; } +.mt-coachmark__scrim{ position:absolute; inset:0; background:var(--scrim); } +.mt-coachmark__panel{ + position:absolute; + min-width:260px; max-width:420px; + border-radius:8px; border:1px solid var(--border-color); + background:var(--bg-primary); box-shadow:var(--shadow-lg); + padding:16px; +} +.mt-coachmark__title{ font:700 var(--type-md); margin:0 0 8px; color:var(--text-primary); } +.mt-coachmark__body{ font:var(--type-sm); color:var(--text-secondary); } +.mt-coachmark__footer{ display:flex; justify-content:flex-end; gap:8px; margin-top:12px; } +.mt-coachmark__close{ + position:absolute; top:8px; right:8px; + width:28px; height:28px; border:0; background:transparent; + color:var(--text-secondary); border-radius:4px; cursor:pointer; +} +.mt-coachmark__close:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-coachmark__close:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +/* ===== Native select ===== */ +select{ cursor:pointer; appearance:none; background-repeat:no-repeat; background-position:right 12px center; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23605E5C' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); } +select option{ background:#ffffff; color:var(--text-primary); padding:8px; } +select option:checked{ background:#F5F5F5; } + +/* ===== Input ===== */ +.mt-input{ + height:32px; padding:0 12px; + display:flex; align-items:center; gap:8px; + border-radius:4px; + border:1px solid var(--input-border); + background:var(--input-bg); + color:var(--input-fg); +} +.mt-input[data-size="sm"]{ height:24px; padding:0 8px; } +.mt-input:hover{ border-color:var(--input-border-hover); } +.mt-input__control{ + width:100%; border:0; outline:none; background:transparent; + color:inherit; font:var(--type-sm); min-width:0; +} +.mt-input[data-size="sm"] .mt-input__control{ font:var(--type-xs); } +.mt-input__control::placeholder{ color:var(--input-placeholder); } +.mt-input:focus-within{ + box-shadow: inset 0 -2px 0 var(--input-border-focus); +} +.mt-input__control:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; border-radius:2px; } +.mt-input[data-disabled="true"]{ background:var(--bg-tertiary); border-color:var(--border-color); color:var(--text-disabled); } +.mt-input__control:disabled{ cursor:not-allowed; } +.mt-input__prefix{ display:inline-flex; align-items:center; justify-content:center; flex:0 0 auto; } +.mt-input__prefix--chip{ + height:20px; padding:0 6px; border-radius:3px; + background:var(--input-prefix-bg); color:var(--input-prefix-fg); + font:600 11px/20px var(--font); +} +.mt-input__prefix--icon{ width:16px; height:16px; border-radius:2px; background:var(--input-icon-bg); } +.mt-input__end{ + width:22px; height:22px; display:grid; place-items:center; + border:0; border-radius:3px; + background:transparent; color:var(--text-secondary); + cursor:pointer; +} +.mt-input__end:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-input__end:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-field[data-state="error"] .mt-input{ + border-color:var(--danger); + box-shadow: inset 0 -2px 0 var(--danger); +} + +/* ===== Key value pair ===== */ +.mt-kvp{ display:flex; flex-direction:column; gap:18px; } +.mt-kvp__row{ display:grid; grid-template-columns:160px 1fr; column-gap:28px; align-items:start; } +.mt-kvp__key{ + font:700 var(--type-xs); color:var(--text-secondary); + text-transform:uppercase; letter-spacing:.02em; +} +.mt-kvp__value{ display:flex; flex-direction:column; gap:6px; } +.mt-kvp__text-title{ font:600 var(--type-xs); color:var(--text-primary); } +.mt-kvp__text-sub{ font:var(--type-xs); color:var(--text-secondary); } + +/* ===== Paragraph ===== */ +.mt-paragraph{ max-width:520px; color:var(--text-primary); } +.mt-paragraph__headline{ margin:0 0 10px; font:700 var(--type-lg); color:var(--text-primary); } +.mt-paragraph__desc{ margin:0 0 18px; font:var(--type-xs); color:var(--text-secondary); } +.mt-paragraph__subhead{ margin:0 0 8px; font:700 var(--type-sm); color:var(--text-primary); } +.mt-paragraph__body{ margin:0 0 14px; font:var(--type-xs); color:var(--text-secondary); } +.mt-paragraph__meta{ margin:0; font:var(--type-xs); color:var(--text-secondary); } + +/* ===== Picker ===== */ +.mt-picker{ position:relative; } +.mt-picker__panel{ + position:absolute; top:calc(100% + 4px); left:0; width:100%; + max-height:280px; overflow:auto; + border-radius:6px; border:1px solid var(--picker-border); + background:var(--picker-bg); box-shadow:var(--picker-shadow); + padding:4px; display:none; z-index:100; +} +.mt-picker[data-open="true"] .mt-picker__panel{ display:block; } +.mt-picker__option{ + width:100%; height:40px; + display:flex; align-items:center; gap:10px; + padding:0 10px; border:0; border-radius:4px; + background:transparent; color:var(--text-primary); + font:var(--type-sm); text-align:left; cursor:pointer; +} +.mt-picker__option:hover{ background:var(--bg-tertiary); } +.mt-picker__option[data-selected="true"]{ background:var(--bg-tertiary); } +.mt-picker__option-label{ font-weight:600; } +.mt-picker__option-meta{ font:var(--type-xs); color:var(--text-secondary); } + +.mt-avatar{ + width:24px; height:24px; border-radius:999px; + background:var(--accent-primary); color:var(--accent-contrast); + font:600 11px/24px var(--font); text-align:center; +} +.mt-picker__file-icon{ width:20px; height:20px; border-radius:3px; background:var(--accent-primary); } + +/* Date */ +.mt-date__header{ display:flex; justify-content:space-between; align-items:center; padding:6px 8px; font:600 var(--type-sm); } +.mt-date__nav{ + width:28px; height:28px; border:0; border-radius:4px; + background:transparent; color:var(--text-secondary); cursor:pointer; +} +.mt-date__nav:hover{ background:var(--bg-tertiary); } +.mt-date__grid{ display:grid; grid-template-columns:repeat(7,1fr); gap:4px; padding:8px; } +.mt-date__cell{ + height:32px; border:0; border-radius:4px; + background:transparent; color:var(--text-primary); + font:var(--type-xs); cursor:pointer; +} +.mt-date__cell:hover{ background:var(--bg-tertiary); } +.mt-date__cell[data-selected="true"]{ background:var(--accent-primary); color:var(--accent-contrast); } + +/* Time */ +.mt-time__row{ display:flex; gap:8px; padding:8px; } +.mt-time__input{ + flex:1; height:32px; border-radius:4px; + border:1px solid var(--border-color); background:var(--bg-primary); + color:var(--text-primary); padding:0 8px; font:var(--type-sm); +} +.mt-time__input:focus{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +/* ===== Pivot ===== */ +.mt-pivot__list{ display:flex; align-items:flex-end; gap:24px; } +.mt-pivot__tab{ + position:relative; height:32px; padding:0; + border:0; background:transparent; cursor:pointer; + font:600 var(--type-sm); + color:var(--pivot-fg); +} +.mt-pivot__tab::after{ + content:""; position:absolute; left:0; right:0; bottom:-2px; + height:2px; border-radius:2px; background:transparent; transition:120ms ease; +} +.mt-pivot__tab:hover::after{ background:var(--pivot-underline-hover); } +.mt-pivot__tab[aria-selected="true"]{ color:var(--pivot-active-fg); } +.mt-pivot__tab[aria-selected="true"]::after{ background:var(--pivot-underline-active); } +.mt-pivot__tab:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:4px; border-radius:4px; } + +/* ===== Progress ===== */ +.mt-progress{ display:flex; align-items:center; gap:14px; color:var(--text-primary); } +.mt-progress__spinner{ + width:16px; height:16px; border-radius:999px; + border:2px solid var(--progress-spinner-track); + border-top-color: var(--progress-spinner-head); + box-sizing:border-box; + animation: mt-spin 900ms linear infinite; +} +@keyframes mt-spin{ to{ transform:rotate(360deg); } } + +.mt-progress[data-variant="determinate"]{ align-items:flex-start; gap:16px; } +.mt-progress__text{ min-width:120px; } +.mt-progress__label{ font:600 var(--type-xs); color:var(--text-primary); margin:0 0 2px; } +.mt-progress__desc{ font:var(--type-xs); color:var(--text-secondary); margin:0; } +.mt-progress__bar{ + flex:1; min-width:220px; height:2px; border-radius:999px; + background:var(--progress-track); overflow:hidden; margin-top:8px; +} +.mt-progress__fill{ height:100%; background:var(--progress-fill); border-radius:999px; transition:width 200ms ease; } +@media (prefers-reduced-motion: reduce){ + .mt-progress__spinner{ animation:none; } + .mt-progress__fill{ transition:none; } +} + +/* ===== Radio ===== */ +.mt-radio-group{ display:flex; flex-direction:column; gap:8px; } +.mt-radio{ + display:inline-flex; align-items:center; gap:8px; + cursor:pointer; font:var(--type-sm); color:var(--text-primary); position:relative; +} +.mt-radio__input{ position:absolute; opacity:0; pointer-events:none; } +.mt-radio__control{ + width:16px; height:16px; border-radius:999px; + border:1px solid var(--radio-border); background:transparent; + position:relative; transition:120ms ease; +} +.mt-radio__control::after{ + content:""; width:8px; height:8px; border-radius:999px; + background:var(--radio-dot); + position:absolute; top:50%; left:50%; + transform:translate(-50%,-50%) scale(.6); + opacity:0; transition:120ms ease; +} +.mt-radio:hover .mt-radio__control{ border-color:var(--radio-border-hover); } +.mt-radio__input:checked + .mt-radio__control{ border-color:var(--accent-primary); } +.mt-radio__input:checked + .mt-radio__control::after{ opacity:1; transform:translate(-50%,-50%) scale(1); } +.mt-radio__input:focus-visible + .mt-radio__control{ outline:2px solid var(--focus-ring); outline-offset:3px; } +.mt-radio__input:disabled + .mt-radio__control{ border-color:var(--radio-disabled-border); background:var(--radio-disabled-bg); } +.mt-radio__input:disabled + .mt-radio__control::after{ background:var(--radio-disabled-dot); } +.mt-radio__input:disabled ~ .mt-radio__label{ color:var(--text-disabled); cursor:not-allowed; } + +/* ===== Scroll region ===== */ +.mt-scroll{ + max-height:120px; overflow:auto; + padding:10px 12px; + border:1px solid var(--border-color); + border-radius:4px; + background:var(--bg-primary); + color:var(--text-primary); + font:var(--type-xs); + box-shadow: inset -10px 0 10px -12px var(--scroll-shadow); + scrollbar-width: thin; + scrollbar-color: var(--scroll-thumb) var(--scroll-track); +} +.mt-scroll::-webkit-scrollbar{ width:10px; height:10px; } +.mt-scroll::-webkit-scrollbar-track{ background:var(--scroll-track); border-radius:999px; } +.mt-scroll::-webkit-scrollbar-thumb{ + background:var(--scroll-thumb); border-radius:999px; + border:3px solid var(--scroll-track); +} +.mt-scroll::-webkit-scrollbar-thumb:hover{ background:var(--scroll-thumb-hover); } +.mt-scroll::-webkit-scrollbar-thumb:active{ background:var(--scroll-thumb-active); } +.mt-scroll::-webkit-scrollbar-corner{ background:transparent; } + +/* ===== Search box ===== */ +.mt-search__box{ + height:32px; padding:0 10px; + display:flex; align-items:center; gap:8px; + border-radius:4px; + border:1px solid transparent; + background:var(--search-bg); + color:var(--text-primary); +} +.mt-search__icon{ width:16px; height:16px; border-radius:2px; background:var(--search-icon); flex:0 0 auto; } +.mt-search__input{ + width:100%; border:0; outline:none; background:transparent; + color:inherit; font:var(--type-sm); min-width:0; +} +.mt-search__input::placeholder{ color:var(--search-placeholder); } +.mt-search__box:focus-within{ box-shadow: inset 0 -2px 0 var(--search-underline); } +.mt-search__input:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; border-radius:2px; } +.mt-search__clear{ + width:22px; height:22px; display:grid; place-items:center; + border:0; border-radius:3px; + background:transparent; color:var(--text-secondary); + cursor:pointer; flex:0 0 auto; +} +.mt-search__clear:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-search__clear:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +/* ===== Side panel ===== */ +.mt-sidepanel{ position:fixed; inset:0; z-index:1200; pointer-events:none; } +.mt-sidepanel__scrim{ position:absolute; inset:0; background:var(--scrim); opacity:0; transition:opacity 180ms ease; } +.mt-sidepanel__panel{ + position:absolute; top:0; right:0; height:100%; + display:flex; flex-direction:column; + background:var(--bg-primary); + border-left:1px solid var(--border-color); + box-shadow:var(--shadow-lg); + transform:translateX(100%); + transition:transform 220ms ease; + pointer-events:auto; +} +.mt-sidepanel__panel[data-size="sm"]{ width:320px; } +.mt-sidepanel__panel[data-size="md"]{ width:400px; } +.mt-sidepanel__panel[data-size="lg"]{ width:480px; } +.mt-sidepanel[data-open="true"]{ pointer-events:auto; } +.mt-sidepanel[data-open="true"] .mt-sidepanel__scrim{ opacity:1; } +.mt-sidepanel[data-open="true"] .mt-sidepanel__panel{ transform:translateX(0); } + +.mt-sidepanel__header{ + height:56px; padding:0 16px; + display:flex; align-items:center; justify-content:space-between; + border-bottom:1px solid var(--border-color); +} +.mt-sidepanel__title{ margin:0; font:700 var(--type-md); color:var(--text-primary); } +.mt-sidepanel__close{ + width:32px; height:32px; border:0; border-radius:4px; + background:transparent; color:var(--text-secondary); cursor:pointer; +} +.mt-sidepanel__close:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-sidepanel__close:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } + +.mt-sidepanel__body{ flex:1; overflow:auto; padding:16px; color:var(--text-primary); } +.mt-sidepanel__footer{ + padding:12px 16px; border-top:1px solid var(--border-color); + display:flex; justify-content:flex-end; gap:8px; + background:var(--bg-primary); +} + +/* ===== Status label ===== */ +.mt-status{ display:inline-flex; align-items:center; gap:6px; font:600 var(--type-xs); color:var(--status-color); } +.mt-status__icon{ + width:14px; height:14px; display:grid; place-items:center; + font:700 10px/1 var(--font); color:currentColor; +} +.mt-status[data-variant="critical"]{ + height:18px; padding:0 8px; border-radius:3px; + background:var(--status-critical-bg); color:var(--status-critical-fg); +} +.mt-status[data-variant="critical"] .mt-status__icon{ color:var(--status-critical-fg); } +.mt-status[data-variant="error"]{ --status-color: var(--status-error); } +.mt-status[data-variant="warning"]{ --status-color: var(--status-warning); } +.mt-status[data-variant="success"]{ --status-color: var(--status-success); } +.mt-status[data-variant="info"]{ --status-color: var(--status-info); } + +/* ===== Toast ===== */ +.mt-toast-stack{ + position:fixed; bottom:16px; right:16px; + display:flex; flex-direction:column; gap:12px; + z-index:1300; +} +.mt-toast{ + width:320px; border-radius:6px; + background:var(--toast-bg); color:var(--text-primary); + border:1px solid var(--toast-border); + box-shadow:var(--shadow-lg); + display:flex; flex-direction:column; overflow:hidden; + animation: mt-toast-in 180ms ease; +} +@keyframes mt-toast-in{ from{ transform:translateY(8px); opacity:0; } to{ transform:translateY(0); opacity:1; } } +.mt-toast__header{ display:flex; align-items:flex-start; gap:10px; padding:12px 12px 8px; } +.mt-toast__avatar{ width:28px; height:28px; border-radius:999px; background:var(--accent-primary); flex:0 0 auto; } +.mt-toast__meta{ flex:1; min-width:0; } +.mt-toast__title{ font:600 var(--type-sm); color:var(--text-primary); } +.mt-toast__subtitle{ font:var(--type-xs); color:var(--text-secondary); } +.mt-toast__close{ + width:28px; height:28px; border:0; background:transparent; + border-radius:4px; color:var(--text-secondary); cursor:pointer; +} +.mt-toast__close:hover{ background:var(--bg-tertiary); color:var(--text-primary); } +.mt-toast__close:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-toast__body{ padding:0 12px 10px; } +.mt-toast__message{ font:var(--type-xs); color:var(--text-secondary); } +.mt-toast__reply{ display:flex; align-items:center; gap:6px; padding:8px 10px 10px; border-top:1px solid var(--border-color); } +.mt-toast__input{ + flex:1; height:28px; border-radius:4px; + border:1px solid var(--border-color); background:var(--bg-primary); + color:var(--text-primary); padding:0 8px; font:var(--type-xs); +} +.mt-toast__input:focus{ outline:2px solid var(--focus-ring); outline-offset:2px; } +.mt-toast__send{ + width:28px; height:28px; border:0; border-radius:4px; + background:var(--accent-primary); color:var(--accent-contrast); cursor:pointer; +} +.mt-toast__send:hover{ background:var(--accent-secondary); } +.mt-toast[data-variant="accent"]{ background:var(--accent-primary); color:var(--accent-contrast); border-color:var(--accent-primary); } +.mt-toast[data-variant="accent"] .mt-toast__subtitle, +.mt-toast[data-variant="accent"] .mt-toast__message{ color:rgba(255,255,255,.9); } +.mt-toast[data-variant="accent"] .mt-toast__close{ color:rgba(255,255,255,.8); } + +/* ===== Toggle ===== */ +.mt-toggle{ display:inline-flex; align-items:center; gap:8px; cursor:pointer; user-select:none; color:var(--text-primary); } +.mt-toggle__input{ position:absolute; opacity:0; pointer-events:none; } +.mt-toggle__track{ + width:28px; height:16px; border-radius:999px; + background:var(--toggle-track-off); + border:1px solid var(--toggle-border-off); + position:relative; transition:120ms ease; box-sizing:border-box; +} +.mt-toggle__thumb{ + width:12px; height:12px; border-radius:999px; + background:var(--toggle-thumb); + position:absolute; top:1px; left:1px; + transition:120ms ease; +} +.mt-toggle__input:checked + .mt-toggle__track{ + background:var(--toggle-track-on); + border-color:var(--toggle-border-on); +} +.mt-toggle__input:checked + .mt-toggle__track .mt-toggle__thumb{ transform:translateX(12px); } + +.mt-toggle:hover .mt-toggle__track{ background:var(--toggle-track-off-hover); border-color:var(--toggle-border-off-hover); } +.mt-toggle:hover .mt-toggle__input:checked + .mt-toggle__track{ background:var(--toggle-track-on-hover); border-color:var(--toggle-border-on-hover); } +.mt-toggle:active .mt-toggle__track{ background:var(--toggle-track-off-pressed); border-color:var(--toggle-border-off-pressed); } +.mt-toggle:active .mt-toggle__input:checked + .mt-toggle__track{ background:var(--toggle-track-on-pressed); border-color:var(--toggle-border-on-pressed); } +.mt-toggle__input:focus-visible + .mt-toggle__track{ outline:2px solid var(--focus-ring); outline-offset:3px; } +.mt-toggle__input:disabled + .mt-toggle__track{ background:var(--toggle-track-disabled); border-color:var(--toggle-border-disabled); } +.mt-toggle__input:disabled + .mt-toggle__track .mt-toggle__thumb{ background:var(--toggle-thumb-disabled); } +.mt-toggle__input:disabled ~ .mt-toggle__label{ color:var(--text-disabled); } + +/* ===== Tooltip ===== */ +.mt-tooltip{ + position:absolute; z-index:1400; + max-width:220px; + padding:6px 8px; + border-radius:4px; + font:var(--type-xs); + white-space:nowrap; + background:var(--tooltip-bg); + color:var(--tooltip-fg); + box-shadow:var(--tooltip-shadow); + opacity:0; + transform:translateY(2px); + pointer-events:none; + transition:opacity 120ms ease, transform 120ms ease; +} +.mt-tooltip[data-open="true"]{ opacity:1; transform:translateY(0); } +.mt-tooltip::after{ + content:""; + position:absolute; + width:8px; height:8px; + background:var(--tooltip-bg); + transform:rotate(45deg); +} +.mt-tooltip[data-placement="top"]::after{ bottom:-4px; left:50%; transform:translateX(-50%) rotate(45deg); } +.mt-tooltip[data-placement="bottom"]::after{ top:-4px; left:50%; transform:translateX(-50%) rotate(45deg); } +.mt-tooltip[data-placement="left"]::after{ right:-4px; top:50%; transform:translateY(-50%) rotate(45deg); } +.mt-tooltip[data-placement="right"]::after{ left:-4px; top:50%; transform:translateY(-50%) rotate(45deg); } diff --git a/teams-page-scripts/.gitkeep b/teams-page-scripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/teams-page-scripts/helpers-v2.js b/teams-page-scripts/helpers-v2.js new file mode 100644 index 0000000..ce0daa8 --- /dev/null +++ b/teams-page-scripts/helpers-v2.js @@ -0,0 +1,222 @@ +(function() { + if (window.__synthOSHelpers) return; + window.__synthOSHelpers = true; + + function _json(method, url, body) { + var opts = { + method: method, + headers: { 'Content-Type': 'application/json' } + }; + if (body !== undefined) { + opts.body = JSON.stringify(body); + } + return fetch(url, opts).then(function(res) { + var ct = res.headers.get('content-type') || ''; + var isJson = ct.indexOf('application/json') !== -1; + return (isJson ? res.json() : res.text()).then(function(data) { + if (!res.ok) { + if (res.status === 404 && isJson && data && data.error === 'table_not_found') { + return null; // for data.get, return null if the table or row is not found instead of throwing an error + } + var err = new Error(isJson && data && data.error ? data.error : data); + err.status = res.status; + throw err; + } + return data; + }); + }); + } + + function _pageName() { + return (window.pageInfo && window.pageInfo.name) || 'default'; + } + + window.synthos = { + data: { + list: function(table, opts) { + var url = '/api/data/' + encodeURIComponent(_pageName()) + '/' + encodeURIComponent(table); + if (opts && typeof opts.limit === 'number') { + url += '?limit=' + opts.limit; + if (typeof opts.offset === 'number') { + url += '&offset=' + opts.offset; + } + } + return _json('GET', url) || []; + }, + get: function(table, id) { + return _json('GET', '/api/data/' + encodeURIComponent(_pageName()) + '/' + encodeURIComponent(table) + '/' + encodeURIComponent(id)) || null; + }, + save: function(table, row) { + return _json('POST', '/api/data/' + encodeURIComponent(_pageName()) + '/' + encodeURIComponent(table), row); + }, + remove: function(table, id) { + return _json('DELETE', '/api/data/' + encodeURIComponent(_pageName()) + '/' + encodeURIComponent(table) + '/' + encodeURIComponent(id)); + } + }, + + generate: { + image: function(opts) { + return _json('POST', '/api/generate/image', opts); + }, + completion: function(opts) { + return _json('POST', '/api/generate/completion', opts); + } + }, + + scripts: { + run: function(id, variables) { + return _json('POST', '/api/scripts/' + encodeURIComponent(id), variables || {}); + } + }, + + pages: { + list: function() { + return _json('GET', '/api/pages'); + }, + get: function(name) { + return _json('GET', '/api/pages/' + encodeURIComponent(name)); + }, + update: function(name, metadata) { + return _json('POST', '/api/pages/' + encodeURIComponent(name), metadata); + }, + remove: function(name) { + return _json('DELETE', '/api/pages/' + encodeURIComponent(name)); + } + }, + + search: { + web: function(query, opts) { + var body = { query: query }; + if (opts) { + if (opts.count) body.count = opts.count; + if (opts.country) body.country = opts.country; + if (opts.freshness) body.freshness = opts.freshness; + } + return _json('POST', '/api/search/web', body); + } + }, + + connectors: { + call: function(connector, method, path, opts) { + var body = { connector: connector, method: method, path: path }; + if (opts) { + if (opts.headers) body.headers = opts.headers; + if (opts.body) body.body = opts.body; + if (opts.query) body.query = opts.query; + } + return _json('POST', '/api/connectors', body); + }, + list: function(opts) { + var url = '/api/connectors'; + var params = []; + if (opts) { + if (opts.category) params.push('category=' + encodeURIComponent(opts.category)); + if (opts.id) params.push('id=' + encodeURIComponent(opts.id)); + } + if (params.length) url += '?' + params.join('&'); + return _json('GET', url); + } + }, + + agents: { + list: function(opts) { + var url = '/api/agents'; + var params = []; + if (opts) { + if (typeof opts.enabled === 'boolean') params.push('enabled=' + opts.enabled); + if (opts.provider) params.push('provider=' + encodeURIComponent(opts.provider)); + } + if (params.length) url += '?' + params.join('&'); + return _json('GET', url); + }, + send: function(agentId, message, attachments) { + var body = { message: message }; + if (attachments) body.attachments = attachments; + return _json('POST', '/api/agents/' + encodeURIComponent(agentId) + '/send', body); + }, + sendStream: function(agentId, message, onEventOrOpts, attachments) { + // Supports two calling conventions: + // sendStream(id, msg, onEvent) — positional callback + // sendStream(id, msg, onEvent, attachments) — positional callback + attachments + // sendStream(id, msg, { onEvent, attachments }) — options object + var onEvent, atts; + if (typeof onEventOrOpts === 'function') { + onEvent = onEventOrOpts; + atts = attachments; + } else if (onEventOrOpts && typeof onEventOrOpts === 'object') { + onEvent = onEventOrOpts.onEvent; + atts = onEventOrOpts.attachments; + } + var body = { message: message }; + if (atts) body.attachments = atts; + // Uses fetch + streaming reader for SSE-style streaming + var aborted = false; + var controller = new AbortController(); + fetch('/api/agents/' + encodeURIComponent(agentId) + '/stream', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + signal: controller.signal + }).then(function(res) { + var reader = res.body.getReader(); + var decoder = new TextDecoder(); + var buffer = ''; + function pump() { + return reader.read().then(function(result) { + if (result.done || aborted) return; + buffer += decoder.decode(result.value, { stream: true }); + var lines = buffer.split('\n'); + buffer = lines.pop() || ''; + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (line.indexOf('data: ') === 0) { + var data = line.substring(6); + if (data === '[DONE]') return; + try { + var event = JSON.parse(data); + if (onEvent) onEvent(event); + } catch(e) { /* skip malformed lines */ } + } + } + return pump(); + }); + } + return pump(); + }).catch(function(err) { + if (!aborted && onEvent) onEvent({ kind: 'error', data: err.message }); + }); + return { close: function() { aborted = true; controller.abort(); } }; + }, + chat: { + send: function(agentId, message, attachments) { + return window.synthos.agents.send(agentId, message, attachments); + }, + sendStream: function(agentId, message, onEventOrOpts, attachments) { + return window.synthos.agents.sendStream(agentId, message, onEventOrOpts, attachments); + }, + history: function(agentId) { + return _json('POST', '/api/agents/' + encodeURIComponent(agentId) + '/chat/history', {}).then(function(res) { + return res.messages || []; + }); + }, + abort: function(agentId) { + return _json('POST', '/api/agents/' + encodeURIComponent(agentId) + '/chat/abort', {}); + }, + clear: function(agentId) { + return _json('POST', '/api/agents/' + encodeURIComponent(agentId) + '/chat/clear', {}); + } + }, + isEnabled: function(agentId) { + return _json('GET', '/api/agents?enabled=true').then(function(agents) { + return agents.some(function(a) { return a.id === agentId; }); + }); + }, + getCapabilities: function(agentId) { + return _json('GET', '/api/agents').then(function(agents) { + var agent = agents.find(function(a) { return a.id === agentId; }); + return agent ? (agent.capabilities || {}) : null; + }); + } + } + }; +})(); diff --git a/teams-page-scripts/page-v2.js b/teams-page-scripts/page-v2.js new file mode 100644 index 0000000..e2787ec --- /dev/null +++ b/teams-page-scripts/page-v2.js @@ -0,0 +1,655 @@ +(function() { + if (window.__synthOSChatPanel) return; + window.__synthOSChatPanel = true; + + // 0. First-run greeting — replace default greeting when ?firstRun=true + (function() { + var params = new URLSearchParams(window.location.search); + if (params.get('firstRun') !== 'true') return; + var greeting = document.getElementById('defaultGreeting'); + if (!greeting) return; + greeting.innerHTML = + '

Welcome to SynthOS!

' + + '

You\'re all set up and ready to start creating. This is the Builder — your main workspace. Just type what you want to build into the chat and SynthOS will generate it for you as a live, interactive page.

' + + '

You can create just about anything: dashboards, tools, games, visualizations, forms, calculators — if it can be expressed as a web page, you can build it here through conversation.

' + + '

How pages work:

' + + '
    ' + + '
  • Each creation lives on its own page. When you save, it gets a name and becomes part of your collection.
  • ' + + '
  • The Pages button (in the link bar above) takes you to the Pages Gallery where you can browse, open, and manage all your saved creations.
  • ' + + '
' + + '

Key actions:

' + + '
    ' + + '
  • Save — saves your current page. You\'ll be prompted for a name the first time. Save often! Your work only persists when you save it.
  • ' + + '
  • Reset — clears the current page back to a blank slate. Useful when you want to start fresh on something new.
  • ' + + '
' + + '

When you\'re ready, we recommend trying the Solar Tutorial — it\'s a guided walkthrough that will show you the basics of creating with SynthOS step by step.

' + + '

Have fun building!

'; + })(); + + // 1. Themed tooltips for chat panel controls + (function() { + var style = document.createElement('style'); + style.textContent = + '.synthos-tooltip {' + + 'position: fixed;' + + 'padding: 6px 10px;' + + 'background: var(--bg-tertiary, #0f0f23);' + + 'color: var(--text-secondary, #b794f6);' + + 'border: 1px solid var(--border-color, rgba(138,43,226,0.3));' + + 'border-radius: 6px;' + + 'font-size: 12px;' + + 'max-width: 150px;' + + 'text-align: center;' + + 'pointer-events: none;' + + 'z-index: 10000;' + + 'box-shadow: 0 2px 8px rgba(0,0,0,0.3);' + + 'opacity: 0;' + + 'transition: opacity 0.15s;' + + '}' + + '.synthos-tooltip.visible { opacity: 1; }'; + document.head.appendChild(style); + + var tip = document.createElement('div'); + tip.className = 'synthos-tooltip'; + document.body.appendChild(tip); + + function show(el) { + tip.textContent = el.getAttribute('data-tooltip'); + tip.style.display = 'block'; + tip.classList.remove('visible'); + var r = el.getBoundingClientRect(); + var tw = tip.offsetWidth; + var left = r.left + (r.width / 2) - (tw / 2); + if (left < 4) left = 4; + if (left + tw > window.innerWidth - 4) left = window.innerWidth - tw - 4; + tip.style.left = left + 'px'; + tip.style.top = (r.top - tip.offsetHeight - 6) + 'px'; + void tip.offsetWidth; + tip.classList.add('visible'); + } + + function hide() { + tip.classList.remove('visible'); + tip.style.display = 'none'; + } + hide(); + + function attach(el, text) { + el.setAttribute('data-tooltip', text); + el.addEventListener('mouseenter', function() { show(el); }); + el.addEventListener('mouseleave', hide); + } + + // Pages link is never renamed + var pagesLink = document.getElementById('pagesLink'); + if (pagesLink) attach(pagesLink, 'Browse all tabs'); + + // Save and Reset tooltips deferred — locked-mode renames them on DOMContentLoaded + document.addEventListener('DOMContentLoaded', function() { + setTimeout(function() { + var s = document.getElementById('saveLink'); + if (s) attach(s, s.textContent.trim() === 'Copy' ? 'Copy page as a new name' : 'Save page as a new name'); + var r = document.getElementById('resetLink'); + if (r) attach(r, r.textContent.trim() === 'Reload' ? 'Reload this page' : 'Reset page to default'); + }, 0); + }); + + window.__synthOSTooltip = attach; + })(); + + var chatInput = document.getElementById('chatInput'); + + // 2. Form submit handler — show overlay + disable inputs + var chatForm = document.getElementById('chatForm'); + if (chatForm) { + chatForm.addEventListener('submit', function() { + var overlay = document.getElementById('loadingOverlay'); + if (overlay) overlay.style.display = 'flex'; + chatForm.action = window.location.pathname; + setTimeout(function() { + var ci = document.getElementById('chatInput'); + if (ci) ci.disabled = true; + var sb = document.querySelector('.chat-submit'); + if (sb) sb.disabled = true; + document.querySelectorAll('.link-group a').forEach(function(a) { + a.style.pointerEvents = 'none'; + a.style.opacity = '0.5'; + }); + }, 50); + }); + } + + // 3. Save link handler — themed modal with title, categories, greeting + (function() { + var saveLink = document.getElementById('saveLink'); + if (!saveLink) return; + + // Detect if current page is a Builders or System page (start with blank fields) + var isBuilder = window.pageInfo && Array.isArray(window.pageInfo.categories) && + (window.pageInfo.categories.indexOf('Builders') !== -1 || + window.pageInfo.categories.indexOf('System') !== -1); + + // Original title for change detection + var originalTitle = (window.pageInfo && window.pageInfo.title) ? window.pageInfo.title : ''; + + // --- Create save modal --- + var modal = document.createElement('div'); + modal.id = 'saveModal'; + modal.className = 'modal-overlay'; + modal.innerHTML = + ''; + document.body.appendChild(modal); + + // --- Create error modal --- + var errorModal = document.createElement('div'); + errorModal.id = 'errorModal'; + errorModal.className = 'modal-overlay'; + errorModal.innerHTML = + ''; + document.body.appendChild(errorModal); + + // --- Element references --- + var titleInput = document.getElementById('saveTitleInput'); + var categoriesInput = document.getElementById('saveCategoriesInput'); + var greetingInput = document.getElementById('saveGreetingInput'); + var greetingHint = document.getElementById('saveGreetingHint'); + var titleError = document.getElementById('saveTitleError'); + var categoriesError = document.getElementById('saveCategoriesError'); + + // --- Greeting enable/disable based on title change --- + titleInput.addEventListener('input', function() { + var changed = titleInput.value.trim() !== originalTitle; + greetingInput.disabled = !changed; + if (changed) { + greetingInput.placeholder = 'Enter a custom greeting...'; + greetingHint.textContent = 'Replaces the initial Synthos greeting and removes chat history.'; + } else { + greetingInput.placeholder = 'Available when title changes'; + greetingInput.value = ''; + greetingHint.textContent = 'Change the title to enable a custom greeting.'; + } + }); + + // --- Open modal --- + function openSaveModal() { + // Pre-fill fields (blank for Builder pages) + titleInput.value = isBuilder ? '' : originalTitle; + categoriesInput.value = isBuilder ? '' : ( + (window.pageInfo && Array.isArray(window.pageInfo.categories)) + ? window.pageInfo.categories.join(', ') + : '' + ); + greetingInput.value = ''; + greetingInput.disabled = true; + greetingInput.placeholder = 'Available when title changes'; + greetingHint.textContent = 'Change the title to enable a custom greeting.'; + titleError.style.display = 'none'; + categoriesError.style.display = 'none'; + modal.classList.add('show'); + titleInput.focus(); + } + + function closeSaveModal() { + modal.classList.remove('show'); + } + + function showError(msg) { + document.getElementById('errorMessage').textContent = msg; + errorModal.classList.add('show'); + } + + function closeError() { + errorModal.classList.remove('show'); + } + + // --- Submit --- + function submitSave() { + var title = titleInput.value.trim(); + var cats = categoriesInput.value.trim(); + var greeting = greetingInput.value.trim(); + var valid = true; + + // Validate + if (!title) { + titleError.style.display = 'block'; + valid = false; + } else { + titleError.style.display = 'none'; + } + if (!cats) { + categoriesError.style.display = 'block'; + valid = false; + } else { + categoriesError.style.display = 'none'; + } + if (!valid) return; + + // Parse categories + var categories = cats.split(',').map(function(c) { return c.trim(); }).filter(Boolean); + + // Disable button during save + var confirmBtn = document.getElementById('saveConfirmBtn'); + confirmBtn.disabled = true; + confirmBtn.textContent = 'Saving...'; + + var body = { title: title, categories: categories }; + if (greeting && !greetingInput.disabled) { + body.greeting = greeting; + } + + fetch(window.location.pathname + '/save', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }) + .then(function(res) { + return res.json().then(function(data) { + return { ok: res.ok, data: data }; + }); + }) + .then(function(result) { + if (result.ok && result.data.redirect) { + window.location.href = result.data.redirect; + } else { + closeSaveModal(); + showError(result.data.error || 'An unknown error occurred'); + confirmBtn.disabled = false; + confirmBtn.textContent = 'Save'; + } + }) + .catch(function(err) { + closeSaveModal(); + showError('Network error: ' + err.message); + confirmBtn.disabled = false; + confirmBtn.textContent = 'Save'; + }); + } + + // --- Event listeners --- + saveLink.addEventListener('click', openSaveModal); + document.getElementById('saveCloseBtn').addEventListener('click', closeSaveModal); + document.getElementById('saveCancelBtn').addEventListener('click', closeSaveModal); + document.getElementById('saveConfirmBtn').addEventListener('click', submitSave); + document.getElementById('errorCloseBtn').addEventListener('click', closeError); + document.getElementById('errorOkBtn').addEventListener('click', closeError); + + var saveModalMouseDownTarget = null; + modal.addEventListener('mousedown', function(e) { saveModalMouseDownTarget = e.target; }); + modal.addEventListener('click', function(e) { + if (e.target === modal && saveModalMouseDownTarget === modal) closeSaveModal(); + saveModalMouseDownTarget = null; + }); + var errorModalMouseDownTarget = null; + errorModal.addEventListener('mousedown', function(e) { errorModalMouseDownTarget = e.target; }); + errorModal.addEventListener('click', function(e) { + if (e.target === errorModal && errorModalMouseDownTarget === errorModal) closeError(); + errorModalMouseDownTarget = null; + }); + + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape') { + if (modal.classList.contains('show')) closeSaveModal(); + if (errorModal.classList.contains('show')) closeError(); + } + }); + + // Enter key in title/categories inputs triggers save + titleInput.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { e.preventDefault(); submitSave(); } + }); + categoriesInput.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { e.preventDefault(); submitSave(); } + }); + })(); + + // 4. Reset link handler + var resetLink = document.getElementById('resetLink'); + if (resetLink) { + resetLink.addEventListener('click', function() { + window.location.href = window.location.pathname + '/reset'; + }); + } + + // 5. Chat scroll to bottom + var chatMessages = document.getElementById('chatMessages'); + if (chatMessages) { + chatMessages.scrollTo({ + top: chatMessages.scrollHeight, + behavior: 'smooth' + }); + } + + // 6. Chat panel header close + rail expand, persist in localStorage + (function() { + var STORAGE_KEY = 'synthos-chat-collapsed'; + var closeBtn = document.querySelector('.chat-panel-close'); + var rail = document.querySelector('.chat-rail'); + + if (localStorage.getItem(STORAGE_KEY) === 'true') { + document.body.classList.add('chat-collapsed'); + } + + function collapse() { + document.body.classList.add('chat-collapsed'); + localStorage.setItem(STORAGE_KEY, 'true'); + } + + function expand() { + document.body.classList.remove('chat-collapsed'); + localStorage.setItem(STORAGE_KEY, 'false'); + var ci = document.getElementById('chatInput'); + if (ci) ci.focus(); + } + + if (closeBtn) closeBtn.addEventListener('click', collapse); + if (rail) rail.addEventListener('click', expand); + })(); + + // 7. Focus management — prevent viewer content from stealing keystrokes + (function() { + var ci = document.getElementById('chatInput'); + var vp = document.getElementById('viewerPanel'); + if (!ci || !vp) return; + + ci.addEventListener('mousedown', function(e) { + e.stopPropagation(); + }); + + ['keydown', 'keyup', 'keypress'].forEach(function(type) { + document.addEventListener(type, function(e) { + if (document.activeElement === ci) { + e.stopImmediatePropagation(); + } + }, true); + }); + + vp.setAttribute('tabindex', '-1'); + ci.addEventListener('blur', function() { + vp.focus(); + }); + })(); + + // 8. Brainstorm — dynamic brainstorming UI (available on every v2 page) + (function() { + var chatInput = document.getElementById('chatInput'); + if (!chatInput) return; + + // --- Wrap chatInput in .chat-input-wrapper if not already wrapped --- + if (!chatInput.parentElement || !chatInput.parentElement.classList.contains('chat-input-wrapper')) { + var wrapper = document.createElement('div'); + wrapper.className = 'chat-input-wrapper'; + chatInput.parentNode.insertBefore(wrapper, chatInput); + wrapper.appendChild(chatInput); + } + + // --- Create brainstorm icon button --- + var brainstormBtn = document.createElement('button'); + brainstormBtn.type = 'button'; + brainstormBtn.className = 'brainstorm-icon-btn'; + if (window.__synthOSTooltip) window.__synthOSTooltip(brainstormBtn, 'Brainstorm ideas'); + brainstormBtn.innerHTML = '' + + '' + + '' + + ''; + chatInput.parentElement.appendChild(brainstormBtn); + + // --- Create brainstorm modal --- + var modal = document.createElement('div'); + modal.id = 'brainstormModal'; + modal.className = 'modal-overlay brainstorm-modal'; + modal.innerHTML = + ''; + document.body.appendChild(modal); + + // --- State --- + var brainstormHistory = []; + + // --- Helpers --- + function openBrainstorm() { + modal.classList.add('show'); + // Grab text from chat input as initial topic + var topic = chatInput.value.trim(); + if (topic) { + chatInput.value = ''; + sendBrainstormText(topic, true); + } else { + // No topic — send context-only opener so LLM starts the brainstorm + sendBrainstormText('', true); + } + } + + function closeBrainstorm() { + modal.classList.remove('show'); + brainstormHistory = []; + document.getElementById('brainstormMessages').innerHTML = ''; + } + + function scrollBrainstormToBottom() { + var el = document.getElementById('brainstormMessages'); + el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' }); + } + + function escapeHtml(str) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; + } + + function appendBrainstormMessage(role, text, prompt, suggestions, isOpener) { + var div = document.createElement('div'); + div.className = 'brainstorm-message ' + (role === 'user' ? 'brainstorm-user' : 'brainstorm-assistant'); + if (role === 'assistant') { + var html; + if (typeof marked !== 'undefined') { + html = marked.parse(text); + } else { + html = escapeHtml(text); + } + div.innerHTML = 'SynthOS: ' + html; + // Clickable suggestion chips + if (suggestions && suggestions.length > 0) { + var chips = document.createElement('div'); + chips.className = 'brainstorm-suggestions'; + suggestions.forEach(function(s) { + var chip = document.createElement('button'); + chip.type = 'button'; + chip.className = 'brainstorm-suggestion-chip'; + chip.textContent = s; + chip.addEventListener('click', function() { + submitSuggestion(s); + }); + chips.appendChild(chip); + }); + div.appendChild(chips); + } + // "Build It" button — skip on the opener response + if (prompt && !isOpener) { + var btnRow = document.createElement('div'); + btnRow.className = 'brainstorm-build-row'; + var buildBtn = document.createElement('button'); + buildBtn.type = 'button'; + buildBtn.className = 'brainstorm-build-btn'; + buildBtn.textContent = 'Build It'; + buildBtn.setAttribute('data-prompt', prompt); + buildBtn.addEventListener('click', function() { + chatInput.value = this.getAttribute('data-prompt'); + closeBrainstorm(); + chatInput.focus(); + }); + btnRow.appendChild(buildBtn); + div.appendChild(btnRow); + } + } else { + div.textContent = text; + } + document.getElementById('brainstormMessages').appendChild(div); + scrollBrainstormToBottom(); + } + + function submitSuggestion(text) { + // Disable old suggestion chips so they can't be double-clicked + var oldChips = document.querySelectorAll('#brainstormMessages .brainstorm-suggestion-chip'); + for (var i = 0; i < oldChips.length; i++) { + oldChips[i].disabled = true; + } + sendBrainstormText(text, false); + } + + function getBrainstormContext() { + var chatEl = document.getElementById('chatMessages'); + if (!chatEl) return '\n'; + var msgs = chatEl.querySelectorAll('.chat-message'); + var lines = []; + var started = false; + for (var i = 0; i < msgs.length; i++) { + var text = msgs[i].innerText; + if (!started && /^User:/i.test(text.trim())) started = true; + if (started) lines.push(text); + } + return '\n' + lines.join('\n'); + } + + // Send from the input field + function sendBrainstormMessage() { + var input = document.getElementById('brainstormInput'); + var text = input.value.trim(); + if (!text) return; + input.value = ''; + sendBrainstormText(text, false); + } + + // Core fetch — isOpener=true means this is the initial call when brainstorm opens + function sendBrainstormText(text, isOpener) { + var input = document.getElementById('brainstormInput'); + var userMsg = text || (isOpener ? 'Look at the conversation so far and suggest what we could build or improve.' : ''); + if (!userMsg) return; + + // Show user message in chat (skip for auto-generated opener) + if (text) { + appendBrainstormMessage('user', text); + } + brainstormHistory.push({ role: 'user', content: userMsg }); + + var thinking = document.createElement('div'); + thinking.className = 'brainstorm-thinking'; + thinking.id = 'brainstormThinking'; + thinking.textContent = 'Thinking...'; + document.getElementById('brainstormMessages').appendChild(thinking); + scrollBrainstormToBottom(); + + input.disabled = true; + document.getElementById('brainstormSendBtn').disabled = true; + + fetch('/api/brainstorm', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + context: getBrainstormContext(), + messages: brainstormHistory + }) + }) + .then(function(res) { + if (!res.ok) throw new Error('Brainstorm request failed'); + return res.json(); + }) + .then(function(data) { + var thinkingEl = document.getElementById('brainstormThinking'); + if (thinkingEl) thinkingEl.remove(); + + var response = data.response || 'Sorry, I didn\'t get a response.'; + var prompt = data.prompt || ''; + var suggestions = Array.isArray(data.suggestions) ? data.suggestions : []; + appendBrainstormMessage('assistant', response, prompt, suggestions, isOpener); + brainstormHistory.push({ + role: 'assistant', + content: response + '\n\n[Suggested prompt: ' + prompt + ']' + }); + }) + .catch(function(err) { + var thinkingEl = document.getElementById('brainstormThinking'); + if (thinkingEl) thinkingEl.remove(); + appendBrainstormMessage('assistant', 'Something went wrong: ' + err.message); + }) + .finally(function() { + input.disabled = false; + document.getElementById('brainstormSendBtn').disabled = false; + input.focus(); + }); + } + + // --- Event listeners --- + brainstormBtn.addEventListener('click', openBrainstorm); + document.getElementById('brainstormCloseBtn').addEventListener('click', closeBrainstorm); + + var brainstormMouseDownTarget = null; + modal.addEventListener('mousedown', function(e) { brainstormMouseDownTarget = e.target; }); + modal.addEventListener('click', function(e) { + if (e.target === modal && brainstormMouseDownTarget === modal) closeBrainstorm(); + brainstormMouseDownTarget = null; + }); + + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape' && modal.classList.contains('show')) closeBrainstorm(); + }); + + document.getElementById('brainstormSendBtn').addEventListener('click', sendBrainstormMessage); + document.getElementById('brainstormInput').addEventListener('keydown', function(e) { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendBrainstormMessage(); + } + }); + })(); + + // Initial focus — run after all setup (including chat-collapsed check) + if (chatInput && !document.body.classList.contains('chat-collapsed')) { + chatInput.focus(); + } +})(); diff --git a/teams-required-pages/.gitkeep b/teams-required-pages/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/teams-required-pages/builder.html b/teams-required-pages/builder.html new file mode 100644 index 0000000..9c9b725 --- /dev/null +++ b/teams-required-pages/builder.html @@ -0,0 +1,42 @@ + + + + Teams + + + + + + + + + +
+
+
+
+
+
+
+
Tab Designer
+
+
Tab Designer
+
+

Teams: What can I create for you? Ask "what can you do?" or "how does this work?" to learn more. Remember to save often!

+
+ +
+ + +
+
+ + + + + + \ No newline at end of file diff --git a/teams-required-pages/builder.json b/teams-required-pages/builder.json new file mode 100644 index 0000000..68086e7 --- /dev/null +++ b/teams-required-pages/builder.json @@ -0,0 +1 @@ +{ "title": "Builder", "categories": ["System"], "pinned": true, "showInAll": true, "pageVersion": 2, "mode": "unlockedS" } diff --git a/teams-required-pages/settings.html b/teams-required-pages/settings.html new file mode 100644 index 0000000..54993db --- /dev/null +++ b/teams-required-pages/settings.html @@ -0,0 +1,1201 @@ + + + + Teams - Settings + + + + + + + + +
+
Settings
+
+ +
+ +
+
+
+ + +
+
+
+ +
+
+
+ +
+ +
+
+ + +
+
Page Builder Model
+
+ + +
+
+ + + +
+
+ + +
+ ▾ More settings +
+ + +
+
+ + +
+
+ +
+
Chat Model
+
+ + +
+
+ + +
+
+ + +
+ ▾ More settings +
+ + +
+
+ + +
+
+ +
+
+ +
+
+
+ +
+ +
+
+
+
Configure agents (A2A or OpenClaw) that your pages can communicate with.
+ +
+
+ +
+
+ Chat with Agent + +
+
+
+ + +
+
+
+
+
+ +
+
+
+
Tab Designer
+
+
Tab Designer
+
+
+

Teams: Configure your settings below. Use the accordion sections to navigate between General settings, Model configuration (Page Builder & Chat Completion), and Additional Features.

+

The Page Builder Model is used when building pages via chat. The Chat Model is used by pages that call synthos.generate.completion().

+
+ +
+ +
+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/teams-required-pages/settings.json b/teams-required-pages/settings.json new file mode 100644 index 0000000..c5e2618 --- /dev/null +++ b/teams-required-pages/settings.json @@ -0,0 +1 @@ +{ "title": "Settings", "categories": ["System"], "pinned": true, "showInAll": true, "pageVersion": 2, "mode": "locked" } diff --git a/teams-required-pages/tabs.html b/teams-required-pages/tabs.html new file mode 100644 index 0000000..12ee74f --- /dev/null +++ b/teams-required-pages/tabs.html @@ -0,0 +1,942 @@ + + + + Teams - Tabs + + + + + + + + + + +
+
Tabs
+ + + + + + +
+

Tabs

+
+
+ +
+ + +
+
+
+ + + + + + + + + +
+
+
Tab Designer
+
+
Tab Designer
+
+
+

Teams: Here are all of your tabs. Right-click any tab to pin it to + favorites, edit its definition, or copy it to a new tab.

+
+
+ +
+ + +
+
+ + +
+
+
Edit page definition
+
Copy to new page
+
Update page
+
+
Download page
+
+ + + + +
+ + + + + \ No newline at end of file diff --git a/teams-required-pages/tabs.json b/teams-required-pages/tabs.json new file mode 100644 index 0000000..ce490e9 --- /dev/null +++ b/teams-required-pages/tabs.json @@ -0,0 +1 @@ +{ "title": "Tabs", "categories": ["System"], "pinned": true, "showInAll": true, "pageVersion": 2, "mode": "locked" } diff --git a/teams-service-connectors/.gitkeep b/teams-service-connectors/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/teams-tests/.gitkeep b/teams-tests/.gitkeep new file mode 100644 index 0000000..e69de29