# Citrus 可复用交互组件 首页是可操作组件参考库。展示与复用使用同一 components.css / components.js / image-viewer.js / image-viewer.css;无需框架或远程CDN。交互统一,不强制所有工具采用同一视觉。 ## 界面总则 布局和控件优先表达用途。减少装饰性文案、重复说明和开发术语堆砌,不做过多解释,减少无关紧要的文字。保留必要控件标签、状态反馈、累计费用及关键风险确认;次要帮助按需展示。精简界面文案不等于删除角色或用户实际资料。 规范源:`rules.json` → `content.concise-interface`。后续界面沿用此标准,外观按项目调整。 ## 引入 ```html ``` 主题在首屏绘制前设置 html[data-theme];组件共享 --paper / --card / --ink / --line / --stack-gap 等tokens。只覆盖tokens即可适配其他外观。手动主题选择存在本机;敏感信息不要存入浏览器偏好。 ## 编辑与确认 ```js const editor = Citrus.mountEditor(element, { title: '资料', value: {title: '原文'}, fields: [{key:'title',label:'标题'}], onSave: async draft => saveWithRevision(draft) }); editor.hasDirty(); editor.getValue(); editor.getDraft(); editor.destroy(); Citrus.confirmAbove(button, { text: '放弃未保存改动?', onConfirm: discardDraft }); ``` 实际值比较clean/dirty;标题右侧编辑变取消;仅dirty显示左侧保存。clean取消直退;dirty保存/放弃图标上方确认、默认焦点在否;Esc、外部点击、滚动仅关闭确认,草稿保留。onSave只提交目标字段,调用方负责版本、权限、冲突与持久化;拒绝Promise保留草稿,不伪装已保存。页面导航前通过hasDirty进行离开保护。 ## 吸顶:文字转图标 ```html
``` ```js const nav = Citrus.mountStickyNav(navShell, {sentinel, offset:0}); // 卸载时 nav.destroy(); ``` sentinel必须放在正常文档流中、导航外且紧邻导航前。其bottom到达offset时收起;反向滚动top到offset+2px时恢复,2px滞回防阈值抖动。导航shell始终保持同一78px布局占位(手机76px),sticky不脱离正常流,收起只改标签宽度/透明度,不替换DOM,不改变选中或键盘焦点。offset与sticky top同步,供固定顶部栏使用。滚动采样由rAF合并,销毁移除监听及observer。移动端保留图标和可访问名称/tooltip。示例页使用同一组件,具有足够滚动空间可实际验证正反向恢复。 ## 选择器、页签与主题 ```js Citrus.segmented(group,{value:'scene',onChange:value=>preview(value)}); Citrus.tabs(tablist); // role=tab / aria-controls 指向panel,支持方向/Home/End Citrus.theme(themeButton); // 初始主题需在head首屏脚本应用 ``` 分段选择的button使用data-value;选择不自动请求。独立唯一职责不可串写。图标必须有aria-label和title。主题存储异常不影响组件基本使用。 ## 异步与迟到响应 ```js const task = Citrus.createTask({onState:renderState, timeoutMs:120000}); task.run(signal => request({signal})); // 仅用户主动操作调用 task.cancel(); task.acknowledge(); task.dispose(); ``` pending阻止重复;超时/取消变unknown,明确确认后只允许新请求,不重试旧请求。普通异常复位failure,迟到Promise不得覆盖新状态。该控制器仅管理前端等待;真实项目仍需持久账本、请求ID去重及服务端门控,不能因页面刷新丢失未知账本。onState接收state/detail/unknownHistory;历史本页保留。切换/导航按项目语义调用cancel或dispose。 ## 相册与查看器 ```html ``` ```js const gallery = UIImageViewer.gallery(resultContainer, {detailsLabel:'图像信息'}); // 每张结构 figure > img + 原有说明;结果追加后 gallery.refresh(); UIImageViewer.attach(chatMessages); // 仅查看,不强改聊天流为网格 ``` 请保证依赖先于调用(ESM项目显式import './image-viewer.js',不可依赖并行脚本偶然顺序)。桌面4列,窄屏2/1列。两模式均同一正方形格子:cover铺满裁切,contain完整等比抵边居中留白,不改变网格或原图。双击/Enter/查看按钮打开,滚轮1–6倍锚点缩放,放大拖动,触屏有明确按钮;空白/Esc/关闭退出,恢复焦点与背景滚动。详见docs/image-viewer.md。 ## 规范来源与旧版 rules.json保存交互语义,build_rules.py生成UI-UE-SPEC.md和docs/image-viewer.md。不是页面清单。修改规范源后再生成,不维护两套文字。已确认与建议通过authority区分。原检查表文件位于_archive可恢复;原浏览器检查键保持不变,新首页不读取、不覆盖这些检查状态。 ## 分类页签、宽屏与图标(2026-10-31 更新) 顶部分类是真实页签,不是锚点链接;顺序:回到顶部、图标查询、按钮状态、编辑确认、表单选择、图片浏览、直接复用。只显示选中panel;不再展示异步分类,createTask复用API仍保留。标题下无重复副标题。旧版代码在_archive中,用户检查存储未读取或重置。 ```js const tabs = Citrus.tabs(tablist, { beforeChange({from,to}) { Citrus.closeConfirm(); UIImageViewer.close(); return true; // false 可阻止切换 }, onChange({from,to}) { /* 仅布局变化,不发请求 */ } }); tabs.getSelected(); tabs.select('category-pictures'); tabs.destroy(); ``` button[role=tab] 使用 aria-controls 指向 role=tabpanel;panel使用 aria-labelledby。左右/Home/End切换并保留可见焦点。组件不销毁panel DOM,因此编辑草稿、表单、图标查询和缩略图模式互不串写且切回保留;切换关闭确认与图片模态层,恢复背景滚动。实例只挂载一次,destroy移除绑定,pagehide统一释放;不要每次切换重复mount。 “回到顶部”页签提供真实窗口滚动空间,顶部横条使用 mountStickyNav 同源组件;滚动到顶边收文字、反向滚动恢复;演示按钮可滚到吸顶状态或回到顶部。切换其他分类后仅保留该分类内容,不再跳到长页面的锚点。 宽屏容器上限3600px(含内边距),padding按视口clamp;1920/2560/3840使用可用宽度,字号不随屏幕放大。图标网格自动多列、按钮示例宽屏并列,正文限制行长;小屏折叠为适合的列数,图片桌面仍4列。不要将整页transform缩放来适配4K。 ### 图标复用 ```html ``` ```js const svg = CitrusIcons.getSVG('action-save'); button.innerHTML = svg; button.setAttribute('aria-label','保存'); button.title = '保存'; // CitrusIcons.records: id/name/body/source/aliases ``` 图标来自实际档案室导航/编辑控件SVG与Citrus已用符号,另含本次回到顶部控件。未引入外部图标全集;每个图标标注项目相对来源,不含角色图片。图标查询仅筛选这份本地注册表,支持复制SVG或标识;复制成功才提示成功,权限失败显示读取注册表的替代方式。原字符图标包装为SVG text,保留实际来源样式;不要把它们当作无字体依赖的路径图标。图库/分类使用同一注册表。 移动端未吸顶时图标上、名称下堆叠;吸顶后收起名称,保持同一高度。pagehide时关闭模态与定时提示;普通卸载销毁监听,bfcache冻结保留单一实例供返回恢复,分类切换不重复挂载。 ## 柠檬视觉、短页滚轮和密度(最新) 当前分类:回到顶部、图标查询、按钮状态、编辑确认、表单选择、图片浏览。“直接复用”与异步展示已移除,开发文件和组件API保留。 `citrus-theme.css` 是唯一配色tokens源:浅色柠檬乳白/青柠黄绿,深色为深绿黑与柔和黄绿。引入components.css、showcase.css后再引入citrus-theme.css;不对图片做颜色滤镜。不重置citrus.theme.v1,原手动主题仍优先,未设置时跟随系统。nav右侧主题按钮和显示设置共用同一主题控制器。显示设置只有主题和相册密度,即时生效且仅本机保存,无模型/API配置。 ```js const sticky = Citrus.mountStickyNav(shell, { sentinel, wheelCompact:true, collapseRoot:document.querySelector('.shell') }); sticky.compact(); sticky.expand(); sticky.destroy(); ``` 正常长页仍按sentinel吸顶;页高不足以将横条滚到顶部时,向下滚轮累计60px才收起顶部品牌/标题并让导航贴顶。原有占位和同一tab DOM保留;反向滚轮在顶部或“回到顶部”恢复。滚轮监听passive,不阻止默认滚动;忽略表单、可编辑区域、原生dialog、确认/图片弹层、内部可滚动容器、ctrl缩放和水平手势;小于2px的微动忽略,280ms累计窗口和方向复位避免抖动。长页中段反向滚动不会突然展开,回到原位才恢复。 紧凑tab使用固定44px宽度靠左;主题与设置靠右。窄屏左侧页签可横向滚动,右侧工具固定;键盘选中项自动进入可视范围。首项演示卡片等高对齐,不再靠人为巨大空白撑长页面。 ```js const album=UIImageViewer.gallery(root, { density:4, onDensityChange:value=>saveLocalDisplayPreference(value) }); album.setDensity(8); album.getDensity(); album.destroy(); ``` 桌面(901px及以上):大=2列,中=4列(默认),标准=6列,小=8列。900px及以下最多4列、700px及以下最多2列、350px及以下1列;选择值保留,提示实际生效列数。两种缩略图模式始终同一正方形框,cover裁切/contain完整抵边不改变格子与位置,查看器始终使用原图。密度偏好只保存citrus.gallery-density.v1,刷新恢复,不影响其他数据。resize监听每个相册仅一个,destroy释放;分类切换不重复初始化。 品牌是原创柠檬SVG;archive-settings与action-edit已规整重画,统一24 viewBox、1.65笔画和round cap/join。图标查询、导航/编辑示例和复制SVG都读取icon-registry.js;assets/lemon.svg、assets/settings.svg、assets/edit.svg由build_icons.py生成,不维护第二套路径。测试16/20/24/48px及两种主题。 最新修正:柠檬仅fill=none与currentColor描边,品牌/查询/复制/生成资产同源。短页chrome收合使用280ms高度/透明度过渡,页签数值宽度与标签淡出协同,保留同一DOM与导航高度;反向平滑恢复。普通长页沿原生sticky连续移动。prefers-reduced-motion禁用过渡,不重置主题。 当前图标注册表共28项(含后续新增狼品牌与环境特质),全库不再使用SVG text或手写字符图标。编辑/关闭/确认重复项并入action-edit/action-cancel/action-yes;主题仅theme-light太阳与theme-dark月亮。旧ID在CitrusIcons.aliases兼容映射,不作为查询副本;旧混合archive-theme兼容到theme-dark,实际主题组件按当前模式使用太阳/月亮。基准standard改标尺、默认default改含勾书签,独立维度,不合并为单一选择,不与保存/确认混淆。 ## 品牌线稿 `CitrusIcons.getSVG("brand-redwolf")` / `assets/redwolf.svg` 为去色透明狼头;`citrus-citrus` / `assets/lemon.svg` 为 Citrus 柠檬。导出的SVG只用 none/currentColor;红底白线由应用容器提供,禁止固化到通用资产。24/48px预览与复制同源。 ## 档案室构建同步 唯一源为 `icon-registry.js`、`components.js` 的 `mountStickyNav` 与 `image-viewer.js` 的 `gallery`。运行 `python tools/build-archive-sync.py --output-dir <档案室>/management-static` 离线构建同源静态模块 `citrus-ui-sync.js`、中性 `redwolf.svg` 和校验元数据,并更新品牌内联与相册控制器。档案室只适配类名,保留自己的主题、页签和业务;HTTP页面不读取本地任意文件。同步后应验证导航过渡、短页滚轮、焦点、手机布局及本项目保存/异步行为。 ## 导航动画与滚轮性能 `mountStickyNav` 的 passive wheel 回调只积累方向意图,样式/坐标读取与状态写入集中到同一 requestAnimationFrame,先读后写。一次状态变化只启动一次 CSS 过渡;短页收折不额外调用 scrollTo,长页由原生滚动驱动。连续同向小增量可积累,反向微抖不触发收折;输入、嵌套滚动容器、弹窗和图片查看器保留自己的滚轮。 文字按实际宽度设置 `--nav-label-width`,与按钮尺寸使用同一 280ms 曲线,opacity 200ms 渐隐;保持文字在正常布局流中,避免 absolute/display 切换或不同缓动造成中途回长。尊重 reduced motion。`citrus-theme.css` 的 shared navigation motion 标记区由 `tools/build-archive-sync.py` 同时生成档案室 CSS 与控制器;不要手改第二套曲线。验收须记录中间透明度、文字/按钮宽度与位置,不仅检查最终 compact 状态。 ## 页头整体收折与下方导航分工 `mountStickyNav(...,{wheelCompact:true,collapseHeaderOnWheel:true})` 可独立启用品牌栏/大标题区域的滚轮收折。下滚意图给根容器加 `is-header-folded`,即使内容很长也触发;回到页面顶部后反向滚轮恢复。短页没有可滚动距离也可收折。该选项不改变现有导航标签的尺寸曲线、紧凑阈值或选中项,默认关闭以兼容已有调用。事件仍 passive、rAF读写合批、排除表单/嵌套滚动/弹层/图片缩放,不调用scrollTo或阻止原生长页滚动。destroy清理状态与监听。 档案室的masthead/intro动画由构建器从Citrus实际header/compact-hero样式生成,单独标记为header motion,与导航条motion区分。应验证品牌栏及大标题各自高度、透明度、transform的中间帧,不能再以页签文字变化代替整个页头验收。遵循reduced-motion。 ## 页头速率的几何阶段 时长相同不代表伸缩进度相同:max-height上限、标题行高和padding同时参与盒高。构建器从Citrus的实际hero样式推导桌面/手机展开与收起的归一化高度进度,输出linear分段曲线;档案室按本身测得的展开高度使用该进度,保持原字号、留白和展开尺寸。时长仍跟随Citrus(当前280ms),透明度仍为展开200ms/收起180ms ease,visibility收起延迟280ms;品牌栏高度曲线保持cubic-bezier(.2,.7,.2,1)。不要另设慢速常量。 padding的展开值声明为CSS变量,收起与resize时仍可测量完整标题高度;数值高度仅在mount/resize更新,不在wheel事件读写布局。旧浏览器不支持linear时保留原有收放作为兼容回退,现代浏览器使用构建得到的阶段曲线。验证应比较相同时刻的相对高度和透明度,不能只比较duration或实际页面的不同绝对像素距离。 ## 档案室应用:颜色语义与操作间距 规则源:`rules.json` 的 `archive.color-semantics`、`layout.action-feedback-spacing`。档案室普通输入、上传和新增使用中性/青蓝边框与焦点;红框只用于重要提醒/警告、危险操作或选中页签,保留品牌红底白狼。新增槽位与普通单行选项同高同padding,悬停仅淡红底。此约定不改变Citrus黄绿主题。 API保存/删除放入可换行的flex操作组,以现有`--stack-gap`派生控件间距;状态反馈独立留白。普通操作、危险删除、保存反馈不能挤在一起。4K及390/320按实际边界验收,不读取或操作真实API,不修改业务和二次确认逻辑。 ## 环境特质页签图标 `archive-environment`:山峰+四角星,单一源`icon-registry.js`;查询预览与复制SVG同源。`build_icons.py`输出`assets/environment.svg`,仅透明背景、fill=none、stroke=currentColor;24 viewBox、1.65笔画、round cap/join。`tools/build-archive-sync.py`同步到档案室environment图标键,保留原主题与导航动画。当前注册表28项,无新增重复别名记录。 档案室顺序:基础人设、外貌服装、环境特质、成长记录、对象档案、测试聊天,右侧设置与主题。窄屏左侧横向滚动,激活/键盘切换只调整导航容器的横向位置,不滚动整页;不修改收折阈值或过渡。环境特质当前仅静态页签和标题,尚无资料字段、数据schema或模型输入。 ## 缩略图适配切换动画 `UIImageViewer.gallery` 保持正方形裁切框及卡片布局固定,图片以 `contain` 为基准,通过均匀 `transform: scale(...)` 在 cover 裁切与完整抵边之间连续缩放。时长为 420ms,缓动为 `cubic-bezier(.2,.7,.2,1)`;不对离散的 `object-fit` 属性做过渡,不拉伸或替换原图。 重复点击和快速反向会从当前绘制状态继续;图片加载及容器 resize 后重新计算适配,首次出现时直接处于正确比例。`refresh()` 可接入新增图片,`destroy()` 释放图片事件和尺寸观察器。`prefers-reduced-motion: reduce` 禁用缩放过渡。 验收应包含横图、竖图、正方图的中间帧、快速反向、四档密度、手机与明暗主题;切换全过程正方格位置、尺寸与卡片高度不变。全屏仍使用原图,双击/Enter、滚轮缩放、拖动及空白/Esc 退出行为保持一致。