ltcai 2.2.7 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +72 -34
  2. package/docs/CHANGELOG.md +119 -0
  3. package/docs/V3_BACKEND_ARCHITECTURE.md +138 -0
  4. package/docs/V3_FRONTEND.md +139 -0
  5. package/knowledge_graph.py +649 -21
  6. package/latticeai/__init__.py +1 -1
  7. package/latticeai/api/admin.py +47 -0
  8. package/latticeai/api/agents.py +54 -31
  9. package/latticeai/api/auth.py +5 -2
  10. package/latticeai/api/chat.py +10 -2
  11. package/latticeai/api/search.py +240 -0
  12. package/latticeai/api/static_routes.py +11 -2
  13. package/latticeai/core/config.py +18 -0
  14. package/latticeai/core/embedding_providers.py +625 -0
  15. package/latticeai/core/local_embeddings.py +86 -0
  16. package/latticeai/core/workspace_os.py +1 -1
  17. package/latticeai/server_app.py +65 -1
  18. package/latticeai/services/agent_runtime.py +245 -0
  19. package/latticeai/services/search_service.py +346 -0
  20. package/package.json +13 -6
  21. package/scripts/build_v3_assets.mjs +164 -0
  22. package/scripts/capture/README.md +28 -0
  23. package/scripts/capture/capture_enterprise.js +8 -0
  24. package/scripts/capture/capture_graph.js +8 -0
  25. package/scripts/capture/capture_onboarding.js +8 -0
  26. package/scripts/capture/capture_page.js +43 -0
  27. package/scripts/capture/capture_release_media.js +125 -0
  28. package/scripts/capture/capture_skills.js +8 -0
  29. package/scripts/capture/capture_workspace.js +8 -0
  30. package/scripts/generate_diagrams.py +513 -0
  31. package/scripts/lint_v3.mjs +33 -0
  32. package/scripts/release-0.3.1.sh +105 -0
  33. package/scripts/take_screenshots.js +69 -0
  34. package/scripts/validate_release_artifacts.py +167 -0
  35. package/static/account.html +9 -9
  36. package/static/activity.html +4 -4
  37. package/static/admin.html +8 -8
  38. package/static/agents.html +4 -4
  39. package/static/chat.html +10 -10
  40. package/static/css/reference/account.css +137 -1
  41. package/static/css/reference/chat.css +31 -37
  42. package/static/css/responsive.css +42 -0
  43. package/static/css/tokens.5a595671.css +260 -0
  44. package/static/css/tokens.css +125 -130
  45. package/static/graph.html +9 -9
  46. package/static/manifest.json +3 -3
  47. package/static/plugins.html +4 -4
  48. package/static/scripts/account.js +4 -4
  49. package/static/scripts/chat.js +40 -8
  50. package/static/scripts/workspace.js +78 -0
  51. package/static/sw.js +3 -1
  52. package/static/v3/asset-manifest.json +47 -0
  53. package/static/v3/css/lattice.base.css +128 -0
  54. package/static/v3/css/lattice.base.e4cdd05d.css +128 -0
  55. package/static/v3/css/lattice.components.011e988b.css +447 -0
  56. package/static/v3/css/lattice.components.css +447 -0
  57. package/static/v3/css/lattice.shell.4920f42d.css +407 -0
  58. package/static/v3/css/lattice.shell.css +407 -0
  59. package/static/v3/css/lattice.tokens.c597ff81.css +132 -0
  60. package/static/v3/css/lattice.tokens.css +132 -0
  61. package/static/v3/css/lattice.views.3ee19d4e.css +277 -0
  62. package/static/v3/css/lattice.views.css +277 -0
  63. package/static/v3/index.html +69 -0
  64. package/static/v3/js/app.46fb61d9.js +26 -0
  65. package/static/v3/js/app.js +26 -0
  66. package/static/v3/js/core/api.22a41d42.js +344 -0
  67. package/static/v3/js/core/api.js +344 -0
  68. package/static/v3/js/core/components.4c83e0a9.js +222 -0
  69. package/static/v3/js/core/components.js +222 -0
  70. package/static/v3/js/core/dom.a2773eb0.js +148 -0
  71. package/static/v3/js/core/dom.js +148 -0
  72. package/static/v3/js/core/router.584570f2.js +37 -0
  73. package/static/v3/js/core/router.js +37 -0
  74. package/static/v3/js/core/routes.f935dd50.js +78 -0
  75. package/static/v3/js/core/routes.js +78 -0
  76. package/static/v3/js/core/shell.1b6199d6.js +363 -0
  77. package/static/v3/js/core/shell.js +363 -0
  78. package/static/v3/js/core/store.34ebd5e6.js +113 -0
  79. package/static/v3/js/core/store.js +113 -0
  80. package/static/v3/js/views/admin-audit.660a1fb1.js +185 -0
  81. package/static/v3/js/views/admin-audit.js +185 -0
  82. package/static/v3/js/views/admin-permissions.a7ae5f09.js +177 -0
  83. package/static/v3/js/views/admin-permissions.js +177 -0
  84. package/static/v3/js/views/admin-policies.3658fd86.js +102 -0
  85. package/static/v3/js/views/admin-policies.js +102 -0
  86. package/static/v3/js/views/admin-private-vpc.7d342d36.js +135 -0
  87. package/static/v3/js/views/admin-private-vpc.js +135 -0
  88. package/static/v3/js/views/admin-security.07c66b72.js +180 -0
  89. package/static/v3/js/views/admin-security.js +180 -0
  90. package/static/v3/js/views/admin-users.03bac88c.js +168 -0
  91. package/static/v3/js/views/admin-users.js +168 -0
  92. package/static/v3/js/views/agents.14e48bdd.js +193 -0
  93. package/static/v3/js/views/agents.js +193 -0
  94. package/static/v3/js/views/chat.718144ce.js +449 -0
  95. package/static/v3/js/views/chat.js +449 -0
  96. package/static/v3/js/views/files.4935197e.js +186 -0
  97. package/static/v3/js/views/files.js +186 -0
  98. package/static/v3/js/views/home.cdde3b32.js +119 -0
  99. package/static/v3/js/views/home.js +119 -0
  100. package/static/v3/js/views/hybrid-search.b22b97e0.js +195 -0
  101. package/static/v3/js/views/hybrid-search.js +195 -0
  102. package/static/v3/js/views/knowledge-graph.a14ea7e7.js +237 -0
  103. package/static/v3/js/views/knowledge-graph.js +237 -0
  104. package/static/v3/js/views/models.a1ffa147.js +256 -0
  105. package/static/v3/js/views/models.js +256 -0
  106. package/static/v3/js/views/my-computer.1b2ff621.js +237 -0
  107. package/static/v3/js/views/my-computer.js +237 -0
  108. package/static/v3/js/views/pipeline.c522f1ce.js +157 -0
  109. package/static/v3/js/views/pipeline.js +157 -0
  110. package/static/v3/js/views/settings.4f777210.js +250 -0
  111. package/static/v3/js/views/settings.js +250 -0
  112. package/static/workflows.html +4 -4
  113. package/static/workspace.css +340 -2
  114. package/static/workspace.html +43 -24
  115. package/docs/images/tmp_frames/frame_00.png +0 -0
  116. package/docs/images/tmp_frames/frame_01.png +0 -0
  117. package/docs/images/tmp_frames/frame_02.png +0 -0
  118. package/docs/images/tmp_frames/frame_03.png +0 -0
  119. package/docs/images/tmp_frames/hero_00.png +0 -0
  120. package/docs/images/tmp_frames/hero_01.png +0 -0
  121. package/docs/images/tmp_frames/hero_02.png +0 -0
  122. package/docs/images/tmp_frames/hero_03.png +0 -0
@@ -15,6 +15,8 @@ const state = {
15
15
 
16
16
  // Skills that match common workspace needs are surfaced under "Recommended".
17
17
  const RECOMMENDED_SKILL_HINTS = ["code", "review", "doc", "test", "security", "research", "changelog", "refactor", "debug"];
18
+ const MODE_KEY = "ltcai_workspace_mode";
19
+ const LANG_KEY = "ltcai_lang";
18
20
 
19
21
  function $(id) {
20
22
  return document.getElementById(id);
@@ -51,6 +53,61 @@ function toast(message) {
51
53
  node._timer = setTimeout(() => node.classList.remove("show"), 2200);
52
54
  }
53
55
 
56
+ function adminAvailableForWorkspace(workspace) {
57
+ const storedAdmin = (() => {
58
+ try { return localStorage.getItem("ltcai_is_admin") === "true"; } catch { return false; }
59
+ })();
60
+ const role = String(workspace?.your_role || "").toLowerCase();
61
+ return storedAdmin || role === "owner" || role === "admin";
62
+ }
63
+
64
+ function currentWorkspaceMode() {
65
+ try {
66
+ const mode = localStorage.getItem(MODE_KEY);
67
+ return ["basic", "advanced", "admin"].includes(mode) ? mode : "basic";
68
+ } catch {
69
+ return "basic";
70
+ }
71
+ }
72
+
73
+ function applyWorkspaceMode(mode, { adminAvailable = false } = {}) {
74
+ if (mode === "admin" && !adminAvailable) mode = "basic";
75
+ const shell = document.querySelector(".workspace-shell");
76
+ if (shell) shell.dataset.workspaceMode = mode;
77
+ document.querySelectorAll("[data-workspace-mode]").forEach((button) => {
78
+ if (button.matches("button")) {
79
+ const active = button.dataset.workspaceMode === mode;
80
+ button.classList.toggle("active", active);
81
+ button.setAttribute("aria-selected", active ? "true" : "false");
82
+ button.disabled = button.dataset.workspaceMode === "admin" && !adminAvailable;
83
+ }
84
+ });
85
+ try { localStorage.setItem(MODE_KEY, mode); } catch {}
86
+ }
87
+
88
+ function updateWorkspaceChrome(activeWorkspace) {
89
+ const shell = document.querySelector(".workspace-shell");
90
+ const adminAvailable = adminAvailableForWorkspace(activeWorkspace);
91
+ if (shell) shell.dataset.adminAvailable = adminAvailable ? "true" : "false";
92
+ applyWorkspaceMode(currentWorkspaceMode(), { adminAvailable });
93
+ const lang = (() => {
94
+ try { return localStorage.getItem(LANG_KEY) || "en"; } catch { return "en"; }
95
+ })();
96
+ document.documentElement.lang = lang;
97
+ const langSelect = $("workspace-language");
98
+ if (langSelect) langSelect.value = lang;
99
+ }
100
+
101
+ async function logoutWorkspace() {
102
+ try { await api("/logout", { method: "POST" }); } catch (_) {}
103
+ try {
104
+ localStorage.removeItem("ltcai_user_email");
105
+ localStorage.removeItem("ltcai_user_nickname");
106
+ localStorage.removeItem("ltcai_is_admin");
107
+ } catch (_) {}
108
+ window.location.href = "/account";
109
+ }
110
+
54
111
  function renderMetrics(os) {
55
112
  const counts = os?.counts || {};
56
113
  const graph = os?.graph || {};
@@ -363,6 +420,7 @@ function renderWorkspaceRegistry(registry, edition) {
363
420
  `).join("");
364
421
  }
365
422
  const active = workspaces.find((ws) => ws.workspace_id === state.activeWorkspace);
423
+ updateWorkspaceChrome(active);
366
424
  const rolePill = $("workspace-role");
367
425
  if (rolePill) rolePill.textContent = active ? (active.your_role || "—") : "";
368
426
  if (edition) {
@@ -837,6 +895,26 @@ document.addEventListener("change", async (event) => {
837
895
  });
838
896
 
839
897
  document.addEventListener("DOMContentLoaded", () => {
898
+ document.querySelectorAll("[data-workspace-mode]").forEach((button) => {
899
+ if (!button.matches("button")) return;
900
+ button.addEventListener("click", () => {
901
+ const shell = document.querySelector(".workspace-shell");
902
+ const adminAvailable = shell?.dataset.adminAvailable === "true";
903
+ applyWorkspaceMode(button.dataset.workspaceMode, { adminAvailable });
904
+ });
905
+ });
906
+ const language = $("workspace-language");
907
+ if (language) {
908
+ language.value = (() => {
909
+ try { return localStorage.getItem(LANG_KEY) || "en"; } catch { return "en"; }
910
+ })();
911
+ language.addEventListener("change", () => {
912
+ try { localStorage.setItem(LANG_KEY, language.value); } catch (_) {}
913
+ document.documentElement.lang = language.value;
914
+ });
915
+ }
916
+ const logoutButton = $("workspace-logout");
917
+ if (logoutButton) logoutButton.addEventListener("click", () => logoutWorkspace());
840
918
  $("refresh-btn").addEventListener("click", () => refreshAll().catch((err) => toast(err.message)));
841
919
  $("snapshot-now").addEventListener("click", () => createSnapshot().catch((err) => toast(err.message)));
842
920
  $("create-snapshot").addEventListener("click", () => createSnapshot().catch((err) => toast(err.message)));
package/static/sw.js CHANGED
@@ -1,9 +1,11 @@
1
1
  // Lattice AI Service Worker — enables PWA install on Android/iOS
2
2
  // Strategy: network-first for API, cache-first for static assets.
3
- const CACHE = "ltcai-v226";
3
+ const CACHE = "ltcai-v310";
4
4
  const STATIC = [
5
5
  "/",
6
+ "/app",
6
7
  "/workspace",
8
+ "/static/v3/asset-manifest.json",
7
9
  "/static/css/tokens.css",
8
10
  "/static/css/reference/base.css",
9
11
  "/static/css/reference/chat.css",
@@ -0,0 +1,47 @@
1
+ {
2
+ "version": "3.1.0",
3
+ "generated_at": "deterministic",
4
+ "entrypoints": {
5
+ "app": "/static/v3/js/app.46fb61d9.js",
6
+ "styles": [
7
+ "/static/css/tokens.5a595671.css",
8
+ "/static/v3/css/lattice.tokens.c597ff81.css",
9
+ "/static/v3/css/lattice.base.e4cdd05d.css",
10
+ "/static/v3/css/lattice.components.011e988b.css",
11
+ "/static/v3/css/lattice.shell.4920f42d.css",
12
+ "/static/v3/css/lattice.views.3ee19d4e.css"
13
+ ]
14
+ },
15
+ "assets": {
16
+ "static/css/tokens.css": "/static/css/tokens.5a595671.css",
17
+ "static/v3/css/lattice.tokens.css": "/static/v3/css/lattice.tokens.c597ff81.css",
18
+ "static/v3/css/lattice.base.css": "/static/v3/css/lattice.base.e4cdd05d.css",
19
+ "static/v3/css/lattice.components.css": "/static/v3/css/lattice.components.011e988b.css",
20
+ "static/v3/css/lattice.shell.css": "/static/v3/css/lattice.shell.4920f42d.css",
21
+ "static/v3/css/lattice.views.css": "/static/v3/css/lattice.views.3ee19d4e.css",
22
+ "static/v3/js/app.js": "/static/v3/js/app.46fb61d9.js",
23
+ "static/v3/js/core/api.js": "/static/v3/js/core/api.22a41d42.js",
24
+ "static/v3/js/core/components.js": "/static/v3/js/core/components.4c83e0a9.js",
25
+ "static/v3/js/core/dom.js": "/static/v3/js/core/dom.a2773eb0.js",
26
+ "static/v3/js/core/router.js": "/static/v3/js/core/router.584570f2.js",
27
+ "static/v3/js/core/routes.js": "/static/v3/js/core/routes.f935dd50.js",
28
+ "static/v3/js/core/shell.js": "/static/v3/js/core/shell.1b6199d6.js",
29
+ "static/v3/js/core/store.js": "/static/v3/js/core/store.34ebd5e6.js",
30
+ "static/v3/js/views/admin-audit.js": "/static/v3/js/views/admin-audit.660a1fb1.js",
31
+ "static/v3/js/views/admin-permissions.js": "/static/v3/js/views/admin-permissions.a7ae5f09.js",
32
+ "static/v3/js/views/admin-policies.js": "/static/v3/js/views/admin-policies.3658fd86.js",
33
+ "static/v3/js/views/admin-private-vpc.js": "/static/v3/js/views/admin-private-vpc.7d342d36.js",
34
+ "static/v3/js/views/admin-security.js": "/static/v3/js/views/admin-security.07c66b72.js",
35
+ "static/v3/js/views/admin-users.js": "/static/v3/js/views/admin-users.03bac88c.js",
36
+ "static/v3/js/views/agents.js": "/static/v3/js/views/agents.14e48bdd.js",
37
+ "static/v3/js/views/chat.js": "/static/v3/js/views/chat.718144ce.js",
38
+ "static/v3/js/views/files.js": "/static/v3/js/views/files.4935197e.js",
39
+ "static/v3/js/views/home.js": "/static/v3/js/views/home.cdde3b32.js",
40
+ "static/v3/js/views/hybrid-search.js": "/static/v3/js/views/hybrid-search.b22b97e0.js",
41
+ "static/v3/js/views/knowledge-graph.js": "/static/v3/js/views/knowledge-graph.a14ea7e7.js",
42
+ "static/v3/js/views/models.js": "/static/v3/js/views/models.a1ffa147.js",
43
+ "static/v3/js/views/my-computer.js": "/static/v3/js/views/my-computer.1b2ff621.js",
44
+ "static/v3/js/views/pipeline.js": "/static/v3/js/views/pipeline.c522f1ce.js",
45
+ "static/v3/js/views/settings.js": "/static/v3/js/views/settings.4f777210.js"
46
+ }
47
+ }
@@ -0,0 +1,128 @@
1
+ /* ============================================================================
2
+ * Lattice AI v3 — Base layer (reset + element defaults + lattice backdrop)
3
+ * Token-native: no themed hex values, everything via var(--*).
4
+ * ========================================================================== */
5
+
6
+ *, *::before, *::after { box-sizing: border-box; }
7
+
8
+ html, body { height: 100%; }
9
+
10
+ body {
11
+ margin: 0;
12
+ font-family: var(--lt3-font-sans);
13
+ font-size: var(--lt3-text-md);
14
+ line-height: var(--lt3-leading-normal);
15
+ color: var(--text);
16
+ background: var(--bg);
17
+ -webkit-font-smoothing: antialiased;
18
+ text-rendering: optimizeLegibility;
19
+ overflow: hidden; /* shell owns scroll regions */
20
+ }
21
+
22
+ /* The signature lattice backdrop — a faint structural mesh of nodes+edges.
23
+ Sits behind the whole app; reinforces the "lattice" identity without noise. */
24
+ .lt3-app::before {
25
+ content: "";
26
+ position: fixed;
27
+ inset: 0;
28
+ z-index: 0;
29
+ pointer-events: none;
30
+ background:
31
+ radial-gradient(circle at center, var(--lt3-mesh-node) 0.9px, transparent 1.1px),
32
+ linear-gradient(var(--lt3-mesh-line) 1px, transparent 1px),
33
+ linear-gradient(90deg, var(--lt3-mesh-line) 1px, transparent 1px),
34
+ var(--app-bg);
35
+ background-size:
36
+ var(--lt3-mesh-size) var(--lt3-mesh-size),
37
+ var(--lt3-mesh-size) var(--lt3-mesh-size),
38
+ var(--lt3-mesh-size) var(--lt3-mesh-size),
39
+ cover;
40
+ background-position: center;
41
+ mask-image: radial-gradient(ellipse 120% 90% at 50% -10%, #000 55%, transparent 100%);
42
+ opacity: 0.9;
43
+ }
44
+
45
+ h1, h2, h3, h4, p, figure { margin: 0; }
46
+
47
+ a { color: inherit; text-decoration: none; }
48
+
49
+ button {
50
+ font: inherit;
51
+ color: inherit;
52
+ cursor: pointer;
53
+ border: none;
54
+ background: none;
55
+ }
56
+
57
+ input, select, textarea { font: inherit; color: inherit; }
58
+
59
+ textarea { resize: vertical; }
60
+
61
+ img, svg { display: block; max-width: 100%; }
62
+
63
+ code, pre, kbd, samp { font-family: var(--lt3-font-mono); }
64
+
65
+ :root[data-lt-icons="fallback"] .ti {
66
+ display: inline-grid;
67
+ place-items: center;
68
+ min-width: 1em;
69
+ min-height: 1em;
70
+ font-family: var(--lt3-font-mono);
71
+ font-size: 0.72em;
72
+ font-style: normal;
73
+ font-weight: 800;
74
+ line-height: 1;
75
+ text-transform: uppercase;
76
+ }
77
+
78
+ :root[data-lt-icons="fallback"] .ti::before {
79
+ content: attr(data-fallback);
80
+ }
81
+
82
+ :focus-visible {
83
+ outline: 2px solid var(--focus-ring);
84
+ outline-offset: 2px;
85
+ border-radius: var(--lt3-radius-xs);
86
+ }
87
+
88
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
89
+ ::-webkit-scrollbar-track { background: transparent; }
90
+ ::-webkit-scrollbar-thumb {
91
+ background: color-mix(in srgb, var(--border-strong) 60%, transparent);
92
+ border-radius: 99px;
93
+ border: 2px solid transparent;
94
+ background-clip: padding-box;
95
+ }
96
+ ::-webkit-scrollbar-thumb:hover { background: var(--border-strong); background-clip: padding-box; }
97
+
98
+ /* Accessible visually-hidden utility */
99
+ .lt3-sr {
100
+ position: absolute !important;
101
+ width: 1px; height: 1px;
102
+ padding: 0; margin: -1px;
103
+ overflow: hidden; clip: rect(0,0,0,0);
104
+ white-space: nowrap; border: 0;
105
+ }
106
+
107
+ .lt3-skip {
108
+ position: fixed;
109
+ top: var(--lt3-space-3);
110
+ left: var(--lt3-space-3);
111
+ z-index: var(--lt3-z-toast);
112
+ padding: var(--lt3-space-2) var(--lt3-space-4);
113
+ background: var(--accent);
114
+ color: #fff;
115
+ border-radius: var(--lt3-radius-sm);
116
+ transform: translateY(-200%);
117
+ transition: transform var(--lt3-dur-2) var(--lt3-ease);
118
+ }
119
+ .lt3-skip:focus { transform: translateY(0); }
120
+
121
+ @media (prefers-reduced-motion: reduce) {
122
+ *, *::before, *::after {
123
+ animation-duration: 0.001ms !important;
124
+ animation-iteration-count: 1 !important;
125
+ transition-duration: 0.001ms !important;
126
+ scroll-behavior: auto !important;
127
+ }
128
+ }
@@ -0,0 +1,128 @@
1
+ /* ============================================================================
2
+ * Lattice AI v3 — Base layer (reset + element defaults + lattice backdrop)
3
+ * Token-native: no themed hex values, everything via var(--*).
4
+ * ========================================================================== */
5
+
6
+ *, *::before, *::after { box-sizing: border-box; }
7
+
8
+ html, body { height: 100%; }
9
+
10
+ body {
11
+ margin: 0;
12
+ font-family: var(--lt3-font-sans);
13
+ font-size: var(--lt3-text-md);
14
+ line-height: var(--lt3-leading-normal);
15
+ color: var(--text);
16
+ background: var(--bg);
17
+ -webkit-font-smoothing: antialiased;
18
+ text-rendering: optimizeLegibility;
19
+ overflow: hidden; /* shell owns scroll regions */
20
+ }
21
+
22
+ /* The signature lattice backdrop — a faint structural mesh of nodes+edges.
23
+ Sits behind the whole app; reinforces the "lattice" identity without noise. */
24
+ .lt3-app::before {
25
+ content: "";
26
+ position: fixed;
27
+ inset: 0;
28
+ z-index: 0;
29
+ pointer-events: none;
30
+ background:
31
+ radial-gradient(circle at center, var(--lt3-mesh-node) 0.9px, transparent 1.1px),
32
+ linear-gradient(var(--lt3-mesh-line) 1px, transparent 1px),
33
+ linear-gradient(90deg, var(--lt3-mesh-line) 1px, transparent 1px),
34
+ var(--app-bg);
35
+ background-size:
36
+ var(--lt3-mesh-size) var(--lt3-mesh-size),
37
+ var(--lt3-mesh-size) var(--lt3-mesh-size),
38
+ var(--lt3-mesh-size) var(--lt3-mesh-size),
39
+ cover;
40
+ background-position: center;
41
+ mask-image: radial-gradient(ellipse 120% 90% at 50% -10%, #000 55%, transparent 100%);
42
+ opacity: 0.9;
43
+ }
44
+
45
+ h1, h2, h3, h4, p, figure { margin: 0; }
46
+
47
+ a { color: inherit; text-decoration: none; }
48
+
49
+ button {
50
+ font: inherit;
51
+ color: inherit;
52
+ cursor: pointer;
53
+ border: none;
54
+ background: none;
55
+ }
56
+
57
+ input, select, textarea { font: inherit; color: inherit; }
58
+
59
+ textarea { resize: vertical; }
60
+
61
+ img, svg { display: block; max-width: 100%; }
62
+
63
+ code, pre, kbd, samp { font-family: var(--lt3-font-mono); }
64
+
65
+ :root[data-lt-icons="fallback"] .ti {
66
+ display: inline-grid;
67
+ place-items: center;
68
+ min-width: 1em;
69
+ min-height: 1em;
70
+ font-family: var(--lt3-font-mono);
71
+ font-size: 0.72em;
72
+ font-style: normal;
73
+ font-weight: 800;
74
+ line-height: 1;
75
+ text-transform: uppercase;
76
+ }
77
+
78
+ :root[data-lt-icons="fallback"] .ti::before {
79
+ content: attr(data-fallback);
80
+ }
81
+
82
+ :focus-visible {
83
+ outline: 2px solid var(--focus-ring);
84
+ outline-offset: 2px;
85
+ border-radius: var(--lt3-radius-xs);
86
+ }
87
+
88
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
89
+ ::-webkit-scrollbar-track { background: transparent; }
90
+ ::-webkit-scrollbar-thumb {
91
+ background: color-mix(in srgb, var(--border-strong) 60%, transparent);
92
+ border-radius: 99px;
93
+ border: 2px solid transparent;
94
+ background-clip: padding-box;
95
+ }
96
+ ::-webkit-scrollbar-thumb:hover { background: var(--border-strong); background-clip: padding-box; }
97
+
98
+ /* Accessible visually-hidden utility */
99
+ .lt3-sr {
100
+ position: absolute !important;
101
+ width: 1px; height: 1px;
102
+ padding: 0; margin: -1px;
103
+ overflow: hidden; clip: rect(0,0,0,0);
104
+ white-space: nowrap; border: 0;
105
+ }
106
+
107
+ .lt3-skip {
108
+ position: fixed;
109
+ top: var(--lt3-space-3);
110
+ left: var(--lt3-space-3);
111
+ z-index: var(--lt3-z-toast);
112
+ padding: var(--lt3-space-2) var(--lt3-space-4);
113
+ background: var(--accent);
114
+ color: #fff;
115
+ border-radius: var(--lt3-radius-sm);
116
+ transform: translateY(-200%);
117
+ transition: transform var(--lt3-dur-2) var(--lt3-ease);
118
+ }
119
+ .lt3-skip:focus { transform: translateY(0); }
120
+
121
+ @media (prefers-reduced-motion: reduce) {
122
+ *, *::before, *::after {
123
+ animation-duration: 0.001ms !important;
124
+ animation-iteration-count: 1 !important;
125
+ transition-duration: 0.001ms !important;
126
+ scroll-behavior: auto !important;
127
+ }
128
+ }