< template >
< div class = "product-standard-page student-training-shell" >
< main class = "training-core" >
< section class = "task-header" >
< div class = "task-badge" > < el -icon > < Document / > < / e l - i c o n > { { b a d g e T e x t } } < / d i v >
< h1 class = "task-title" > { { taskTitle } } < / h1 >
< / section >
< TrainingTaskBrief :background ="taskBackground" :goals ="taskGoals" :requirements ="taskRequirement" :show-material ="false" / >
< section class = "task-card" >
< div class = "workbench-head workbench-head--actions-only" >
< div class = "workbench-actions" >
< TrainingMaterialButton :material-name ="taskConfig?.materialName" :material-url ="taskConfig?.materialUrl" / >
< button type = "button" class = "outline-action" @click ="exportOutcome" > < el -icon > < Download / > < / e l - i c o n > 导 出 实 训 成 果 < / b u t t o n >
< / div >
< / div >
< nav v-if ="hasConfiguredSteps" class="step-indicator" :aria-label="`${taskTitle}步骤`" >
< template v-for ="(step, index) in configuredSteps" :key="`${step}-${index}`" >
< button type = "button" : class = "['step-tab', { active: activeStep === index + 1, complete: activeStep > index + 1 }]" : aria -current = " activeStep = = = index + 1 ? ' step ' : undefined " : aria -label = " ` 第 $ { index + 1 } 步 : $ { step } ` " @ click = "setCurrentStep(index + 1)" >
< span class = "step-marker" > { { String ( index + 1 ) . padStart ( 2 , '0' ) } } < / span >
< span class = "step-copy" > < strong class = "step-name" :title ="step" > { { step } } < / strong > < span class = "step-action" > { { activeStep === index + 1 ? "当前步骤" : "点击切换" } } < span aria -hidden = " true " > → < / span > < / span > < / span >
< / button >
< span v-if ="index < configuredSteps.length - 1" class="step-connector" aria-hidden="true" > < / span >
< / template >
< / nav >
< section class = "step-card" >
< div class = "step-title-row" >
< div >
< p v-if ="hasConfiguredSteps" class="section-eyebrow" > Step {{ String ( activeStep ) .padStart ( 2 , " 0 " ) }} < / p >
< h3 > { { activeStepName } } < / h3 >
< / div >
< / div >
< div class = "step-content" >
< slot v -if = " hasCustomContent " name = "step-content" :active-step ="activeStep" :active-step-name ="activeStepName" :has-configured-steps ="hasConfiguredSteps" / >
< template v-else >
< p v-if ="page.keyPoints?.length && isFirstStep" class="step-desc" > 请 围 绕 以 下 内 容 组 织 需 求 说 明 书 , 确 保 文 档 能 够 支 撑 设 计 、 开 发 、 测 试 和 验 收 协 作 。 < / p >
< div v-if ="page.keyPoints?.length && isFirstStep" class="key-point-grid" >
< span v-for ="item in page.keyPoints" :key ="item" > {{ item }} < / span >
< / div >
< label v-for ="field in visibleFields" :key="field.key" class="field-block" >
< span > { { field . label } } < / span >
< el -input v -model = " form [ field.key ] " : type = "field.type === 'textarea' ? 'textarea' : 'text'" : autosize = "field.type === 'textarea' ? { minRows: 6, maxRows: 11 } : undefined" :placeholder ="field.placeholder" / >
< / label >
< div v-if ="showWorkTable" class="table-panel" >
< div class = "panel-title" > < h4 > { { page . tableTitle || "实训工作表" } } < / h4 > < p > 请结合产品对象填写对应内容 , 形成可提交的实训成果 。 < / p > < / div >
< div class = "table-wrap" >
< table >
< thead > < tr > < th > 项目 < / th > < th > 说明 < / th > < th > 学生填写 < / th > < / tr > < / thead >
< tbody > < tr v-for ="row in workRows" :key="row.name"><th scope="row" > {{ row.name }} < / th > < td > { { row . description } } < / td > < td > < el -input v -model = " row.answer " type = "textarea" : autosize = "{ minRows: 3, maxRows: 6 }" placeholder = "填写本项分析内容" / > < / td > < / tr > < / tbody >
< / table >
< / div >
< / div >
< p v-if ="!visibleFields.length && !showWorkTable" class="step-desc" > 请 根 据 本 步 骤 要 求 梳 理 前 述 内 容 , 确 认 信 息 完 整 后 继 续 下 一 步 。 < / p >
< / template >
< / div >
< / section >
< div v-if ="hasConfiguredSteps" class="step-navigation" >
< button type = "button" class = "btn-nav" : disabled = "activeStep === 1" @click ="goPreviousStep" > < el -icon > < ArrowLeft / > < / e l - i c o n > 上 一 步 < / b u t t o n >
< button type = "button" class = "btn-nav btn-save" :disabled ="saving" @click ="saveCurrentProgress" > < el -icon > < CircleCheck / > < / e l - i c o n > 保 存 当 前 进 度 < / b u t t o n >
< button type = "button" class = "btn-nav btn-primary" : disabled = "activeStep === configuredSteps.length" @click ="goNextStep" > 下 一 步 < el -icon > < ArrowRight / > < / e l - i c o n > < / b u t t o n >
< / div >
< div class = "training-actions" >
< el -button class = "training-action training-action--submit" :loading ="saving" @click ="submitTask" > 提 交 任 务 < / el -button >
< el -button class = "training-action training-action--reset" :loading ="saving" @click ="resetTask" > 清 空 填 写 < / el -button >
< / div >
< / section >
< / main >
< TrainingAiSidebar :study-tip ="`当前任务建议:围绕“${taskTitle}”先明确产品对象,再按配置步骤补全实训内容。`" :progress-items ="progressItems" / >
< / div >
< / template >
< script setup >
import { computed , getCurrentInstance , onMounted , ref , useSlots } from "vue" ;
import { ArrowLeft , ArrowRight , CircleCheck , Document , Download } from "@element-plus/icons-vue" ;
import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue" ;
import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue" ;
import TrainingTaskBrief from "@/views/components/TrainingTaskBrief.vue" ;
import { getTrainingTaskByKey } from "@/api/trainingTask" ;
import { getStudentTrainingAnswer , saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer" ;
import { getStudentTrainingPage } from "@/views/training/studentTrainingPages" ;
import { parseTrainingArray } from "@/views/training/taskKeyMap" ;
const props = defineProps ( { taskKey : { type : String , required : true } , title : { type : String , default : "" } , background : { type : String , default : "" } , requirement : { type : String , default : "" } , goals : { type : Array , default : ( ) => [ ] } , badgeText : { type : String , default : "核心实训 · 产品规划与设计" } } ) ;
const emit = defineEmits ( [ "save" , "submit" , "reset" ] ) ;
const slots = useSlots ( ) ;
const { proxy } = getCurrentInstance ( ) ;
const taskConfig = ref ( null ) ;
const saving = ref ( false ) ;
const activeStep = ref ( 1 ) ;
const page = computed ( ( ) => getStudentTrainingPage ( props . taskKey ) ) ;
const form = ref ( createForm ( ) ) ;
const workRows = ref ( createRows ( ) ) ;
const hasCustomContent = computed ( ( ) => Boolean ( slots [ "step-content" ] ) ) ;
const taskTitle = computed ( ( ) => taskConfig . value ? . taskName || props . title || page . value . title ) ;
const taskBackground = computed ( ( ) => taskConfig . value ? . background || props . background || page . value . background ) ;
const taskRequirement = computed ( ( ) => taskConfig . value ? . requirements || props . requirement || page . value . task ) ;
const taskGoals = computed ( ( ) => parseTrainingArray ( taskConfig . value ? . objectives , props . goals . length ? props . goals : page . value . goals ) ) ;
const configuredSteps = computed ( ( ) => parseTrainingArray ( taskConfig . value ? . steps ) ) ;
const hasConfiguredSteps = computed ( ( ) => configuredSteps . value . length > 0 ) ;
const activeStepName = computed ( ( ) => configuredSteps . value [ activeStep . value - 1 ] || page . value . title ) ;
const fieldsPerStep = computed ( ( ) => hasConfiguredSteps . value ? Math . max ( 1 , Math . ceil ( ( page . value . fields ? . length || 0 ) / configuredSteps . value . length ) ) : page . value . fields ? . length || 0 ) ;
const visibleFields = computed ( ( ) => {
const fields = page . value . fields || [ ] ;
if ( ! hasConfiguredSteps . value ) return fields ;
const start = ( activeStep . value - 1 ) * fieldsPerStep . value ;
return fields . slice ( start , start + fieldsPerStep . value ) ;
} ) ;
const showWorkTable = computed ( ( ) => Boolean ( workRows . value . length ) && ( ! hasConfiguredSteps . value || activeStep . value === configuredSteps . value . length ) ) ;
const isFirstStep = computed ( ( ) => ! hasConfiguredSteps . value || activeStep . value === 1 ) ;
const progressItems = computed ( ( ) => configuredSteps . value . map ( ( text , index ) => ( { text , status : index + 1 < activeStep . value ? "done" : index + 1 === activeStep . value ? "doing" : "" } ) ) ) ;
function createForm ( ) { return Object . fromEntries ( ( page . value . fields || [ ] ) . map ( ( field ) => [ field . key , "" ] ) ) ; }
function createRows ( ) { return ( page . value . tableRows || [ ] ) . map ( ( [ name , description ] ) => ( { name , description , answer : "" } ) ) ; }
function normalizeActiveStep ( value ) { if ( ! configuredSteps . value . length ) return 1 ; return Math . min ( Math . max ( Number ( value ) || 1 , 1 ) , configuredSteps . value . length ) ; }
function buildProgress ( ) { return { activeStep : activeStep . value , form : form . value , workRows : workRows . value , updatedAt : new Date ( ) . toISOString ( ) } ; }
function applyProgress ( value ) { if ( ! value || typeof value !== "object" ) return ; activeStep . value = normalizeActiveStep ( value . activeStep ) ; form . value = { ... createForm ( ) , ... ( value . form || { } ) } ; workRows . value = createRows ( ) . map ( ( row , index ) => ( { ... row , answer : value . workRows ? . [ index ] ? . answer || "" } ) ) ; }
onMounted ( async ( ) => { await loadTaskConfig ( ) ; await loadSavedProgress ( ) ; } ) ;
async function loadTaskConfig ( ) { try { const res = await getTrainingTaskByKey ( props . taskKey ) ; taskConfig . value = res ? . data || null ; activeStep . value = normalizeActiveStep ( activeStep . value ) ; } catch ( error ) { taskConfig . value = null ; activeStep . value = 1 ; } }
async function loadSavedProgress ( ) { try { const res = await getStudentTrainingAnswer ( props . taskKey ) ; const raw = res ? . data ? . step1Answer ; if ( raw ) applyProgress ( JSON . parse ( raw ) ) ; } catch ( error ) { /* The training task remains available without previous progress. */ } }
async function persistProgress ( saveAction = "SAVE" , silent = false ) { await saveStudentTrainingAnswer ( props . taskKey , { saveAction , currentStep : activeStep . value , step1Answer : JSON . stringify ( buildProgress ( ) ) } ) ; if ( ! silent ) proxy ? . $modal ? . msgSuccess ( saveAction === "SUBMIT" ? "提交成功" : "保存成功" ) ; }
function setCurrentStep ( step ) { activeStep . value = normalizeActiveStep ( step ) ; persistProgress ( "SAVE" , true ) . catch ( ( ) => { } ) ; }
function goPreviousStep ( ) { setCurrentStep ( activeStep . value - 1 ) ; }
function goNextStep ( ) { setCurrentStep ( activeStep . value + 1 ) ; }
async function saveCurrentProgress ( ) { if ( saving . value ) return ; saving . value = true ; try { if ( hasCustomContent . value ) { emit ( "save" ) ; await persistProgress ( "SAVE" , true ) ; } else { await persistProgress ( ) ; } } finally { saving . value = false ; } }
async function submitTask ( ) { if ( saving . value ) return ; saving . value = true ; try { if ( hasCustomContent . value ) { emit ( "submit" ) ; await persistProgress ( "SAVE" , true ) ; } else { await persistProgress ( "SUBMIT" ) ; } } finally { saving . value = false ; } }
async function resetTask ( ) { if ( saving . value ) return ; saving . value = true ; try { if ( hasCustomContent . value ) emit ( "reset" ) ; form . value = createForm ( ) ; workRows . value = createRows ( ) ; activeStep . value = 1 ; await persistProgress ( "RESET" , hasCustomContent . value ) ; if ( ! hasCustomContent . value ) proxy ? . $modal ? . msgSuccess ( "清空填写成功" ) ; } finally { saving . value = false ; } }
function exportOutcome ( ) { const blob = new Blob ( [ JSON . stringify ( buildProgress ( ) , null , 2 ) ] , { type : "application/json;charset=utf-8" } ) ; const url = URL . createObjectURL ( blob ) ; const link = document . createElement ( "a" ) ; link . href = url ; link . download = ` ${ taskTitle . value } -实训成果.json ` ; link . click ( ) ; URL . revokeObjectURL ( url ) ; }
< / script >
< style scoped lang = "scss" >
. product - standard - page { display : grid ; grid - template - columns : minmax ( 720 px , 1 fr ) 340 px ; gap : 24 px ; min - height : calc ( 100 vh - 76 px ) ; padding : 24 px 24 px 32 px ; color : # eef5ff ; background : # 06111 d ; } . training - core { min - width : 0 ; padding : 28 px ; border : 1 px solid rgba ( 0 , 180 , 255 , .25 ) ; border - radius : 36 px ; background : rgba ( 8 , 18 , 28 , .6 ) ; box - shadow : inset 0 1 px 0 rgba ( 169 , 229 , 255 , .08 ) , 0 22 px 48 px rgba ( 0 , 0 , 0 , .22 ) ; } . task - header { margin - bottom : 24 px ; } . task - badge { display : inline - flex ; align - items : center ; gap : 8 px ; min - height : 28 px ; padding : 4 px 14 px ; border : 1 px solid rgba ( 99 , 217 , 255 , .55 ) ; border - radius : 999 px ; color : # dff5ff ; background : rgba ( 17 , 126 , 178 , .36 ) ; font - size : 12 px ; font - weight : 700 ; } . task - title { margin : 12 px 0 0 ; color : # fff ; font - size : 30 px ; font - weight : 900 ; line - height : 1.3 ; }
. task - card , . step - card { border : 1 px solid rgba ( 82 , 174 , 226 , .42 ) ; border - radius : 26 px ; background : rgba ( 0 , 25 , 42 , .64 ) ; } . task - card { margin - top : 28 px ; padding : 18 px 22 px 22 px ; } . workbench - head { display : flex ; align - items : flex - start ; justify - content : space - between ; gap : 18 px ; margin - bottom : 22 px ; padding - bottom : 18 px ; border - bottom : 1 px solid rgba ( 0 , 180 , 255 , .2 ) ; } . section - eyebrow { margin : 0 ; color : # 71 dfff ; font - size : 12 px ; font - weight : 800 ; line - height : 1.4 ; } . workbench - head h3 { margin : 4 px 0 8 px ; color : # fff ; font - size : 26 px ; font - weight : 800 ; line - height : 1.3 ; } . workbench - description , . step - desc { margin : 0 ; color : # d7ecff ; font - size : 15 px ; line - height : 1.8 ; } . workbench - actions { display : flex ; flex - wrap : wrap ; justify - content : flex - end ; margin - left : auto ; gap : 10 px ; flex - shrink : 0 ; } . outline - action , . btn - nav { display : inline - flex ; align - items : center ; justify - content : center ; gap : 8 px ; min - height : 40 px ; padding : 0 18 px ; border : 1 px solid rgba ( 99 , 217 , 255 , .55 ) ; border - radius : 999 px ; color : # e9fbff ; background : rgba ( 17 , 126 , 178 , .36 ) ; font : inherit ; font - size : 14 px ; font - weight : 800 ; cursor : pointer ; } . outline - action : hover , . outline - action : focus - visible , . btn - nav : hover : not ( : disabled ) , . btn - nav : focus - visible { border - color : rgba ( 116 , 235 , 255 , .85 ) ; background : rgba ( 18 , 146 , 204 , .5 ) ; outline : none ; transform : translateY ( - 1 px ) ; }
. step - indicator { display : flex ; align - items : center ; gap : 12 px ; width : calc ( 100 % + 24 px ) ; min - height : 0 ; margin : 0 - 12 px 32 px ; padding : 18 px 22 px ; overflow - x : auto ; border : 1 px solid rgba ( 0 , 180 , 255 , .3 ) ; border - radius : 18 px ; background : linear - gradient ( 110 deg , rgba ( 4 , 39 , 58 , .9 ) , rgba ( 2 , 20 , 33 , .94 ) ) ; box - shadow : inset 0 1 px 0 rgba ( 169 , 229 , 255 , .08 ) ; } . step - tab { display : flex ; flex : 1 0 145 px ; align - items : center ; min - height : 66 px ; padding : 8 px 10 px ; border : 1 px solid rgba ( 68 , 141 , 177 , .35 ) ; border - radius : 8 px ; color : # 9 ab3d0 ; background : rgba ( 3 , 27 , 43 , .52 ) ; font : inherit ; font - size : 14 px ; font - weight : 800 ; text - align : left ; cursor : pointer ; transition : transform .2 s , color .2 s , background .2 s , border - color .2 s , box - shadow .2 s ; } . step - tab : hover , . step - tab : focus - visible { transform : translateY ( - 2 px ) ; border - color : rgba ( 89 , 223 , 255 , .82 ) ; color : # fff ; background : rgba ( 8 , 92 , 131 , .52 ) ; box - shadow : 0 8 px 18 px rgba ( 0 , 0 , 0 , .2 ) ; outline : none ; } . step - tab . active { border - color : rgba ( 89 , 226 , 255 , .85 ) ; color : # fff ; background : linear - gradient ( 115 deg , rgba ( 4 , 109 , 155 , .78 ) , rgba ( 5 , 62 , 102 , .64 ) ) ; box - shadow : 0 0 18 px rgba ( 24 , 183 , 235 , .22 ) , inset 0 1 px 0 rgba ( 176 , 246 , 255 , .2 ) ; } . step - marker { display : inline - flex ; flex : 0 0 38 px ; align - items : center ; justify - content : center ; width : 38 px ; height : 38 px ; border : 1 px solid rgba ( 101 , 176 , 208 , .6 ) ; border - radius : 50 % ; color : # a6c4d9 ; background : rgba ( 3 , 25 , 40 , .8 ) ; box - shadow : inset 0 0 12 px rgba ( 38 , 174 , 233 , .08 ) ; font - size : 12 px ; font - weight : 900 ; letter - spacing : .04 em ; } . step - copy { display : grid ; min - width : 0 ; gap : 4 px ; margin - left : 10 px ; } . step - copy strong { display : - webkit - box ; overflow : hidden ; color : inherit ; font - size : 14 px ; line - height : 1.25 ; - webkit - box - orient : vertical ; - webkit - line - clamp : 2 ; } . step - copy span { color : # 66 b6d4 ; font - size : 10 px ; font - weight : 800 ; letter - spacing : .06 em ; } . step - tab . active . step - marker { border - color : # 72 e8ff ; color : # fff ; background : linear - gradient ( 135 deg , # 08 b7e8 , # 1375 d0 ) ; box - shadow : 0 0 0 5 px rgba ( 25 , 179 , 237 , .14 ) , 0 0 18 px rgba ( 46 , 208 , 255 , .58 ) ; } . step - tab . complete { color : # dffbff ; } . step - tab . complete . step - marker { border - color : rgba ( 84 , 230 , 221 , .85 ) ; color : # 062 b37 ; background : # 6 ef0e1 ; box - shadow : 0 0 12 px rgba ( 79 , 231 , 218 , .35 ) ; } . step - connector { position : relative ; flex : 0 1 64 px ; min - width : 22 px ; height : 2 px ; margin : 0 14 px ; color : transparent ; background : rgba ( 98 , 169 , 201 , .34 ) ; } . step - connector : : after { position : absolute ; top : 50 % ; right : - 1 px ; width : 6 px ; height : 6 px ; border - top : 1 px solid # 73 b1ce ; border - right : 1 px solid # 73 b1ce ; content : "" ; transform : translateY ( - 50 % ) rotate ( 45 deg ) ; } . step - connector . complete { background : linear - gradient ( 90 deg , # 6 ef0e1 , # 1 cb8e9 ) ; box - shadow : 0 0 9 px rgba ( 53 , 218 , 235 , .46 ) ; } . step - connector . complete : : after { border - color : # 63 e6f1 ; } . step - card { margin - top : 32 px ; padding : 18 px 22 px 22 px ; } . step - indicator + . step - card , . workbench - head + . step - card { margin - top : 0 ; } . step - title - row { margin - bottom : 14 px ; } . step - title - row h3 { margin : 4 px 0 0 ; color : # fff ; font - size : 22 px ; font - weight : 900 ; line - height : 1.3 ; } . key - point - grid { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; gap : 10 px ; margin : 18 px 0 ; } . key - point - grid span , . table - panel { border : 1 px solid rgba ( 0 , 180 , 255 , .22 ) ; border - radius : 16 px ; background : rgba ( 1 , 14 , 26 , .72 ) ; } . key - point - grid span { padding : 12 px 14 px ; color : # d7ecff ; font - size : 14 px ; font - weight : 700 ; line - height : 1.55 ; } . field - block { display : block ; margin : 16 px 0 0 ; } . field - block span { display : block ; margin - bottom : 8 px ; color : # dff5ff ; font - size : 14 px ; font - weight : 800 ; line - height : 1.6 ; } . field - block : deep ( . el - input _ _wrapper ) , . field - block : deep ( . el - textarea _ _inner ) , . table - wrap : deep ( . el - textarea _ _inner ) { border : 1 px solid rgba ( 45 , 95 , 126 , .95 ) ; border - radius : 14 px ; box - shadow : none ; background : # 0 f1a24 ; } . field - block : deep ( . el - input _ _wrapper ) { min - height : 42 px ; padding : 0 14 px ; } . field - block : deep ( . el - input _ _inner ) , . field - block : deep ( . el - textarea _ _inner ) , . table - wrap : deep ( . el - textarea _ _inner ) { color : # eef5ff ; font - size : 14 px ; } . field - block : deep ( . el - textarea _ _inner ) , . table - wrap : deep ( . el - textarea _ _inner ) { min - height : 110 px ! important ; padding : 12 px 14 px ; line - height : 1.65 ; } . table - panel { margin - top : 22 px ; padding : 16 px ; } . panel - title { m
@ media ( max - width : 1100 px ) { . product - standard - page { grid - template - columns : 1 fr ; } } @ media ( max - width : 900 px ) { . product - standard - page { padding : 14 px ; } . training - core { padding : 18 px ; border - radius : 24 px ; } . workbench - head , . workbench - actions , . step - navigation { flex - direction : column ; align - items : stretch ; } . workbench - actions , . outline - action , . btn - nav , . training - actions : deep ( . training - action ) { width : 100 % ; } . step - indicator { align - items : stretch ; flex - direction : column ; width : 100 % ; margin - right : 0 ; margin - left : 0 ; overflow : visible ; border - radius : 20 px ; } . step - tab { flex : 0 0 auto ; width : 100 % ; } . step - connector { flex : 0 0 22 px ; width : 2 px ; height : 22 px ; margin - left : 29 px ; text - align : center ; writing - mode : vertical - rl ; } . key - point - grid { grid - template - columns : 1 fr ; } }
/* Standard page step navigation */
. step - indicator { position : relative ; display : flex ; align - items : center ; gap : 0 ; width : calc ( 100 % + 24 px ) ; margin : 0 - 12 px 32 px ; padding : 18 px 22 px ; overflow - x : auto ; border : 1 px solid rgba ( 0 , 180 , 255 , .3 ) ; border - radius : 18 px ; background : linear - gradient ( 90 deg , rgba ( 6 , 42 , 61 , .58 ) , rgba ( 1 , 18 , 31 , .42 ) ) , rgba ( 0 , 0 , 0 , .3 ) ; box - shadow : inset 0 1 px 0 rgba ( 145 , 233 , 255 , .08 ) ; scrollbar - width : thin ; }
. step - tab { display : flex ; flex : 1 0 145 px ; align - items : center ; min - width : 0 ; min - height : 66 px ; padding : 8 px 10 px ; border : 1 px solid rgba ( 68 , 141 , 177 , .35 ) ; border - radius : 8 px ; color : # 9 ab3d0 ; background : rgba ( 3 , 27 , 43 , .52 ) ; box - shadow : inset 0 1 px 0 rgba ( 135 , 221 , 255 , .04 ) ; font : inherit ; font - size : 14 px ; font - weight : 800 ; text - align : left ; cursor : pointer ; transition : transform .2 s ease , color .2 s ease , border - color .2 s ease , background .2 s ease , box - shadow .2 s ease ; }
. step - tab : hover { transform : translateY ( - 2 px ) ; border - color : rgba ( 89 , 223 , 255 , .82 ) ; color : # fff ; background : rgba ( 8 , 92 , 131 , .52 ) ; box - shadow : 0 7 px 18 px rgba ( 0 , 0 , 0 , .24 ) , inset 0 1 px 0 rgba ( 152 , 239 , 255 , .16 ) ; }
. step - tab : hover . step - action { color : # 9 bf4ff ; }
. step - tab : focus - visible { outline : 2 px solid rgba ( 92 , 224 , 255 , .7 ) ; outline - offset : 2 px ; }
. step - tab . active { border - color : rgba ( 89 , 226 , 255 , 0.85 ) ; color : # fff ; background : linear - gradient ( 115 deg , rgba ( 4 , 109 , 155 , 0.78 ) , rgba ( 5 , 62 , 102 , 0.64 ) ) ; box - shadow : 0 0 18 px rgba ( 24 , 183 , 235 , .22 ) , inset 0 1 px 0 rgba ( 176 , 246 , 255 , .2 ) ; }
. step - marker { display : inline - flex ; flex : 0 0 38 px ; align - items : center ; justify - content : center ; width : 38 px ; height : 38 px ; border : 1 px solid rgba ( 101 , 176 , 208 , .6 ) ; border - radius : 50 % ; color : # a6c4d9 ; background : rgba ( 3 , 25 , 40 , .8 ) ; box - shadow : inset 0 0 12 px rgba ( 38 , 174 , 233 , .08 ) ; font - size : 12 px ; font - weight : 900 ; letter - spacing : .04 em ; }
. step - copy { display : grid ; min - width : 0 ; gap : 4 px ; margin - left : 10 px ; }
. step - name { display : - webkit - box ; overflow : hidden ; color : inherit ; font - size : 14 px ; font - weight : 800 ; line - height : 1.25 ; text - overflow : ellipsis ; - webkit - box - orient : vertical ; - webkit - line - clamp : 2 ; }
. step - action { color : # 66 b6d4 ; font - size : 10 px ; font - weight : 800 ; letter - spacing : .06 em ; line - height : 1 ; }
. step - tab . active . step - marker { border - color : # 72 e8ff ; color : # fff ; background : linear - gradient ( 135 deg , # 08 b7e8 , # 1375 d0 ) ; box - shadow : 0 0 0 5 px rgba ( 25 , 179 , 237 , .14 ) , 0 0 18 px rgba ( 46 , 208 , 255 , .58 ) ; }
. step - tab . complete . step - marker { border - color : rgba ( 84 , 230 , 221 , .85 ) ; color : # 062 b37 ; background : # 6 ef0e1 ; box - shadow : 0 0 12 px rgba ( 79 , 231 , 218 , .35 ) ; }
. step - connector { position : relative ; flex : 0 1 40 px ; min - width : 18 px ; height : 2 px ; margin : 0 8 px ; overflow : visible ; background : rgba ( 98 , 169 , 201 , .34 ) ; }
. step - connector : : after { position : absolute ; top : 50 % ; right : - 1 px ; width : 6 px ; height : 6 px ; border - top : 1 px solid # 73 b1ce ; border - right : 1 px solid # 73 b1ce ; content : "" ; transform : translateY ( - 50 % ) rotate ( 45 deg ) ; }
. step - tab . complete + . step - connector { background : linear - gradient ( 90 deg , # 6 ef0e1 , # 1 cb8e9 ) ; box - shadow : 0 0 9 px rgba ( 53 , 218 , 235 , .46 ) ; }
. step - tab . complete + . step - connector : : after { border - color : # 63 e6f1 ; }
@ media ( max - width : 900 px ) { . step - indicator { align - items : stretch ; flex - direction : column ; width : 100 % ; margin - right : 0 ; margin - left : 0 ; overflow : visible ; border - radius : 20 px ; } . step - tab { flex : 0 0 auto ; width : 100 % ; } . step - connector { flex : 0 0 20 px ; min - width : 0 ; width : 2 px ; height : 20 px ; margin - left : 29 px ; } }
. workbench - head -- actions - only { justify - content : flex - end ; margin - bottom : 18 px ; padding - bottom : 0 ; border - bottom : 0 ; }
< / style >