feat: add bundled OpenCode skills and refine module flow
This commit is contained in:
@@ -1,22 +1,10 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { DisclosureContent } from '@/components/ui/disclosure';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { aiModules, getAiModuleForPath, type AiModuleId } from '@/lib/ai-modules';
|
||||
|
||||
const moduleToneClasses: Record<AiModuleId, { active: string }> = {
|
||||
programming: { active: 'bg-brand-soft' },
|
||||
painting: { active: 'bg-brand-soft' },
|
||||
learning: { active: 'bg-brand-soft' },
|
||||
robot: { active: 'bg-brand-soft' },
|
||||
};
|
||||
import { AI_MODULE_SELECTION_PATH, aiModules, getAiModuleForPath } from '@/lib/ai-modules';
|
||||
|
||||
export function ModuleSwitcher({ sidebarCollapsed, compact = false }: { sidebarCollapsed: boolean; compact?: boolean }) {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const [open, setOpen] = useState(false);
|
||||
const switcherRef = useRef<HTMLDivElement | null>(null);
|
||||
const activeModuleId = getAiModuleForPath(location.pathname);
|
||||
const activeModule = aiModules.find((module) => module.id === activeModuleId) ?? aiModules[0];
|
||||
const getModuleLabel = (module: typeof aiModules[number]) => module.switcherLabel
|
||||
@@ -24,88 +12,24 @@ export function ModuleSwitcher({ sidebarCollapsed, compact = false }: { sidebarC
|
||||
const activeModuleLabel = getModuleLabel(activeModule);
|
||||
const compactTrigger = compact && !sidebarCollapsed;
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return undefined;
|
||||
const handleMouseDown = (event: MouseEvent) => {
|
||||
const target = event.target;
|
||||
if (target instanceof Node && !switcherRef.current?.contains(target)) setOpen(false);
|
||||
};
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') setOpen(false);
|
||||
};
|
||||
document.addEventListener('mousedown', handleMouseDown);
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleMouseDown);
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<div ref={switcherRef} data-testid="sidebar-module-switcher" className="relative">
|
||||
<div data-testid="sidebar-module-switcher" className="relative">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="sidebar-module-switcher-trigger"
|
||||
aria-label={`切换模块:${activeModuleLabel}`}
|
||||
aria-expanded={open}
|
||||
aria-controls="sidebar-module-switcher-menu"
|
||||
onClick={() => setOpen((current) => !current)}
|
||||
aria-label="返回首页"
|
||||
title="返回首页"
|
||||
onClick={() => navigate(AI_MODULE_SELECTION_PATH)}
|
||||
className={cn(
|
||||
'motion-press items-center bg-transparent text-left font-semibold text-foreground transition-[background-color,color,transform] hover:bg-surface-subtle active:scale-[0.985]',
|
||||
compactTrigger
|
||||
? 'inline-flex min-h-9 w-fit translate-y-2 justify-start gap-1 rounded-xl px-2 text-xs'
|
||||
: 'flex min-h-11 w-full justify-between gap-2 rounded-2xl px-3 py-2 text-sm',
|
||||
open && 'bg-surface-subtle',
|
||||
? 'inline-flex min-h-9 w-fit translate-y-2 justify-start rounded-xl p-2 text-xs'
|
||||
: 'flex min-h-11 w-full justify-between gap-2 rounded-2xl p-3 text-sm',
|
||||
sidebarCollapsed ? 'justify-center px-0' : '',
|
||||
)}
|
||||
>
|
||||
{!sidebarCollapsed ? <span className="min-w-0 whitespace-nowrap">{activeModuleLabel}</span> : null}
|
||||
{!sidebarCollapsed ? <ChevronDown className={cn(compactTrigger ? 'h-3.5 w-3.5' : 'h-4 w-4', 'shrink-0 transition-transform duration-200 ease-out', open && 'rotate-180')} /> : null}
|
||||
</button>
|
||||
|
||||
<DisclosureContent
|
||||
open={open}
|
||||
id="sidebar-module-switcher-menu"
|
||||
role="menu"
|
||||
data-testid="sidebar-module-switcher-menu"
|
||||
className={cn(
|
||||
'absolute top-[calc(100%+0.5rem)] z-50 rounded-2xl border border-border/80 bg-background p-2 text-foreground shadow-float',
|
||||
sidebarCollapsed ? 'left-0 w-64' : compactTrigger ? 'right-0 min-w-56' : 'inset-x-0',
|
||||
)}
|
||||
innerClassName="grid gap-1.5"
|
||||
>
|
||||
<div className="px-2 py-1 text-[11px] font-semibold text-muted-foreground">选择模块</div>
|
||||
{aiModules.map((module) => {
|
||||
const active = module.id === activeModuleId;
|
||||
const tone = moduleToneClasses[module.id];
|
||||
const moduleLabel = getModuleLabel(module);
|
||||
return (
|
||||
<button
|
||||
key={module.id}
|
||||
type="button"
|
||||
role="menuitem"
|
||||
data-testid={`sidebar-module-${module.id}`}
|
||||
aria-label={`${moduleLabel}${module.enabled ? '' : ',即将上线'}`}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
disabled={!module.enabled}
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
if (module.route) navigate(module.route);
|
||||
}}
|
||||
className={cn(
|
||||
'flex min-h-9 w-full items-center rounded-md border px-2 py-1.5 text-left text-xs font-semibold text-foreground transition-[background-color,color,transform,opacity] hover:bg-background active:scale-[0.985]',
|
||||
active ? `border-brand/20 ${tone.active} shadow-soft` : 'border-transparent',
|
||||
!module.enabled && 'cursor-not-allowed border-foreground/15 bg-surface-subtle text-muted-foreground/70 opacity-70 hover:bg-surface-subtle',
|
||||
)}
|
||||
>
|
||||
<span className="min-w-0 flex-1 whitespace-nowrap">
|
||||
<span className="block whitespace-nowrap">{moduleLabel}</span>
|
||||
{!module.enabled ? <span className="mt-0.5 block text-[10px] font-medium opacity-80">{module.subtitle}</span> : null}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</DisclosureContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { agentAvatarOptions } from '@/lib/agent-avatars';
|
||||
import { formatModelRefLabel, type ConfiguredModelOption } from '@/lib/model-options';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { ProjectAgentConfig } from '../../../shared/project-config';
|
||||
import { DEFAULT_PROJECT_AGENT_SKILL_IDS } from '../../../shared/opencode-skills';
|
||||
|
||||
export type AgentCreationSkillOption = {
|
||||
id: string;
|
||||
@@ -81,6 +82,7 @@ export function AgentCreationDialog({
|
||||
}: AgentCreationDialogProps) {
|
||||
const editing = Boolean(agent);
|
||||
const [input, setInput] = useState<AgentCreationInput>(() => createInitialInput(modelOptions, agent));
|
||||
const defaultSkillsAppliedRef = useRef(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [skillQuery, setSkillQuery] = useState('');
|
||||
const [avatarPickerOpen, setAvatarPickerOpen] = useState(false);
|
||||
@@ -88,11 +90,25 @@ export function AgentCreationDialog({
|
||||
const avatarUploadInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
if (!open) {
|
||||
defaultSkillsAppliedRef.current = false;
|
||||
return;
|
||||
}
|
||||
defaultSkillsAppliedRef.current = editing;
|
||||
setInput(createInitialInput(modelOptions, agent));
|
||||
setSkillQuery('');
|
||||
setAvatarPickerOpen(false);
|
||||
}, [agent, modelOptions, open]);
|
||||
}, [agent, editing, modelOptions, open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || editing || defaultSkillsAppliedRef.current) return;
|
||||
const defaultSkillIds = DEFAULT_PROJECT_AGENT_SKILL_IDS.filter((skillId) => skills.some((skill) => skill.id === skillId));
|
||||
if (defaultSkillIds.length === 0) return;
|
||||
setInput((current) => current.skillIds.length > 0
|
||||
? current
|
||||
: { ...current, skillIds: [...defaultSkillIds] });
|
||||
defaultSkillsAppliedRef.current = true;
|
||||
}, [editing, open, skills]);
|
||||
|
||||
const handleAvatarFileChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
|
||||
@@ -41,7 +41,7 @@ export const aiModules: readonly AiModuleDefinition[] = [
|
||||
subtitle: 'AI 学习',
|
||||
description: '用顶尖的方法解锁万物规律',
|
||||
route: null,
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
Icon: Sigma,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,6 +8,18 @@ const SKILL_DISPLAY_BY_ID: Record<string, SkillDisplayInfo> = {
|
||||
name: '开发浏览器',
|
||||
description: '打开、查看或调试本地及公网网页,读取 Console、Network、DOM 和样式信息。',
|
||||
},
|
||||
'frontend-slides': {
|
||||
name: '项目演示',
|
||||
description: '把项目内容整理成可播放的 16:9 HTML 幻灯片,不生成 PPTX 或云端发布。',
|
||||
},
|
||||
grilling: {
|
||||
name: '方案质询',
|
||||
description: '在重要工作开始前逐项澄清目标、范围和关键取舍,确认后再执行。',
|
||||
},
|
||||
'planning-with-files': {
|
||||
name: '项目规划',
|
||||
description: '为复杂任务维护可恢复的计划、发现和进展,记录在项目的 .niancode/agent-planning/ 中。',
|
||||
},
|
||||
};
|
||||
|
||||
export function getSkillDisplayInfo(skillId: string): SkillDisplayInfo {
|
||||
|
||||
@@ -219,6 +219,39 @@ const courseSkills: Skill[] = [
|
||||
appliesTo: ['需要网页调试的项目伙伴'],
|
||||
statusLabel: '平台内置 · 可手动选择',
|
||||
},
|
||||
{
|
||||
id: 'grilling',
|
||||
name: '方案质询',
|
||||
description: '在重要工作开始前逐项澄清目标、范围和关键取舍,确认后再执行。',
|
||||
prompt: '当任务涉及重要实现或设计决策时,先逐项确认关键选择,不要在未确认前修改项目。',
|
||||
inputs: ['用户目标', '范围与约束', '验收标准'],
|
||||
outputs: ['已确认的方案', '主要取舍', '剩余不确定性'],
|
||||
guardrails: ['每轮只问一个问题', '先查代码事实', '确认前不执行实现'],
|
||||
appliesTo: ['需要澄清复杂需求的项目伙伴'],
|
||||
statusLabel: '平台内置 · 默认勾选',
|
||||
},
|
||||
{
|
||||
id: 'planning-with-files',
|
||||
name: '项目规划',
|
||||
description: '为复杂任务维护可恢复的计划、发现和进展,记录在项目的 .niancode/agent-planning/ 中。',
|
||||
prompt: '复杂任务开始前在 .niancode/agent-planning/ 建立并持续更新 task_plan.md、findings.md 和 progress.md。',
|
||||
inputs: ['复杂任务目标', '阶段与成功标准', '代码和验证结果'],
|
||||
outputs: ['可恢复的计划', '研究发现', '进展与验证记录'],
|
||||
guardrails: ['简单任务不创建规划文件', '不记录密钥和敏感数据', '遵守项目 AGENTS.md'],
|
||||
appliesTo: ['需要持续推进复杂任务的项目伙伴'],
|
||||
statusLabel: '平台内置 · 默认勾选',
|
||||
},
|
||||
{
|
||||
id: 'frontend-slides',
|
||||
name: '项目演示',
|
||||
description: '把项目内容整理成可播放的 16:9 HTML 幻灯片,不生成 PPTX 或云端发布。',
|
||||
prompt: '当用户准备汇报、结题展示或项目介绍时,主动把真实项目内容整理成可播放的本地 HTML 幻灯片,并用浏览器检查布局和素材。',
|
||||
inputs: ['项目目标与受众', '功能截图或素材', '想讲清楚的过程与结果'],
|
||||
outputs: ['16:9 HTML 演示', '风格草图', '浏览器验收结果'],
|
||||
guardrails: ['只生成本地 HTML', '不生成 PPTX', '不部署或上传到云端', '不编造项目数据'],
|
||||
appliesTo: ['需要结题展示的项目伙伴'],
|
||||
statusLabel: '平台内置 · 可手动选择',
|
||||
},
|
||||
];
|
||||
|
||||
const knowledgeFiles: KnowledgeFile[] = [
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import moduleCanvasImage from '@/assets/module-canvas-neon.jpg';
|
||||
import moduleGameImage from '@/assets/module-game.jpg';
|
||||
@@ -70,7 +69,8 @@ export function ModuleSelection({ authRequired = true }: { authRequired?: boolea
|
||||
!module.enabled && 'module-option-card-disabled',
|
||||
)}
|
||||
>
|
||||
<div className="module-option-card-surface relative z-10 flex h-full w-full overflow-hidden rounded-lg bg-background">
|
||||
<span className="module-option-card-frame" aria-hidden="true" />
|
||||
<div className="module-option-card-surface relative flex h-full w-full rounded-lg bg-background">
|
||||
<div className="module-option-card-image relative shrink-0">
|
||||
<img
|
||||
src={content.image}
|
||||
@@ -78,18 +78,14 @@ export function ModuleSelection({ authRequired = true }: { authRequired?: boolea
|
||||
className={cn('module-option-card-image-media h-full w-full object-cover', !module.enabled && 'grayscale')}
|
||||
draggable="false"
|
||||
/>
|
||||
{module.enabled ? (
|
||||
<span className="module-option-card-arrow absolute flex items-center justify-center rounded-full bg-background/90 text-foreground shadow-sm">
|
||||
<ArrowRight className="module-option-card-arrow-icon" strokeWidth={2.6} />
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="module-option-card-content min-h-0 flex-1">
|
||||
<div className="module-option-card-content relative min-h-0 flex-1">
|
||||
<p className="module-option-card-title font-medium tracking-[-0.02em]">{content.label}</p>
|
||||
<p className="module-option-card-description font-medium text-muted-foreground">{module.description}</p>
|
||||
{!module.enabled ? <p className="module-option-card-status font-semibold">暂未开放</p> : null}
|
||||
</div>
|
||||
</div>
|
||||
<span className="module-option-card-hit-area" aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -87,8 +87,8 @@ function DrawerFrame({ title, desc, onClose, children, closeLabel = '关闭', cl
|
||||
.from('.drawer-motion-header', { x: 8, duration: 0.22 })
|
||||
.from('.drawer-motion-body > *', { y: 6, duration: 0.22, stagger: 0.035 }, '-=0.12');
|
||||
}, { scope: drawerRef });
|
||||
const closeButton = <Button type="button" variant="ghost" size="icon" onClick={onClose} aria-label={closeLabel} title={closeLabel}>{closeIcon === 'back' ? <ArrowLeft className="h-4 w-4" /> : <X className="h-4 w-4" />}</Button>;
|
||||
return <div ref={drawerRef} className="flex min-h-0 flex-1 flex-col"><div className="glass-surface drawer-motion-header flex items-start justify-between gap-3 border-b border-border/70 bg-background/80 px-5 py-4"><div><SheetTitle className="text-lg font-semibold">{title}</SheetTitle><SheetDescription className="mt-1 text-sm font-medium text-muted-foreground">{desc}</SheetDescription></div>{closeIcon === 'back' ? closeButton : <SheetClose asChild>{closeButton}</SheetClose>}</div><div className="drawer-motion-body min-h-0 flex-1 overflow-auto p-5">{children}</div></div>;
|
||||
const closeButton = <Button type="button" variant="ghost" size="icon" onClick={onClose} aria-label={closeLabel} title={closeLabel} className="relative z-10 shrink-0">{closeIcon === 'back' ? <ArrowLeft className="h-4 w-4" /> : <X className="h-4 w-4" />}</Button>;
|
||||
return <div ref={drawerRef} className="flex min-h-0 flex-1 flex-col"><div className="glass-surface drawer-motion-header flex items-center justify-between gap-3 border-b border-border/70 bg-background/80 px-5 py-4"><div className="min-w-0 flex-1"><SheetTitle className="text-lg font-semibold">{title}</SheetTitle><SheetDescription className="mt-1 text-sm font-medium text-muted-foreground">{desc}</SheetDescription></div>{closeIcon === 'back' ? closeButton : <SheetClose asChild>{closeButton}</SheetClose>}</div><div className="drawer-motion-body min-h-0 flex-1 overflow-auto p-5">{children}</div></div>;
|
||||
}
|
||||
|
||||
function SkillDetail({ skill }: { skill: SkillInfo }) {
|
||||
|
||||
@@ -445,14 +445,23 @@ html[data-ui-language='ru'] {
|
||||
}
|
||||
}
|
||||
|
||||
/* Adapted from Uiverse.io card by Pravins01 (odd-fly-66). The card keeps the
|
||||
* original pseudo-element rotation and blur animation, with Makelore colors. */
|
||||
/* Adapted from Uiverse.io card by Smit-Prajapati (smart-liger-5). The
|
||||
* original glass layers are softened for Makelore's horizontal module cards
|
||||
* and single light visual system. */
|
||||
.module-option-card {
|
||||
--module-option-frame-orange: 237 114 47;
|
||||
--module-option-inner-gap-y: 0.0625rem;
|
||||
--module-option-inner-gap-x: 0.0625rem;
|
||||
--module-option-plate-inset: 0;
|
||||
--module-option-motion-duration: 280ms;
|
||||
--module-option-motion-ease: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
overflow: visible;
|
||||
height: clamp(7rem, 11vw, 8.5rem);
|
||||
min-width: 0;
|
||||
perspective: 1100px;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
/* The module chooser uses a single vertical rail so each horizontal card has
|
||||
@@ -470,54 +479,128 @@ html[data-ui-language='ru'] {
|
||||
}
|
||||
|
||||
.module-option-card-surface {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
min-height: 100%;
|
||||
min-height: 0;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
border: 1px solid hsl(var(--border) / 0.75);
|
||||
box-shadow: 0 1px 2px hsl(220 20% 16% / 0.035), 0 12px 30px hsl(220 20% 16% / 0.045);
|
||||
}
|
||||
|
||||
.module-option-card::before {
|
||||
position: absolute;
|
||||
top: -1%;
|
||||
left: -1%;
|
||||
z-index: -10;
|
||||
content: '';
|
||||
width: 102%;
|
||||
height: 102%;
|
||||
margin: auto;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(-45deg, hsl(var(--brand)) 0%, hsl(var(--brand-blue)) 100%);
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
transition: transform 600ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
z-index: 1;
|
||||
inset: var(--module-option-plate-inset);
|
||||
width: auto;
|
||||
height: auto;
|
||||
border: 1px solid transparent;
|
||||
border-radius: calc(0.75rem - var(--module-option-inner-gap-x));
|
||||
background: hsl(var(--background));
|
||||
box-shadow:
|
||||
0 1px 0 hsl(var(--background) / 0.96),
|
||||
0 0.5rem 1rem hsl(var(--foreground) / 0.07);
|
||||
-webkit-backdrop-filter: none;
|
||||
backdrop-filter: none;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
will-change: transform;
|
||||
transform: none;
|
||||
transform-style: preserve-3d;
|
||||
transition:
|
||||
background 260ms ease,
|
||||
border-color 220ms ease,
|
||||
box-shadow 260ms ease,
|
||||
-webkit-backdrop-filter 260ms ease,
|
||||
backdrop-filter 260ms ease,
|
||||
transform var(--module-option-motion-duration) var(--module-option-motion-ease),
|
||||
opacity 260ms ease;
|
||||
}
|
||||
|
||||
.module-option-card::after {
|
||||
.module-option-card-frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
content: '';
|
||||
background: linear-gradient(-45deg, hsl(var(--brand)) 0%, hsl(var(--brand-blue)) 100%);
|
||||
transform: translate3d(0, 0, 0) scale(0.95);
|
||||
filter: blur(20px);
|
||||
z-index: 0;
|
||||
display: none;
|
||||
border: 0;
|
||||
border-radius: 0.75rem;
|
||||
background: rgb(var(--module-option-frame-orange));
|
||||
box-shadow:
|
||||
0 1px 2px hsl(220 20% 16% / 0.035),
|
||||
0 12px 30px hsl(220 20% 16% / 0.045);
|
||||
pointer-events: none;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
will-change: transform;
|
||||
transform-origin: center center;
|
||||
transform-style: preserve-3d;
|
||||
transform: none;
|
||||
transition:
|
||||
transform var(--module-option-motion-duration) var(--module-option-motion-ease);
|
||||
}
|
||||
|
||||
.module-option-card-hit-area {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 20;
|
||||
pointer-events: auto;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.module-option-card-surface * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.module-option-card:hover::after,
|
||||
.module-option-card:focus-visible::after {
|
||||
filter: blur(30px);
|
||||
.module-option-card:hover .module-option-card-frame,
|
||||
.module-option-card:focus-visible .module-option-card-frame {
|
||||
transform: rotate3d(1, 1, 0, 16deg) translate3d(0.5rem, 0.2rem, 0);
|
||||
}
|
||||
|
||||
.module-option-card:hover .module-option-card-surface,
|
||||
.module-option-card:focus-visible .module-option-card-surface {
|
||||
background: linear-gradient(
|
||||
140deg,
|
||||
hsl(var(--background) / 0.98) 0%,
|
||||
hsl(var(--background) / 0.82) 52%,
|
||||
hsl(var(--background) / 0.94) 100%
|
||||
);
|
||||
border-color: rgb(255 255 255);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(1.35);
|
||||
backdrop-filter: blur(14px) saturate(1.35);
|
||||
box-shadow:
|
||||
0 0 0 1px hsl(var(--brand) / 0.24),
|
||||
0 0 1.25rem hsl(var(--brand) / 0.2),
|
||||
0 0 2.5rem hsl(var(--brand) / 0.12),
|
||||
0 0.9rem 1.5rem hsl(var(--foreground) / 0.1);
|
||||
transform: rotate3d(1, 1, 0, 16deg) translate3d(0.25rem, -0.25rem, 28px);
|
||||
}
|
||||
|
||||
.module-option-card-image {
|
||||
width: clamp(7rem, 22%, 10rem);
|
||||
height: 100%;
|
||||
flex: 0 0 clamp(7rem, 22%, 10rem);
|
||||
width: clamp(7.125rem, calc(26% - var(--module-option-inner-gap-x)), 10.625rem);
|
||||
height: calc(100% - var(--module-option-inner-gap-y) - var(--module-option-inner-gap-y));
|
||||
flex: 0 0 clamp(7.125rem, calc(26% - var(--module-option-inner-gap-x)), 10.625rem);
|
||||
align-self: center;
|
||||
margin: var(--module-option-inner-gap-y) 0 var(--module-option-inner-gap-y) var(--module-option-inner-gap-x);
|
||||
overflow: hidden;
|
||||
border-radius: 0.75rem 0 0 0.75rem;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
will-change: transform;
|
||||
transform: none;
|
||||
transform-style: preserve-3d;
|
||||
transition: transform var(--module-option-motion-duration) var(--module-option-motion-ease);
|
||||
}
|
||||
|
||||
.module-option-card-image-media {
|
||||
transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
display: block;
|
||||
border-radius: inherit;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
image-rendering: auto;
|
||||
transform: translate3d(0, 0, 0);
|
||||
will-change: transform;
|
||||
transition: transform 240ms var(--module-option-motion-ease);
|
||||
}
|
||||
|
||||
.module-option-card:hover .module-option-card-image,
|
||||
.module-option-card:focus-visible .module-option-card-image {
|
||||
transform: translate3d(-0.08rem, -0.08rem, 48px) scale(1.015);
|
||||
}
|
||||
|
||||
.module-option-card-content {
|
||||
@@ -528,61 +611,112 @@ html[data-ui-language='ru'] {
|
||||
justify-content: center;
|
||||
padding: clamp(0.75rem, 1.6vw, 1.25rem);
|
||||
padding-right: clamp(2.75rem, 5vw, 4rem);
|
||||
transform: none;
|
||||
transform-style: preserve-3d;
|
||||
transition: transform var(--module-option-motion-duration) var(--module-option-motion-ease);
|
||||
}
|
||||
|
||||
.module-option-card-content > * {
|
||||
position: relative;
|
||||
transform-style: preserve-3d;
|
||||
transition: transform var(--module-option-motion-duration) var(--module-option-motion-ease);
|
||||
}
|
||||
|
||||
.module-option-card:hover .module-option-card-content,
|
||||
.module-option-card:focus-visible .module-option-card-content {
|
||||
transform: translate3d(0, 0, 44px);
|
||||
}
|
||||
|
||||
.module-option-card-title {
|
||||
font-size: clamp(1rem, 1.8vw, 1.35rem);
|
||||
line-height: 1.25;
|
||||
transform: none;
|
||||
transition-delay: 0ms;
|
||||
}
|
||||
|
||||
.module-option-card:hover .module-option-card-title,
|
||||
.module-option-card:focus-visible .module-option-card-title {
|
||||
transform: translate3d(0, 0, 54px);
|
||||
transition-delay: 40ms;
|
||||
}
|
||||
|
||||
.module-option-card-description {
|
||||
margin-top: clamp(0.375rem, 0.7vw, 0.625rem);
|
||||
font-size: clamp(0.75rem, 1.1vw, 0.95rem);
|
||||
line-height: clamp(1.1rem, 1.5vw, 1.5rem);
|
||||
transform: none;
|
||||
transition-delay: 0ms;
|
||||
}
|
||||
|
||||
.module-option-card:hover .module-option-card-description,
|
||||
.module-option-card:focus-visible .module-option-card-description {
|
||||
transform: translate3d(0, 0, 24px);
|
||||
transition-delay: 60ms;
|
||||
}
|
||||
|
||||
.module-option-card-status {
|
||||
margin-top: clamp(0.375rem, 0.7vw, 0.625rem);
|
||||
font-size: clamp(0.625rem, 0.75vw, 0.75rem);
|
||||
line-height: 1.25;
|
||||
transform: none;
|
||||
transition-delay: 0ms;
|
||||
}
|
||||
|
||||
.module-option-card-arrow {
|
||||
top: clamp(0.5rem, 1vw, 0.75rem);
|
||||
right: clamp(0.5rem, 1vw, 0.75rem);
|
||||
width: clamp(1.75rem, 2.8vw, 2rem);
|
||||
height: clamp(1.75rem, 2.8vw, 2rem);
|
||||
}
|
||||
|
||||
.module-option-card-arrow-icon {
|
||||
width: clamp(1rem, 1.8vw, 1.25rem);
|
||||
height: clamp(1rem, 1.8vw, 1.25rem);
|
||||
.module-option-card:hover .module-option-card-status,
|
||||
.module-option-card:focus-visible .module-option-card-status {
|
||||
transform: translate3d(0, 0, 20px);
|
||||
transition-delay: 80ms;
|
||||
}
|
||||
|
||||
.module-option-card.module-option-card-disabled::before,
|
||||
.module-option-card.module-option-card-disabled::after {
|
||||
background: hsl(var(--muted-foreground) / 0.28);
|
||||
filter: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.module-option-card.module-option-card-disabled:hover::before,
|
||||
.module-option-card.module-option-card-disabled:focus-visible::before {
|
||||
.module-option-card.module-option-card-disabled:hover .module-option-card-surface,
|
||||
.module-option-card.module-option-card-disabled:focus-visible .module-option-card-surface {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.module-option-card.module-option-card-disabled:hover::after,
|
||||
.module-option-card.module-option-card-disabled:focus-visible::after {
|
||||
filter: none;
|
||||
.module-option-card.module-option-card-disabled:hover,
|
||||
.module-option-card.module-option-card-disabled:focus-visible {
|
||||
box-shadow:
|
||||
0 1px 2px hsl(220 20% 16% / 0.035),
|
||||
0 12px 30px hsl(220 20% 16% / 0.045);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.module-option-card:hover::before,
|
||||
.module-option-card:focus-visible::before {
|
||||
transform: rotate(-1deg) scaleX(1.01) scaleY(1.04);
|
||||
.module-option-card.module-option-card-disabled .module-option-card-frame {
|
||||
border: 0;
|
||||
background: linear-gradient(135deg, hsl(var(--brand) / 0.18) 0%, hsl(var(--brand-soft) / 0.34) 100%);
|
||||
box-shadow:
|
||||
0 1px 2px hsl(220 20% 16% / 0.035),
|
||||
0 12px 30px hsl(220 20% 16% / 0.045);
|
||||
}
|
||||
|
||||
.module-option-card.module-option-card-disabled:hover .module-option-card-frame,
|
||||
.module-option-card.module-option-card-disabled:focus-visible .module-option-card-frame {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.module-option-card.module-option-card-disabled:hover .module-option-card-image,
|
||||
.module-option-card.module-option-card-disabled:focus-visible .module-option-card-image,
|
||||
.module-option-card.module-option-card-disabled:hover .module-option-card-content,
|
||||
.module-option-card.module-option-card-disabled:focus-visible .module-option-card-content {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.module-option-card.module-option-card-disabled .module-option-card-content > * {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.module-option-card.module-option-card-disabled .module-option-card-surface {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.module-option-card:hover .module-option-card-image-media,
|
||||
.module-option-card:focus-visible .module-option-card-image-media {
|
||||
transform: scale(1.04);
|
||||
transform: scale(1.015);
|
||||
}
|
||||
|
||||
.module-option-card:disabled:hover .module-option-card-image-media,
|
||||
@@ -751,13 +885,28 @@ html[data-ui-language='ru'] {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.module-option-card::before {
|
||||
.module-option-card:hover .module-option-card-frame,
|
||||
.module-option-card:focus-visible .module-option-card-frame,
|
||||
.module-option-card:hover .module-option-card-surface,
|
||||
.module-option-card:focus-visible .module-option-card-surface {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.module-option-card:hover::after,
|
||||
.module-option-card:focus-visible::after {
|
||||
filter: blur(20px);
|
||||
.module-option-card:hover .module-option-card-image,
|
||||
.module-option-card:focus-visible .module-option-card-image,
|
||||
.module-option-card:hover .module-option-card-content,
|
||||
.module-option-card:focus-visible .module-option-card-content {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.module-option-card:hover .module-option-card-content > *,
|
||||
.module-option-card:focus-visible .module-option-card-content > * {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.module-option-card:hover .module-option-card-image-media,
|
||||
.module-option-card:focus-visible .module-option-card-image-media {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.brand-spark {
|
||||
|
||||
Reference in New Issue
Block a user