-
模块与菜单按实际菜单层级组织
-
{addOpen &&
}
+ const markDirty = () => setDirty(true);
+ const save = () => {
+ setDirty(false);
+ setToast("配置已保存到当前工作区");
+ window.setTimeout(() => setToast(""), 2600);
+ };
+ const addDomain = (parentId: number | null) => {
+ const id = Math.max(...domains.map((item) => item.id), 0) + 1;
+ setDomains((items) => [
+ ...items,
+ {
+ id,
+ code: `new-domain-${id}`,
+ name: "新业务域",
+ parentId,
+ enabled: true,
+ description: "请补充业务域说明",
+ },
+ ]);
+ setSelectedDomainId(id);
+ setWorkspace("domainEntities");
+ markDirty();
+ };
+ const addEntity = () => {
+ const id = Math.max(...entities.map((item) => item.id), 0) + 1;
+ setEntities((items) => [
+ ...items,
+ {
+ id,
+ domainId: selectedDomain.id,
+ code: `new_entity_${id}`,
+ name: "新实体",
+ view: "",
+ save: "",
+ key: "b_id",
+ type: "table",
+ enabled: true,
+ },
+ ]);
+ setSelectedEntityId(id);
+ setWorkspace("domainEntities");
+ markDirty();
+ };
+ const addMenu = (type: Menu["type"], parentId: number | null) => {
+ const id = Math.max(...menus.map((item) => item.id), 0) + 1;
+ const item: Menu = {
+ id,
+ parentId,
+ code: `new-menu-${id}`,
+ name:
+ type === "directory"
+ ? "新目录"
+ : type === "data"
+ ? "新数据页面"
+ : "新页面",
+ type,
+ route: type === "directory" ? "" : "/new-route",
+ entityId: type === "data" ? (selectedEntity?.id ?? null) : null,
+ domainId: selectedDomain.id,
+ enabled: true,
+ visible: true,
+ };
+ setMenus((items) => [...items, item]);
+ setSelectedMenuId(id);
+ setWorkspace("menuPermissions");
+ setMenuTab("basic");
+ if (type !== "directory")
+ setPowers((items) => [
+ ...items,
+ ...actionNames.map((action, index) => ({
+ id: id * 10 + index,
+ menuId: id,
+ code: `${item.code}.${action}`,
+ name: actionLabels[action],
+ enabled: action !== "delete",
+ })),
+ ]);
+ markDirty();
+ };
+ return (
+
+ );
+}
+function renderBusinessTree(
+ domains: Domain[],
+ entities: Entity[],
+ relations: Relation[],
+ parentId: number | null,
+ depth: number,
+ selectedDomainId: number,
+ selectedEntityId: number,
+ onSelectDomain: (id: number) => void,
+ onSelectEntity: (id: number) => void,
+): ReactNode[] {
+ return domains
+ .filter((item) => item.parentId === parentId)
+ .flatMap((item) => {
+ const ownedTargets = new Set(
+ relations
+ .filter(
+ (relation) =>
+ relation.ownership === "owned" &&
+ entities.find((entity) => entity.id === relation.entityId)
+ ?.domainId === item.id,
+ )
+ .map((relation) => relation.targetEntityId),
+ );
+ const domainEntities = entities.filter(
+ (entity) => entity.domainId === item.id && !ownedTargets.has(entity.id),
+ );
+ const hasChildren =
+ domains.some((child) => child.parentId === item.id) ||
+ domainEntities.length > 0;
+ return [
+
,
+ ...domainEntities.flatMap((entity) =>
+ renderEntityBranch(
+ entity,
+ entities,
+ relations,
+ depth + 1,
+ selectedEntityId,
+ onSelectEntity,
+ new Set(),
+ ),
+ ),
+ ...renderBusinessTree(
+ domains,
+ entities,
+ relations,
+ item.id,
+ depth + 1,
+ selectedDomainId,
+ selectedEntityId,
+ onSelectDomain,
+ onSelectEntity,
+ ),
+ ];
+ });
+}
+function renderEntityBranch(
+ entity: Entity,
+ entities: Entity[],
+ relations: Relation[],
+ depth: number,
+ selectedEntityId: number,
+ onSelectEntity: (id: number) => void,
+ visited: Set
,
+): ReactNode[] {
+ if (visited.has(entity.id)) return [];
+ const nextVisited = new Set(visited).add(entity.id);
+ const ownedChildren = relations
+ .filter(
+ (relation) =>
+ relation.entityId === entity.id && relation.ownership === "owned",
+ )
+ .map((relation) =>
+ entities.find((item) => item.id === relation.targetEntityId),
+ )
+ .filter((item): item is Entity => Boolean(item));
+ return [
+ ,
- ...render(node.id, depth + 1),
- ]);
- return {render(null)}
;
+ ...ownedChildren.flatMap((child) =>
+ renderEntityBranch(
+ child,
+ entities,
+ relations,
+ depth + 1,
+ selectedEntityId,
+ onSelectEntity,
+ nextVisited,
+ ),
+ ),
+ ];
}
-
-function BasicPanel({ node, nodes, onChange, onSelectNode }: { node: ModuleNode; nodes: ModuleNode[]; onChange: (patch: Partial) => void; onSelectNode: (node: ModuleNode) => void }) {
- const dataNodes = nodes.filter((item) => item.type === "data");
- const relatedData = dataNodes.find((item) => item.id === node.dataNodeId);
- return
-
-
-
-
-
-
- {node.type === "page" && <>
-
-
-
-
- >}
- {node.type === "data" && <>
-
-
-
-
- >}
+function MenuPermissionWorkspace({
+ menus,
+ domains,
+ entities,
+ roles,
+ powers,
+ selectedMenu,
+ selectedRole,
+ tab,
+ onTab,
+ onSelectMenu,
+ onSelectRole,
+ onAdd,
+ onMenuChange,
+ onToggle,
+ onPublish,
+}: {
+ menus: Menu[];
+ domains: Domain[];
+ entities: Entity[];
+ roles: Role[];
+ powers: Power[];
+ selectedMenu: Menu;
+ selectedRole: Role;
+ tab: "basic" | "powers" | "scope";
+ onTab: (tab: "basic" | "powers" | "scope") => void;
+ onSelectMenu: (id: number) => void;
+ onSelectRole: (id: number) => void;
+ onAdd: (type: Menu["type"], parentId: number | null) => void;
+ onMenuChange: (patch: Partial
-
{node.type === "page" ? "这里只登记已开发页面的入口与关联数据,不会生成或改写页面代码。" : node.type === "data" ? "数据节点集中维护字段、列表、表单和查询规则,可被一个或多个页面使用。" : "目录与用户实际看到的菜单层级保持一致。"}
-
;
+ );
+}
+function renderMenuTree(
+ menus: Menu[],
+ parentId: number | null,
+ depth: number,
+ selectedId: number,
+ onSelect: (id: number) => void,
+): ReactNode[] {
+ return menus
+ .filter((item) => item.parentId === parentId)
+ .flatMap((item) => [
+ ,
+ ...renderMenuTree(menus, item.id, depth + 1, selectedId, onSelect),
+ ]);
}
-function FieldsPanel({ fields, update }: { fields: Field[]; update: (id: number, patch: Partial) => void }) {
- return ;
-}
-
-function ListPanel({ fields }: { fields: Field[] }) {
- return {fields.slice(0, 8).map((field, index) => {field.code}{field.name} undefined} />
)};
-}
-
-function EditPanel({ fields }: { fields: Field[] }) {
- return {fields.slice(0, 8).map((field, index) => {field.code}{field.name} undefined} /> undefined} />
)};
-}
-
-function QueryPanel({ fields }: { fields: Field[] }) {
- return 新增条件} heads={["查询字段", "查询组件", "操作符", "默认值", "启用"]}>{fields.filter((field) => field.code !== "b_freight").slice(0, 7).map((field, index) => {field.name}{field.code} undefined} />
)};
-}
-
-function ConfigTable({ title, desc, action, heads, children }: { title: string; desc: string; action?: ReactNode; heads: string[]; children: ReactNode }) {
- return {heads.map((head) => {head})}
{children}
;
-}
-
-function AutoCodePanel() {
- return 目标字段b_ywno · 业务单号
示例结果NBSE2608200001
undefined} /> ;
-}
-
-function PowerPanel() {
- const powers = [["查看订舱", "booking.view"], ["新增订舱", "booking.create"], ["编辑订舱", "booking.update"], ["审核订舱", "booking.audit"], ["导出订舱", "booking.export"]];
- return 新增权限} />{powers.map(([name, code], index) =>
{name}{code} undefined} />
)}
;
-}
-
-function I18nPanel({ node, fields }: { node: ModuleNode; fields: Field[] }) {
- return ;
+function RoleWorkspace({ roles }: { roles: Role[] }) {
+ return (
+
+
+
+ 新建角色
+
+ }
+ />
+
+
+
+
+
角色
+
{roles.length} 个授权主体
+
+
+ {roles.map((role) => (
+
+
{role.name.slice(0, 1)}
+
+ {role.name}
+
+ {role.code} · {role.members} 人
+
+
+
+
+ ))}
+
+
+
+
+
授权方式
+
角色管理和菜单授权分开定位,维护时仍然保持连贯。
+
+
+
+
+
+ 在“菜单与权限”中选中菜单,再选择角色并勾选动作;数据范围也按角色在同一处设置。
+
+
+
+
+
+ );
}
diff --git a/code/test-demo/src/index.css b/code/test-demo/src/index.css
index 02125dc6..937ccbc7 100644
--- a/code/test-demo/src/index.css
+++ b/code/test-demo/src/index.css
@@ -1,145 +1,1469 @@
-@import "tailwindcss";
+@import url("https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@400;500;600;700&display=swap");
:root {
- font-family: Inter, "PingFang SC", "Microsoft YaHei", sans-serif;
- color: #26344a;
- background: #f3f5f8;
+ font-family: Manrope, "PingFang SC", "Microsoft YaHei", sans-serif;
+ color: #1f3147;
+ background: #eef2f6;
font-synthesis: none;
+ --ink: #1f3147;
+ --muted: #7d8da2;
+ --line: #dfe6ee;
+ --surface: #fff;
+ --navy: #12243c;
+ --blue: #2f6fca;
+ --soft-blue: #edf4ff;
+ --green: #279574;
}
-
-* { box-sizing: border-box; }
-body { margin: 0; min-width: 1120px; background: #f3f5f8; }
-button, input, select { font: inherit; }
-button { cursor: pointer; }
-
-.app { min-height: 100vh; }
-.topbar { height: 64px; display: flex; align-items: center; gap: 32px; padding: 0 24px; background: #fff; border-bottom: 1px solid #e1e6ed; }
-.brand { width: 250px; display: flex; align-items: center; gap: 11px; }
-.brand .logo { width: 32px; height: 32px; display: grid; place-items: center; color: #fff; background: #2468d8; border-radius: 6px; }
-.brand b { display: block; color: #24344e; font-size: 14px; }
-.brand small { display: block; margin-top: 3px; color: #98a4b4; font-size: 10px; }
-.top-module { display: flex; align-items: center; gap: 10px; padding-left: 24px; border-left: 1px solid #edf0f4; font-size: 11px; }
-.top-module span { color: #98a4b4; }
-.top-module b { color: #586a83; }
-.top-actions { margin-left: auto; display: flex; align-items: center; gap: 16px; color: #7b899d; font-size: 10px; }
-.save-state i { display: inline-block; width: 6px; height: 6px; margin-right: 6px; border-radius: 50%; background: #32a879; }
-.save-state.dirty i { background: #d99736; }
-.save { padding: 7px 12px; border: 1px solid #d9e0e9; border-radius: 5px; background: #fff; color: #576a84; font-size: 10px; font-weight: 600; }
-.save:disabled, .primary:disabled { cursor: default; opacity: .45; }
-.avatar { width: 28px; height: 28px; display: grid; place-items: center; border-radius: 50%; background: #e7effd; color: #2463c9; font-size: 11px; font-weight: 700; }
-
-.layout { display: grid; grid-template-columns: 286px minmax(760px, 1fr); min-height: calc(100vh - 64px); }
-.tree-panel { position: relative; padding: 20px 13px; background: #fff; border-right: 1px solid #e1e6ed; }
-.tree-title { display: flex; justify-content: space-between; align-items: center; padding: 0 9px 15px; }
-.tree-title b { display: block; color: #40526d; font-size: 13px; }
-.tree-title small { display: block; margin-top: 4px; color: #9aa6b5; font-size: 9px; }
-.icon-btn { width: 30px; height: 30px; display: grid; place-items: center; padding: 0; border: 1px solid #dce3ec; border-radius: 5px; background: #fff; color: #60728b; }
-.icon-btn:hover { border-color: #9bb7e6; color: #2769d4; background: #f7faff; }
-.add-wrap { position: relative; }
-.add-menu { position: absolute; z-index: 5; top: 35px; right: 0; width: 138px; padding: 5px; border: 1px solid #dde4ed; border-radius: 6px; background: #fff; box-shadow: 0 10px 28px rgba(37, 52, 74, .14); }
-.add-menu button { width: 100%; display: flex; align-items: center; gap: 8px; padding: 8px; border: 0; border-radius: 4px; background: transparent; color: #586a83; text-align: left; font-size: 10px; }
-.add-menu button:hover { color: #2468d8; background: #f1f6fe; }
-.tree-search { height: 34px; display: flex; align-items: center; gap: 7px; margin-bottom: 11px; padding: 0 9px; border: 1px solid #dfe5ed; border-radius: 5px; color: #9aa6b5; }
-.tree-search:focus-within { border-color: #83a6df; box-shadow: 0 0 0 2px rgba(36, 104, 216, .07); }
-.tree-search input { min-width: 0; flex: 1; border: 0; outline: 0; color: #50627c; font-size: 10px; }
-.tree-search button { display: grid; place-items: center; padding: 0; border: 0; background: transparent; color: #99a5b5; }
-.module-tree { min-height: 400px; }
-.tree-row { width: 100%; min-height: 43px; display: flex; align-items: center; gap: 8px; padding-top: 6px; padding-right: 8px; padding-bottom: 6px; border: 0; border-radius: 5px; background: transparent; color: #5f7089; text-align: left; }
-.tree-row:hover { background: #f5f8fc; }
-.tree-row.selected { color: #2468d8; background: #edf4ff; }
-.tree-icon { width: 17px; display: grid; place-items: center; flex: 0 0 auto; }
-.tree-icon.directory { color: #c78628; }
-.tree-icon.page { color: #6c64b6; }
-.tree-icon.data { color: #39847b; }
-.tree-name { min-width: 0; flex: 1; overflow: hidden; font-size: 11px; white-space: nowrap; text-overflow: ellipsis; }
-.tree-name small { display: block; margin-top: 2px; overflow: hidden; color: #a0abb9; font-size: 8px; white-space: nowrap; text-overflow: ellipsis; }
-.tree-type { padding: 2px 4px; border-radius: 3px; background: #eef1f5; color: #9aa5b3; font-size: 8px; }
-.row-more { display: none; color: #7e8ca0; }
-.tree-row:hover .row-more, .tree-row.selected .row-more { display: block; }
-.tree-row:hover .tree-type, .tree-row.selected .tree-type { display: none; }
-.tree-legend { display: flex; gap: 13px; margin: 17px 9px 0; padding-top: 13px; border-top: 1px solid #edf0f4; color: #8c98a9; font-size: 9px; }
-.tree-legend span { display: flex; align-items: center; gap: 5px; }
-.dot { width: 6px; height: 6px; display: inline-block; border-radius: 50%; }
-.dot.directory { background: #cf9137; }.dot.page { background: #7770c5; }.dot.data { background: #459489; }
-.tree-help { display: flex; align-items: flex-start; gap: 7px; margin: 14px 5px 0; padding: 10px; border-radius: 5px; background: #f7f9fc; color: #7e8da2; font-size: 9px; line-height: 1.6; }
-.tree-help svg { flex: 0 0 auto; margin-top: 1px; color: #557fbe; }
-
-.detail { width: 100%; max-width: 1280px; margin: 0 auto; padding: 27px 34px 40px; }
-.detail-head { min-height: 54px; display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 20px; }
-.crumb { margin-bottom: 9px; color: #9ba6b5; font-size: 10px; white-space: pre-wrap; }
-.title-line { display: flex; align-items: center; gap: 9px; }
-.title-line h1 { margin: 0; color: #293a54; font-size: 21px; letter-spacing: 0; }
-.title-line code { color: #98a4b4; font-size: 9px; }
-.node-badge { padding: 3px 7px; border-radius: 4px; font-size: 9px; }
-.node-badge.directory { background: #fff1d9; color: #a66e1e; }
-.node-badge.page { background: #efedff; color: #6860b6; }
-.node-badge.data { background: #e7f5f1; color: #287a70; }
-.head-actions { display: flex; align-items: center; gap: 13px; }
-.head-actions label { display: flex; align-items: center; gap: 6px; color: #738198; font-size: 10px; }
-.more-button { width: 32px; height: 32px; }
-.primary, .outline { min-height: 31px; display: inline-flex; align-items: center; justify-content: center; gap: 5px; padding: 7px 12px; border-radius: 5px; font-size: 10px; font-weight: 600; }
-.primary { border: 1px solid #2468d8; background: #2468d8; color: #fff; }
-.primary:not(:disabled):hover { background: #1e5fc8; }
-.outline { border: 1px solid #d8e0e9; background: #fff; color: #596b84; }
-.outline:hover { border-color: #a6bde0; color: #2468d8; }
-
-.detail-body { display: grid; grid-template-columns: 148px minmax(0, 1fr); gap: 18px; }
-.section-nav { padding-top: 2px; }
-.section-nav button { width: 100%; height: 38px; display: flex; align-items: center; gap: 9px; padding: 0 10px; border: 0; border-left: 2px solid transparent; border-radius: 0 4px 4px 0; background: transparent; color: #7f8da1; text-align: left; font-size: 10px; }
-.section-nav button:hover { color: #516b92; background: rgba(255,255,255,.55); }
-.section-nav button.active { border-left-color: #2468d8; background: #fff; color: #2468d8; font-weight: 600; box-shadow: 0 1px 7px rgba(45, 68, 101, .05); }
-.content { min-width: 0; }
-.panel { overflow: hidden; border: 1px solid #e0e6ee; border-radius: 7px; background: #fff; box-shadow: 0 2px 10px rgba(40, 59, 86, .04); }
-.panel-header { min-height: 68px; display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 15px 19px; border-bottom: 1px solid #edf0f4; }
-.panel-header h2 { margin: 0 0 4px; color: #34455f; font-size: 14px; }
-.panel-header p { margin: 0; color: #96a2b2; font-size: 10px; }
-.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 20px; padding: 21px; }
-.form-grid label { position: relative; display: block; color: #66768d; font-size: 10px; }
-.form-grid input, .form-grid select, .table-row input, .table-row select, .rule-step input, .rule-step select { width: 100%; height: 32px; display: block; margin-top: 6px; padding: 0 9px; border: 1px solid #dbe2eb; border-radius: 4px; outline: 0; background: #fff; color: #51627b; font-size: 10px; }
-.form-grid input:focus, .form-grid select:focus, .table-row input:focus, .table-row select:focus { border-color: #789bd5; box-shadow: 0 0 0 2px rgba(36, 104, 216, .06); }
-.form-grid input[readonly], .form-grid select:disabled { background: #f7f8fa; color: #8d99a8; }
-.related-field select { padding-right: 104px; }
-.link-button { position: absolute; right: 7px; bottom: 7px; padding: 0; border: 0; background: #fff; color: #2468d8; font-size: 9px; }
-.info-strip { display: flex; align-items: center; gap: 8px; margin: 0 20px 20px; padding: 10px 11px; border-left: 2px solid #83a7df; background: #f6f9fd; color: #77879d; font-size: 9px; line-height: 1.55; }
-.info-strip svg { flex: 0 0 auto; color: #527fbe; }
-
-.table-head, .table-row { display: grid; align-items: center; column-gap: 12px; }
-.table-head { min-height: 37px; padding: 0 18px; background: #fafbfd; color: #8d99aa; font-size: 9px; font-weight: 600; }
-.table-row { min-height: 54px; padding: 0 18px; border-top: 1px solid #edf1f5; color: #5b6c84; font-size: 10px; }
-.table-row:hover { background: #fcfdff; }
-.table-row input, .table-row select { margin-top: 0; }
-.table-row code { color: #75869d; font-size: 9px; }
-.table-row b { display: block; margin-bottom: 2px; color: #4f6078; font-size: 10px; }
-.table-head.fields, .table-row.fields { grid-template-columns: 1.15fr 1fr 1fr 1.2fr 46px; }
-.table-head.config, .table-row.config { grid-template-columns: 1.15fr 1fr 70px 110px 90px; }
-.table-head.i18n, .table-row.i18n { grid-template-columns: 1.35fr 1fr 1fr 1fr; }
-.i18n-table .table-row { min-height: 55px; }
-
-.toggle { width: 28px; height: 17px; display: inline-block; padding: 2px; border: 0; border-radius: 20px; background: #d8e0e9; vertical-align: middle; }
-.toggle i { width: 13px; height: 13px; display: block; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(38, 52, 74, .16); transition: transform .15s; }
-.toggle.on { background: #36a57b; }
-.toggle.on i { transform: translateX(11px); }
-.power-list > div { min-height: 62px; display: flex; align-items: center; gap: 12px; padding: 0 19px; border-top: 1px solid #edf1f5; color: #4c79b8; }
-.power-list > div:first-child { border-top: 0; }
-.power-list > div > span { flex: 1; }
-.power-list b { display: block; color: #50617a; font-size: 11px; }
-.power-list small { display: block; margin-top: 3px; color: #95a1b1; font-size: 9px; }
-.more { display: grid; place-items: center; padding: 4px; border: 0; background: transparent; color: #8f9bac; }
-
-.rule-summary { display: grid; grid-template-columns: 1fr 1fr auto; align-items: center; gap: 20px; padding: 18px 20px; border-bottom: 1px solid #edf1f5; background: #fafbfd; }
-.rule-summary span { display: block; margin-bottom: 4px; color: #98a3b2; font-size: 9px; }
-.rule-summary b { color: #53647c; font-size: 11px; }
-.rule-summary code { color: #24705f; font-size: 12px; font-weight: 700; }
-.rule-builder { padding: 20px; }
-.rule-step { min-height: 50px; display: grid; grid-template-columns: 24px 140px 1fr 28px; align-items: center; gap: 10px; padding: 7px 10px; border-bottom: 1px solid #edf1f5; }
-.rule-step > i { width: 22px; height: 22px; display: grid; place-items: center; border-radius: 50%; background: #edf3fc; color: #3c71bd; font-size: 9px; font-style: normal; }
-.rule-step input, .rule-step select { margin: 0; }
-.rule-step > button { display: grid; place-items: center; padding: 0; border: 0; background: transparent; color: #8f9bab; }
-.inline-inputs { display: flex; align-items: center; gap: 8px; color: #7a899d; font-size: 10px; }
-.inline-inputs input { width: 62px; }
-.add-rule { display: flex; align-items: center; gap: 6px; margin-top: 14px; padding: 7px 9px; border: 0; background: transparent; color: #2468d8; font-size: 10px; }
-
-@media (max-width: 1220px) {
- .detail { padding-left: 24px; padding-right: 24px; }
- .top-module { display: none; }
+* {
+ box-sizing: border-box;
+}
+body {
+ min-width: 1100px;
+ min-height: 100vh;
+ margin: 0;
+ background: #eef2f6;
+}
+button,
+input,
+select,
+textarea {
+ font: inherit;
+}
+button {
+ cursor: pointer;
+}
+button:focus-visible,
+input:focus-visible,
+select:focus-visible,
+textarea:focus-visible {
+ outline: 2px solid #75a8e8;
+ outline-offset: 2px;
+}
+code {
+ font-family: "DM Mono", monospace;
+}
+.app-shell {
+ min-height: 100vh;
+}
+.topbar {
+ height: 64px;
+ display: flex;
+ align-items: center;
+ gap: 30px;
+ padding: 0 28px;
+ background: var(--navy);
+ color: #fff;
+}
+.brand {
+ display: flex;
+ align-items: center;
+ gap: 11px;
+ min-width: 285px;
+}
+.brand-mark {
+ width: 32px;
+ height: 32px;
+ display: grid;
+ place-items: center;
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ border-radius: 8px;
+ background: #284668;
+ color: #9ec5ff;
+}
+.brand strong {
+ display: block;
+ font-size: 14px;
+ letter-spacing: 0.02em;
+}
+.brand span:not(.brand-mark) {
+ display: block;
+ margin-top: 3px;
+ color: #91a6bf;
+ font-size: 10px;
+}
+.top-context {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ color: #adc0d5;
+ font-size: 11px;
+}
+.context-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: #53c48f;
+ box-shadow: 0 0 0 3px rgba(83, 196, 143, 0.14);
+}
+.top-actions {
+ display: flex;
+ align-items: center;
+ gap: 17px;
+ margin-left: auto;
+}
+.save-state {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ color: #a6b7c9;
+ font-size: 11px;
+}
+.save-state i {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: #53c48f;
+}
+.save-state.dirty {
+ color: #ffd18a;
+}
+.save-state.dirty i {
+ background: #f1b552;
+}
+.user-chip {
+ width: 31px;
+ height: 31px;
+ display: grid;
+ place-items: center;
+ border-radius: 50%;
+ background: #d8e8ff;
+ color: #2f5e9d;
+ font-weight: 700;
+ font-size: 12px;
+}
+.app-body {
+ display: flex;
+ min-height: calc(100vh - 64px);
+}
+.sidebar {
+ width: 242px;
+ flex: 0 0 242px;
+ display: flex;
+ flex-direction: column;
+ padding: 25px 14px 18px;
+ border-right: 1px solid #d8e1eb;
+ background: #f7f9fb;
+}
+.sidebar-label {
+ margin: 0 12px 10px;
+ color: #99a7b8;
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+}
+.side-nav {
+ position: relative;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 11px;
+ min-height: 44px;
+ padding: 0 12px;
+ border: 0;
+ border-radius: 7px;
+ background: transparent;
+ color: #6f8095;
+ text-align: left;
+ font-size: 12px;
+}
+.side-nav:hover {
+ background: #edf2f8;
+ color: #4d6e98;
+}
+.side-nav.active {
+ background: #e8f1fe;
+ color: #2768ba;
+ font-weight: 700;
+}
+.side-nav-icon {
+ display: grid;
+ place-items: center;
+}
+.side-nav small {
+ display: block;
+ margin-top: 3px;
+ color: #9aa8b9;
+ font-size: 9px;
+ font-weight: 500;
+}
+.side-active {
+ position: absolute;
+ right: 0;
+ width: 3px;
+ height: 23px;
+ border-radius: 3px 0 0 3px;
+ background: #3b7bd0;
+}
+.sidebar-divider {
+ height: 1px;
+ margin: 22px 12px;
+ background: #e3e8ef;
+}
+.status-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 11px 12px;
+ border-radius: 7px;
+ background: #edf8f4;
+}
+.status-icon {
+ width: 26px;
+ height: 26px;
+ display: grid;
+ place-items: center;
+ border-radius: 6px;
+}
+.status-icon.green {
+ background: #d8f1e7;
+ color: #268d6f;
+}
+.status-item strong {
+ display: block;
+ color: #4b6b67;
+ font-size: 10px;
+}
+.status-item small {
+ display: block;
+ margin-top: 3px;
+ color: #8ba49e;
+ font-size: 9px;
+}
+.status-live {
+ width: 6px;
+ height: 6px;
+ margin-left: auto;
+ border-radius: 50%;
+ background: #42ae84;
+}
+.sidebar-footer {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ margin-top: auto;
+ padding: 11px 10px;
+ color: #8797aa;
+ font-size: 10px;
+ line-height: 1.6;
+}
+.sidebar-footer svg {
+ flex: 0 0 auto;
+ margin-top: 2px;
+ color: #5485bd;
+}
+.main-content {
+ min-width: 0;
+ flex: 1;
+ padding: 30px 37px 48px;
+}
+.workspace {
+ max-width: 1250px;
+ margin: 0 auto;
+}
+.page-header {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ gap: 25px;
+ margin-bottom: 24px;
+}
+.eyebrow {
+ color: #8e9dad;
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: 0.11em;
+}
+.page-header h1 {
+ margin: 8px 0 5px;
+ color: #253953;
+ font-size: 25px;
+ letter-spacing: -0.02em;
+}
+.page-header p {
+ max-width: 690px;
+ margin: 0;
+ color: #8391a3;
+ font-size: 12px;
+ line-height: 1.6;
+}
+.button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 7px;
+ min-height: 34px;
+ padding: 0 13px;
+ border: 1px solid transparent;
+ border-radius: 6px;
+ font-size: 11px;
+ font-weight: 700;
+ transition:
+ background 0.15s,
+ border-color 0.15s,
+ color 0.15s;
+}
+.primary-button {
+ background: #3175ce;
+ color: #fff;
+ box-shadow: 0 3px 9px rgba(49, 117, 206, 0.18);
+}
+.primary-button:hover:not(:disabled) {
+ background: #2867b8;
+}
+.primary-button:disabled {
+ cursor: not-allowed;
+ opacity: 0.45;
+}
+.outline-button {
+ border-color: #d6e0eb;
+ background: #fff;
+ color: #58708e;
+}
+.outline-button:hover {
+ border-color: #94b5df;
+ color: #2868bb;
+}
+.button-group {
+ display: flex;
+ gap: 8px;
+}
+.stat-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 14px;
+ margin-bottom: 18px;
+}
+.stat-card {
+ display: flex;
+ align-items: center;
+ gap: 13px;
+ min-height: 105px;
+ padding: 17px;
+ border: 1px solid var(--line);
+ border-radius: 9px;
+ background: #fff;
+ box-shadow: 0 2px 9px rgba(31, 57, 88, 0.035);
+}
+.stat-icon {
+ width: 36px;
+ height: 36px;
+ display: grid;
+ place-items: center;
+ border-radius: 8px;
+}
+.stat-icon.blue,
+.model-icon.blue {
+ background: #e7f1ff;
+ color: #4b82cb;
+}
+.stat-icon.teal,
+.model-icon.teal {
+ background: #e4f5f1;
+ color: #318d7f;
+}
+.stat-icon.amber,
+.model-icon.amber {
+ background: #fff2db;
+ color: #c28a35;
+}
+.stat-icon.violet,
+.model-icon.violet {
+ background: #eeeafd;
+ color: #756bb7;
+}
+.stat-card small,
+.stat-card span {
+ display: block;
+ color: #8998aa;
+ font-size: 10px;
+}
+.stat-card strong {
+ display: block;
+ margin: 4px 0;
+ color: #2e425d;
+ font-size: 23px;
+ line-height: 1;
+}
+.surface {
+ border: 1px solid var(--line);
+ border-radius: 9px;
+ background: var(--surface);
+ box-shadow: 0 2px 10px rgba(31, 57, 88, 0.035);
+}
+.overview-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1.22fr) minmax(340px, 0.78fr);
+ gap: 16px;
+}
+.surface-heading {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 15px;
+ padding: 18px 20px;
+ border-bottom: 1px solid #edf1f5;
+}
+.surface-heading h2 {
+ margin: 0 0 5px;
+ color: #344963;
+ font-size: 14px;
+}
+.surface-heading p {
+ margin: 0;
+ color: #98a5b5;
+ font-size: 10px;
+}
+.surface-kicker {
+ color: #9caabb;
+ font-size: 9px;
+ font-weight: 700;
+ letter-spacing: 0.12em;
+}
+.workflow-step {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 13px;
+ min-height: 73px;
+ padding: 11px 20px;
+ border: 0;
+ border-top: 1px solid #edf1f5;
+ background: #fff;
+ color: #68809e;
+ text-align: left;
+}
+.workflow-step:hover {
+ background: #fbfdff;
+}
+.step-number {
+ width: 29px;
+ color: #a1afc0;
+ font-family: "DM Mono", monospace;
+ font-size: 10px;
+}
+.step-icon {
+ width: 30px;
+ height: 30px;
+ display: grid;
+ place-items: center;
+ border-radius: 7px;
+ background: #eef4fd;
+ color: #4e83c9;
+}
+.step-copy {
+ flex: 1;
+}
+.step-copy strong {
+ display: block;
+ color: #47607d;
+ font-size: 12px;
+}
+.step-copy small {
+ display: block;
+ margin-top: 4px;
+ color: #95a3b3;
+ font-size: 10px;
+ line-height: 1.45;
+}
+.workflow-step > svg {
+ color: #a2afbd;
+}
+.model-chain {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 9px;
+ padding: 30px 20px;
+}
+.model-node {
+ display: flex;
+ min-width: 71px;
+ flex-direction: column;
+ align-items: center;
+ gap: 5px;
+}
+.model-icon {
+ width: 31px;
+ height: 31px;
+ display: grid;
+ place-items: center;
+ border-radius: 8px;
+}
+.model-node strong {
+ color: #50647f;
+ font-size: 10px;
+}
+.model-node code {
+ color: #a0acba;
+ font-size: 8px;
+}
+.chain-line {
+ width: 27px;
+ height: 1px;
+ background: #d8e1eb;
+}
+.model-note {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ margin: 0 20px 20px;
+ padding: 11px;
+ border-left: 2px solid #8eb1dc;
+ background: #f6f9fd;
+ color: #7e8ea2;
+ font-size: 10px;
+ line-height: 1.55;
+}
+.model-note svg {
+ flex: 0 0 auto;
+ color: #5788c1;
+}
+.model-note code {
+ color: #5477a1;
+}
+.split-workspace {
+ display: grid;
+ grid-template-columns: minmax(280px, 0.7fr) minmax(0, 1.3fr);
+ gap: 16px;
+}
+.tree-surface,
+.detail-surface,
+.entity-list,
+.entity-detail {
+ min-width: 0;
+}
+.search-box {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ height: 35px;
+ margin: 14px 18px 10px;
+ padding: 0 10px;
+ border: 1px solid #dfe6ee;
+ border-radius: 6px;
+ color: #9ba8b7;
+}
+.search-box input {
+ width: 100%;
+ border: 0;
+ outline: 0;
+ background: transparent;
+ color: #53677f;
+ font-size: 11px;
+}
+.domain-tree,
+.menu-tree {
+ padding: 0 8px 14px;
+}
+.domain-row {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ min-height: 43px;
+ padding-top: 6px;
+ padding-right: 10px;
+ padding-bottom: 6px;
+ border: 0;
+ border-radius: 6px;
+ background: transparent;
+ color: #657890;
+ text-align: left;
+ font-size: 11px;
+}
+.domain-row:hover {
+ background: #f3f7fc;
+}
+.domain-row.selected {
+ background: #eaf2fe;
+ color: #2d6abd;
+}
+.domain-row > span:not(.tree-caret) {
+ min-width: 0;
+}
+.domain-row > span:nth-last-of-type(2),
+.domain-row > span:nth-last-of-type(2) small {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.domain-row small {
+ display: block;
+ margin-top: 3px;
+ color: #a0acba;
+ font-family: "DM Mono", monospace;
+ font-size: 8px;
+}
+.tree-caret {
+ width: 14px;
+ display: grid;
+ place-items: center;
+ flex: 0 0 auto;
+}
+.row-more {
+ margin-left: auto;
+ color: #b1bdc9;
+ opacity: 0;
+}
+.domain-row:hover .row-more {
+ opacity: 1;
+}
+.detail-surface,
+.entity-detail {
+ padding-bottom: 0;
+}
+.detail-title {
+ display: flex;
+ align-items: center;
+ gap: 11px;
+ padding: 19px 20px;
+ border-bottom: 1px solid #edf1f5;
+}
+.detail-title-icon {
+ width: 31px;
+ height: 31px;
+ display: grid;
+ place-items: center;
+ border-radius: 8px;
+ background: #eaf2fe;
+ color: #3975c3;
+}
+.detail-title h2 {
+ margin: 4px 0 0;
+ color: #344962;
+ font-size: 15px;
+}
+.status-badge {
+ margin-left: auto;
+ padding: 4px 8px;
+ border-radius: 5px;
+ background: #e7f6f0;
+ color: #36896f;
+ font-size: 9px;
+ font-weight: 700;
+}
+.form-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 16px 18px;
+ padding: 21px;
+}
+.form-grid label,
+.full-field {
+ color: #718197;
+ font-size: 10px;
+}
+.form-grid input,
+.form-grid select,
+.form-grid textarea,
+.field-row input,
+.field-row select {
+ width: 100%;
+ height: 34px;
+ margin-top: 6px;
+ padding: 0 9px;
+ border: 1px solid #dbe3ec;
+ border-radius: 5px;
+ outline: 0;
+ background: #fff;
+ color: #51657f;
+ font-size: 10px;
+}
+.form-grid textarea {
+ height: 78px;
+ padding-top: 8px;
+ resize: vertical;
+ line-height: 1.5;
+}
+.form-grid input[readonly] {
+ background: #f6f8fa;
+ color: #929eac;
+}
+.full-field {
+ grid-column: 1 / -1;
+}
+.section-divider {
+ height: 1px;
+ background: #edf1f5;
+}
+.detail-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 18px;
+ padding: 18px 21px 21px;
+}
+.detail-footer strong {
+ color: #50647e;
+ font-size: 11px;
+}
+.detail-footer p {
+ max-width: 490px;
+ margin: 5px 0 0;
+ color: #96a3b3;
+ font-size: 10px;
+ line-height: 1.5;
+}
+.icon-button {
+ width: 31px;
+ height: 31px;
+ display: grid;
+ place-items: center;
+ border: 1px solid #dce4ed;
+ border-radius: 6px;
+ background: #fff;
+ color: #73869e;
+}
+.icon-button:hover {
+ border-color: #99b6da;
+ color: #3976c2;
+}
+.icon-button.subtle {
+ border-color: transparent;
+ background: transparent;
+ color: #a0acb8;
+}
+.icon-button.subtle:hover {
+ color: #d26868;
+}
+.entity-layout {
+ display: grid;
+ grid-template-columns: 285px minmax(0, 1fr);
+ gap: 16px;
+}
+.domain-pills {
+ padding: 14px 15px 8px;
+}
+.domain-pills button {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ min-height: 35px;
+ padding: 0 9px;
+ border: 0;
+ border-radius: 5px;
+ background: transparent;
+ color: #70839b;
+ text-align: left;
+ font-size: 10px;
+}
+.domain-pills button:hover {
+ background: #f5f8fb;
+}
+.domain-pills button.active {
+ background: #eaf2fe;
+ color: #2c6abd;
+ font-weight: 700;
+}
+.domain-pills span {
+ min-width: 20px;
+ padding: 2px 5px;
+ border-radius: 10px;
+ background: #eef2f6;
+ color: #93a1b1;
+ text-align: center;
+ font-size: 9px;
+}
+.entity-items {
+ padding: 5px 15px 16px;
+}
+.entity-item {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ min-height: 57px;
+ padding: 8px 9px;
+ border: 0;
+ border-top: 1px solid #edf1f5;
+ background: #fff;
+ color: #60758e;
+ text-align: left;
+}
+.entity-item:first-child {
+ border-top: 0;
+}
+.entity-item:hover {
+ background: #fbfdff;
+}
+.entity-item.selected {
+ color: #2b68b5;
+}
+.entity-item-icon {
+ width: 28px;
+ height: 28px;
+ display: grid;
+ place-items: center;
+ border-radius: 6px;
+ background: #e7f5f1;
+ color: #318b7e;
+}
+.entity-item span:nth-child(2) {
+ flex: 1;
+}
+.entity-item strong,
+.entity-item small {
+ display: block;
+}
+.entity-item strong {
+ font-size: 11px;
+}
+.entity-item small {
+ margin-top: 3px;
+ color: #9ba8b7;
+ font-family: "DM Mono", monospace;
+ font-size: 8px;
+}
+.entity-item > svg {
+ color: #b3bfca;
+}
+.subsection-heading {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 18px 21px 12px;
+}
+.subsection-heading h3 {
+ margin: 0;
+ color: #47607b;
+ font-size: 13px;
+}
+.subsection-heading h3 span {
+ display: inline-grid;
+ min-width: 19px;
+ height: 18px;
+ place-items: center;
+ margin-left: 4px;
+ border-radius: 9px;
+ background: #edf2f7;
+ color: #8393a5;
+ font-size: 9px;
+}
+.subsection-heading p {
+ margin: 5px 0 0;
+ color: #98a6b5;
+ font-size: 10px;
+}
+.subsection-actions {
+ display: flex;
+ gap: 7px;
+}
+.field-table {
+ padding-bottom: 10px;
+ overflow-x: auto;
+}
+.field-head,
+.field-row {
+ min-width: 610px;
+ display: grid;
+ grid-template-columns: 1.15fr 1fr 1fr 45px 45px 31px;
+ align-items: center;
+ gap: 11px;
+ padding: 0 21px;
+}
+.field-head {
+ min-height: 35px;
+ background: #fafbfd;
+ color: #92a0b0;
+ font-size: 9px;
+ font-weight: 700;
+}
+.field-row {
+ min-height: 50px;
+ border-top: 1px solid #edf1f5;
+ color: #5f7188;
+ font-size: 10px;
+}
+.field-row code {
+ color: #74869c;
+ font-size: 9px;
+}
+.field-row input,
+.field-row select {
+ margin-top: 0;
+}
+.empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 9px;
+ padding: 35px 10px;
+ color: #a0acb9;
+ font-size: 11px;
+}
+.empty-state > span {
+ color: #81a3c9;
+}
+.menu-row .row-meta {
+ margin-left: auto;
+ color: #9ba8b7;
+ font-size: 9px;
+}
+.menu-type-icon {
+ display: grid;
+ place-items: center;
+}
+.menu-type-icon.directory {
+ color: #b78736;
+}
+.menu-type-icon.page {
+ color: #756dc0;
+}
+.menu-type-icon.data {
+ color: #308b7d;
+}
+.tree-note {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ margin: 2px 18px 18px;
+ padding: 10px;
+ background: #f7f9fc;
+ color: #8392a3;
+ font-size: 10px;
+ line-height: 1.5;
+}
+.tree-note svg {
+ flex: 0 0 auto;
+ color: #5784bd;
+}
+.menu-rule {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin: 0 21px 21px;
+ padding: 14px;
+ border: 1px solid #e3eaf3;
+ border-radius: 7px;
+ background: #f8fbff;
+}
+.rule-icon {
+ width: 30px;
+ height: 30px;
+ display: grid;
+ place-items: center;
+ border-radius: 7px;
+ background: #e7f0fc;
+ color: #4a7ec4;
+}
+.menu-rule strong {
+ display: block;
+ color: #4f6580;
+ font-size: 11px;
+}
+.menu-rule p {
+ margin: 4px 0 0;
+ color: #8e9dad;
+ font-size: 10px;
+ line-height: 1.45;
+}
+.menu-rule > div:nth-child(2) {
+ flex: 1;
+}
+.domain-entity-layout {
+ display: grid;
+ grid-template-columns: 285px minmax(0, 1fr);
+ gap: 16px;
+ align-items: start;
+}
+.domain-panel,
+.entity-panel,
+.menu-panel,
+.menu-detail {
+ min-width: 0;
+}
+.domain-quick {
+ margin: 0 15px 16px;
+ padding: 12px;
+ border: 1px solid #e1e9f1;
+ border-radius: 7px;
+ background: #f8fbfe;
+}
+.domain-quick strong,
+.domain-quick span {
+ display: block;
+}
+.domain-quick strong {
+ color: #4c6380;
+ font-size: 11px;
+}
+.domain-quick span {
+ margin: 5px 0 10px;
+ color: #91a0b0;
+ font-size: 9px;
+ line-height: 1.5;
+}
+.entity-strip {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(145px, 1fr));
+ gap: 8px;
+ padding: 14px 18px;
+}
+.entity-strip .entity-item {
+ min-height: 58px;
+ border: 1px solid #e1e8f0;
+ border-radius: 7px;
+}
+.entity-strip .entity-item.selected {
+ border-color: #9ebde4;
+ background: #f2f7fe;
+}
+.tab-bar {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ padding: 0 21px;
+ border-bottom: 1px solid #edf1f5;
+}
+.tab-bar button {
+ min-height: 39px;
+ padding: 0 12px;
+ border: 0;
+ border-bottom: 2px solid transparent;
+ background: transparent;
+ color: #8492a3;
+ font-size: 10px;
+}
+.tab-bar button:hover {
+ color: #4b77ad;
+}
+.tab-bar button.active {
+ border-bottom-color: #3b79c9;
+ color: #2e6bb8;
+ font-weight: 700;
+}
+.permission-tab {
+ min-height: 315px;
+}
+.scope-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px;
+ padding: 16px 21px 0;
+}
+.scope-grid label {
+ color: #718197;
+ font-size: 10px;
+}
+.scope-grid select {
+ width: 100%;
+ height: 34px;
+ margin-top: 6px;
+ padding: 0 9px;
+ border: 1px solid #dbe3ec;
+ border-radius: 5px;
+ background: #fff;
+ color: #51657f;
+ font-size: 10px;
+}
+.menu-permission-layout {
+ display: grid;
+ grid-template-columns: minmax(265px, 0.72fr) minmax(0, 1.28fr);
+ gap: 16px;
+ align-items: start;
+}
+.role-admin-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(300px, 0.8fr);
+ gap: 16px;
+}
+.admin-role-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-height: 68px;
+ padding: 11px 18px;
+ border-top: 1px solid #edf1f5;
+}
+.admin-role-row > div {
+ flex: 1;
+}
+.admin-role-row strong,
+.admin-role-row small {
+ display: block;
+}
+.admin-role-row strong {
+ color: #50647f;
+ font-size: 11px;
+}
+.admin-role-row small {
+ margin-top: 3px;
+ color: #98a6b5;
+ font-family: "DM Mono", monospace;
+ font-size: 8px;
+}
+.role-explain {
+ min-height: 190px;
+}
+.permission-layout {
+ display: grid;
+ grid-template-columns: 255px minmax(270px, 0.9fr) minmax(330px, 1.1fr);
+ gap: 14px;
+}
+.permission-resource-note,
+.permission-helper,
+.role-summary {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ margin: 12px 15px;
+ padding: 10px;
+ border-left: 2px solid #8eb2dc;
+ background: #f6f9fd;
+ color: #7e8da1;
+ font-size: 10px;
+ line-height: 1.5;
+}
+.permission-resource-note svg,
+.permission-helper svg,
+.role-summary svg {
+ flex: 0 0 auto;
+ color: #5486be;
+}
+.permission-menu {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ min-height: 56px;
+ padding: 8px 15px;
+ border: 0;
+ border-top: 1px solid #edf1f5;
+ background: #fff;
+ color: #60758d;
+ text-align: left;
+}
+.permission-menu:hover {
+ background: #fbfdff;
+}
+.permission-menu.selected {
+ background: #eaf2fe;
+ color: #2f6dbd;
+}
+.permission-menu > span:nth-child(2) {
+ flex: 1;
+}
+.permission-menu strong,
+.permission-menu small {
+ display: block;
+}
+.permission-menu strong {
+ font-size: 11px;
+}
+.permission-menu small {
+ margin-top: 3px;
+ color: #9aa8b8;
+ font-family: "DM Mono", monospace;
+ font-size: 8px;
+}
+.action-grid {
+ padding: 4px 0;
+}
+.action-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-height: 55px;
+ padding: 0 19px;
+ border-top: 1px solid #edf1f5;
+}
+.action-symbol {
+ width: 27px;
+ height: 27px;
+ display: grid;
+ place-items: center;
+ border-radius: 6px;
+ background: #f0edfd;
+ color: #746cba;
+}
+.action-row > span:nth-child(2) {
+ flex: 1;
+}
+.action-row strong,
+.action-row small {
+ display: block;
+}
+.action-row strong {
+ color: #556a84;
+ font-size: 11px;
+}
+.action-row small {
+ margin-top: 3px;
+ color: #9da9b6;
+ font-family: "DM Mono", monospace;
+ font-size: 8px;
+}
+.role-tabs {
+ display: flex;
+ gap: 4px;
+ padding: 13px 15px;
+ overflow-x: auto;
+}
+.role-tabs button {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ min-width: 105px;
+ padding: 7px;
+ border: 1px solid transparent;
+ border-radius: 6px;
+ background: #f7f9fb;
+ color: #718197;
+ text-align: left;
+}
+.role-tabs button.active {
+ border-color: #a6c2e7;
+ background: #eef5ff;
+ color: #2f6cba;
+}
+.role-avatar {
+ width: 24px;
+ height: 24px;
+ display: grid;
+ place-items: center;
+ border-radius: 50%;
+ background: #dbe9fa;
+ color: #3e75b9;
+ font-size: 10px;
+ font-weight: 700;
+}
+.role-tabs strong,
+.role-tabs small {
+ display: block;
+}
+.role-tabs small {
+ margin-top: 2px;
+ color: #9ba8b7;
+ font-size: 8px;
+}
+.role-matrix {
+ margin: 0 15px;
+ overflow-x: auto;
+ border: 1px solid #e1e8f0;
+ border-radius: 6px;
+}
+.matrix-header,
+.matrix-row {
+ min-width: 420px;
+ display: grid;
+ grid-template-columns: 1.25fr repeat(5, 54px);
+ align-items: center;
+}
+.matrix-header {
+ min-height: 33px;
+ background: #fafbfd;
+ color: #8b9aaa;
+ font-size: 9px;
+ text-align: center;
+}
+.matrix-header span:first-child,
+.matrix-row span:first-child {
+ padding-left: 10px;
+ text-align: left;
+}
+.matrix-row {
+ min-height: 49px;
+ border-top: 1px solid #edf1f5;
+ color: #5c708a;
+ font-size: 10px;
+}
+.matrix-check {
+ width: 23px;
+ height: 23px;
+ display: grid;
+ place-items: center;
+ justify-self: center;
+ border: 1px solid #d8e1eb;
+ border-radius: 5px;
+ background: #fff;
+ color: #fff;
+}
+.matrix-check.checked {
+ border-color: #3b9a7a;
+ background: #3b9a7a;
+}
+.matrix-check span {
+ line-height: 1;
+ font-size: 13px;
+}
+.role-summary {
+ margin-top: 15px;
+ margin-bottom: 18px;
+ border-left-color: #78bc9f;
+ background: #f0faf6;
+}
+.role-summary strong {
+ color: #3b806b;
+}
+.toggle {
+ width: 29px;
+ height: 17px;
+ display: inline-block;
+ padding: 2px;
+ border: 0;
+ border-radius: 20px;
+ background: #d7e0e9;
+ vertical-align: middle;
+}
+.toggle i {
+ display: block;
+ width: 13px;
+ height: 13px;
+ border-radius: 50%;
+ background: #fff;
+ box-shadow: 0 1px 2px rgba(30, 50, 70, 0.15);
+ transition: transform 0.16s ease;
+}
+.toggle.on {
+ background: #3ba17d;
+}
+.toggle.on i {
+ transform: translateX(12px);
+}
+.toast {
+ position: fixed;
+ right: 27px;
+ bottom: 24px;
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ padding: 11px 15px;
+ border: 1px solid #c9e7db;
+ border-radius: 7px;
+ background: #f1fbf7;
+ box-shadow: 0 7px 24px rgba(30, 58, 82, 0.13);
+ color: #367e69;
+ font-size: 11px;
+}
+.toast-check {
+ width: 18px;
+ height: 18px;
+ display: grid;
+ place-items: center;
+ border-radius: 50%;
+ background: #42a681;
+ color: #fff;
+ font-size: 11px;
+}
+@media (max-width: 1250px) {
+ .main-content {
+ padding-left: 25px;
+ padding-right: 25px;
+ }
+ .permission-layout {
+ grid-template-columns: 230px minmax(250px, 0.85fr) minmax(300px, 1.1fr);
+ }
+}
+@media (max-width: 1050px) {
+ body {
+ min-width: 900px;
+ }
+ .sidebar {
+ width: 210px;
+ flex-basis: 210px;
+ }
+ .overview-grid,
+ .split-workspace,
+ .entity-layout,
+ .domain-entity-layout,
+ .menu-permission-layout,
+ .role-admin-grid {
+ grid-template-columns: 1fr;
+ }
+ .permission-layout {
+ grid-template-columns: 1fr 1fr;
+ }
+ .role-surface {
+ grid-column: 1 / -1;
+ }
+ .entity-strip {
+ grid-template-columns: 1fr 1fr;
+ }
+ .top-context {
+ display: none;
+ }
+}
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ scroll-behavior: auto !important;
+ transition-duration: 0.01ms !important;
+ animation-duration: 0.01ms !important;
+ }
+}
+/* Entity relationships stay readable at dashboard density; narrow screens scroll this table instead of collapsing labels. */
+.relation-table {
+ overflow-x: auto;
+ padding-bottom: 10px;
+}
+.relation-head,
+.relation-row {
+ min-width: 780px;
+ display: grid;
+ grid-template-columns: 1.25fr 0.75fr 1fr 1fr 0.8fr 0.9fr 45px;
+ align-items: center;
+ gap: 9px;
+ padding: 0 21px;
+}
+.relation-head {
+ min-height: 35px;
+ background: #fafbfd;
+ color: #92a0b0;
+ font-size: 9px;
+ font-weight: 700;
+}
+.relation-row {
+ min-height: 50px;
+ border-top: 1px solid #edf1f5;
+}
+.relation-row input,
+.relation-row select {
+ width: 100%;
+ height: 32px;
+ margin-top: 0;
+ padding: 0 7px;
+ border: 1px solid #dbe3ec;
+ border-radius: 5px;
+ outline: 0;
+ background: #fff;
+ color: #51657f;
+ font-size: 9px;
+}
+.relation-heading {
+ padding-top: 22px;
+}
+.entity-tree-row {
+ color: #4d7188;
+ background: #fbfdfd;
+}
+.entity-tree-row:hover {
+ background: #eef8f5;
+}
+.entity-tree-row.selected {
+ color: #258675;
+ background: #e6f6f1;
+}
+.domain-detail-note {
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ margin: 0 21px 21px;
+ padding: 12px;
+ border: 1px solid #e1e9f1;
+ border-radius: 7px;
+ background: #f8fbfe;
+ color: #8393a5;
+ font-size: 10px;
+ line-height: 1.5;
+}
+.domain-detail-note svg {
+ flex: 0 0 auto;
+ color: #5d8fc6;
+}
+.domain-detail-note span {
+ flex: 1;
}