@andrestobelem/clone 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -23
  3. package/app.js +645 -0
  4. package/bin/clone.js +11 -230
  5. package/dist/actors.js +49 -0
  6. package/dist/actors.js.map +1 -0
  7. package/dist/automations.js +57 -0
  8. package/dist/automations.js.map +1 -0
  9. package/dist/cli.js +565 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/database.js +62 -0
  12. package/dist/database.js.map +1 -0
  13. package/dist/errors.js +46 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/issues.js +264 -0
  16. package/dist/issues.js.map +1 -0
  17. package/dist/paths.js +42 -0
  18. package/dist/paths.js.map +1 -0
  19. package/dist/projects.js +134 -0
  20. package/dist/projects.js.map +1 -0
  21. package/dist/recurring.js +45 -0
  22. package/dist/recurring.js.map +1 -0
  23. package/dist/schema.js +52 -0
  24. package/dist/schema.js.map +1 -0
  25. package/dist/seed.js +498 -0
  26. package/dist/seed.js.map +1 -0
  27. package/dist/server.js +232 -0
  28. package/dist/server.js.map +1 -0
  29. package/dist/sync.js +625 -0
  30. package/dist/sync.js.map +1 -0
  31. package/dist/teams.js +77 -0
  32. package/dist/teams.js.map +1 -0
  33. package/dist/time.js +4 -0
  34. package/dist/time.js.map +1 -0
  35. package/dist/types.js +2 -0
  36. package/dist/types.js.map +1 -0
  37. package/dist/validation.js +254 -0
  38. package/dist/validation.js.map +1 -0
  39. package/dist/workspace-helpers.js +75 -0
  40. package/dist/workspace-helpers.js.map +1 -0
  41. package/dist/workspace-mutations.js +160 -0
  42. package/dist/workspace-mutations.js.map +1 -0
  43. package/dist/workspace-reads.js +62 -0
  44. package/dist/workspace-reads.js.map +1 -0
  45. package/dist/workspace.js +8 -0
  46. package/dist/workspace.js.map +1 -0
  47. package/index.html +73 -0
  48. package/package.json +33 -4
  49. package/styles.css +108 -0
@@ -0,0 +1,62 @@
1
+ import { actorRecord, actorRecords } from "./actors.js";
2
+ import { one, rows, sql } from "./database.js";
3
+ import { issueRow } from "./issues.js";
4
+ import { projectRecord } from "./projects.js";
5
+ import { cycleRecord } from "./teams.js";
6
+ import { parseJson } from "./workspace-helpers.js";
7
+ export function bootstrap(database, currentActor = 1) {
8
+ const workspace = one(database, "SELECT * FROM workspace LIMIT 1") ?? {};
9
+ const teams = rows(database, "SELECT * FROM teams ORDER BY id").map((team) => ({
10
+ ...team,
11
+ members: rows(database, "SELECT member_id FROM team_members WHERE team_id=?", sql(team.id)).map((row) => actorRecord(database, Number(row.member_id))),
12
+ statuses: rows(database, "SELECT * FROM issue_statuses WHERE team_id=? ORDER BY position", sql(team.id)),
13
+ resources: rows(database, "SELECT * FROM team_resources WHERE team_id=? ORDER BY section,id", sql(team.id)),
14
+ }));
15
+ const projects = rows(database, "SELECT * FROM projects WHERE archived=0 ORDER BY id DESC").map((project) => projectRecord(database, project));
16
+ const issues = rows(database, "SELECT id FROM issues WHERE archived=0 ORDER BY number DESC").map((row) => issueRow(database, Number(row.id)));
17
+ const views = rows(database, "SELECT * FROM views ORDER BY is_favorite DESC,updated_at DESC").map(({ filters_json, display_json, ...view }) => ({
18
+ ...view,
19
+ filters: parseJson(filters_json, {}),
20
+ display: parseJson(display_json, {}),
21
+ }));
22
+ const cycles = rows(database, "SELECT c.*,t.name AS team_name,t.key FROM cycles c JOIN teams t ON t.id=c.team_id ORDER BY c.starts_at DESC").map((cycle) => cycleRecord(database, cycle));
23
+ const settings = Object.fromEntries(rows(database, "SELECT * FROM app_settings").map((row) => [
24
+ String(row.key),
25
+ parseJson(row.value_json, {}),
26
+ ]));
27
+ return {
28
+ workspace,
29
+ teams,
30
+ members: rows(database, "SELECT * FROM members WHERE active=1 ORDER BY name"),
31
+ issues,
32
+ projects,
33
+ views,
34
+ notifications: rows(database, "SELECT * FROM notifications WHERE archived=0 AND recipient_id=? ORDER BY created_at DESC", currentActor),
35
+ cycles,
36
+ statuses: rows(database, "SELECT * FROM issue_statuses ORDER BY team_id,position"),
37
+ labels: rows(database, "SELECT * FROM labels ORDER BY name"),
38
+ recurring: rows(database, "SELECT * FROM recurring_rules ORDER BY id"),
39
+ automations: rows(database, "SELECT * FROM automations ORDER BY id"),
40
+ archives: {
41
+ issues: rows(database, "SELECT id FROM issues WHERE archived=1 ORDER BY updated_at DESC").map((row) => issueRow(database, Number(row.id))),
42
+ projects: rows(database, "SELECT id,name,summary,status,updated_at FROM projects WHERE archived=1 ORDER BY updated_at DESC"),
43
+ },
44
+ settings,
45
+ currentMemberId: 1,
46
+ currentActorId: currentActor,
47
+ actors: actorRecords(database, null, true),
48
+ };
49
+ }
50
+ export function search(database, query, scope = "All") {
51
+ const trimmed = query.trim();
52
+ if (!trimmed)
53
+ return { results: [] };
54
+ const like = `%${trimmed}%`;
55
+ const result = [];
56
+ if (["All", "Issues"].includes(scope))
57
+ result.push(...rows(database, "SELECT i.id,i.identifier AS key,i.title,s.name AS status,'Issue' AS type FROM issues i JOIN issue_statuses s ON s.id=i.status_id WHERE i.archived=0 AND (i.identifier LIKE ? OR i.title LIKE ? OR i.description LIKE ?) ORDER BY i.updated_at DESC LIMIT 40", like, like, like));
58
+ if (["All", "Projects"].includes(scope))
59
+ result.push(...rows(database, "SELECT id,id AS key,name AS title,status,'Project' AS type FROM projects WHERE archived=0 AND (name LIKE ? OR summary LIKE ? OR description LIKE ?) LIMIT 30", like, like, like));
60
+ return { results: result.slice(0, 80) };
61
+ }
62
+ //# sourceMappingURL=workspace-reads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-reads.js","sourceRoot":"","sources":["../src/workspace-reads.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export { actorRecord, actorRecords, requireActor } from "./actors.js";
2
+ export { issueRow } from "./issues.js";
3
+ export { runDueRecurring } from "./recurring.js";
4
+ export { createMemberActors, initializeSchema, seedDemo, seedEmpty, } from "./seed.js";
5
+ export { now } from "./time.js";
6
+ export { dispatchMutation } from "./workspace-mutations.js";
7
+ export { bootstrap, search } from "./workspace-reads.js";
8
+ //# sourceMappingURL=workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,EACR,SAAS,GACV,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEhC,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC"}
package/index.html ADDED
@@ -0,0 +1,73 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="theme-color" content="#f0f0f2" />
7
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='14' fill='%23158bc5'/%3E%3Cpath d='M10 23 15 8h2l5 15h-3l-1-4h-5l-1 4Zm4-7h3l-1.5-5Z' fill='white'/%3E%3C/svg%3E" />
8
+ <title>Clone — Issues and projects</title>
9
+ <link rel="stylesheet" href="styles.css" />
10
+ </head>
11
+ <body>
12
+ <div id="app" class="app-shell">
13
+ <div class="window-bar">
14
+ <div class="window-lights" aria-hidden="true"><i></i><i></i><i></i></div>
15
+ <button class="window-sidebar" title="Toggle sidebar">▣</button>
16
+ <button class="window-history" aria-label="Recently viewed unavailable" disabled title="Recently viewed unavailable">◷</button>
17
+ <button class="window-arrow" aria-label="Back unavailable" disabled title="Back unavailable">‹</button>
18
+ <button class="window-arrow" aria-label="Forward unavailable" disabled title="Forward unavailable">›</button>
19
+ <div class="window-tab"><span id="tab-mark">◉</span><span id="tab-title">Issues</span></div>
20
+ </div>
21
+ <div class="workspace-shell">
22
+ <aside class="sidebar" id="sidebar">
23
+ <div class="workspace-switcher">
24
+ <button class="workspace-current" id="workspace-open" aria-haspopup="menu" title="Open workspace menu">
25
+ <span class="workspace-avatar">A</span>
26
+ <span class="workspace-name">acme studio</span>
27
+ <span class="chevron">⌄</span>
28
+ </button>
29
+ <button class="icon-button workspace-search" id="search-open" aria-label="Search workspace" title="Search · ⌘ K"><svg viewBox="0 0 24 24"><circle cx="10.8" cy="10.8" r="6.6"/><path d="m16 16 4.2 4.2"/></svg></button>
30
+ <button class="create-round" id="create-open" aria-label="Create new issue" title="Create issue · C"><svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg></button>
31
+ </div>
32
+ <nav class="sidebar-nav" aria-label="Main navigation">
33
+ <div class="primary-links">
34
+ <button class="nav-item" aria-label="Inbox" data-page="Inbox"><svg viewBox="0 0 24 24"><path d="M4 5.5h16l1 10.8a2 2 0 0 1-2 2.2h-4.3l-2.7-3h-2l-2.7 3H3a2 2 0 0 1-2-2.2l1-10.8h2Z"/><path d="M3 14h4l2 2h6l2-2h4"/></svg><span>Inbox</span><span class="nav-count">3</span></button>
35
+ <button class="nav-item" aria-label="My issues" data-page="My issues"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M12 7v5l3.4 2"/><path d="M4 4 2.5 2.5M20 4l1.5-1.5"/></svg><span>My issues</span></button>
36
+ </div>
37
+ <div class="nav-section"><button class="section-label"><span>Workspace</span><span>⌄</span></button>
38
+ <button class="nav-item" aria-label="Projects" data-page="Projects"><svg viewBox="0 0 24 24"><path d="m12 2.8 9 5.1v8.2l-9 5.1-9-5.1V7.9l9-5.1Z"/><path d="m3.5 8.2 8.5 5 8.5-5M12 13.2v7.5"/></svg><span>Projects</span></button>
39
+ <button class="nav-item" aria-label="Views" data-page="Views"><svg viewBox="0 0 24 24"><path d="m12 3 9 5-9 5-9-5 9-5Z"/><path d="m3 12 9 5 9-5M3 16l9 5 9-5"/></svg><span>Views</span></button>
40
+ <button class="nav-item" aria-label="More" data-page="More"><svg viewBox="0 0 24 24"><circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/></svg><span>More</span></button>
41
+ </div>
42
+ <div class="nav-section team-section"><button class="section-label"><span>Your teams</span><span>⌄</span></button><button class="add-team" aria-label="Create team">+</button>
43
+ <button class="team-name" type="button" aria-label="Select team"><span class="team-emoji">✳</span><span>Product</span><span class="chevron">⌄</span></button>
44
+ <button class="nav-item team-item" aria-label="Home" data-page="Home"><svg viewBox="0 0 24 24"><path d="m3 10 9-7 9 7v10a1 1 0 0 1-1 1h-6v-7h-4v7H4a1 1 0 0 1-1-1V10Z"/></svg><span>Home</span></button>
45
+ <button class="nav-item team-item active" aria-label="Issues" data-page="Issues"><svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="4"/><path d="M7 9h10M7 13h7M7 17h4"/></svg><span>Issues</span></button>
46
+ <button class="nav-item team-item" aria-label="Team projects" data-page="Team projects"><svg viewBox="0 0 24 24"><path d="m12 2.8 9 5.1v8.2l-9 5.1-9-5.1V7.9l9-5.1Z"/><path d="m3.5 8.2 8.5 5 8.5-5M12 13.2v7.5"/></svg><span>Projects</span></button>
47
+ <button class="nav-item team-item" aria-label="Team views" data-page="Team views"><svg viewBox="0 0 24 24"><path d="m12 3 9 5-9 5-9-5 9-5Z"/><path d="m3 12 9 5 9-5"/></svg><span>Views</span></button>
48
+ <button class="nav-item team-item" aria-label="Members" data-page="Members"><svg viewBox="0 0 24 24"><circle cx="9" cy="8" r="3"/><path d="M3 20c.4-3.4 2.4-5 6-5s5.6 1.6 6 5M17 5.5a3 3 0 0 1 0 5.8M17 15c2.6.3 3.8 1.7 4 4"/></svg><span>Members</span></button>
49
+ </div>
50
+ <div class="sidebar-bottom">
51
+ <div class="user-profile"><div class="user-avatar">AM</div><div class="user-meta"><b>Alex Morgan</b><small>Local workspace</small></div><button class="profile-more" aria-label="Profile menu unavailable" disabled>···</button></div>
52
+ </div>
53
+ </nav>
54
+ </aside>
55
+ <main class="main-panel">
56
+ <header class="page-header">
57
+ <div class="page-title-wrap"><div class="breadcrumb" id="breadcrumb">Product / Issues</div><h1 id="page-title">Issues</h1></div>
58
+ <div class="header-actions"><button class="quiet-button" id="share-view" aria-label="Copy view link"><svg viewBox="0 0 24 24"><path d="M12 16V3m-5 5 5-5 5 5"/><path d="M5 13v6a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6"/></svg><span>Copy link</span></button><button class="new-project-button" id="new-issue-header"><span>+</span> New issue</button><button class="new-project-button" id="new-project"><span>+</span> New project</button></div>
59
+ </header>
60
+ <section class="view-toolbar" id="view-toolbar">
61
+ <div class="view-tabs" id="view-tabs"><button class="view-pill selected" data-view="All issues">All issues</button><button class="view-pill" data-view="Active">Active</button><button class="view-pill" data-view="Backlog">Backlog</button><button class="saved-view" title="Add view">◈</button></div>
62
+ <div class="toolbar-actions"><button class="toolbar-button" id="filter-button" aria-label="Filter"><svg viewBox="0 0 24 24"><path d="M4 6h16M7 12h10m-7 6h4"/></svg><span>Filter</span></button><button class="toolbar-button" id="display-button" aria-label="Display"><svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h16"/><circle cx="9" cy="6" r="2"/><circle cx="15" cy="12" r="2"/><circle cx="8" cy="18" r="2"/></svg><span>Display</span></button><button class="icon-button details-toggle" id="details-toggle" title="Toggle details panel"><svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M15 4v16"/></svg></button></div>
63
+ </section>
64
+ <section class="content-area" id="content-area"></section>
65
+ <div class="toast" id="toast" role="status"></div>
66
+ </main>
67
+ </div>
68
+ <div class="modal-layer hidden" id="modal-layer"></div>
69
+ <div class="command-layer hidden" id="command-layer"></div>
70
+ </div>
71
+ <script src="app.js"></script>
72
+ </body>
73
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andrestobelem/clone",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Run the Clone issue and project workspace locally.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -9,15 +9,44 @@
9
9
  },
10
10
  "homepage": "https://clone.andrestobelem.dev",
11
11
  "bugs": "https://github.com/andrestobelem/clone/issues",
12
- "type": "commonjs",
12
+ "type": "module",
13
13
  "bin": {
14
14
  "clone": "bin/clone.js"
15
15
  },
16
16
  "files": [
17
- "bin/clone.js"
17
+ "bin/clone.js",
18
+ "dist",
19
+ "index.html",
20
+ "app.js",
21
+ "styles.css",
22
+ "README.md",
23
+ "LICENSE"
18
24
  ],
19
25
  "engines": {
20
- "node": ">=18"
26
+ "node": ">=24.15.0"
27
+ },
28
+ "scripts": {
29
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
30
+ "build": "npm run clean && tsc -p tsconfig.build.json",
31
+ "check": "npm run format:check && npm run typecheck && npm test && npm run test:ui",
32
+ "format": "prettier --write \"src/**/*.ts\" \"tests-ts/**/*.ts\" \"bin/**/*.js\" package.json tsconfig.json tsconfig.build.json",
33
+ "format:check": "prettier --check \"src/**/*.ts\" \"tests-ts/**/*.ts\" \"bin/**/*.js\" package.json tsconfig.json tsconfig.build.json",
34
+ "pretest": "npm run build",
35
+ "start": "npm run build && node bin/clone.js --data-dir data/clone start",
36
+ "typecheck": "tsc --noEmit",
37
+ "test": "vitest run",
38
+ "test:ui": "node --check app.js && node --test tests/test_ui.js",
39
+ "prepack": "npm run build"
40
+ },
41
+ "dependencies": {
42
+ "proper-lockfile": "4.1.2"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "26.6.6",
46
+ "@types/proper-lockfile": "4.1.4",
47
+ "prettier": "3.9.10",
48
+ "typescript": "7.0.2",
49
+ "vitest": "5.0.5"
21
50
  },
22
51
  "publishConfig": {
23
52
  "access": "public",
package/styles.css ADDED
@@ -0,0 +1,108 @@
1
+ :root{font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#29292e;background:#efeff1;font-synthesis:none;font-size:13px;font-weight:450;--muted:#77777f;--line:#e8e8eb;--panel:#fbfbfc;--purple:#7778d9;--purple-dark:#6668cd;--hover:#e8e8eb;--sidebar:#efeff1}
2
+ *{box-sizing:border-box}body{margin:0;min-width:700px;min-height:100vh;overflow:hidden}button{font:inherit;color:inherit;border:0;background:none;cursor:pointer}button:focus-visible,input:focus-visible,textarea:focus-visible{outline:2px solid #8b8ce8;outline-offset:2px}.app-shell{height:100vh;display:flex;flex-direction:column}.window-bar{height:42px;flex:none;display:flex;align-items:center;gap:12px;padding:0 16px;background:#f0f0f2;border-bottom:1px solid #e8e8ea}.window-lights{display:flex;gap:8px;margin-right:12px}.window-lights i{width:14px;height:14px;border-radius:50%;background:#fb6158;border:1px solid #ed5149}.window-lights i:nth-child(2){background:#f4bd4f;border-color:#e6ab3c}.window-lights i:nth-child(3){background:#53c75d;border-color:#46b34d}.window-sidebar,.window-history,.window-arrow{width:24px;height:26px;display:grid;place-items:center;color:#77777f;font-size:18px}.window-history{margin-left:12px}.window-tab{margin-left:4px;height:30px;width:min(220px,30vw);background:#fff;border-radius:10px;display:flex;align-items:center;gap:9px;padding:0 12px;font-size:13px;font-weight:600}.window-tab span:first-child{font-size:16px}.workspace-shell{display:flex;min-height:0;flex:1;gap:0}.sidebar{width:244px;flex:none;background:var(--sidebar);padding:13px 12px 10px;display:flex;flex-direction:column;overflow:hidden;transition:width .18s ease,padding .18s ease}.sidebar.collapsed{width:62px;padding-left:9px;padding-right:9px}.workspace-switcher{height:35px;display:flex;align-items:center;gap:8px;padding:0 5px 0 3px;margin-bottom:15px;white-space:nowrap}.workspace-avatar{background:#1685c7;color:white;width:23px;height:23px;border-radius:50%;display:grid;place-items:center;font-size:12px;font-weight:600}.workspace-name{font-weight:650;font-size:14px}.chevron{color:#85858b;font-size:14px}.workspace-search{margin-left:auto!important}.icon-button{width:29px;height:29px;display:grid;place-items:center;border-radius:50%;color:#77777f}.icon-button:hover,.toolbar-button:hover,.quiet-button:hover{background:#e7e7ea}.icon-button svg,.toolbar-button svg,.quiet-button svg,.nav-item svg,.create-round svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.create-round{width:30px;height:30px;border-radius:50%;display:grid;place-items:center;background:#fff;box-shadow:0 1px 3px #00000012;color:#62626b;margin-left:2px;flex:none}.create-round svg{width:17px;height:17px}.sidebar-nav{min-height:0;display:flex;flex:1;flex-direction:column;overflow:auto;scrollbar-width:none}.sidebar-nav::-webkit-scrollbar{display:none}.primary-links{display:grid;gap:2px}.nav-item{height:30px;display:flex;align-items:center;gap:10px;text-align:left;padding:0 9px;border-radius:7px;color:#55555d;white-space:nowrap;width:100%;font-size:13px}.nav-item svg{width:16px;height:16px;flex:none;color:#686870}.nav-item:hover{background:#e7e7e9}.nav-item.active{background:#e0e0e3;color:#242429;font-weight:600}.nav-item.active svg{color:#33333a}.nav-count{margin-left:auto;color:#96969d;font-size:11px}.nav-section{margin-top:19px}.section-label{width:100%;height:25px;display:flex;align-items:center;gap:7px;padding:0 9px;color:#76767d;font-size:12px;font-weight:550;text-align:left}.section-label span:last-child{margin-left:auto}.section-label .add-team{display:none;color:#9999a0}.section-label:hover .add-team{display:inline}.team-name{display:flex;align-items:center;gap:8px;height:29px;padding:0 9px;font-weight:600;color:#44444b;white-space:nowrap}.team-emoji{font-size:15px;color:#df8065}.team-name .chevron{margin-left:auto}.team-item{height:29px;padding-left:22px}.sidebar-bottom{margin-top:auto;padding-top:12px}.sidebar-bottom .nav-item{margin-bottom:7px}.user-profile{height:45px;border-top:1px solid #e4e4e7;display:flex;align-items:center;gap:8px;padding:9px 4px 0;margin-top:4px}.user-avatar{width:25px;height:25px;border-radius:50%;display:grid;place-items:center;background:#d8b5a4;color:white;font-size:9px;font-weight:700}.user-meta{display:flex;flex-direction:column;gap:2px;font-size:11px}.user-meta small{color:#8d8d94;font-size:10px}.profile-more{margin-left:auto;font-size:18px;color:#77777f}.sidebar.collapsed .workspace-name,.sidebar.collapsed .workspace-switcher .chevron,.sidebar.collapsed .workspace-search,.sidebar.collapsed .create-round,.sidebar.collapsed .nav-item span,.sidebar.collapsed .section-label,.sidebar.collapsed .team-name span:not(.team-emoji),.sidebar.collapsed .user-meta,.sidebar.collapsed .profile-more{display:none}.sidebar.collapsed .nav-item{justify-content:center;padding:0}.sidebar.collapsed .team-emoji{margin:auto}.sidebar.collapsed .user-profile{justify-content:center}.main-panel{flex:1;min-width:0;margin:0 8px 8px 0;background:var(--panel);border:1px solid #e6e6e9;border-radius:15px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 1px 2px #00000008}.page-header{height:44px;flex:none;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 18px}.page-title-wrap{display:flex;align-items:center;gap:10px;min-width:0}.breadcrumb{display:none;color:#92929a;font-size:11px}.page-header h1{font-size:14px;font-weight:600;margin:0}.header-actions{display:flex;align-items:center;gap:8px}.quiet-button,.new-project-button{height:29px;border-radius:7px;padding:0 9px;display:flex;align-items:center;gap:7px;font-size:12px;color:#62626a}.quiet-button svg{height:14px;width:14px}.new-project-button{font-weight:550;color:#4d4d54}.new-project-button span{font-size:17px;color:#7a7a83}.new-project-button:hover{background:#f0f0f2}.view-toolbar{height:47px;flex:none;border-bottom:1px solid #f0f0f1;display:flex;align-items:center;justify-content:space-between;padding:0 10px 0 10px}.view-tabs{display:flex;align-items:center;gap:6px}.view-pill{height:29px;border:1px solid transparent;border-radius:18px;padding:0 12px;color:#686870;font-size:12px}.view-pill:hover,.view-pill.selected{background:#f0f0f2;color:#313137}.view-pill.selected{font-weight:600;border-color:#e9e9ec}.saved-view{height:27px;width:27px;border-radius:7px;color:#92929a;font-size:15px}.saved-view:hover{background:#f0f0f2}.toolbar-actions{display:flex;align-items:center;gap:5px}.toolbar-button{height:29px;border:1px solid #e8e8eb;border-radius:7px;display:flex;align-items:center;gap:6px;padding:0 9px;color:#62626a;font-size:11px}.toolbar-button svg{width:14px;height:14px}.details-toggle{width:29px;height:29px;border:1px solid #e8e8eb;border-radius:7px;margin-left:2px}.details-toggle svg{height:14px;width:14px}.content-area{min-height:0;flex:1;overflow:auto;position:relative;padding:16px 22px 24px}.issue-list{max-width:1200px;margin:0 auto}.group-head{height:34px;display:flex;align-items:center;gap:8px;color:#62626a;font-size:11px;font-weight:600}.group-chevron{font-size:12px;color:#8c8c94}.group-count{color:#a2a2a8;font-weight:450}.issue-row{min-height:38px;display:grid;grid-template-columns:74px minmax(140px,1fr) 100px 92px 100px 105px;align-items:center;gap:10px;border-top:1px solid #f0f0f2;padding:0 7px;border-radius:5px;cursor:pointer}.issue-row:hover{background:#f6f6f8}.issue-id{font-size:10px;color:#9999a1;font-weight:500;white-space:nowrap}.issue-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;color:#38383e}.issue-status,.priority-chip,.assignee-chip,.project-chip{display:flex;align-items:center;gap:6px;color:#777780;font-size:10px;white-space:nowrap}.status-dot{width:9px;height:9px;border:1.6px solid #a2a2aa;border-radius:50%;flex:none}.status-dot.progress{border-color:#8c8ce4;border-top-color:transparent;transform:rotate(35deg)}.status-dot.done{background:#5ab993;border-color:#5ab993}.status-dot.todo{border-color:#67a4dc}.status-dot.backlog{border-style:dashed}.priority-mark{font-size:12px;line-height:1;font-weight:700}.p1{color:#e76d68}.p2{color:#eaa34f}.p3{color:#6d9add}.p4{color:#a1a1a8}.assignee-chip{justify-content:flex-start}.mini-avatar{width:17px;height:17px;border-radius:50%;display:grid;place-items:center;background:#d8b5a4;color:#fff;font-size:7px;font-weight:700}.mini-avatar.maya{background:#ad98d6}.mini-avatar.sam{background:#79a9a0}.mini-avatar.jordan{background:#d99ca5}.updated-cell{font-size:10px;color:#a0a0a7;text-align:right}.empty-state{position:absolute;left:50%;top:48%;transform:translate(-50%,-50%);width:min(370px,90%);text-align:center;color:#77777f}.empty-illustration{width:82px;height:82px;border:1.5px solid #77777f;border-radius:21px;transform:rotate(30deg);margin:0 auto 27px;position:relative;display:grid;place-items:center;color:#57575e}.empty-illustration:before,.empty-illustration:after{content:"";position:absolute;inset:11px;border:1px solid #9898a0;border-radius:16px}.empty-illustration:after{inset:23px;border-radius:12px}.empty-illustration span{transform:rotate(-30deg);font-size:28px;z-index:1}.empty-state h2{font-size:15px;color:#39393f;margin:0 0 11px;font-weight:600}.empty-state p{font-size:13px;line-height:1.55;margin:0 auto 23px;max-width:350px;color:#73737b}.primary-button{height:31px;background:var(--purple);border-radius:8px;padding:0 15px;color:white;font-weight:550;font-size:12px;box-shadow:0 1px 2px #00000015}.primary-button:hover{background:var(--purple-dark)}.project-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:12px;max-width:1100px;margin:4px auto}.project-card{padding:16px;border:1px solid #e9e9ec;border-radius:10px;background:#fff;cursor:pointer;transition:box-shadow .15s,border-color .15s}.project-card:hover{border-color:#d5d5dc;box-shadow:0 4px 15px #00000009}.project-card-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.project-icon{height:22px;width:22px;border-radius:7px;background:#eef0ff;color:#7476d7;display:grid;place-items:center}.project-status{color:#6b6b73;font-size:10px}.project-card h3{font-size:13px;margin:0 0 6px;font-weight:600}.project-card p{font-size:11px;line-height:1.45;color:#808088;margin:0 0 17px}.progress-track{height:4px;border-radius:4px;background:#eeeef1;overflow:hidden}.progress-fill{height:100%;border-radius:4px;background:#7778d9}.project-foot{display:flex;justify-content:space-between;margin-top:9px;color:#8a8a92;font-size:10px}.simple-list{max-width:850px;margin:10px auto}.simple-row{display:flex;align-items:center;gap:12px;min-height:58px;border-bottom:1px solid #eeeef0}.simple-row strong{font-size:12px;font-weight:550}.simple-row small{display:block;color:#93939a;margin-top:4px;font-size:10px}.simple-row .row-end{margin-left:auto;color:#92929a;font-size:11px}.toast{position:absolute;bottom:24px;left:50%;transform:translate(-50%,12px);padding:10px 14px;background:#26262b;color:white;border-radius:8px;font-size:11px;opacity:0;pointer-events:none;transition:.2s;box-shadow:0 4px 12px #0002}.toast.show{opacity:1;transform:translate(-50%,0)}.modal-layer,.command-layer{position:fixed;inset:0;background:#25252c4d;backdrop-filter:blur(2px);z-index:10;display:grid;place-items:center;padding:20px}.hidden{display:none!important}.issue-modal{background:#fff;border:1px solid #ececf0;border-radius:15px;width:min(750px,94vw);box-shadow:0 20px 70px #0003;overflow:hidden}.modal-top{height:46px;padding:0 17px;display:flex;align-items:center;gap:9px;color:#777780}.team-token{height:25px;border:1px solid #eeeef0;border-radius:15px;padding:0 8px;display:flex;align-items:center;gap:6px;color:#55555c;font-weight:600;font-size:11px}.team-token i{font-style:normal;color:#d98368}.modal-top strong{font-size:12px;font-weight:500;color:#55555d}.modal-top .spacer{flex:1}.modal-close{font-size:21px;color:#77777f;width:28px;height:28px;border-radius:5px}.modal-close:hover{background:#f1f1f3}.issue-form{padding:18px 20px 14px}.issue-form input.title-input{display:block;width:100%;border:0;outline:0;font:600 21px Inter,-apple-system,sans-serif;color:#323239;padding:8px 0 12px}.issue-form input::placeholder,.issue-form textarea::placeholder{color:#aaaab1}.issue-form textarea{width:100%;height:105px;resize:vertical;border:0;outline:0;font:13px/1.5 Inter,-apple-system,sans-serif;color:#494950;padding:4px 0}.form-property-row{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin-top:13px}.property-button{height:28px;border:1px solid #e9e9ed;border-radius:16px;padding:0 10px;display:flex;align-items:center;gap:7px;color:#65656e;font-size:11px;background:#fff}.property-button:hover{background:#f6f6f7}.property-button .mini-avatar{width:18px;height:18px}.modal-bottom{display:flex;align-items:center;gap:8px;padding:14px 16px 15px;border-top:1px solid #f4f4f5}.attach-button{height:28px;width:28px;border:1px solid #ececf0;border-radius:50%;display:grid;place-items:center;color:#77777f}.attach-button svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.8}.modal-bottom .spacer{flex:1}.create-more{display:flex;align-items:center;gap:7px;font-size:11px;color:#777780;margin-right:6px;cursor:pointer}.toggle{width:28px;height:16px;background:#d4d4d8;border-radius:10px;position:relative}.toggle:after{content:"";position:absolute;left:2px;top:2px;width:12px;height:12px;background:white;border-radius:50%;transition:.15s}.toggle.on{background:#7778d9}.toggle.on:after{left:14px}.popover{position:fixed;z-index:20;width:210px;background:#fff;border:1px solid #e7e7eb;border-radius:9px;box-shadow:0 8px 25px #0002;padding:6px}.popover-item{height:30px;width:100%;display:flex;align-items:center;gap:8px;padding:0 8px;border-radius:5px;text-align:left;font-size:11px;color:#55555d}.popover-item:hover{background:#f3f3f5}.popover-item .check{margin-left:auto;color:#7778d9}.search-modal{width:min(560px,92vw);align-self:start;margin-top:15vh;border-radius:12px;background:#fff;box-shadow:0 20px 65px #0003;overflow:hidden}.search-field{display:flex;align-items:center;gap:10px;height:52px;padding:0 17px;border-bottom:1px solid #eeeef1;color:#85858d}.search-field svg{height:17px;width:17px;stroke:currentColor;fill:none;stroke-width:1.7}.search-field input{flex:1;border:0;outline:0;font:13px Inter,sans-serif}.search-results{max-height:320px;overflow:auto;padding:6px}.search-hint{padding:9px 10px;color:#9a9aa1;font-size:10px}.search-result{height:37px;width:100%;display:flex;align-items:center;gap:9px;text-align:left;padding:0 10px;border-radius:6px;font-size:11px}.search-result:hover{background:#f3f3f5}.search-result .issue-id{width:60px}.search-result span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.details-drawer{position:absolute;top:0;right:0;bottom:0;width:340px;background:#fff;border-left:1px solid #e6e6e9;box-shadow:-5px 0 15px #00000008;padding:20px;z-index:3}.details-drawer h2{font-size:14px;margin:0 0 17px}.details-drawer p{font-size:11px;color:#7d7d85;line-height:1.5}.drawer-close{float:right}.drawer-property{display:flex;justify-content:space-between;align-items:center;padding:9px 0;border-bottom:1px solid #f0f0f2;color:#86868e;font-size:10px}.drawer-property strong{color:#55555d;font-weight:500}.shortcut{font-family:inherit;font-size:9px;background:#f0f0f2;border:1px solid #e3e3e7;border-radius:4px;padding:2px 5px;color:#88888f}
3
+ @media(max-width:1000px){.sidebar{width:210px}.issue-row{grid-template-columns:64px minmax(120px,1fr) 85px 70px 90px}.updated-cell{display:none}.quiet-button span{display:none}}
4
+ @media(max-width:760px){.sidebar{width:58px;padding-left:8px;padding-right:8px}.workspace-name,.workspace-switcher .chevron,.workspace-search,.create-round,.nav-item span,.section-label,.team-name span:not(.team-emoji),.user-meta,.profile-more{display:none}.nav-item{justify-content:center;padding:0}.team-emoji{margin:auto}.user-profile{justify-content:center}.window-history,.window-arrow{display:none}.content-area{padding:12px}.issue-row{grid-template-columns:57px minmax(100px,1fr) 75px 55px}.assignee-chip{display:none}.toolbar-button span{display:none}.toolbar-button{width:30px;justify-content:center;padding:0}}
5
+
6
+ /* Persistent-workspace surfaces */
7
+ body{min-width:360px}
8
+ .is-active{background:#ececf7!important;border-color:#d9d9f0!important;color:#595bc0!important}
9
+ .group-head{width:100%;border:0;background:transparent;text-align:left;padding:0 5px;border-radius:5px;cursor:pointer}
10
+ .group-head:hover{background:#f7f7f8}
11
+ .hidden{display:none!important}
12
+ .issue-status{overflow:hidden;text-overflow:ellipsis}
13
+ .board-grid{height:100%;display:flex;align-items:flex-start;gap:12px;overflow:auto;padding:2px 2px 16px}
14
+ .board-column{flex:0 0 255px;max-width:290px;min-height:170px;padding:8px;border-radius:9px;background:#f5f5f7;border:1px solid #ededf0}
15
+ .board-column>header{height:32px;display:flex;align-items:center;gap:8px;padding:0 5px;color:#66666e;font-size:11px}
16
+ .board-column>header span{margin-left:auto;color:#9999a0}
17
+ .board-card{width:100%;display:block;text-align:left;background:#fff;border:1px solid #e9e9ec;border-radius:8px;padding:10px;margin:5px 0;box-shadow:0 1px 2px #00000007}
18
+ .board-card:hover{border-color:#d3d3df;box-shadow:0 3px 9px #0000000c}
19
+ .board-card strong{display:block;font-size:11px;line-height:1.45;margin:7px 0 12px;font-weight:550}
20
+ .card-foot{display:flex;align-items:center;gap:8px;color:#888891;font-size:10px}
21
+ .card-foot>span:nth-child(2){overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
22
+ .project-card{display:block;width:100%;text-align:left;color:inherit}
23
+ .project-card small{display:block;margin-top:12px;color:#909098}
24
+ .project-icon.large{height:38px;width:38px;font-size:20px;flex:none}
25
+ .detail-page{max-width:910px;margin:0 auto;padding:0 0 36px}
26
+ .detail-top{display:flex;align-items:center;gap:9px;height:34px;margin-bottom:19px}
27
+ .detail-top .spacer,.spacer{flex:1}
28
+ .detail-page h2{font-size:19px;font-weight:600}
29
+ .detail-title{display:block;width:100%;border:0;background:transparent;padding:10px 0;font-size:23px;font-weight:600;color:#303036}
30
+ .detail-description{display:block;width:100%;min-height:72px;padding:8px 0;border:0;background:transparent;resize:vertical;font:13px/1.65 Inter,-apple-system,sans-serif;color:#55555d}
31
+ .property-grid{display:grid;grid-template-columns:repeat(4,minmax(120px,1fr));gap:10px;margin:19px 0 25px}
32
+ .property-control{display:flex;flex-direction:column;gap:7px;border:1px solid #ebebee;border-radius:7px;padding:8px 10px;min-width:0}
33
+ .property-control>span{color:#898991;font-size:10px}
34
+ .property-control select,.property-control input{min-width:0;width:100%;border:0;background:transparent;color:#414148;font:11px Inter,-apple-system,sans-serif;outline:none}
35
+ .detail-section{border-top:1px solid #ededf0;padding:17px 0;margin-top:5px}
36
+ .detail-section h3,.section-heading h3{font-size:12px;font-weight:600;margin:0;color:#55555d}
37
+ .section-heading{display:flex;align-items:center;gap:9px;margin-bottom:10px}
38
+ .section-heading>span{margin-left:auto;color:#85858d;font-size:10px}
39
+ .tiny-button,.secondary-button{border:1px solid #e7e7eb;border-radius:6px;padding:5px 8px;color:#66666e;font-size:10px}
40
+ .tiny-button:hover,.secondary-button:hover{background:#f4f4f6}
41
+ .chips{display:flex;flex-wrap:wrap;gap:7px;margin-top:10px}
42
+ .label-chip{display:inline-flex;align-items:center;padding:4px 8px;border-radius:12px;color:#5a5a62;font-size:10px;background:color-mix(in srgb,var(--label-color,#8585cc) 15%,white);border:1px solid color-mix(in srgb,var(--label-color,#8585cc) 35%,white)}
43
+ .relation-line,.milestone-row{display:flex;align-items:center;gap:9px;padding:8px 4px;border-bottom:1px solid #f0f0f2;color:#5c5c64;font-size:11px}
44
+ .milestone-row span:first-of-type{margin-left:auto}
45
+ .milestone-row span:last-child{width:85px;text-align:right;color:#888891}
46
+ .link-row{width:100%;display:flex;align-items:center;gap:10px;text-align:left;min-height:35px;border-bottom:1px solid #f0f0f2;padding:0 6px;color:#55555d;font-size:11px}
47
+ .link-row:hover{background:#f7f7f8}
48
+ .link-row .row-end{margin-left:auto;color:#93939a}
49
+ #comment-form{display:grid;grid-template-columns:1fr auto;gap:9px;margin:10px 0 15px}
50
+ #comment-form textarea{min-height:57px;resize:vertical;border:1px solid #e8e8eb;border-radius:7px;padding:8px;font:12px Inter,-apple-system,sans-serif}
51
+ .activity-item{display:flex;gap:9px;padding:10px 4px;border-bottom:1px solid #f1f1f3;font-size:10px;color:#616169}
52
+ .activity-item>div{min-width:0}.activity-item p{margin:5px 0;color:#515159;font-size:11px}.activity-item small{color:#9999a0}
53
+ .activity-dot{width:9px;height:9px;border:2px solid #aaa;border-radius:50%;margin:4px}
54
+ .attachment-row{display:flex;justify-content:space-between;gap:12px;padding:9px 11px;margin:4px 0;border:1px solid #ececf0;border-radius:6px;color:#565660;font-size:11px;text-decoration:none}.attachment-row:hover{background:#f7f7f9}.attachment-row small{color:#96969e}
55
+ .detail-actions{display:flex;gap:8px;padding-top:15px}
56
+ .project-heading{display:flex;align-items:center;gap:14px;margin:12px 0 24px}
57
+ .project-heading h2{margin:0 0 6px}.project-heading p{margin:0;color:#82828a;font-size:12px}
58
+ .project-due.overdue{color:#bf514c;font-weight:600}.project-due.upcoming{color:#a77420;font-weight:600}.cycle-capacity{font-size:11px;color:#777780}.cycle-card>.muted{font-size:10px}
59
+ .inbox-layout{height:100%;min-height:400px;display:grid;grid-template-columns:minmax(280px,43%) 1fr;border:1px solid #ececf0;border-radius:9px;overflow:hidden}
60
+ .inbox-list{overflow:auto;border-right:1px solid #ececf0}
61
+ .inbox-toolbar{height:40px;display:flex;align-items:center;justify-content:space-between;padding:0 11px;border-bottom:1px solid #eeeef1;color:#74747c;font-size:10px}
62
+ .inbox-toolbar input{vertical-align:middle}
63
+ .inbox-toolbar{height:auto;min-height:40px;flex-wrap:wrap;justify-content:flex-start;gap:7px}
64
+ .inbox-toolbar label{white-space:nowrap}
65
+ .inbox-toolbar .tiny-button{white-space:nowrap}
66
+ .notification-row{width:100%;min-height:64px;display:flex;align-items:flex-start;gap:9px;padding:12px 10px;border-bottom:1px solid #f0f0f2;text-align:left;cursor:pointer}
67
+ .notification-row>input[type=checkbox]{margin:2px 1px 0 0;flex:none}
68
+ .notification-row>.status-dot{margin-top:3px;flex:none}
69
+ .notification-row.selected,.notification-row:hover{background:#f5f5f8}.notification-row.unread b{color:#35353c}.notification-row:not(.unread) b{font-weight:500;color:#71717a}
70
+ .notification-row>span{min-width:0;flex:1}.notification-row b,.notification-row small{display:block}.notification-row b{font-size:10px;line-height:1.4}.notification-row small{font-size:10px;line-height:1.5;color:#898991;margin-top:4px}.notification-row time{font-size:9px;color:#96969e;white-space:nowrap}.notification-row.filtered-out{display:none}
71
+ .inbox-detail{padding:23px;overflow:auto}.inbox-detail h2{font-size:16px;margin:25px 0 12px}.inbox-detail p{font-size:12px;color:#71717a;line-height:1.6}.inbox-detail>.primary-button{margin-top:22px}
72
+ .empty-inline{padding:25px 12px;text-align:center;color:#95959c;font-size:11px}
73
+ .member-row{display:flex;align-items:center;gap:12px;min-height:62px;padding:9px 11px;border-bottom:1px solid #eeeef1}
74
+ .triage-row{display:flex;align-items:center;gap:10px;min-height:54px;padding:9px 11px;border-bottom:1px solid #eeeef1}.triage-title{flex:1;min-width:0;text-align:left;background:none;border:0;color:inherit;font:inherit;cursor:pointer}.triage-title:hover{text-decoration:underline}
75
+ .archive-row{display:flex;align-items:center;gap:12px;min-height:48px;padding:8px 11px;border-bottom:1px solid #eeeef1}.archive-row>span:nth-child(2),.archive-row>span:first-child:not(.issue-id){flex:1}.archive-row small{color:#909098;font-size:10px}
76
+ .resource-section h4{margin:12px 0 5px;color:#85858e;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em}.resource-row{display:flex;align-items:center;gap:8px}.resource-title{flex:1;display:block;padding:8px 0;border:0;background:none;color:#62626b;text-align:left;text-decoration:none;font:inherit}.resource-title[href]:hover{text-decoration:underline}.resource-row .tiny-button{opacity:.45}.resource-row:hover .tiny-button{opacity:1}
77
+ .project-update{padding:12px;margin-top:8px;border:1px solid #eeeef1;border-radius:7px}.project-update p{margin:8px 0;color:#55555f;font-size:11px;line-height:1.6;white-space:pre-wrap}.project-update small{color:#92929a;font-size:10px}
78
+ .member-row>span:nth-child(2){min-width:0;flex:1}.member-row b{font-size:11px;font-weight:550}.member-row small{display:block;color:#909098;font-size:10px;margin-top:5px}
79
+ .status-pill{border-radius:12px;padding:4px 8px;background:#f1f1f3;color:#74747c;font-size:10px;white-space:nowrap}
80
+ .content-heading{display:flex;justify-content:space-between;align-items:center;margin:0 auto 14px;max-width:1000px}.content-heading h2{font-size:15px;margin:0 0 5px}.content-heading p{font-size:11px;color:#85858c;margin:0}
81
+ .member-list,.cycle-list{max-width:1000px;margin:0 auto}.member-row .row-end{margin-left:auto;color:#898991;font-size:10px}
82
+ .team-home{max-width:1100px;margin:0 auto}.team-hero{display:flex;align-items:center;gap:13px;padding:12px 5px 20px;border-bottom:1px solid #eeeef1}.team-hero h2{margin:0 0 5px;font-size:18px}.team-hero p{margin:0;color:#83838b;font-size:11px}.team-hero-icon{width:40px;height:40px;border-radius:11px;display:grid;place-items:center;background:#f0effa;color:#7778d9;font-size:21px}.team-shortcuts{display:flex;gap:9px;padding:14px 0 18px}.team-shortcuts button{border:1px solid #e9e9ec;border-radius:7px;padding:9px 12px;color:#65656d;font-size:11px}.team-shortcuts b{margin-left:12px;color:#9898a0}.home-columns{display:grid;grid-template-columns:1fr 1fr;gap:20px}.home-columns .detail-section:last-child{grid-column:1/-1}
83
+ .view-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:11px}.view-card{display:flex;align-items:flex-start;gap:4px;border:1px solid #e9e9ec;border-radius:9px;padding:13px}.view-open{flex:1;min-width:0;text-align:left}.view-open h3{font-size:12px;margin:9px 0 5px}.view-open p{margin:0 0 9px;color:#85858d;font-size:10px}.view-open small{color:#9a9aa1;font-size:9px}
84
+ .settings-layout{max-width:1100px;margin:0 auto;display:grid;grid-template-columns:190px minmax(0,1fr);gap:28px}.settings-nav{display:flex;flex-direction:column;gap:4px;position:sticky;top:10px;align-self:start}.settings-nav b{padding:10px;color:#44444b;font-size:12px}.settings-nav button{text-align:left;border-radius:6px;padding:8px 10px;color:#76767d;font-size:11px}.settings-nav button:hover,.settings-nav button.selected{background:#eeeeF1;color:#414148}.settings-content>h2{font-size:17px;margin:8px 0}.settings-content>p,.muted{font-size:11px;color:#8b8b93}.settings-form{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;padding:14px 0 18px}.settings-form>.primary-button{justify-self:start;grid-column:1/-1}.form-field{display:flex;flex-direction:column;gap:6px;min-width:0;color:#777780;font-size:10px}.form-field.wide{grid-column:1/-1}.form-field input,.form-field select,.form-field textarea{width:100%;min-height:32px;padding:7px 9px;border:1px solid #e6e6ea;border-radius:6px;background:#fff;color:#43434a;font:11px Inter,-apple-system,sans-serif}.form-field textarea{min-height:90px;resize:vertical}.form-field select[multiple]{min-height:94px}.form-field input[type=checkbox]{width:16px;min-height:16px;align-self:flex-start}.form-field input[type=color]{width:54px;padding:3px}
85
+ .settings-content{min-width:0;max-width:760px}.settings-list{padding-top:8px}.settings-actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;padding-top:18px}.settings-actions>div{padding:16px;border:1px solid var(--line);border-radius:9px;background:#fff}.settings-actions h3{margin-bottom:7px}.settings-actions .secondary-button{margin-top:9px}
86
+ .cycle-card{display:grid;grid-template-columns:1fr 130px;gap:12px;border:1px solid #e9e9ec;border-radius:9px;padding:15px;margin:10px 0}.cycle-card h3{font-size:13px;margin:9px 0 4px}.cycle-card p{font-size:10px;color:#898991;margin:0}.cycle-capacity{font-size:10px;color:#85858c}.cycle-capacity b{display:block;color:#44444b;font-size:15px;margin-top:5px}.cycle-card .progress-track{grid-column:1/-1}
87
+ .wide-modal{width:min(790px,95vw);max-height:92vh;display:flex;flex-direction:column}.wide-modal .issue-form{overflow:auto}.modal-fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.modal-fields .form-field{font-size:10px}.modal-bottom .secondary-button{margin-right:2px}.search-scopes{display:flex;gap:5px;padding:8px 13px;border-bottom:1px solid #f0f0f2}.scope-chip{font-size:10px;color:#888891;padding:5px 8px;border-radius:12px}.scope-chip.selected{background:#eeeeF2;color:#484850}.search-result small{margin-left:auto;color:#96969e;font-size:9px}.popover{max-height:70vh;overflow:auto}.popover-item{font-size:10px}
88
+ .dark-theme{--panel:#222228;--line:#38383f;--sidebar:#25252b;background:#18181d;color:#e5e5e9}.dark-theme .main-panel,.dark-theme .sidebar,.dark-theme .window-bar{background:#25252b;color:#e5e5e9;border-color:#3a3a42}.dark-theme .content-area,.dark-theme .page-header,.dark-theme .view-toolbar{background:#222228;border-color:#393940}.dark-theme .issue-row:hover,.dark-theme .view-pill.selected,.dark-theme .team-shortcuts button{background:#33333a}.dark-theme .detail-title,.dark-theme .detail-description{color:#e5e5e9}.dark-theme .property-control,.dark-theme .project-card,.dark-theme .board-card,.dark-theme .settings-actions>div{background:#2c2c33;border-color:#414149;color:#ddd}.dark-theme .form-field input,.dark-theme .form-field select,.dark-theme .form-field textarea{background:#303037;color:#eee;border-color:#484850}
89
+ .preference-hidden{display:none!important}
90
+ .team-name{cursor:pointer}
91
+ .form-field input,.form-field select,.form-field textarea{font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
92
+ .issue-row{grid-template-columns:var(--issue-columns,74px minmax(140px,1fr) 100px 92px 100px 105px)}
93
+ .secondary-issue-group{padding-left:14px}.secondary-head{height:29px;font-size:10px}
94
+ .project-chip{max-width:140px;overflow:hidden;text-overflow:ellipsis}
95
+ .workspace-current{min-width:0;flex:1;height:35px;display:flex;align-items:center;gap:8px;padding:0 3px;border-radius:7px;text-align:left;white-space:nowrap}
96
+ .workspace-avatar{flex:none}
97
+ .workspace-current:hover{background:#e7e7ea}
98
+ .sidebar.collapsed .workspace-current{justify-content:center}
99
+ @media(max-width:1000px){.issue-row{grid-template-columns:var(--issue-columns,64px minmax(120px,1fr) 85px 70px 90px)} }
100
+ @media(max-width:760px){.issue-row{grid-template-columns:minmax(48px,60px) minmax(95px,1fr) repeat(auto-fit,minmax(48px,auto));gap:6px}.issue-row .assignee-chip,.issue-row .updated-cell{display:none}.secondary-issue-group{padding-left:5px}}
101
+ @media(max-width:900px){.property-grid{grid-template-columns:repeat(2,minmax(120px,1fr))}.settings-layout{grid-template-columns:145px minmax(0,1fr);gap:15px}}
102
+ @media(max-width:650px){.inbox-layout{grid-template-columns:1fr}.inbox-detail{display:none}.home-columns{grid-template-columns:1fr}.home-columns .detail-section:last-child{grid-column:auto}.settings-layout{grid-template-columns:1fr}.settings-nav{position:static;display:flex;flex-direction:row;overflow:auto}.settings-nav b{display:none}.settings-nav button{flex:none}.settings-form,.settings-actions{grid-template-columns:1fr}.settings-form>.primary-button{grid-column:1}.modal-fields{grid-template-columns:1fr}.form-field.wide{grid-column:auto}.property-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.team-shortcuts{overflow:auto}.issue-row{grid-template-columns:55px minmax(110px,1fr) 77px 52px;gap:6px}.issue-status{display:none}.priority-chip{font-size:9px}.updated-cell{display:none}.detail-page{padding:0 2px}.detail-title{font-size:19px}}
103
+
104
+ /* Keep keyboard focus visible on controls that remove default outlines. */
105
+ :where(button,a,input,select,textarea,[role="button"]):focus-visible{outline:2px solid #7778d9;outline-offset:3px}
106
+ .team-section>.add-team{float:right;margin-top:-23px;padding:3px 8px}
107
+ .team-name{width:100%;text-align:left}
108
+ @media(max-width:760px){.team-section>.add-team{float:none;margin-top:0}}