chore: snapshot audited HoloLake convergence baseline

This commit is contained in:
冰朔 2026-08-18 17:58:07 +08:00
commit 1b6b17b5ab
47 changed files with 10722 additions and 87 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,97 @@
import { useMemo } from 'react'
import { $createParagraphNode, $getSelection, FORMAT_TEXT_COMMAND, REDO_COMMAND, UNDO_COMMAND } from 'lexical'
import { LexicalComposer } from '@lexical/react/LexicalComposer'
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'
import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin'
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import {
$convertFromMarkdownString,
$convertToMarkdownString,
BOLD_ITALIC_STAR,
BOLD_STAR,
CHECK_LIST,
HEADING,
ITALIC_STAR,
ORDERED_LIST,
QUOTE,
UNORDERED_LIST,
type Transformer,
} from '@lexical/markdown'
import { $createHeadingNode, HeadingNode, QuoteNode } from '@lexical/rich-text'
import { INSERT_CHECK_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, ListItemNode, ListNode } from '@lexical/list'
import { $setBlocksType } from '@lexical/selection'
// Keep the built-in engine deliberately small and only register Markdown
// transformers whose node types are present below. Lexical's full TRANSFORMERS
// set also requires code/link nodes; mounting it without those dependencies
// throws during editor startup and used to blank the whole document screen.
const CHANNEL_DOCUMENT_TRANSFORMERS: Transformer[] = [
HEADING,
QUOTE,
UNORDERED_LIST,
ORDERED_LIST,
CHECK_LIST,
BOLD_ITALIC_STAR,
BOLD_STAR,
ITALIC_STAR,
]
function ChannelDocumentToolbar() {
const [editor] = useLexicalComposerContext()
const setBlock = (kind: 'paragraph' | 'heading') => editor.update(() => {
const selection = $getSelection()
if (kind === 'heading') $setBlocksType(selection, () => $createHeadingNode('h2'))
else $setBlocksType(selection, () => $createParagraphNode())
})
return <div className="education-engine-toolbar" aria-label="频道内置文档引擎工具">
<span></span>
<button type="button" onMouseDown={(event) => event.preventDefault()} onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold')}><b></b></button>
<button type="button" onMouseDown={(event) => event.preventDefault()} onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic')}><i></i></button>
<button type="button" onMouseDown={(event) => event.preventDefault()} onClick={() => setBlock('heading')}></button>
<button type="button" onMouseDown={(event) => event.preventDefault()} onClick={() => setBlock('paragraph')}></button>
<button type="button" onMouseDown={(event) => event.preventDefault()} onClick={() => editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined)}></button>
<button type="button" onMouseDown={(event) => event.preventDefault()} onClick={() => editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined)}></button>
<i/>
<button type="button" onMouseDown={(event) => event.preventDefault()} onClick={() => editor.dispatchCommand(UNDO_COMMAND, undefined)}></button>
<button type="button" onMouseDown={(event) => event.preventDefault()} onClick={() => editor.dispatchCommand(REDO_COMMAND, undefined)}></button>
</div>
}
export interface ChannelDocumentEngineProps {
body: string
onChange: (body: string) => void
placeholder?: string
}
export default function ChannelDocumentEngine({ body, onChange, placeholder = '从这里开始记录……' }: ChannelDocumentEngineProps) {
const initialConfig = useMemo(() => ({
namespace: 'HoloLakeChannelDocumentEngine',
nodes: [HeadingNode, QuoteNode, ListNode, ListItemNode],
theme: {
paragraph: 'education-engine-paragraph',
heading: { h2: 'education-engine-heading-two' },
quote: 'education-engine-quote',
list: { ul: 'education-engine-list', checklist: 'education-engine-checklist' },
text: { bold: 'education-engine-bold', italic: 'education-engine-italic' },
},
onError: (error: Error) => { throw error },
editorState: () => $convertFromMarkdownString(body, CHANNEL_DOCUMENT_TRANSFORMERS),
}), [])
return <LexicalComposer initialConfig={initialConfig}>
<ChannelDocumentToolbar/>
<div className="education-engine-canvas">
<RichTextPlugin
contentEditable={<ContentEditable className="education-engine-editor" aria-label="频道文档正文"/>}
placeholder={<div className="education-engine-placeholder">{placeholder}</div>}
ErrorBoundary={LexicalErrorBoundary}
/>
<HistoryPlugin/>
<MarkdownShortcutPlugin transformers={CHANNEL_DOCUMENT_TRANSFORMERS}/>
<OnChangePlugin ignoreSelectionChange onChange={(editorState) => editorState.read(() => onChange($convertToMarkdownString(CHANNEL_DOCUMENT_TRANSFORMERS)))}/>
</div>
</LexicalComposer>
}

View file

@ -0,0 +1,17 @@
export const channelWorkbenchManifest = {
schema: 'hololake.channel-module/v1',
moduleId: 'hololake.builtin.channel-workbench',
name: '频道轻量工作台',
version: '0.2.0',
slot: 'channel-workbench',
origin: 'resident',
state: 'BUILT_IN_FOUNDATION',
marketplaceRegistration: 'NOT_A_MARKETPLACE_MODULE',
engines: {
document: 'LEXICAL_0_49',
spreadsheet: 'FORTUNE_SHEET_1_0_4',
},
exports: ['ChannelDocumentEngine', 'ChannelSpreadsheetEngine'],
consumerRule: 'INDUSTRY_MODULES_CONSUME_FOUNDATION_WITHOUT_DUPLICATING_ENGINES',
authority: 'CURRENT_CHANNEL_LOCAL_DATA_ONLY',
} as const

View file

@ -0,0 +1,67 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { Workbook, type WorkbookInstance } from '@fortune-sheet/react'
import '@fortune-sheet/react/dist/index.css'
import { createInitialWorkbook, readWorkbookValue, type ChannelSpreadsheetValue, type FortuneSheet } from './spreadsheet-model'
export type { ChannelSpreadsheetValue } from './spreadsheet-model'
interface ChannelSpreadsheetEngineProps extends ChannelSpreadsheetValue {
tableId: string
title: string
onChange: (value: ChannelSpreadsheetValue) => void
}
export default function ChannelSpreadsheetEngine({ tableId, title, columns, rows, onChange }: ChannelSpreadsheetEngineProps) {
const workbookRef = useRef<WorkbookInstance>(null)
const [initialData] = useState(() => createInitialWorkbook(title, tableId, { columns, rows }))
const [latest, setLatest] = useState<ChannelSpreadsheetValue>({ columns, rows })
const latestRef = useRef<ChannelSpreadsheetValue>({ columns, rows })
const publish = useCallback((data: FortuneSheet[]) => {
const next = readWorkbookValue(data[0], latestRef.current)
latestRef.current = next
setLatest(next)
onChange(next)
}, [onChange])
useEffect(() => {
// FortuneSheet preserves formula expressions but does not automatically
// execute its persisted calc chain when a workbook is mounted. Use its
// public calculation API after the instance is ready so reopening a local
// channel workbook restores rendered results as well as expressions.
// The workbook builds its cell matrix in an effect of its own. Waiting for
// that initialization avoids an upstream race where the public API sees an
// empty matrix and returns without calculating anything.
const timer = window.setTimeout(() => workbookRef.current?.calculateFormula(), 300)
return () => window.clearTimeout(timer)
}, [tableId])
return <section className="channel-spreadsheet-engine" aria-label="频道内置工作表引擎">
<header>
<div><b></b><span> · </span></div>
<em></em>
</header>
<div className="channel-spreadsheet-canvas">
<Workbook
ref={workbookRef}
data={initialData}
lang="zh"
onChange={publish}
allowEdit
showToolbar
showFormulaBar
showSheetTabs
// Persisted native rows store the formula expression rather than a
// stale rendered result. Recalculate on mount so saved workbooks open
// with both the expression and its current value restored.
forceCalculation
row={Math.max(40, latest.rows.length + 10)}
column={Math.max(12, latest.columns.length + 4)}
defaultColWidth={112}
defaultRowHeight={30}
defaultFontSize={12}
toolbarItems={['undo', 'redo', '|', 'currency-format', 'percentage-format', 'format', '|', 'bold', 'italic', 'underline', '|', 'font-color', 'background', 'border', 'merge-cell', '|', 'horizontal-align', 'vertical-align', 'text-wrap', '|', 'freeze', 'conditionFormat', 'filter', 'quick-formula', 'dataVerification', 'search']}
cellContextMenu={['copy', 'paste', '|', 'insert-row', 'insert-column', 'delete-row', 'delete-column', 'delete-cell', 'hide-row', 'hide-column', 'set-row-height', 'set-column-width', '|', 'clear', 'sort', 'orderAZ', 'orderZA']}
/>
</div>
</section>
}

View file

@ -0,0 +1,54 @@
import { describe, expect, it } from 'vitest'
import { createInitialWorkbook, readWorkbookValue, type ChannelSpreadsheetValue, type FortuneCell } from './spreadsheet-model'
const native: ChannelSpreadsheetValue = {
columns: [
{ columnId: 'COL-NAME', title: '项目' },
{ columnId: 'COL-AMOUNT', title: '金额' },
],
rows: [
{ rowId: 'ROW-1', cells: ['第一项', '100'] },
{ rowId: 'ROW-2', cells: ['合计', '=B2*2'] },
],
}
describe('channel spreadsheet model', () => {
it('boots native channel rows into a real workbook without losing formulas', () => {
const [sheet] = createInitialWorkbook('验收工作表', 'TABLE-1', native)
expect(sheet.celldata).toEqual(expect.arrayContaining([
expect.objectContaining({ r: 1, c: 1, v: expect.objectContaining({ v: 100 }) }),
expect.objectContaining({ r: 2, c: 1, v: expect.objectContaining({ f: '=B2*2' }) }),
]))
expect(sheet.row).toBeGreaterThanOrEqual(40)
expect(sheet.column).toBeGreaterThanOrEqual(12)
expect(sheet.calcChain).toEqual([{ r: 2, c: 1, id: 'TABLE-1' }])
expect(sheet.luckysheet_select_save).toEqual([
{ row: [0, 0], column: [0, 0], row_focus: 0, column_focus: 0 },
])
})
it('round-trips edited workbook cells while retaining stable native ids', () => {
const value = readWorkbookValue({
name: '验收工作表',
data: [
[{ v: '项目' }, { v: '金额' }, { v: '备注' }],
[{ v: '第一项' }, { v: 100 }, { v: '已核验' }],
[{ v: '合计' }, { f: '=B2*2', v: 200 }, null],
],
}, native, () => 'NEW-ID')
expect(value.columns.map((column) => column.columnId)).toEqual(['COL-NAME', 'COL-AMOUNT', 'COL-NEW-ID'])
expect(value.rows[0].rowId).toBe('ROW-1')
expect(value.rows[1].cells[1]).toBe('=B2*2')
expect(value.rows[0].cells[2]).toBe('已核验')
})
it('trims the engine canvas to meaningful rows instead of saving its empty viewport', () => {
const matrix: Array<Array<FortuneCell | null>> = Array.from({ length: 40 }, () => Array.from({ length: 12 }, () => null))
matrix[0][0] = { v: '字段' }
matrix[1][0] = { v: '内容' }
const value = readWorkbookValue({ name: '空白工作表', data: matrix }, { columns: [], rows: [] }, () => 'ID')
expect(value.columns).toHaveLength(1)
expect(value.rows).toHaveLength(1)
expect(value.rows[0].cells).toEqual(['内容'])
})
})

View file

@ -0,0 +1,115 @@
export interface ChannelWorkbenchColumn {
columnId: string
title: string
}
export interface ChannelWorkbenchRow {
rowId: string
cells: string[]
}
export interface ChannelSpreadsheetValue {
columns: ChannelWorkbenchColumn[]
rows: ChannelWorkbenchRow[]
}
export type FortuneCell = {
v?: string | number | boolean
m?: string | number
f?: string
bg?: string
fc?: string
bl?: number
fs?: number
ht?: number
vt?: number
tb?: string
}
export type FortuneSheet = {
name: string
id?: string
status?: number
row?: number
column?: number
data?: Array<Array<FortuneCell | null>>
celldata?: Array<{ r: number; c: number; v: FortuneCell | null }>
calcChain?: Array<{ r: number; c: number; id: string }>
luckysheet_select_save?: Array<{
row: number[]
column: number[]
row_focus?: number
column_focus?: number
}>
}
function nativeCell(value: string, header = false): FortuneCell {
if (header) return { v: value, m: value, bl: 1, fs: 12, bg: '#ece8dc', fc: '#202632', ht: 0, vt: 0, tb: '2' }
if (value.startsWith('=')) return { f: value }
const normalized = value.trim().replaceAll(',', '')
if (normalized && /^-?(?:\d+\.?\d*|\.\d+)$/.test(normalized)) return { v: Number(normalized), m: value }
return { v: value, m: value, tb: '2' }
}
export function createInitialWorkbook(title: string, tableId: string, value: ChannelSpreadsheetValue): FortuneSheet[] {
const width = Math.max(1, value.columns.length)
const matrix: Array<Array<FortuneCell | null>> = [
value.columns.map((column) => nativeCell(column.title, true)),
...value.rows.map((row) => Array.from({ length: width }, (_, index) => nativeCell(row.cells[index] || ''))),
]
const celldata = matrix.flatMap((row, r) => row.map((v, c) => ({ r, c, v })))
const calcChain = celldata.flatMap(({ r, c, v }) => typeof v?.f === 'string' && v.f
? [{ r, c, id: tableId }]
: [])
return [{
name: title || '工作表 1',
id: tableId,
status: 1,
row: Math.max(40, matrix.length + 10),
column: Math.max(12, width + 4),
celldata,
// FortuneSheet requires formula cells in calcChain during initialization;
// without it, the expression survives persistence but is not recalculated.
calcChain,
// FortuneSheet 1.0.4 creates `[0]` instead of `[0, 0]` for its first
// automatic selection. Seed a complete A1 range for the location box,
// formula editor and keyboard navigation.
luckysheet_select_save: [{ row: [0, 0], column: [0, 0], row_focus: 0, column_focus: 0 }],
}]
}
function cellText(cell: FortuneCell | null | undefined): string {
if (!cell) return ''
if (typeof cell.f === 'string' && cell.f) return cell.f
const value = cell.m ?? cell.v ?? ''
return String(value)
}
export function readWorkbookValue(
sheet: FortuneSheet | undefined,
previous: ChannelSpreadsheetValue,
createId: () => string = () => crypto.randomUUID(),
): ChannelSpreadsheetValue {
const matrix = sheet?.data || []
let lastRow = 0
let lastColumn = Math.max(0, previous.columns.length - 1)
matrix.forEach((row, rowIndex) => row?.forEach((cell, columnIndex) => {
if (cellText(cell).trim()) {
lastRow = Math.max(lastRow, rowIndex)
lastColumn = Math.max(lastColumn, columnIndex)
}
}))
const columnCount = Math.min(64, Math.max(1, lastColumn + 1))
const columns = Array.from({ length: columnCount }, (_, index) => ({
columnId: previous.columns[index]?.columnId || `COL-${createId()}`,
title: cellText(matrix[0]?.[index]).trim() || previous.columns[index]?.title || `字段 ${index + 1}`,
}))
const rows = Array.from({ length: Math.min(5000, lastRow) }, (_, offset) => {
const rowIndex = offset + 1
return {
rowId: previous.rows[offset]?.rowId || `ROW-${createId()}`,
cells: Array.from({ length: columnCount }, (_, columnIndex) => cellText(matrix[rowIndex]?.[columnIndex])),
}
})
return { columns, rows }
}

View file

@ -0,0 +1,81 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
export const EDUCATION_EDITABLE_CELL_BUDGET = 240
export type EducationAdaptiveView = 'grid' | 'cards' | 'board' | 'dashboard'
export interface EducationAdaptiveInputs {
rowCount: number
columnCount: number
numericColumnCount: number
groupableColumnCount: number
focusConfidence: 'high' | 'medium' | 'low'
}
export interface EducationModelRenderProposal {
primaryFocus: string
groupColumnIndex?: number
measureColumnIndex?: number
views: EducationAdaptiveView[]
pageSize?: number
}
export interface EducationAdaptiveRenderPlan {
source: 'LOCAL_BASELINE' | 'MODEL_PROPOSAL'
state: 'READY' | 'DEGRADED' | 'WAITING_HUMAN_CONFIRMATION' | 'MODEL_PROPOSAL_REJECTED'
views: EducationAdaptiveView[]
pageSize: number
primaryFocus: string
reasons: string[]
}
export function educationEditablePageSize(columnCount: number): number {
const boundedColumns = Math.max(1, Math.min(30, columnCount))
return Math.max(5, Math.min(20, Math.floor(EDUCATION_EDITABLE_CELL_BUDGET / boundedColumns)))
}
export function buildEducationBaselineRenderPlan(inputs: EducationAdaptiveInputs): EducationAdaptiveRenderPlan {
const reasons: string[] = []
const views: EducationAdaptiveView[] = ['grid', 'cards']
if (inputs.groupableColumnCount > 0) views.push('board')
else reasons.push('没有可靠分类字段,分类看板已降级停用。')
if (inputs.numericColumnCount > 0) views.push('dashboard')
else reasons.push('没有可靠数值字段,数据概览降级为计数摘要。')
if (inputs.focusConfidence === 'low') reasons.push('重点置信度不足,保留人工选择,不自动突出字段。')
if (inputs.rowCount * inputs.columnCount > EDUCATION_EDITABLE_CELL_BUDGET) reasons.push('可编辑单元格超过本机安全预算,已启用动态分页。')
return {
source: 'LOCAL_BASELINE',
state: reasons.length ? 'DEGRADED' : 'READY',
views,
pageSize: educationEditablePageSize(inputs.columnCount),
primaryFocus: inputs.focusConfidence === 'low' ? '重点待选择' : '本地基础算法重点',
reasons,
}
}
export function validateEducationModelRenderProposal(
inputs: EducationAdaptiveInputs,
proposal: EducationModelRenderProposal,
humanConfirmed: boolean,
): EducationAdaptiveRenderPlan {
const baseline = buildEducationBaselineRenderPlan(inputs)
const reasons: string[] = []
const uniqueViews = [...new Set(proposal.views)]
if (!proposal.primaryFocus.trim() || proposal.primaryFocus.length > 120) reasons.push('模型建议的重点标题无效。')
if (!uniqueViews.length || uniqueViews.some((view) => !['grid', 'cards', 'board', 'dashboard'].includes(view))) reasons.push('模型建议包含未登记视图。')
if (proposal.groupColumnIndex !== undefined && (proposal.groupColumnIndex < 0 || proposal.groupColumnIndex >= inputs.columnCount)) reasons.push('模型建议的分类字段不存在。')
if (proposal.measureColumnIndex !== undefined && (proposal.measureColumnIndex < 0 || proposal.measureColumnIndex >= inputs.columnCount)) reasons.push('模型建议的统计字段不存在。')
const requestedPageSize = proposal.pageSize ?? baseline.pageSize
const maximumSafePageSize = educationEditablePageSize(inputs.columnCount)
if (!Number.isInteger(requestedPageSize) || requestedPageSize < 5 || requestedPageSize > maximumSafePageSize) reasons.push('模型建议的页面密度超过本机安全预算。')
if (reasons.length) return { ...baseline, state: 'MODEL_PROPOSAL_REJECTED', reasons: [...reasons, '已优雅退回本地基础算法。'] }
if (!humanConfirmed) return { ...baseline, state: 'WAITING_HUMAN_CONFIRMATION', reasons: ['模型建议已通过本地校验,等待用户确认后启用。'] }
return {
source: 'MODEL_PROPOSAL',
state: 'READY',
views: uniqueViews,
pageSize: requestedPageSize,
primaryFocus: proposal.primaryFocus.trim(),
reasons: ['模型建议已通过本地结构校验、资源上限与用户确认。'],
}
}

View file

@ -0,0 +1,60 @@
import { describe, expect, it } from 'vitest'
import { analyzeEducationTableData } from './education-data'
describe('education data cleanup', () => {
it('trims cells, removes only fully empty rows and keeps the first exact row', () => {
const result = analyzeEducationTableData({
columns: [
{ columnId: 'name', title: '姓名' },
{ columnId: 'course', title: '课程' },
],
rows: [
{ rowId: 'row-1', cells: [' 冰朔 ', ' 写作 '] },
{ rowId: 'row-2', cells: ['冰朔', '写作'] },
{ rowId: 'row-3', cells: [' ', ''] },
{ rowId: 'row-4', cells: ['另一位学员', ''] },
],
})
expect(result.originalRows).toBe(4)
expect(result.emptyRows).toBe(1)
expect(result.duplicateRows).toBe(1)
expect(result.trimmedCells).toBe(3)
expect(result.changeCount).toBe(5)
expect(result.changePreview).toHaveLength(5)
expect(result.cleanedRows).toEqual([
{ rowId: 'row-1', cells: ['冰朔', '写作'] },
{ rowId: 'row-4', cells: ['另一位学员', ''] },
])
})
it('does not merge rows that differ in any field', () => {
const result = analyzeEducationTableData({
columns: [{ columnId: 'status', title: '状态' }],
rows: [
{ rowId: 'row-1', cells: ['已完成'] },
{ rowId: 'row-2', cells: ['进行中'] },
],
})
expect(result.duplicateRows).toBe(0)
expect(result.cleanedRows).toHaveLength(2)
})
it('analyzes the entire table while keeping the human preview bounded', () => {
const result = analyzeEducationTableData({
columns: Array.from({ length: 23 }, (_, index) => ({ columnId: `column-${index}`, title: `字段 ${index + 1}` })),
rows: Array.from({ length: 55 }, (_, rowIndex) => ({
rowId: `row-${rowIndex}`,
cells: Array.from({ length: 23 }, (_, columnIndex) => ` value-${rowIndex}-${columnIndex} `),
})),
})
expect(result.originalRows).toBe(55)
expect(result.cleanedRows).toHaveLength(55)
expect(result.trimmedCells).toBe(55 * 23)
expect(result.changeCount).toBe(55 * 23)
expect(result.changePreview).toHaveLength(12)
expect(result.changePreview.every((change) => change.before.length <= 120 && change.after.length <= 120)).toBe(true)
})
})

View file

@ -0,0 +1,75 @@
export interface EducationDataColumn { columnId: string; title: string }
export interface EducationDataRow { rowId: string; cells: string[] }
export interface EducationDataTable { columns: EducationDataColumn[]; rows: EducationDataRow[] }
export const EDUCATION_CLEANUP_CHANGE_PREVIEW_LIMIT = 12
export type EducationDataChangeKind = 'TRIM_CELL' | 'REMOVE_EMPTY_ROW' | 'REMOVE_DUPLICATE_ROW'
export interface EducationDataChangePreview {
kind: EducationDataChangeKind
rowNumber: number
columnTitle: string
before: string
after: string
}
export interface EducationDataAnalysis {
originalRows: number
cleanedRows: EducationDataRow[]
emptyRows: number
duplicateRows: number
trimmedCells: number
changeCount: number
changePreview: EducationDataChangePreview[]
}
function previewCell(value: string): string {
const singleLine = value.replace(/\t/g, '⇥').replace(/\r?\n/g, '↵').replace(/ /g, '·')
return singleLine.length > 120 ? `${singleLine.slice(0, 117)}` : singleLine
}
export function analyzeEducationTableData(table: EducationDataTable): EducationDataAnalysis {
let emptyRows = 0
let duplicateRows = 0
let trimmedCells = 0
let changeCount = 0
const seen = new Set<string>()
const cleanedRows: EducationDataRow[] = []
const changePreview: EducationDataChangePreview[] = []
const addPreview = (change: EducationDataChangePreview) => {
changeCount += 1
if (changePreview.length < EDUCATION_CLEANUP_CHANGE_PREVIEW_LIMIT) changePreview.push(change)
}
for (const [rowIndex, row] of table.rows.entries()) {
const cells = table.columns.map((_, index) => {
const original = row.cells[index] || ''
const cleaned = original.trim()
if (cleaned !== original) {
trimmedCells += 1
addPreview({
kind: 'TRIM_CELL',
rowNumber: rowIndex + 1,
columnTitle: table.columns[index]?.title || `字段 ${index + 1}`,
before: previewCell(original),
after: previewCell(cleaned),
})
}
return cleaned
})
if (cells.every((cell) => !cell)) {
emptyRows += 1
addPreview({ kind: 'REMOVE_EMPTY_ROW', rowNumber: rowIndex + 1, columnTitle: '整行', before: '完整空行', after: '移除' })
continue
}
const signature = JSON.stringify(cells)
if (seen.has(signature)) {
duplicateRows += 1
addPreview({ kind: 'REMOVE_DUPLICATE_ROW', rowNumber: rowIndex + 1, columnTitle: '整行', before: '与前文完全重复', after: '移除' })
continue
}
seen.add(signature)
cleanedRows.push({ ...row, cells })
}
return { originalRows: table.rows.length, cleanedRows, emptyRows, duplicateRows, trimmedCells, changeCount, changePreview }
}

View file

@ -0,0 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import ChannelDocumentEngine from './channel-workbench/document-engine'
export default function EducationDocumentEngine({ body, onChange }: { body: string; onChange: (body: string) => void }) {
return <ChannelDocumentEngine body={body} onChange={onChange} placeholder="从这里开始记录教学计划、会议纪要或课程资料……"/>
}

View file

@ -0,0 +1,137 @@
import type { CSSProperties } from 'react'
export { nativeCompositionManifest } from './manifest'
export type CompositionDimension = 'SOURCE' | 'TOP_LEVEL_FOLDER'
export type CompositionMeasure = 'DOCUMENT_COUNT' | 'TOTAL_BYTES' | 'DUPLICATE_COUNT'
export type ProjectionView = 'DASHBOARD' | 'COMPARISON' | 'VERTICAL_BAR' | 'CLASSIFICATION' | 'TABLE'
export interface NativeCompositionRow {
rowId: string
source: string
path: string
title: string
topLevelFolder: string
sizeBytes: number
duplicateCount: number
updatedAtUnixMs: number
}
export interface NativeCompositionGroup {
key: string
label: string
documentCount: number
totalBytes: number
duplicateCount: number
measureValue: number
share: number
}
export interface NativeCompositionProjection {
schema: string
state: string
executionId: string
executedAtUnixMs: number
sourceIsRealAccountData: boolean
readOnly: boolean
dimension: CompositionDimension
measure: CompositionMeasure
views: ProjectionView[]
nativeObject: {
schema: string
objectId: string
title: string
rowCount: number
truncated: boolean
sourceReceipt: string
rows: NativeCompositionRow[]
}
groups: NativeCompositionGroup[]
metrics: {
rawDocumentCount: number
uniqueDocumentCount: number
duplicateDocumentCount: number
totalBytes: number
groupCount: number
}
recipe: {
schema: string
recipeId: string
title: string
nodes: { nodeId: string; moduleId: string }[]
edges: { from: string; to: string }[]
}
dataSha256: string
receiptSha256: string
}
const viewNames: Record<ProjectionView, string> = {
DASHBOARD: '仪表盘',
COMPARISON: '对比',
VERTICAL_BAR: '柱状图',
CLASSIFICATION: '分类',
TABLE: '明细表',
}
const measureNames: Record<CompositionMeasure, string> = {
DOCUMENT_COUNT: '文档数量',
TOTAL_BYTES: '数据量',
DUPLICATE_COUNT: '重复数量',
}
export function formatCompositionValue(value: number, measure: CompositionMeasure): string {
if (measure !== 'TOTAL_BYTES') return `${value.toLocaleString('zh-CN')}`
if (value < 1024) return `${value.toLocaleString('zh-CN')} B`
if (value < 1024 * 1024) return `${(value / 1024).toFixed(value < 10 * 1024 ? 1 : 0)} KB`
return `${(value / 1024 / 1024).toFixed(1)} MB`
}
export function toggleProjectionView(current: ProjectionView[], view: ProjectionView): ProjectionView[] {
if (current.includes(view)) return current.length === 1 ? current : current.filter((item) => item !== view)
return [...current, view]
}
interface NativeCompositionStudioProps {
projection: NativeCompositionProjection | null
dimension: CompositionDimension
measure: CompositionMeasure
selectedViews: ProjectionView[]
busy: boolean
message: string
onDimensionChange: (value: CompositionDimension) => void
onMeasureChange: (value: CompositionMeasure) => void
onViewsChange: (value: ProjectionView[]) => void
onExecute: () => void
}
export function NativeCompositionStudio(props: NativeCompositionStudioProps) {
const { projection, dimension, measure, selectedViews, busy, message } = props
const groups = projection?.groups || []
const activeDimension = projection?.dimension || dimension
const activeMeasure = projection?.measure || measure
const activeViews = projection?.views || selectedViews
const maximum = Math.max(1, ...groups.map((group) => group.measureValue))
return <div className="native-composition-layout">
<aside className="composition-recipe-panel">
<header><span></span><h2></h2><p> · </p></header>
<section className="composition-source"><i/><div><span></span><b></b><small>{projection ? `${projection.nativeObject.rowCount} 条原生对象` : '等待系统读取'}</small></div></section>
<div className="composition-connector" aria-hidden="true"/>
<label><span></span><select aria-label="组合分类维度" value={dimension} onChange={(event) => props.onDimensionChange(event.target.value as CompositionDimension)}><option value="TOP_LEVEL_FOLDER"></option><option value="SOURCE"></option></select></label>
<label><span></span><select aria-label="组合统计指标" value={measure} onChange={(event) => props.onMeasureChange(event.target.value as CompositionMeasure)}><option value="DOCUMENT_COUNT"></option><option value="TOTAL_BYTES"></option><option value="DUPLICATE_COUNT"></option></select></label>
<fieldset><legend></legend>{(Object.keys(viewNames) as ProjectionView[]).map((view) => <label key={view}><input type="checkbox" checked={selectedViews.includes(view)} onChange={() => props.onViewsChange(toggleProjectionView(selectedViews, view))}/><span>{viewNames[view]}</span></label>)}</fieldset>
<button className="composition-execute" type="button" disabled={busy} onClick={props.onExecute}>{busy ? '系统正在组合…' : '按当前配方重新组合'}</button>
<footer>{projection ? <><b></b><span> {projection.receiptSha256.slice(0, 12)}</span><small>{new Date(projection.executedAtUnixMs).toLocaleString('zh-CN')}</small></> : <span>{message || '组合执行只读,不修改知识原文。'}</span>}</footer>
</aside>
<main className="composition-projection-stage" aria-busy={busy}>
<header><div><span>HUMAN PROJECTION</span><h1></h1><p>{projection ? `${activeDimension === 'SOURCE' ? '知识来源' : '一级目录'}观察${measureNames[activeMeasure]} · 数据来自当前账号真实知识目录` : '正在准备当前账号的原生知识对象'}</p></div><div className="composition-state"><i/><span>{projection?.sourceIsRealAccountData ? '真实数据已接入' : '等待数据'}</span><small>{projection?.readOnly ? '只读组合' : '未执行'}</small></div></header>
{message && <p className="composition-message">{message}</p>}
{projection && projection.nativeObject.rowCount === 0 && <section className="composition-empty"><span></span><h2></h2><p></p></section>}
{projection && projection.nativeObject.rowCount > 0 && <div className="composition-view-grid">
{activeViews.includes('DASHBOARD') && <section className="composition-view composition-dashboard"><header><span></span><small></small></header><div><article><span></span><strong>{projection.metrics.uniqueDocumentCount.toLocaleString('zh-CN')}</strong><small></small></article><article><span></span><strong>{projection.metrics.rawDocumentCount.toLocaleString('zh-CN')}</strong><small></small></article><article><span></span><strong>{formatCompositionValue(projection.metrics.totalBytes, 'TOTAL_BYTES')}</strong><small></small></article><article><span></span><strong>{projection.metrics.groupCount}</strong><small></small></article></div></section>}
{activeViews.includes('VERTICAL_BAR') && <section className="composition-view composition-bars"><header><span></span><small>{measureNames[activeMeasure]} · </small></header><div className="composition-bar-plot">{groups.slice(0, 12).map((group) => <article key={group.key}><div className="composition-bar-track"><i style={{ '--bar-height': `${Math.max(4, group.measureValue / maximum * 100)}%` } as CSSProperties}/><em>{formatCompositionValue(group.measureValue, activeMeasure)}</em></div><b title={group.label}>{group.label}</b></article>)}</div></section>}
{activeViews.includes('COMPARISON') && <section className="composition-view composition-comparison"><header><span></span><small></small></header><div>{groups.slice(0, 10).map((group) => <article key={group.key}><div><b>{group.label}</b><span>{formatCompositionValue(group.measureValue, activeMeasure)}</span></div><i><em style={{ '--share': `${group.share * 100}%` } as CSSProperties}/></i><small>{(group.share * 100).toFixed(1)}%</small></article>)}</div></section>}
{activeViews.includes('CLASSIFICATION') && <section className="composition-view composition-classification"><header><span></span><small></small></header><div>{groups.map((group, index) => <article key={group.key}><i>{String(index + 1).padStart(2, '0')}</i><div><b>{group.label}</b><span>{group.documentCount} · {formatCompositionValue(group.totalBytes, 'TOTAL_BYTES')}</span></div><strong>{formatCompositionValue(group.measureValue, activeMeasure)}</strong></article>)}</div></section>}
{activeViews.includes('TABLE') && <section className="composition-view composition-table"><header><span></span><small>{projection.nativeObject.schema} · </small></header><div><table><thead><tr><th></th><th></th><th></th><th></th><th></th><th></th></tr></thead><tbody>{projection.nativeObject.rows.slice(0, 100).map((row) => <tr key={row.rowId}><td title={row.path}>{row.title}</td><td>{row.topLevelFolder}</td><td>{row.source === 'native' ? '光湖原生' : row.source}</td><td>{formatCompositionValue(row.sizeBytes, 'TOTAL_BYTES')}</td><td>{row.duplicateCount}</td><td>{new Date(row.updatedAtUnixMs).toLocaleDateString('zh-CN')}</td></tr>)}</tbody></table></div></section>}
</div>}
</main>
</div>
}

View file

@ -0,0 +1,11 @@
export const nativeCompositionManifest = {
schema: 'hololake.composition-module/v1',
moduleId: 'hololake.native-composition-projection',
name: '原生组合视图',
version: '0.1.0',
slot: 'education-composition',
origin: 'resident',
input: 'hololake.human-projection/v1',
exports: ['NativeCompositionStudio', 'formatCompositionValue', 'toggleProjectionView'],
authority: 'READ_ONLY_HUMAN_PROJECTION',
} as const

View file

@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest'
import { formatCompositionValue, toggleProjectionView, type ProjectionView } from './index'
describe('native composition human projection', () => {
it('formats one native measure consistently across projections', () => {
expect(formatCompositionValue(12, 'DOCUMENT_COUNT')).toBe('12 篇')
expect(formatCompositionValue(1536, 'TOTAL_BYTES')).toBe('1.5 KB')
expect(formatCompositionValue(2, 'DUPLICATE_COUNT')).toBe('2 篇')
})
it('allows free projection combinations but never an empty projection', () => {
const initial: ProjectionView[] = ['DASHBOARD', 'TABLE']
expect(toggleProjectionView(initial, 'VERTICAL_BAR')).toEqual(['DASHBOARD', 'TABLE', 'VERTICAL_BAR'])
expect(toggleProjectionView(initial, 'TABLE')).toEqual(['DASHBOARD'])
expect(toggleProjectionView(['TABLE'], 'TABLE')).toEqual(['TABLE'])
})
})

View file

@ -147,7 +147,35 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.search-box input { min-width: 0; border: 0; outline: 0; color: var(--content-secondary); background: transparent; font-size: 14px; }
.search-box button { border: 0; color: var(--content-muted); background: transparent; font-size: 12px; cursor: pointer; }
.knowledge-counts { display: flex; justify-content: space-between; gap: 8px; padding: 13px 16px 10px; color: var(--content-muted); font-size: 11.5px; font-weight: 540; }
.knowledge-tree { min-height: 0; overflow: auto; padding: 2px 8px 18px; }
.knowledge-mode-switch { display: grid; grid-template-columns: 1fr 1fr; gap: 3px; margin: 10px 14px 0; padding: 3px; border: 1px solid var(--panel-edge); border-radius: 9px; background: color-mix(in srgb, var(--surface-depth) 68%, transparent); }
.knowledge-mode-switch button { height: 30px; border: 0; border-radius: 6px; color: var(--content-muted); background: transparent; font-size: 12px; font-weight: 650; cursor: pointer; }
.knowledge-mode-switch button.active { color: var(--content-primary); background: var(--primitive-glass-hover); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-light) 20%, transparent); }
.knowledge-organizer-overview { flex: 0 1 auto; max-height: 45%; overflow: auto; margin: 10px 10px 0; padding: 12px; border: 1px solid color-mix(in srgb, var(--panel-edge) 72%, transparent); border-radius: 11px; background: color-mix(in srgb, var(--primitive-glass) 42%, transparent); }
.knowledge-organization-metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.knowledge-organization-metrics span { display: grid; gap: 3px; padding: 8px 5px; border-radius: 8px; color: var(--content-muted); background: color-mix(in srgb, var(--surface-depth) 55%, transparent); text-align: center; font-size: 9.5px; }
.knowledge-organization-metrics b { color: var(--content-primary); font-size: 16px; }
.knowledge-organizer-overview section { margin-top: 14px; }
.knowledge-organizer-overview section > header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 7px; }
.knowledge-organizer-overview section > header b { color: var(--content-secondary); font-size: 11.5px; }
.knowledge-organizer-overview section > header button { border: 0; color: var(--content-muted); background: transparent; font-size: 10px; cursor: pointer; }
.knowledge-filter-results { padding: 9px; border: 1px solid color-mix(in srgb, var(--accent-light) 24%, var(--panel-edge)); border-radius: 9px; background: color-mix(in srgb, var(--accent-light) 5%, transparent); }
.knowledge-filter-results > header span { color: var(--content-muted); font-size: 10px; }
.knowledge-filter-results > div { display: grid; gap: 4px; }
.knowledge-filter-results > div > button { display: grid; gap: 3px; width: 100%; padding: 8px 9px; border: 0; border-radius: 7px; color: var(--content-primary); background: transparent; text-align: left; cursor: pointer; }
.knowledge-filter-results > div > button:hover { background: var(--primitive-glass-hover); }
.knowledge-filter-results > div > button span { overflow: hidden; font-size: 11.5px; text-overflow: ellipsis; white-space: nowrap; }
.knowledge-filter-results > div > button small { overflow: hidden; color: var(--content-muted); font-size: 9.5px; text-overflow: ellipsis; white-space: nowrap; }
.knowledge-filter-results > p { margin: 7px 0 0; color: var(--content-muted); font-size: 9.5px; line-height: 1.45; }
.knowledge-category-list { display: grid; gap: 3px; }
.knowledge-category-list button { display: flex; align-items: center; justify-content: space-between; min-height: 30px; padding: 0 8px; border: 0; border-radius: 7px; color: var(--content-muted); background: transparent; font-size: 11.5px; text-align: left; cursor: pointer; }
.knowledge-category-list button:hover, .knowledge-category-list button.active { color: var(--content-primary); background: var(--primitive-glass-hover); }
.knowledge-category-list em { color: var(--content-muted); font-size: 10px; font-style: normal; }
.knowledge-tag-cloud { display: flex; flex-wrap: wrap; gap: 5px; }
.knowledge-tag-cloud button { display: flex; gap: 5px; align-items: center; padding: 5px 8px; border: 1px solid color-mix(in srgb, var(--panel-edge) 78%, transparent); border-radius: 999px; color: var(--content-muted); background: transparent; font-size: 10.5px; cursor: pointer; }
.knowledge-tag-cloud button.active { border-color: color-mix(in srgb, var(--accent-light) 48%, transparent); color: var(--accent-light); background: color-mix(in srgb, var(--accent-light) 8%, transparent); }
.knowledge-tag-cloud em { font-size: 9px; font-style: normal; opacity: .72; }
.knowledge-tag-cloud p { margin: 0; color: var(--content-muted); font-size: 10.5px; line-height: 1.5; }
.knowledge-tree { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 2px 8px 18px; }
.tree-folder, .tree-document {
width: 100%; min-height: 38px; display: grid; align-items: center; gap: 7px;
border: 0; border-radius: 8px; color: var(--content-muted); background: transparent; text-align: left; cursor: pointer;
@ -164,6 +192,7 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.tree-chevron.open { transform: rotate(90deg); }
.knowledge-browser > footer, .code-channels > footer { min-height: 44px; padding: 12px 16px; border-top: 1px solid var(--panel-edge); color: var(--content-muted); font-size: 12px; line-height: 1.5; }
.document-workspace, .code-reader { min-width: 0; min-height: 0; display: grid; grid-template-rows: 49px minmax(0, 1fr); }
.document-workspace.knowledge-overview-open { grid-template-rows: minmax(0, 1fr); }
.document-toolbar, .code-reader > header {
min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 18px;
padding: 0 20px; border-bottom: 1px solid var(--panel-edge); background: color-mix(in srgb, var(--surface-depth) 66%, transparent);
@ -181,6 +210,33 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.reader-heading div { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 20px; }
.reader-heading span { padding: 5px 8px; border-radius: 6px; color: var(--content-muted); background: var(--primitive-glass); font-size: 12px; }
.reader-heading .meta-stats { margin-top: 15px; color: var(--content-muted); font-size: 12.5px; letter-spacing: .03em; }
.reader-heading .reader-organization-line { margin: 0 0 12px; align-items: center; }
.reader-heading .reader-organization-line span { padding: 0; color: var(--accent-light); background: transparent; font-size: 11px; font-weight: 700; letter-spacing: .08em; }
.reader-heading .reader-organization-line em { padding: 2px 7px; border-radius: 999px; color: var(--content-muted); background: var(--primitive-glass); font-size: 9px; font-style: normal; }
.knowledge-structure-overview { min-width: 0; min-height: 0; overflow: auto; padding: clamp(34px, 6vh, 70px) clamp(34px, 6vw, 88px) 80px; }
.knowledge-structure-overview > header { max-width: 760px; }
.knowledge-structure-overview > header > span { color: var(--accent-light); font-size: 10px; font-weight: 800; letter-spacing: .18em; }
.knowledge-structure-overview h2 { margin: 9px 0 10px; color: var(--content-primary); font-size: clamp(30px, 3.4vw, 44px); font-weight: 720; letter-spacing: -.035em; }
.knowledge-structure-overview > header p { margin: 0; color: var(--content-muted); font-size: 14px; font-weight: 520; line-height: 1.75; }
.knowledge-structure-stats { display: grid; grid-template-columns: repeat(4, minmax(110px, 1fr)); gap: 10px; max-width: 820px; margin: 28px 0 34px; }
.knowledge-structure-stats article { display: grid; gap: 5px; padding: 17px 18px; border: 1px solid var(--panel-edge); border-radius: 12px; background: var(--primitive-glass); }
.knowledge-structure-stats b { color: var(--content-primary); font-size: 25px; font-weight: 700; }
.knowledge-structure-stats span { color: var(--content-muted); font-size: 11px; }
.knowledge-structure-overview > section { display: grid; gap: 6px; max-width: 820px; }
.knowledge-structure-overview > section h3 { margin: 0 0 8px; color: var(--content-secondary); font-size: 13px; font-weight: 700; }
.knowledge-structure-overview > section button { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 13px 15px; border: 0; border-bottom: 1px solid var(--panel-edge); color: var(--content-secondary); background: transparent; text-align: left; cursor: pointer; }
.knowledge-structure-overview > section button:hover { border-radius: 9px; background: var(--primitive-glass-hover); }
.knowledge-structure-overview > section button span { display: grid; gap: 4px; }
.knowledge-structure-overview > section button b { color: var(--content-primary); font-size: 14px; }
.knowledge-structure-overview > section button small { color: var(--content-muted); font-size: 10.5px; }
.knowledge-structure-overview > section button strong { color: var(--accent-light); font-size: 16px; }
.knowledge-structure-actions { display: flex; gap: 10px; margin-top: 28px; }
.knowledge-organization-editor label { display: grid; gap: 5px; margin: 10px 0; }
.knowledge-organization-editor label span { color: var(--content-muted); font-size: 10.5px; }
.knowledge-organization-editor input { width: 100%; padding: 8px 9px; border: 1px solid var(--panel-edge); border-radius: 8px; outline: 0; color: var(--content-secondary); background: color-mix(in srgb, var(--surface-depth) 74%, transparent); font-size: 11px; }
.knowledge-organization-editor input:focus { border-color: color-mix(in srgb, var(--accent-light) 46%, transparent); }
.knowledge-organization-editor button { width: 100%; padding: 8px 10px; border: 1px solid color-mix(in srgb, var(--accent-light) 40%, transparent); border-radius: 8px; color: var(--content-primary); background: color-mix(in srgb, var(--accent-light) 8%, transparent); font-size: 11px; font-weight: 650; cursor: pointer; }
.knowledge-organization-editor button:disabled { opacity: .4; cursor: default; }
.knowledge-tree .tree-branch { position: relative; }
.tree-folder-tools { position: absolute; top: 4px; right: 6px; z-index: 5; }
.tree-folder-menu-button { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: 0; border-radius: 6px; background: transparent; color: var(--content-muted); cursor: pointer; opacity: .55; }
@ -554,6 +610,7 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.world-title-actions { position: absolute; right: 16px; display: flex; align-items: center; gap: 15px; }
.world-title-actions span { color: var(--content-muted); font-size: 12px; font-weight: 600; }
.world-title-actions button { font-size: 12.5px; }
.world-title-actions .main-world-return { color: var(--accent-light); }
.world-scene { position: absolute; z-index: 5; inset: 44px 0 45px; min-height: 0; }
.world-footer { position: absolute; z-index: 24; inset: auto 0 0; height: 45px; display: flex; align-items: center; justify-content: center; gap: 14px; }
.world-footer b { color: var(--content-muted); font-size: 11.5px; font-weight: 600; letter-spacing: .11em; }
@ -599,6 +656,7 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.official-hero h1 { margin: 0; color: var(--content-primary); font-size: clamp(24px, 3vw, 34px); font-weight: 700; letter-spacing: .09em; }
.official-hero p { margin: 10px 0 0; color: var(--accent-light); font-size: 12px; font-weight: 600; letter-spacing: .31em; }
.world-pool { position: absolute; z-index: 8; width: 190px; height: 100px; padding: 0; border: 0; background: transparent; cursor: pointer; color: inherit; }
.world-pool:disabled { cursor: default; }
.pool-bay { position: absolute; left: 50%; top: 0; width: 180px; height: 58px; transform: translateX(-50%); animation: world-pool-float 6.8s ease-in-out infinite; }
@keyframes world-pool-float { 0%, 100% { translate: 0 0; } 50% { translate: 0 -9px; } }
.pool-halo, .pool-heart, .pool-ring { position: absolute; left: 50%; top: 50%; border-radius: 50%; transform: translate(-50%, -50%); }
@ -642,6 +700,8 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.nucleus-locus { position: absolute; inset: 0; width: 100%; display: grid; align-content: center; justify-items: center; gap: 18px; padding: 0; border: 0; background: transparent; cursor: pointer; transition: opacity .45s ease, transform .55s ease; }
.nucleus-locus i { width: 250px; height: 88px; border-radius: 50%; filter: blur(7px); background: radial-gradient(closest-side, color-mix(in srgb, var(--primitive-warm-glow) 70%, transparent), color-mix(in srgb, var(--primitive-warm-glow) 22%, transparent) 52%, transparent 76%); animation: world-pool-breathe 5.2s ease-in-out infinite; }
.nucleus-locus b { color: var(--content-secondary); font-size: 16px; font-weight: 700; letter-spacing: .13em; }
.authenticated-world-entry .nucleus-locus { gap: 10px; }
.authenticated-world-entry .nucleus-locus small { color: var(--content-muted); font-size: 12px; font-weight: 650; letter-spacing: .08em; }
.number-nucleus.open .nucleus-locus { opacity: 0; transform: rotateX(58deg) translateY(-12px); pointer-events: none; }
.nucleus-panel, .domain-credential { border: 1px solid var(--panel-edge); border-radius: 18px; background: color-mix(in srgb, var(--panel-bg) 64%, transparent); backdrop-filter: blur(22px); box-shadow: 0 26px 80px rgba(0, 0, 0, .48); }
.nucleus-panel { position: absolute; left: 50%; top: 16px; width: 440px; padding: 27px 36px 25px; opacity: 0; transform: translateX(-50%) rotateX(-64deg); transform-origin: 50% 100%; pointer-events: none; transition: opacity .5s ease, transform .55s cubic-bezier(.22, 1, .36, 1); }
@ -681,6 +741,409 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.channel-main .pool-bay { width: 260px; height: 84px; } .channel-main .pool-label { top: 92px; }
.channel-knowledge { left: 14%; top: 48%; } .channel-code { left: 31%; top: 61%; } .channel-light { right: 31%; top: 61%; } .channel-status { right: 14%; top: 48%; }
.channel-time { left: 50%; bottom: 4%; transform: translateX(-50%); }
.subdomain-industry-entry .pool-label b { color: var(--accent-light); }
.subdomain-industry-world { background: radial-gradient(circle at 50% 44%, color-mix(in srgb, var(--primitive-cool-glow) 10%, transparent), transparent 42%); }
.public-industry-world { z-index: 24; background-color: color-mix(in srgb, var(--surface-depth) 96%, transparent); backdrop-filter: blur(20px); }
.industry-pool { width: 240px; }
.industry-pool .pool-bay { width: 220px; height: 72px; }
.industry-pool .pool-label { top: 78px; }
.industry-pool:disabled { opacity: .52; }
.industry-pool:disabled .pool-heart { box-shadow: 0 0 18px color-mix(in srgb, var(--primitive-cool-glow) 24%, transparent); }
.industry-web-novel { left: 12%; top: 47%; }
.industry-pet { right: 12%; top: 47%; }
.industry-education { left: 50%; top: 42%; width: 300px; transform: translateX(-50%); }
.industry-education .pool-bay { width: 280px; height: 92px; }
.industry-education .pool-label { top: 98px; }
.education-channel-core { left: 50%; top: 34%; width: 320px; transform: translateX(-50%); }
.education-channel-core .pool-bay { width: 300px; height: 92px; }
.education-channel-core .pool-label { top: 98px; }
.education-module-slot { opacity: .62; }
.education-module-slot:not(:disabled) { opacity: 1; }
.education-module-slot .pool-heart { width: 20px; height: 20px; }
.education-module-slot .pool-label b { color: var(--content-secondary); }
.education-module-1 { left: 9%; top: 48%; }
.education-module-2 { left: 28%; top: 64%; }
.education-module-3 { left: 50%; top: 72%; transform: translateX(-50%); }
.education-module-4 { right: 28%; top: 64%; }
.education-module-5 { right: 9%; top: 48%; }
.education-channel-boundary { position: absolute; z-index: 10; left: 50%; bottom: 7%; width: min(820px, calc(100% - 72px)); margin: 0; transform: translateX(-50%); color: var(--content-muted); text-align: center; font-size: 12.5px; font-weight: 650; line-height: 1.75; letter-spacing: .055em; text-wrap: balance; }
.education-workspace-world { position: absolute; z-index: 52; inset: 0; display: grid; grid-template-rows: 64px minmax(0, 1fr); color: var(--content-primary); background: radial-gradient(circle at 74% 16%, color-mix(in srgb, var(--primitive-cool-glow) 12%, transparent), transparent 32%), color-mix(in srgb, var(--surface-depth) 97%, transparent); backdrop-filter: blur(24px); }
.education-workspace-bar { display: grid; grid-template-columns: minmax(160px, 1fr) auto minmax(160px, 1fr); align-items: center; gap: 18px; padding: 0 24px; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 72%, transparent); background: color-mix(in srgb, var(--panel-bg) 84%, transparent); }
.education-workspace-bar > button { justify-self: start; border: 0; background: transparent; color: var(--content-muted); font-size: 12.5px; font-weight: 650; cursor: pointer; }
.education-workspace-bar > div { display: grid; justify-items: center; gap: 3px; }
.education-workspace-bar > div span { color: var(--content-muted); font-size: 9.5px; font-weight: 750; letter-spacing: .2em; }
.education-workspace-bar > div b { color: var(--content-primary); font-size: 17px; font-weight: 750; letter-spacing: .08em; }
.education-workspace-bar > .education-create-control { justify-self: end; display: flex; grid-auto-flow: column; align-items: center; gap: 7px; }
.education-create-control button { display: inline-flex; align-items: center; gap: 7px; padding: 9px 12px; border: 1px solid color-mix(in srgb, var(--panel-edge) 82%, transparent); border-radius: 10px; color: var(--content-secondary); background: color-mix(in srgb, var(--primitive-glass) 74%, transparent); font-size: 11.5px; font-weight: 680; cursor: pointer; }
.education-create-control button svg { width: 14px; height: 14px; }
.education-create-control select { max-width: 118px; padding: 8px 28px 8px 10px; border: 1px solid color-mix(in srgb, var(--panel-edge) 80%, transparent); border-radius: 9px; outline: 0; color: var(--content-secondary); background: color-mix(in srgb, var(--panel-bg) 94%, transparent); font-size: 11.5px; font-weight: 650; }
.education-workspace-bar .education-primary-action { justify-self: end; padding: 9px 15px; border: 1px solid color-mix(in srgb, var(--accent-light) 42%, transparent); border-radius: 10px; color: var(--content-primary); background: color-mix(in srgb, var(--primitive-warm-glow) 12%, transparent); }
.education-workspace-bar button:disabled { opacity: .45; cursor: default; }
.education-import-receipt { position: absolute; z-index: 120; top: 76px; right: 24px; width: min(560px, calc(100% - 48px)); overflow: hidden; border: 1px solid color-mix(in srgb, var(--accent-light) 34%, var(--panel-edge)); border-radius: 15px; background: color-mix(in srgb, var(--panel-bg) 96%, transparent); box-shadow: 0 28px 80px rgba(0, 0, 0, .48); backdrop-filter: blur(26px); }
.education-import-receipt > header { display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 16px 17px 13px; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 64%, transparent); }
.education-import-receipt > header div { display: grid; gap: 4px; }
.education-import-receipt > header span { color: var(--accent-light); font-size: 9px; font-weight: 800; letter-spacing: .14em; }
.education-import-receipt > header b { color: var(--content-primary); font-size: 14px; font-weight: 750; }
.education-import-receipt > header button { width: 30px; height: 30px; border: 0; border-radius: 8px; color: var(--content-muted); background: transparent; font-size: 20px; cursor: pointer; }
.education-import-metrics { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: color-mix(in srgb, var(--panel-edge) 50%, transparent); }
.education-import-metrics span { display: grid; justify-items: center; gap: 2px; padding: 12px 8px; color: var(--content-muted); background: var(--panel-bg); font-size: 9.5px; font-weight: 650; }
.education-import-metrics b { color: var(--content-primary); font-size: 18px; font-weight: 760; }
.education-import-pages { max-height: 220px; overflow: auto; padding: 8px; }
.education-import-pages button { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 10px; width: 100%; padding: 10px 9px; border: 0; border-radius: 8px; color: var(--content-secondary); background: transparent; text-align: left; cursor: pointer; }
.education-import-pages button:hover { background: var(--primitive-glass-hover); }
.education-import-pages b { overflow: hidden; color: var(--content-primary); font-size: 11.5px; text-overflow: ellipsis; white-space: nowrap; }
.education-import-pages span, .education-import-pages em { color: var(--content-muted); font-size: 9.5px; font-style: normal; font-weight: 650; white-space: nowrap; }
.education-import-pages em { color: var(--accent-light); }
.education-import-receipt > footer { display: flex; justify-content: space-between; gap: 12px; padding: 10px 15px; border-top: 1px solid color-mix(in srgb, var(--panel-edge) 58%, transparent); color: var(--content-muted); font-size: 9px; font-weight: 600; }
.education-import-receipt.needs-help { border-color: color-mix(in srgb, rgb(244, 190, 92) 45%, var(--panel-edge)); }
.education-import-receipt.needs-help > p { margin: 0; padding: 16px 17px 12px; color: var(--content-secondary); font-size: 11.5px; font-weight: 600; line-height: 1.7; }
.education-import-receipt.needs-help dl { display: grid; gap: 1px; margin: 0 16px; background: color-mix(in srgb, var(--panel-edge) 55%, transparent); }
.education-import-receipt.needs-help dl div { display: flex; justify-content: space-between; gap: 16px; padding: 9px 10px; background: var(--panel-bg); font-size: 10px; }
.education-import-receipt.needs-help dt { color: var(--content-muted); }
.education-import-receipt.needs-help dd { margin: 0; color: var(--content-secondary); font-weight: 700; }
.education-assistance-actions { display: flex; justify-content: flex-end; gap: 8px; padding: 14px 16px 16px; }
.education-assistance-actions button { padding: 8px 11px; border: 1px solid color-mix(in srgb, var(--panel-edge) 78%, transparent); border-radius: 8px; color: var(--content-secondary); background: var(--primitive-glass-hover); font-size: 10.5px; font-weight: 680; cursor: pointer; }
.education-workspace-layout { min-height: 0; display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: 14px; padding: 14px; }
.education-library, .education-document-workbench, .education-table-workbench, .education-data-workbench, .education-automation-workbench { min-width: 0; min-height: 0; border: 1px solid color-mix(in srgb, var(--panel-edge) 80%, transparent); border-radius: 16px; background: color-mix(in srgb, var(--panel-bg) 76%, transparent); box-shadow: 0 24px 70px rgba(0, 0, 0, .24); overflow: hidden; }
.education-library { display: grid; grid-template-rows: auto auto minmax(0, 1fr) auto; }
.education-library > header { display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 20px 18px 16px; }
.education-library > header div { display: grid; gap: 5px; }
.education-library > header span { color: var(--content-muted); font-size: 10px; font-weight: 700; letter-spacing: .12em; }
.education-library > header h2 { margin: 0; color: var(--content-primary); font-size: 19px; font-weight: 750; }
.education-library > header strong { display: grid; place-items: center; min-width: 38px; height: 38px; border-radius: 50%; color: var(--accent-light); background: color-mix(in srgb, var(--primitive-cool-glow) 11%, transparent); font-size: 14px; }
.education-directory-search { display: flex; align-items: center; gap: 8px; margin: 0 12px 12px; padding: 8px 10px; border: 1px solid color-mix(in srgb, var(--panel-edge) 72%, transparent); border-radius: 9px; color: var(--content-muted); background: color-mix(in srgb, var(--primitive-glass) 58%, transparent); }
.education-directory-search span { font-size: 15px; font-weight: 700; }
.education-directory-search input { min-width: 0; width: 100%; border: 0; outline: 0; color: var(--content-primary); background: transparent; font-size: 11.5px; font-weight: 600; }
.education-directory-search input::placeholder { color: var(--content-muted); }
.education-library-list { min-height: 0; overflow: auto; padding: 0 9px 12px; }
.education-library-list > button { display: grid; gap: 6px; width: 100%; padding: 13px 12px; border: 0; border-radius: 11px; background: transparent; color: var(--content-secondary); text-align: left; cursor: pointer; }
.education-library-list > button:hover { background: color-mix(in srgb, var(--primitive-cool-glow) 7%, transparent); }
.education-library-list > button.active { background: color-mix(in srgb, var(--primitive-cool-glow) 12%, transparent); box-shadow: inset 2px 0 0 var(--accent-light); }
.education-library-list b { color: var(--content-primary); font-size: 13.5px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.education-library-list small { color: var(--content-muted); font-size: 10.5px; font-weight: 600; }
.education-library > footer { min-height: 50px; padding: 13px 17px; border-top: 1px solid color-mix(in srgb, var(--panel-edge) 58%, transparent); color: var(--content-muted); font-size: 11px; font-weight: 600; line-height: 1.55; }
.education-empty-list { padding: 26px 12px; color: var(--content-muted); text-align: center; font-size: 12px; }
.education-document-workbench, .education-table-workbench { display: grid; grid-template-rows: auto minmax(0, 1fr); }
.education-editor-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 58px; padding: 10px 14px 10px 18px; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 64%, transparent); }
.education-editor-toolbar > input { min-width: 180px; flex: 1; border: 0; outline: 0; background: transparent; color: var(--content-primary); font-size: 18px; font-weight: 750; letter-spacing: .02em; }
.education-editor-toolbar > div { display: flex; align-items: center; gap: 8px; }
.education-editor-toolbar span { margin-right: 4px; color: var(--content-muted); font-size: 10.5px; font-weight: 650; white-space: nowrap; }
.education-editor-toolbar button { padding: 8px 11px; border: 1px solid color-mix(in srgb, var(--panel-edge) 80%, transparent); border-radius: 8px; color: var(--content-secondary); background: color-mix(in srgb, var(--primitive-glass) 78%, transparent); font-size: 11.5px; font-weight: 650; cursor: pointer; }
.education-editor-toolbar button.primary { border-color: color-mix(in srgb, var(--accent-light) 44%, transparent); color: var(--content-primary); background: color-mix(in srgb, var(--primitive-warm-glow) 14%, transparent); }
.education-editor-toolbar button svg { width: 14px; height: 14px; vertical-align: -2px; }
.education-editor-toolbar button:disabled { opacity: .42; cursor: default; }
.education-document-toolbar-title { min-width: 0; flex: 1; display: grid !important; gap: 3px !important; }
.education-document-toolbar-title b { overflow: hidden; color: var(--content-primary); font-size: 17px; font-weight: 750; text-overflow: ellipsis; white-space: nowrap; }
.education-document-toolbar-title small { color: var(--content-muted); font-size: 9.5px; font-weight: 600; }
.education-settings { position: relative; display: inline-flex; }
.education-settings > button { display: inline-flex; align-items: center; gap: 6px; }
.education-settings-menu { position: absolute; z-index: 90; top: calc(100% + 7px); right: 0; display: flex; flex-direction: column; min-width: 210px; padding: 6px; border: 1px solid color-mix(in srgb, var(--panel-edge) 90%, transparent); border-radius: 11px; background: var(--panel-bg); box-shadow: 0 18px 44px rgba(0, 0, 0, .42); }
.education-settings-menu button { display: flex; align-items: center; gap: 9px; width: 100%; padding: 9px 10px; border: 0; color: var(--content-secondary); background: transparent; text-align: left; white-space: nowrap; }
.education-settings-menu button:hover { color: var(--content-primary); background: var(--primitive-glass-hover); }
.education-settings-menu button.danger { color: rgb(251, 146, 160); }
.education-settings-menu > span { display: block; height: 1px; margin: 5px 4px; background: var(--panel-edge); }
.education-document-compose { min-height: 0; display: grid; grid-template-rows: auto minmax(0, 1fr) 35px; background: color-mix(in srgb, var(--surface-depth) 58%, transparent); }
.education-format-toolbar { display: flex; align-items: center; gap: 5px; min-height: 43px; padding: 6px 16px; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 60%, transparent); background: color-mix(in srgb, var(--primitive-glass) 48%, transparent); }
.education-format-toolbar button { min-width: 42px; padding: 6px 9px; border: 1px solid color-mix(in srgb, var(--panel-edge) 72%, transparent); border-radius: 7px; color: var(--content-secondary); background: color-mix(in srgb, var(--panel-bg) 48%, transparent); font-size: 10.5px; font-weight: 650; cursor: pointer; }
.education-format-toolbar button:hover { color: var(--content-primary); background: var(--primitive-glass-hover); }
.education-format-toolbar span { margin-left: auto; color: var(--content-muted); font-size: 9.5px; font-weight: 600; }
.education-engine-toolbar { display: flex; align-items: center; gap: 5px; min-height: 43px; padding: 6px 16px; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 60%, transparent); background: color-mix(in srgb, var(--primitive-glass) 48%, transparent); }
.education-engine-toolbar > span { margin-right: 8px; color: var(--accent-light); font-size: 9px; font-weight: 800; letter-spacing: .12em; }
.education-engine-toolbar > i { flex: 1; }
.education-engine-toolbar button { min-width: 42px; padding: 6px 9px; border: 1px solid color-mix(in srgb, var(--panel-edge) 72%, transparent); border-radius: 7px; color: var(--content-secondary); background: color-mix(in srgb, var(--panel-bg) 48%, transparent); font-size: 10.5px; font-weight: 650; cursor: pointer; }
.education-engine-toolbar button:hover { color: var(--content-primary); background: var(--primitive-glass-hover); }
.education-engine-loading { min-height: 240px; display: grid; place-items: center; color: var(--content-muted); background: color-mix(in srgb, var(--surface-depth) 58%, transparent); font-size: 12px; font-weight: 650; letter-spacing: .04em; }
.education-engine-canvas { position: relative; min-height: 0; overflow: auto; background: color-mix(in srgb, var(--surface-depth) 58%, transparent); }
.education-engine-editor { width: min(900px, calc(100% - 52px)); min-height: 100%; margin: 0 auto; padding: 42px clamp(34px, 6vw, 76px) 80px; border-right: 1px solid color-mix(in srgb, var(--panel-edge) 50%, transparent); border-left: 1px solid color-mix(in srgb, var(--panel-edge) 50%, transparent); outline: 0; color: var(--content-primary); background: color-mix(in srgb, var(--panel-bg) 72%, transparent); font: 500 15px/1.9 -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; }
.education-engine-placeholder { position: absolute; top: 43px; left: max(calc((100% - 900px) / 2 + clamp(34px, 6vw, 76px)), calc(26px + clamp(34px, 6vw, 76px))); color: var(--content-faint); font-size: 14px; pointer-events: none; }
.education-engine-paragraph { margin: 0 0 12px; }
.education-engine-heading-two { margin: 24px 0 10px; color: var(--content-primary); font-size: 22px; line-height: 1.4; }
.education-engine-quote { margin: 15px 0; padding: 8px 14px; border-left: 3px solid color-mix(in srgb, var(--accent-light) 66%, transparent); color: var(--content-secondary); background: color-mix(in srgb, var(--primitive-cool-glow) 6%, transparent); }
.education-engine-list { margin: 8px 0 14px; padding-left: 26px; }
.education-engine-checklist { padding-left: 7px; list-style: none; }
.education-engine-bold { font-weight: 780; }
.education-engine-italic { font-style: italic; }
.education-document-compose > textarea { width: min(900px, calc(100% - 52px)); height: 100%; min-height: 0; justify-self: center; resize: none; padding: 42px clamp(34px, 6vw, 76px) 80px; border: 0; border-right: 1px solid color-mix(in srgb, var(--panel-edge) 50%, transparent); border-left: 1px solid color-mix(in srgb, var(--panel-edge) 50%, transparent); outline: 0; color: var(--content-primary); background: color-mix(in srgb, var(--panel-bg) 72%, transparent); font: 500 15px/1.9 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.education-document-compose > footer { display: flex; align-items: center; justify-content: space-between; padding: 0 18px; border-top: 1px solid color-mix(in srgb, var(--panel-edge) 56%, transparent); color: var(--content-muted); font-size: 9.5px; font-weight: 600; }
.education-document-reading { min-height: 0; overflow: auto; padding: 28px 30px 80px; background: color-mix(in srgb, var(--surface-depth) 58%, transparent); }
.education-document-paper { width: min(900px, 100%); min-height: calc(100% - 8px); margin: 0 auto; padding: clamp(44px, 7vw, 78px) clamp(40px, 8vw, 96px) 96px; border: 1px solid color-mix(in srgb, var(--panel-edge) 68%, transparent); border-radius: 14px; background: color-mix(in srgb, var(--panel-bg) 82%, transparent); box-shadow: 0 26px 64px rgba(0, 0, 0, .18); }
.education-document-paper > header { margin: 0 0 26px; padding-bottom: 22px; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 66%, transparent); }
.education-document-paper > header span { color: var(--accent-light); font-size: 9.5px; font-weight: 800; letter-spacing: .16em; }
.education-document-paper > header h1 { margin: 8px 0 10px; color: var(--content-primary); font-size: clamp(30px, 4vw, 46px); font-weight: 730; letter-spacing: -.035em; line-height: 1.16; }
.education-document-paper > header p { margin: 0; color: var(--content-muted); font-size: 10.5px; font-weight: 600; }
.education-document-paper > .markdown-document { max-width: none; padding: 0; font-size: 16px; }
.education-document-split { min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.education-document-editor, .education-document-preview { min-width: 0; min-height: 0; display: grid; grid-template-rows: 38px minmax(0, 1fr); }
.education-document-editor { border-right: 1px solid color-mix(in srgb, var(--panel-edge) 55%, transparent); }
.education-document-editor > header, .education-document-preview > header { display: flex; align-items: center; justify-content: space-between; padding: 0 16px; color: var(--content-muted); background: color-mix(in srgb, var(--primitive-glass) 44%, transparent); }
.education-document-editor > header b, .education-document-preview > header b { color: var(--content-secondary); font-size: 11px; font-weight: 700; }
.education-document-editor > header span, .education-document-preview > header span { font-size: 9.5px; font-weight: 650; letter-spacing: .09em; }
.education-document-editor textarea { width: 100%; height: 100%; resize: none; padding: 24px; border: 0; outline: 0; color: var(--content-primary); background: transparent; font: 500 14px/1.8 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.education-document-preview > div { min-height: 0; overflow: auto; padding: 24px 30px 54px; }
.education-workspace-empty { align-self: center; justify-self: center; display: grid; justify-items: center; width: min(480px, calc(100% - 48px)); text-align: center; }
.education-workspace-empty > span { display: grid; place-items: center; width: 70px; height: 70px; border-radius: 50%; color: var(--accent-light); background: radial-gradient(circle, color-mix(in srgb, var(--primitive-warm-glow) 28%, transparent), transparent 72%); font-size: 25px; font-weight: 750; }
.education-workspace-empty h2 { margin: 18px 0 8px; color: var(--content-primary); font-size: 24px; font-weight: 750; }
.education-workspace-empty p { margin: 0; color: var(--content-muted); font-size: 13px; font-weight: 600; line-height: 1.7; }
.education-workspace-empty button { margin-top: 20px; padding: 10px 18px; border: 1px solid color-mix(in srgb, var(--accent-light) 44%, transparent); border-radius: 10px; color: var(--content-primary); background: color-mix(in srgb, var(--primitive-warm-glow) 12%, transparent); cursor: pointer; }
.education-template-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-top: 20px; }
.education-template-actions button { margin-top: 0; }
.education-table-workbench { grid-template-rows: auto auto minmax(0, 1fr) auto; }
.education-table-viewbar { display: flex; flex-wrap: wrap; align-items: end; gap: 8px; padding: 10px 14px; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 56%, transparent); background: color-mix(in srgb, var(--primitive-glass) 38%, transparent); }
.education-table-viewbar label { display: grid; flex: 0 1 170px; gap: 5px; }
.education-table-viewbar > label:first-child { flex: 1 1 220px; }
.education-table-viewbar label > span { color: var(--content-muted); font-size: 9px; font-weight: 700; letter-spacing: .08em; }
.education-table-viewbar input, .education-table-viewbar select { min-width: 0; height: 32px; padding: 0 10px; border: 1px solid color-mix(in srgb, var(--panel-edge) 76%, transparent); border-radius: 8px; outline: 0; color: var(--content-primary); background: color-mix(in srgb, var(--panel-bg) 82%, transparent); font-size: 11px; font-weight: 600; }
.education-view-switch { display: flex; flex-wrap: wrap; padding: 3px; border: 1px solid color-mix(in srgb, var(--panel-edge) 70%, transparent); border-radius: 9px; background: color-mix(in srgb, var(--panel-bg) 70%, transparent); }
.education-view-switch button, .education-sort-direction { height: 32px; padding: 0 11px; border: 0; border-radius: 7px; color: var(--content-muted); background: transparent; font-size: 10.5px; font-weight: 700; cursor: pointer; }
.education-view-switch button.active { color: var(--content-primary); background: color-mix(in srgb, var(--primitive-cool-glow) 14%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-light) 25%, transparent); }
.education-sort-direction { border: 1px solid color-mix(in srgb, var(--panel-edge) 76%, transparent); background: color-mix(in srgb, var(--panel-bg) 72%, transparent); }
.education-sensitive-toggle { height: 32px; padding: 0 11px; border: 1px solid color-mix(in srgb, var(--accent-light) 34%, var(--panel-edge)); border-radius: 7px; color: var(--content-secondary); background: color-mix(in srgb, var(--primitive-warm-glow) 8%, var(--panel-bg)); font-size: 10.5px; font-weight: 700; cursor: pointer; }
.education-sensitive-masked { -webkit-text-security: disc; }
.education-grid-scroll { position: relative; min-height: 0; overflow: auto; }
.channel-spreadsheet-engine { min-width: 0; min-height: 0; display: grid; grid-template-rows: 42px minmax(0, 1fr); overflow: hidden; background: #f6f7f9; }
.channel-spreadsheet-engine > header { display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 0 14px; border-bottom: 1px solid #d9dde4; color: #202632; background: #f8f9fb; }
.channel-spreadsheet-engine > header div { min-width: 0; display: flex; align-items: baseline; gap: 10px; }
.channel-spreadsheet-engine > header b { font-size: 12.5px; font-weight: 760; }
.channel-spreadsheet-engine > header span, .channel-spreadsheet-engine > header em { overflow: hidden; color: #667085; text-overflow: ellipsis; white-space: nowrap; font-size: 9.5px; font-style: normal; font-weight: 620; }
.channel-spreadsheet-canvas { min-width: 0; min-height: 0; overflow: hidden; color: #202632; background: #fff; }
.channel-spreadsheet-canvas .fortune-container { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; }
.channel-spreadsheet-canvas .fortune-toolbar { border-bottom-color: #d9dde4; }
.channel-spreadsheet-canvas .fortune-toolbar-button:hover, .channel-spreadsheet-canvas .fortune-toolbar-combo:hover { background: #edf1f6; }
.education-workbook-privacy, .education-engine-loading { min-height: 0; display: grid; place-content: center; justify-items: center; gap: 9px; padding: 40px; color: var(--content-secondary); text-align: center; }
.education-workbook-privacy b { color: var(--content-primary); font-size: 16px; }
.education-workbook-privacy p { max-width: 560px; margin: 0; color: var(--content-muted); font-size: 11px; font-weight: 620; line-height: 1.7; }
.education-workbook-privacy button { padding: 9px 13px; border: 1px solid color-mix(in srgb, var(--accent-light) 40%, transparent); border-radius: 9px; color: var(--content-primary); background: color-mix(in srgb, var(--primitive-warm-glow) 12%, transparent); font-size: 11px; font-weight: 700; cursor: pointer; }
.education-data-grid { width: max-content; min-width: 100%; border-collapse: separate; border-spacing: 0; table-layout: fixed; }
.education-data-grid th, .education-data-grid td { width: 148px; min-width: 148px; max-width: 148px; border-right: 1px solid color-mix(in srgb, var(--panel-edge) 48%, transparent); border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 48%, transparent); background: color-mix(in srgb, var(--panel-bg) 31%, transparent); }
.education-data-grid thead th { position: sticky; z-index: 3; top: 0; height: 46px; background: color-mix(in srgb, var(--panel-bg) 96%, transparent); }
.education-data-grid th > div { display: flex; align-items: center; }
.education-data-grid input { width: 100%; min-width: 0; padding: 9px 10px; border: 0; outline: 0; color: var(--content-secondary); background: transparent; font-size: 11.5px; font-weight: 580; text-overflow: ellipsis; }
.education-data-grid th input { font-weight: 750; }
.education-data-grid input:focus { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-light) 60%, transparent); background: color-mix(in srgb, var(--primitive-cool-glow) 7%, transparent); }
.education-data-grid tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--primitive-glass) 46%, var(--panel-bg)); }
.education-data-grid tbody tr:hover td { background: color-mix(in srgb, var(--primitive-cool-glow) 7%, var(--panel-bg)); }
.education-data-grid th button, .education-row-action button { width: 30px; border: 0; background: transparent; color: var(--content-muted); font-size: 17px; cursor: pointer; }
.education-data-grid th button:disabled { opacity: .28; cursor: default; }
.education-data-grid .education-row-index { position: sticky; z-index: 2; left: 0; min-width: 48px; width: 48px; color: var(--content-muted); background: color-mix(in srgb, var(--panel-bg) 96%, transparent); text-align: center; font-size: 10.5px; font-weight: 700; }
.education-data-grid thead .education-row-index { z-index: 4; }
.education-data-grid thead th:nth-child(2) { position: sticky; z-index: 4; left: 48px; box-shadow: 8px 0 18px rgba(0, 0, 0, .18); }
.education-data-grid tbody td:nth-child(2) { position: sticky; z-index: 1; left: 48px; box-shadow: 8px 0 18px rgba(0, 0, 0, .12); }
.education-data-grid .education-row-action { min-width: 42px; width: 42px; text-align: center; }
.education-grid-empty { display: grid; justify-items: center; padding: 70px 20px; color: var(--content-muted); font-size: 12.5px; }
.education-grid-empty button { padding: 8px 13px; border: 1px solid var(--panel-edge); border-radius: 8px; color: var(--content-secondary); background: transparent; cursor: pointer; }
.education-card-view { min-height: 0; overflow: auto; display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); align-content: start; gap: 12px; padding: 14px; }
.education-card-view article { min-width: 0; display: grid; gap: 12px; padding: 15px; border: 1px solid color-mix(in srgb, var(--panel-edge) 72%, transparent); border-radius: 13px; background: color-mix(in srgb, var(--primitive-glass) 62%, transparent); box-shadow: 0 14px 34px rgba(0, 0, 0, .12); }
.education-card-view article > header { display: grid; grid-template-columns: 24px minmax(0, 1fr) 24px; align-items: center; gap: 8px; }
.education-card-view article > header span { color: var(--accent-light); font-size: 10px; font-weight: 800; }
.education-card-view article > header input { min-width: 0; border: 0; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 60%, transparent); outline: 0; color: var(--content-primary); background: transparent; font-size: 14px; font-weight: 750; }
.education-card-view article > header button { border: 0; color: var(--content-muted); background: transparent; font-size: 16px; cursor: pointer; }
.education-card-view article > label { display: grid; gap: 5px; }
.education-card-view article > label span { color: var(--content-muted); font-size: 9.5px; font-weight: 700; letter-spacing: .05em; }
.education-card-view article > label input { min-height: 38px; padding: 8px 9px; border: 1px solid color-mix(in srgb, var(--panel-edge) 62%, transparent); border-radius: 8px; outline: 0; color: var(--content-secondary); background: color-mix(in srgb, var(--panel-bg) 48%, transparent); font: 600 11.5px/1.55 inherit; }
.education-card-view article textarea { min-height: 54px; resize: vertical; padding: 8px 9px; border: 1px solid color-mix(in srgb, var(--panel-edge) 62%, transparent); border-radius: 8px; outline: 0; color: var(--content-secondary); background: color-mix(in srgb, var(--panel-bg) 48%, transparent); font: 600 11.5px/1.55 inherit; }
.education-board-view { min-height: 0; overflow: auto; display: flex; align-items: flex-start; gap: 12px; padding: 14px; }
.education-board-view > section { flex: 0 0 286px; max-height: 100%; overflow: hidden; display: grid; grid-template-rows: auto minmax(0, 1fr); border: 1px solid color-mix(in srgb, var(--panel-edge) 70%, transparent); border-radius: 13px; background: color-mix(in srgb, var(--primitive-glass) 52%, transparent); }
.education-board-view > section > header { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 13px; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 56%, transparent); }
.education-board-view > section > header div { display: grid; gap: 2px; min-width: 0; }
.education-board-view > section > header b { overflow: hidden; color: var(--content-primary); font-size: 12.5px; text-overflow: ellipsis; white-space: nowrap; }
.education-board-view > section > header span, .education-board-view > section > header em { color: var(--content-muted); font-size: 9.5px; font-style: normal; font-weight: 650; }
.education-board-view > section > div { min-height: 0; overflow: auto; display: grid; align-content: start; gap: 8px; padding: 9px; }
.education-board-view article { display: grid; gap: 8px; padding: 11px; border: 1px solid color-mix(in srgb, var(--panel-edge) 58%, transparent); border-radius: 10px; background: color-mix(in srgb, var(--panel-bg) 78%, transparent); }
.education-board-view article p { display: grid; grid-template-columns: 72px minmax(0, 1fr); gap: 8px; margin: 0; }
.education-board-view article p span { overflow: hidden; color: var(--content-muted); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
.education-board-view article p b { overflow: hidden; color: var(--content-secondary); font-size: 10.5px; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; }
.education-board-view article button { justify-self: end; padding: 5px 8px; border: 0; color: var(--accent-light); background: transparent; font-size: 9.5px; font-weight: 700; cursor: pointer; }
.education-dashboard-view { min-height: 0; overflow: auto; display: grid; align-content: start; gap: 14px; padding: 18px; }
.education-focus-hero { display: grid; grid-template-columns: minmax(0, 1fr) minmax(240px, .42fr); gap: 22px; align-items: center; padding: 22px 24px; border: 1px solid color-mix(in srgb, var(--primitive-warm-glow) 32%, var(--panel-edge)); border-radius: 16px; background: radial-gradient(80% 160% at 100% 50%, color-mix(in srgb, var(--primitive-warm-glow) 15%, transparent), transparent 68%), color-mix(in srgb, var(--panel-bg) 72%, transparent); }
.education-focus-hero > div > span { color: var(--accent-light); font-size: 9.5px; font-weight: 800; letter-spacing: .14em; }
.education-focus-hero h2 { margin: 7px 0 5px; color: var(--content-primary); font-size: clamp(24px, 3vw, 34px); font-weight: 780; }
.education-focus-hero p { margin: 0; color: var(--content-muted); font-size: 11px; font-weight: 600; line-height: 1.7; }
.education-focus-hero > article { display: grid; justify-items: end; gap: 4px; }
.education-focus-hero > article > span { color: var(--content-muted); font-size: 9.5px; font-weight: 700; }
.education-focus-hero > article > b { color: var(--accent-light); font-size: clamp(30px, 4vw, 46px); font-weight: 780; letter-spacing: -.04em; }
.education-focus-hero > article > div { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px; }
.education-focus-hero > article em { color: var(--content-muted); font-size: 9.5px; font-style: normal; font-weight: 650; }
.education-focus-hero.needs-selection { border-style: dashed; }
.education-model-mode-banner { display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 11px 13px; border: 1px solid color-mix(in srgb, var(--panel-edge) 56%, transparent); border-radius: 10px; background: color-mix(in srgb, var(--primitive-glass) 42%, transparent); }
.education-model-mode-banner div { display: grid; gap: 3px; }
.education-model-mode-banner b { color: var(--content-secondary); font-size: 10.5px; }
.education-model-mode-banner span { color: var(--content-muted); font-size: 9.5px; line-height: 1.5; }
.education-model-mode-banner button { flex: 0 0 auto; padding: 7px 10px; border: 1px solid color-mix(in srgb, var(--accent-light) 32%, var(--panel-edge)); border-radius: 8px; color: var(--accent-light); background: transparent; font-size: 9.5px; font-weight: 700; cursor: pointer; }
.education-dashboard-metrics { display: grid; grid-template-columns: repeat(4, minmax(140px, 1fr)); gap: 10px; }
.education-dashboard-metrics article { display: grid; gap: 5px; padding: 15px; border: 1px solid color-mix(in srgb, var(--panel-edge) 66%, transparent); border-radius: 12px; background: color-mix(in srgb, var(--primitive-glass) 58%, transparent); }
.education-dashboard-metrics span { color: var(--content-muted); font-size: 9.5px; font-weight: 750; letter-spacing: .08em; }
.education-dashboard-metrics b { color: var(--content-primary); font-size: 25px; font-weight: 760; }
.education-dashboard-metrics em { overflow: hidden; color: var(--content-muted); font-size: 9px; font-style: normal; text-overflow: ellipsis; white-space: nowrap; }
.education-dashboard-chart { display: grid; gap: 14px; padding: 17px; border: 1px solid color-mix(in srgb, var(--panel-edge) 68%, transparent); border-radius: 14px; background: color-mix(in srgb, var(--panel-bg) 55%, transparent); }
.education-dashboard-chart > header { display: flex; align-items: end; justify-content: space-between; gap: 18px; }
.education-dashboard-chart > header div { display: grid; gap: 4px; }
.education-dashboard-chart > header span { color: var(--accent-light); font-size: 9px; font-weight: 800; letter-spacing: .12em; }
.education-dashboard-chart h3 { margin: 0; color: var(--content-primary); font-size: 16px; }
.education-dashboard-chart > header > b { color: var(--content-muted); font-size: 10px; }
.education-dashboard-chart > div { display: grid; gap: 9px; }
.education-dashboard-chart article { display: grid; grid-template-columns: minmax(90px, 150px) minmax(160px, 1fr) 34px minmax(120px, auto); align-items: center; gap: 10px; }
.education-dashboard-chart article > span { overflow: hidden; color: var(--content-secondary); font-size: 10.5px; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; }
.education-dashboard-chart article > div { overflow: hidden; height: 9px; border-radius: 999px; background: color-mix(in srgb, var(--panel-edge) 62%, transparent); }
.education-dashboard-chart article i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, color-mix(in srgb, var(--accent-light) 70%, transparent), color-mix(in srgb, var(--primitive-cool-glow) 70%, var(--accent-light))); }
.education-dashboard-chart article b { color: var(--content-primary); font-size: 10.5px; text-align: right; }
.education-dashboard-chart article em { overflow: hidden; color: var(--content-muted); font-size: 9.5px; font-style: normal; text-overflow: ellipsis; white-space: nowrap; }
.education-dashboard-view > footer { color: var(--content-muted); font-size: 9.5px; font-weight: 600; text-align: right; }
.education-table-status { display: flex; justify-content: space-between; gap: 18px; padding: 10px 16px; border-top: 1px solid color-mix(in srgb, var(--panel-edge) 58%, transparent); color: var(--content-muted); font-size: 10.5px; font-weight: 650; }
.education-degradation-receipt { color: var(--accent-light); cursor: help; }
.education-table-pagination { display: flex; align-items: center; gap: 8px; }
.education-table-pagination b { color: var(--content-secondary); font-size: 10.5px; font-weight: 700; }
.education-table-pagination button { padding: 4px 9px; border: 1px solid color-mix(in srgb, var(--panel-edge) 75%, transparent); border-radius: 6px; color: var(--content-secondary); background: color-mix(in srgb, var(--panel-bg) 74%, transparent); font-size: 10px; cursor: pointer; }
.education-table-pagination button:disabled { opacity: .38; cursor: default; }
.education-data-workbench { overflow: auto; padding: 20px; }
.education-data-workbench > header { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 2px 2px 18px; }
.education-data-workbench > header div { display: grid; gap: 5px; }
.education-data-workbench > header span { color: var(--content-muted); font-size: 9.5px; font-weight: 750; letter-spacing: .12em; }
.education-data-workbench > header h2 { margin: 0; color: var(--content-primary); font-size: 22px; font-weight: 760; }
.education-data-workbench > header > b { color: var(--accent-light); font-size: 11px; font-weight: 750; }
.education-cleanup-metrics { display: grid; grid-template-columns: repeat(5, minmax(110px, 1fr)); gap: 10px; }
.education-cleanup-metrics article { display: grid; grid-template-columns: 1fr auto; align-items: end; gap: 4px 10px; padding: 14px; border: 1px solid color-mix(in srgb, var(--panel-edge) 70%, transparent); border-radius: 12px; background: color-mix(in srgb, var(--primitive-glass) 60%, transparent); }
.education-cleanup-metrics article span { grid-column: 1 / -1; color: var(--content-muted); font-size: 9.5px; font-weight: 700; }
.education-cleanup-metrics article strong { color: var(--content-primary); font-size: 25px; font-weight: 760; }
.education-cleanup-metrics article small { padding-bottom: 3px; color: var(--content-muted); font-size: 9.5px; font-weight: 650; }
.education-cleanup-rules { margin-top: 13px; padding: 16px 18px; border: 1px solid color-mix(in srgb, var(--panel-edge) 68%, transparent); border-radius: 13px; background: color-mix(in srgb, var(--primitive-glass) 44%, transparent); }
.education-cleanup-rules h3, .education-cleanup-preview h3 { margin: 0; color: var(--content-primary); font-size: 14px; font-weight: 750; }
.education-cleanup-rules ol { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; margin: 14px 0 0; padding: 0; list-style: none; counter-reset: cleanup-rule; }
.education-cleanup-rules li { counter-increment: cleanup-rule; display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: 4px 9px; }
.education-cleanup-rules li::before { content: counter(cleanup-rule, decimal-leading-zero); grid-row: 1 / 3; color: var(--accent-light); font-size: 10px; font-weight: 800; }
.education-cleanup-rules li b { color: var(--content-secondary); font-size: 11.5px; font-weight: 750; }
.education-cleanup-rules li span { color: var(--content-muted); font-size: 10px; font-weight: 600; line-height: 1.55; }
.education-cleanup-preview { margin-top: 13px; overflow: hidden; border: 1px solid color-mix(in srgb, var(--panel-edge) 68%, transparent); border-radius: 13px; }
.education-cleanup-preview > header { padding: 13px 15px; background: color-mix(in srgb, var(--primitive-glass) 54%, transparent); }
.education-cleanup-preview > header div { display: flex; align-items: baseline; justify-content: space-between; gap: 18px; }
.education-cleanup-preview > header span { color: var(--content-muted); font-size: 9.5px; font-weight: 650; }
.education-cleanup-preview > div { overflow: hidden; }
.education-cleanup-change-list > header, .education-cleanup-change-list > article { display: grid; grid-template-columns: minmax(150px, .9fr) minmax(125px, .7fr) minmax(170px, 1.2fr) minmax(170px, 1.2fr); }
.education-cleanup-change-list > header { background: color-mix(in srgb, var(--primitive-glass) 38%, transparent); }
.education-cleanup-change-list > header span, .education-cleanup-change-list > article > span, .education-cleanup-change-list > article > b { min-width: 0; overflow: hidden; padding: 10px 12px; border-right: 1px solid color-mix(in srgb, var(--panel-edge) 52%, transparent); border-top: 1px solid color-mix(in srgb, var(--panel-edge) 52%, transparent); color: var(--content-secondary); text-overflow: ellipsis; white-space: nowrap; font-size: 10.5px; font-weight: 600; }
.education-cleanup-change-list > header span { color: var(--content-primary); font-weight: 750; }
.education-cleanup-change-list > article > b { color: var(--accent-light); font-weight: 700; }
.education-cleanup-preview p { margin: 0; padding: 32px; color: var(--content-muted); text-align: center; font-size: 11.5px; }
.education-automation-library { grid-template-rows: auto auto minmax(100px, 1fr) auto auto; }
.education-new-rule { margin: 0 12px 12px; padding: 9px 11px; border: 1px solid color-mix(in srgb, var(--accent-light) 32%, transparent); border-radius: 9px; color: var(--content-secondary); background: color-mix(in srgb, var(--primitive-cool-glow) 7%, transparent); font-size: 11px; font-weight: 700; cursor: pointer; }
.education-new-rule:disabled { opacity: .42; cursor: default; }
.education-run-receipts { max-height: 190px; overflow: auto; padding: 12px 14px; border-top: 1px solid color-mix(in srgb, var(--panel-edge) 58%, transparent); }
.education-run-receipts h3 { margin: 0 0 9px; color: var(--content-primary); font-size: 11px; font-weight: 750; }
.education-run-receipts article { display: grid; grid-template-columns: auto 1fr; gap: 3px 8px; padding: 8px 0; border-top: 1px solid color-mix(in srgb, var(--panel-edge) 38%, transparent); }
.education-run-receipts article b { color: var(--accent-light); font-size: 9.5px; font-weight: 750; }
.education-run-receipts article span { color: var(--content-secondary); font-size: 9.5px; font-weight: 650; }
.education-run-receipts article small { grid-column: 1 / -1; color: var(--content-muted); font-size: 8.5px; font-weight: 600; }
.education-run-receipts p { margin: 0; color: var(--content-muted); font-size: 10px; }
.education-automation-workbench { overflow: auto; padding: 20px; }
.education-automation-workbench > header { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 2px 2px 18px; }
.education-automation-workbench > header > div:first-child { display: grid; gap: 5px; }
.education-automation-workbench > header span { color: var(--content-muted); font-size: 9.5px; font-weight: 750; letter-spacing: .1em; }
.education-automation-workbench > header h2 { margin: 0; color: var(--content-primary); font-size: 22px; font-weight: 760; }
.education-automation-workbench > header > div:last-child { display: flex; align-items: center; gap: 10px; }
.education-automation-workbench > header label { display: flex; align-items: center; gap: 7px; color: var(--content-secondary); font-size: 10.5px; font-weight: 700; }
.education-automation-workbench button { padding: 8px 12px; border: 1px solid color-mix(in srgb, var(--panel-edge) 72%, transparent); border-radius: 9px; color: var(--content-secondary); background: color-mix(in srgb, var(--primitive-glass) 58%, transparent); font-size: 10.5px; font-weight: 700; cursor: pointer; }
.education-automation-workbench button:disabled { opacity: .42; cursor: default; }
.education-automation-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; padding: 17px; border: 1px solid color-mix(in srgb, var(--panel-edge) 68%, transparent); border-radius: 13px; background: color-mix(in srgb, var(--primitive-glass) 42%, transparent); }
.education-automation-form .wide { grid-column: 1 / -1; }
.education-automation-form label { display: grid; gap: 6px; min-width: 0; }
.education-automation-form label span { color: var(--content-muted); font-size: 9px; font-weight: 700; letter-spacing: .08em; }
.education-automation-form input, .education-automation-form select { min-width: 0; width: 100%; height: 38px; padding: 0 11px; border: 1px solid color-mix(in srgb, var(--panel-edge) 70%, transparent); border-radius: 9px; outline: 0; color: var(--content-primary); background: color-mix(in srgb, var(--surface-depth) 82%, transparent); font-size: 11.5px; font-weight: 650; }
.education-automation-form input:focus, .education-automation-form select:focus { border-color: color-mix(in srgb, var(--accent-light) 60%, transparent); }
.education-automation-sentence { display: flex; align-items: end; gap: 10px; padding: 12px; border-radius: 11px; background: color-mix(in srgb, var(--primitive-cool-glow) 6%, transparent); }
.education-automation-sentence > b { align-self: center; display: grid; place-items: center; flex: 0 0 34px; height: 34px; border-radius: 50%; color: var(--accent-light); background: color-mix(in srgb, var(--primitive-warm-glow) 13%, transparent); font-size: 13px; }
.education-automation-sentence label { flex: 1 1 180px; }
.education-automation-sentence label.grow { flex-grow: 2; }
.education-automation-boundary, .education-automation-preview { margin-top: 13px; padding: 15px 17px; border: 1px solid color-mix(in srgb, var(--panel-edge) 68%, transparent); border-radius: 13px; background: color-mix(in srgb, var(--primitive-glass) 42%, transparent); }
.education-automation-boundary h3, .education-automation-preview h3 { margin: 0; color: var(--content-primary); font-size: 14px; font-weight: 750; }
.education-automation-boundary p, .education-automation-preview > p { margin: 8px 0 0; color: var(--content-muted); font-size: 10.5px; font-weight: 600; line-height: 1.65; }
.education-automation-preview > header { display: flex; align-items: center; justify-content: space-between; gap: 18px; }
.education-automation-preview > header > div { display: grid; gap: 4px; }
.education-automation-preview > header span { color: var(--content-muted); font-size: 9px; font-weight: 700; letter-spacing: .09em; }
.education-automation-metrics { display: grid; grid-template-columns: repeat(3, minmax(100px, 1fr)) minmax(170px, auto); gap: 10px; margin-top: 13px; }
.education-automation-metrics article { display: grid; grid-template-columns: 1fr auto; align-items: end; gap: 3px 8px; padding: 12px; border-radius: 10px; background: color-mix(in srgb, var(--primitive-cool-glow) 7%, transparent); }
.education-automation-metrics article span { grid-column: 1 / -1; color: var(--content-muted); font-size: 9px; font-weight: 700; }
.education-automation-metrics article strong { color: var(--content-primary); font-size: 23px; font-weight: 760; }
.education-automation-metrics article small { padding-bottom: 3px; color: var(--content-muted); font-size: 9px; font-weight: 650; }
.education-automation-metrics .education-run-action { align-self: stretch; border-color: color-mix(in srgb, var(--accent-light) 42%, transparent); color: var(--content-primary); background: color-mix(in srgb, var(--primitive-warm-glow) 12%, transparent); }
.composition-workspace-world { background: radial-gradient(circle at 70% 18%, color-mix(in srgb, var(--primitive-cool-glow) 14%, transparent), transparent 34%), radial-gradient(circle at 30% 80%, color-mix(in srgb, var(--primitive-warm-glow) 7%, transparent), transparent 34%), color-mix(in srgb, var(--surface-depth) 98%, transparent); }
.native-composition-layout { min-width: 0; min-height: 0; display: grid; grid-template-columns: 294px minmax(0, 1fr); gap: 14px; padding: 14px; }
.composition-recipe-panel, .composition-projection-stage { min-width: 0; min-height: 0; border: 1px solid color-mix(in srgb, var(--panel-edge) 78%, transparent); border-radius: 17px; background: color-mix(in srgb, var(--panel-bg) 76%, transparent); box-shadow: 0 26px 74px rgba(0, 0, 0, .24); overflow: hidden; }
.composition-recipe-panel { overflow: auto; padding: 20px 18px; }
.composition-recipe-panel > header { display: grid; gap: 5px; padding-bottom: 17px; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 55%, transparent); }
.composition-recipe-panel > header span, .composition-projection-stage > header span { color: var(--accent-light); font-size: 9.5px; font-weight: 800; letter-spacing: .16em; }
.composition-recipe-panel > header h2 { margin: 0; color: var(--content-primary); font-size: 21px; font-weight: 760; }
.composition-recipe-panel > header p { margin: 0; color: var(--content-muted); font-size: 10.5px; font-weight: 620; }
.composition-source { display: flex; align-items: center; gap: 12px; margin: 17px 0 0; padding: 13px; border: 1px solid color-mix(in srgb, var(--accent-light) 23%, transparent); border-radius: 12px; background: color-mix(in srgb, var(--primitive-cool-glow) 8%, transparent); }
.composition-source > i { flex: 0 0 33px; width: 33px; height: 33px; border-radius: 50%; background: radial-gradient(circle at 40% 35%, #fffce8, var(--accent-light) 24%, color-mix(in srgb, var(--primitive-cool-glow) 55%, transparent) 58%, transparent 70%); box-shadow: 0 0 22px color-mix(in srgb, var(--primitive-cool-glow) 38%, transparent); }
.composition-source > div { min-width: 0; display: grid; gap: 3px; }
.composition-source span, .composition-recipe-panel > label > span, .composition-recipe-panel legend { color: var(--content-muted); font-size: 9px; font-weight: 750; letter-spacing: .08em; }
.composition-source b { overflow: hidden; color: var(--content-primary); font-size: 12px; font-weight: 730; text-overflow: ellipsis; white-space: nowrap; }
.composition-source small { color: var(--content-muted); font-size: 9.5px; font-weight: 600; }
.composition-connector { width: 1px; height: 20px; margin: 0 auto; background: linear-gradient(transparent, color-mix(in srgb, var(--accent-light) 45%, transparent)); }
.composition-recipe-panel > label { display: grid; gap: 6px; margin-bottom: 11px; }
.composition-recipe-panel select { width: 100%; height: 38px; padding: 0 11px; border: 1px solid color-mix(in srgb, var(--panel-edge) 72%, transparent); border-radius: 9px; outline: 0; color: var(--content-primary); background: color-mix(in srgb, var(--surface-depth) 84%, transparent); font-size: 11.5px; font-weight: 650; }
.composition-recipe-panel fieldset { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; margin: 16px 0 0; padding: 12px; border: 1px solid color-mix(in srgb, var(--panel-edge) 64%, transparent); border-radius: 11px; }
.composition-recipe-panel legend { padding: 0 6px; }
.composition-recipe-panel fieldset label { display: flex; align-items: center; gap: 6px; color: var(--content-secondary); font-size: 10.5px; font-weight: 650; }
.composition-recipe-panel input { accent-color: var(--accent-light); }
.composition-execute { width: 100%; margin-top: 14px; padding: 11px 12px; border: 1px solid color-mix(in srgb, var(--accent-light) 40%, transparent); border-radius: 10px; color: var(--content-primary); background: linear-gradient(135deg, color-mix(in srgb, var(--primitive-warm-glow) 13%, transparent), color-mix(in srgb, var(--primitive-cool-glow) 9%, transparent)); font-size: 11.5px; font-weight: 750; cursor: pointer; }
.composition-execute:disabled { opacity: .5; cursor: default; }
.composition-recipe-panel > footer { display: grid; gap: 4px; margin-top: 15px; padding-top: 14px; border-top: 1px solid color-mix(in srgb, var(--panel-edge) 55%, transparent); color: var(--content-muted); font-size: 9.5px; font-weight: 600; }
.composition-recipe-panel > footer b { color: var(--accent-light); font-size: 10px; font-weight: 750; }
.composition-projection-stage { overflow: auto; padding: 20px; }
.composition-projection-stage > header { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 2px 2px 18px; }
.composition-projection-stage > header > div:first-child { display: grid; gap: 5px; }
.composition-projection-stage > header h1 { margin: 0; color: var(--content-primary); font-size: 25px; font-weight: 770; letter-spacing: .02em; }
.composition-projection-stage > header p { margin: 0; color: var(--content-muted); font-size: 11px; font-weight: 620; }
.composition-state { display: grid; grid-template-columns: 9px auto; align-items: center; gap: 2px 7px; padding: 9px 12px; border: 1px solid color-mix(in srgb, var(--accent-light) 24%, transparent); border-radius: 10px; background: color-mix(in srgb, var(--primitive-cool-glow) 7%, transparent); }
.composition-state i { grid-row: 1 / 3; width: 7px; height: 7px; border-radius: 50%; background: var(--accent-light); box-shadow: 0 0 10px var(--accent-light); }
.composition-state span { color: var(--content-secondary); font-size: 10px; font-weight: 700; }
.composition-state small { color: var(--content-muted); font-size: 8.5px; font-weight: 600; }
.composition-message { margin: -8px 2px 13px; color: var(--content-muted); font-size: 10px; font-weight: 620; }
.composition-view-grid { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(260px, .85fr); gap: 12px; align-items: start; }
.composition-view { min-width: 0; overflow: hidden; border: 1px solid color-mix(in srgb, var(--panel-edge) 66%, transparent); border-radius: 14px; background: color-mix(in srgb, var(--primitive-glass) 46%, transparent); }
.composition-view > header { display: flex; align-items: baseline; justify-content: space-between; gap: 15px; padding: 13px 15px; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 50%, transparent); }
.composition-view > header span { color: var(--content-primary); font-size: 13px; font-weight: 750; }
.composition-view > header small { color: var(--content-muted); font-size: 9px; font-weight: 620; }
.composition-dashboard, .composition-table { grid-column: 1 / -1; }
.composition-dashboard > div { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; }
.composition-dashboard article { display: grid; grid-template-columns: 1fr auto; align-items: end; gap: 4px 8px; padding: 15px; background: color-mix(in srgb, var(--panel-bg) 32%, transparent); }
.composition-dashboard article span { grid-column: 1 / -1; color: var(--content-muted); font-size: 9px; font-weight: 700; }
.composition-dashboard article strong { color: var(--content-primary); font-size: 24px; font-weight: 760; }
.composition-dashboard article small { padding-bottom: 3px; color: var(--content-muted); font-size: 9px; font-weight: 650; }
.composition-bar-plot { height: 222px; display: flex; align-items: stretch; gap: 8px; padding: 18px 16px 12px; }
.composition-bar-plot article { min-width: 0; flex: 1 1 44px; display: grid; grid-template-rows: minmax(0, 1fr) 32px; gap: 7px; }
.composition-bar-track { position: relative; display: flex; align-items: end; justify-content: center; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 70%, transparent); }
.composition-bar-track > i { width: min(38px, 72%); height: var(--bar-height); min-height: 4px; border-radius: 7px 7px 2px 2px; background: linear-gradient(to top, color-mix(in srgb, var(--primitive-cool-glow) 50%, transparent), color-mix(in srgb, var(--accent-light) 76%, #fff)); box-shadow: 0 -4px 18px color-mix(in srgb, var(--primitive-cool-glow) 24%, transparent); }
.composition-bar-track > em { position: absolute; top: -4px; color: var(--content-muted); font-size: 8px; font-style: normal; font-weight: 650; white-space: nowrap; }
.composition-bar-plot article > b { overflow: hidden; color: var(--content-secondary); text-align: center; text-overflow: ellipsis; white-space: nowrap; font-size: 9px; font-weight: 650; }
.composition-comparison > div { display: grid; gap: 10px; padding: 14px; }
.composition-comparison article { display: grid; grid-template-columns: 1fr auto; gap: 5px 9px; }
.composition-comparison article > div { grid-column: 1 / -1; display: flex; justify-content: space-between; gap: 12px; }
.composition-comparison b { color: var(--content-secondary); font-size: 10px; font-weight: 700; }
.composition-comparison span, .composition-comparison small { color: var(--content-muted); font-size: 9px; font-weight: 620; }
.composition-comparison article > i { overflow: hidden; height: 6px; border-radius: 5px; background: color-mix(in srgb, var(--panel-edge) 56%, transparent); }
.composition-comparison article > i > em { display: block; width: var(--share); height: 100%; border-radius: inherit; background: linear-gradient(90deg, color-mix(in srgb, var(--primitive-cool-glow) 48%, transparent), var(--accent-light)); }
.composition-classification > div { display: grid; max-height: 270px; overflow: auto; }
.composition-classification article { display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; align-items: center; gap: 9px; padding: 11px 14px; border-top: 1px solid color-mix(in srgb, var(--panel-edge) 42%, transparent); }
.composition-classification article > i { color: var(--accent-light); font-size: 9px; font-style: normal; font-weight: 800; }
.composition-classification article > div { min-width: 0; display: grid; gap: 3px; }
.composition-classification b { overflow: hidden; color: var(--content-primary); font-size: 10.5px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
.composition-classification span { color: var(--content-muted); font-size: 8.5px; font-weight: 600; }
.composition-classification strong { color: var(--content-secondary); font-size: 10px; font-weight: 700; }
.composition-table > div { max-height: 320px; overflow: auto; }
.composition-table table { min-width: 100%; border-collapse: collapse; }
.composition-table th, .composition-table td { max-width: 260px; padding: 10px 12px; border-top: 1px solid color-mix(in srgb, var(--panel-edge) 45%, transparent); color: var(--content-secondary); text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 9.5px; font-weight: 600; }
.composition-table th { position: sticky; z-index: 2; top: 0; color: var(--content-primary); background: color-mix(in srgb, var(--panel-bg) 96%, transparent); font-weight: 750; }
.composition-empty { display: grid; justify-items: center; width: min(520px, calc(100% - 40px)); margin: 15vh auto 0; text-align: center; }
.composition-empty > span { display: grid; place-items: center; width: 66px; height: 66px; border-radius: 50%; color: var(--accent-light); background: radial-gradient(circle, color-mix(in srgb, var(--primitive-cool-glow) 22%, transparent), transparent 72%); font-size: 20px; font-weight: 760; }
.composition-empty h2 { margin: 16px 0 7px; color: var(--content-primary); font-size: 21px; }
.composition-empty p { margin: 0; color: var(--content-muted); font-size: 11.5px; line-height: 1.7; }
@media (max-width: 980px) {
.native-composition-layout { grid-template-columns: 250px minmax(0, 1fr); }
.composition-view-grid { grid-template-columns: 1fr; }
.composition-view { grid-column: 1; }
.composition-dashboard > div { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.private-route-note { position: absolute; z-index: 10; left: 50%; bottom: 11%; width: min(720px, calc(100% - 64px)); margin: 0; transform: translateX(-50%); color: var(--content-muted); text-align: center; font-size: 12.5px; font-weight: 650; letter-spacing: .08em; }
.personal-node-guide { position: absolute; z-index: 12; left: 50%; top: 55%; width: min(760px, calc(100% - 72px)); max-height: calc(100% - 190px); overflow: auto; padding: 26px 30px; transform: translate(-50%, -50%); border: 1px solid var(--panel-edge); border-radius: 20px; color: var(--content-secondary); background: color-mix(in srgb, var(--panel-bg) 88%, transparent); box-shadow: 0 30px 90px rgba(0, 0, 0, .42); backdrop-filter: blur(22px); }
.personal-node-guide header span { color: var(--accent-light); font-size: 11px; font-weight: 750; letter-spacing: .2em; }
@ -763,6 +1226,7 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.time-module-strip em { color: var(--content-faint); font-size: 10px; font-style: normal; white-space: nowrap; }
@media (max-width: 820px) {
.world-title-actions > span { display: none; }
.era-home-entry { left: 14px; bottom: 10px; width: 282px; }
.era-timeline-panel li { grid-template-columns: 104px 18px minmax(0, 1fr); gap: 10px; }
.era-timeline-panel ol::before { left: 142px; }
@ -772,11 +1236,21 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.receipt-message { color: var(--accent-light) !important; font-weight: 650; text-align: right; }
@media (max-width: 900px) {
.education-focus-hero { grid-template-columns: 1fr; }
.education-focus-hero > article { justify-items: start; }
.education-focus-hero > article > div { justify-content: flex-start; }
.education-dashboard-metrics { grid-template-columns: repeat(2, minmax(130px, 1fr)); }
.education-dashboard-chart article { grid-template-columns: minmax(80px, 120px) minmax(120px, 1fr) 30px; }
.education-dashboard-chart article em { grid-column: 2 / 4; }
.d-main { left: 2%; } .d-sub { left: 22%; } .d-zero { right: 22%; } .d-zs { right: 2%; }
.domain-info-card.info-main { left: 18px; } .domain-info-card.info-sub { left: calc(22% - 75px); }
.domain-info-card.info-zero { right: calc(22% - 75px); } .domain-info-card.info-zs { right: 18px; }
.world-pool { width: 150px; } .pool-bay { width: 145px; }
.channel-knowledge { left: 4%; } .channel-code { left: 25%; } .channel-light { right: 25%; } .channel-status { right: 4%; }
.industry-pool { width: 180px; }
.industry-pool .pool-bay { width: 170px; }
.industry-web-novel { left: 4%; } .industry-pet { right: 4%; }
.education-module-1 { left: 3%; } .education-module-2 { left: 23%; } .education-module-3 { right: 23%; } .education-module-4 { right: 3%; }
.broadcast-stream { left: 5%; }
.personal-node-guide ol { grid-template-columns: 1fr; }
}