@@ -0,0 +1,853 @@
< template >
< div class = "complexity-demo" >
< header class = "demo-header" >
< div class = "demo-icon" > 📊 < / div >
< div >
< h1 class = "demo-title" > 时间复杂度动态演示 < / h1 >
< p class = "demo-subtitle" > 通过动画和计数直观理解不同算法的时间复杂度 < / p >
< / div >
< / header >
<!-- 控制面板 -- >
< section class = "ctrl-section" >
< div class = "ctrl-row" >
< label class = "ctrl-label" > 输入规模 n : < / label >
< input type = "range" min = "1" max = "100" v -model .number = " n " class = "n-slider" / >
< span class = "n-value" > { { n } } < / span >
< / div >
< div class = "ctrl-row" >
< label class = "ctrl-label" > 选择算法 : < / label >
< select v-model = "selectedAlgo" class="algo-select" >
< option v-for = "a in algorithms" :key="a.id" :value="a.id" >
{{ a.label }} — {{ a.complexity }}
< / option >
< / select >
< / div >
< div class = "ctrl-actions" >
< button class = "ctrl-btn" @click ="run" :disabled = "running" > ▶ 运行演示 < / button >
< button class = "ctrl-btn" @click ="stepForward" : disabled = "running || stepIndex >= totalSteps - 1" > 下一步 < / button >
< button class = "ctrl-btn" @click ="resetDemo" > ⟳ 重置 < / button >
< / div >
< / section >
< ! - - 算法说明 - - >
< section class = "info-section" v-if = "currentAlgo" >
< div class = "algo-info-card" >
< h3 > { { currentAlgo . label } } < / h3 >
< div class = "complexity-badges" >
< span class = "badge time-badge" > 时间复杂度 : { { currentAlgo . complexity } } < / span >
< span class = "badge space-badge" > 空间复杂度 : { { currentAlgo . space } } < / span >
< / div >
< p > { { currentAlgo . description } } < / p >
< div class = "op-count" v-if = "opCount !== null" >
操作次数 : < strong > { { opCount } } < / strong >
< span class = "op-formula" > ≈ { { currentAlgo . formula . replace ( 'n' , n ) } } < / span >
< / div >
< / div >
< / section >
<!-- 动态演示区域 -- >
< section class = "demo-section" v-if = "currentAlgo" >
< div class = "visualization-area" >
<!-- 数组可视化 -- >
< div class = "array-display" v-if = "arrayData.length" >
< div class = "array-label" > 数据状态 : < / div >
< div class = "array-bars" >
< div
v-for = "(val, idx) in arrayData"
:key = "idx"
class = "array-bar-wrapper"
: style = "{ height: barHeight(val) + 'px' }"
>
< div
class = "array-bar"
:class = "barClass(idx)"
: style = "{ height: '100%' }"
>
< span class = "bar-val" > { { val } } < / span >
< / div >
< / div >
< / div >
< / div >
<!-- 日志 / 步骤说明 -- >
< div class = "step-log" v-if = "steps.length" >
< div class = "log-header" > 📝 执行步骤 < / div >
< div class = "log-body" ref = "logRef" >
< div
v-for = "(step, idx) in visibleSteps"
:key = "idx"
class = "log-line"
: class = "{ current: idx === stepIndex, done: idx < stepIndex }"
>
< span class = "log-step-num" > { { idx + 1 } } < / span >
< span class = "log-text" > { { step } } < / span >
< / div >
< / div >
< / div >
<!-- 复杂度对比图表 -- >
< div class = "chart-section" v-if = "showChart" >
< div class = "chart-header" > 📈 复杂度增长对比 < / div >
< div class = "chart-canvas" >
< div class = "chart-row" v-for = "item in chartData" :key="item.label" >
< div class = "chart-label" > { { item . label } } < / div >
< div class = "chart-bar-track" >
< div
class = "chart-bar"
: style = "{ width: item.percent + '%', backgroundColor: item.color }"
>
< span class = "chart-bar-text" v-if = "item.percent > 15" > {{ item.count }} < / span >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / section >
< div class = "nav-back" >
< router-link to = "/chapter/ch1" class = "back-link" > ← 返回第一章 < / router-link >
< / div >
< / div >
< / template >
< script setup >
import { ref , computed , watch } from 'vue'
const n = ref ( 10 )
const selectedAlgo = ref ( 'linear' )
const running = ref ( false )
const opCount = ref ( null )
const steps = ref ( [ ] )
const stepIndex = ref ( - 1 )
const arrayData = ref ( [ ] )
const highlights = ref ( { } )
const showChart = ref ( false )
const logRef = ref ( null )
const algorithms = [
{
id : 'constant' ,
label : '常数阶 — 数组随机访问' ,
complexity : 'O(1)' ,
space : 'O(1)' ,
formula : '1' ,
description : '直接通过索引访问数组元素,无论数组多大,执行时间恒定。这是最优的时间复杂度。'
} ,
{
id : 'logarithmic' ,
label : '对数阶 — 二分查找' ,
complexity : 'O(log n)' ,
space : 'O(1)' ,
formula : '⌈log₂(n)⌉ = ' ,
description : '每次将搜索范围缩小一半。即使 n 很大,需要的步数也增长很慢。例如 n=1024 时只需 10 步。'
} ,
{
id : 'linear' ,
label : '线性阶 — 线性查找' ,
complexity : 'O(n)' ,
space : 'O(1)' ,
formula : 'n = ' ,
description : '顺序遍历所有元素。执行时间与输入规模成正比,是最直观的算法复杂度。'
} ,
{
id : 'nlogn' ,
label : '线性对数阶 — 归并排序分解' ,
complexity : 'O(n log n)' ,
space : 'O(n)' ,
formula : 'n·⌈log₂(n)⌉ = ' ,
description : '常见的"分治"算法复杂度。每一层处理 n 个元素,共有 log n 层。'
} ,
{
id : 'quadratic' ,
label : '平方阶 — 冒泡排序' ,
complexity : 'O(n²)' ,
space : 'O(1)' ,
formula : 'n(n-1)/2 = ' ,
description : '双重循环嵌套,最常见于简单排序算法。当 n 翻倍时,运行时间变为原来的 4 倍。'
} ,
{
id : 'exponential' ,
label : '指数阶 — 递归斐波那契' ,
complexity : 'O(2ⁿ)' ,
space : 'O(n)' ,
formula : '2ⁿ = ' ,
description : '递归树呈指数增长。n 稍大时就会爆炸,因此实用中必须优化(如动态规划)。'
}
]
const currentAlgo = computed ( ( ) => algorithms . find ( a => a . id === selectedAlgo . value ) )
const totalSteps = computed ( ( ) => steps . value . length )
const visibleSteps = computed ( ( ) => {
// 最多显示最近 50 步
if ( stepIndex . value < 50 ) return steps . value . slice ( 0 , Math . max ( stepIndex . value + 1 , 0 ) )
return steps . value . slice ( stepIndex . value - 49 , stepIndex . value + 1 )
} )
const maxVal = computed ( ( ) => {
if ( ! arrayData . value . length ) return 1
return Math . max ( ... arrayData . value , 1 )
} )
function barHeight ( val ) {
return Math . max ( 20 , ( val / maxVal . value ) * 200 )
}
function barClass ( idx ) {
const h = highlights . value || { }
const classes = [ ]
if ( h . sorted ? . includes ( idx ) ) classes . push ( 'bar-sorted' )
if ( h . comparing ? . includes ( idx ) ) classes . push ( 'bar-comparing' )
if ( h . current === idx ) classes . push ( 'bar-current' )
return classes
}
function resetDemo ( ) {
running . value = false
opCount . value = null
steps . value = [ ]
stepIndex . value = - 1
arrayData . value = [ ]
highlights . value = { }
showChart . value = false
}
function sleep ( ms ) {
return new Promise ( r => setTimeout ( r , ms ) )
}
async function run ( ) {
if ( running . value ) return
running . value = true
showChart . value = false
steps . value = [ ]
stepIndex . value = - 1
opCount . value = 0
highlights . value = { }
const algo = selectedAlgo . value
switch ( algo ) {
case 'constant' : await runConstant ( ) ; break
case 'logarithmic' : await runLogarithmic ( ) ; break
case 'linear' : await runLinear ( ) ; break
case 'nlogn' : await runNlogn ( ) ; break
case 'quadratic' : await runQuadratic ( ) ; break
case 'exponential' : await runExponential ( ) ; break
}
running . value = false
showChart . value = true
scrollToLogBottom ( )
}
function scrollToLogBottom ( ) {
setTimeout ( ( ) => {
if ( logRef . value ) {
logRef . value . scrollTop = logRef . value . scrollHeight
}
} , 50 )
}
function addStep ( msg ) {
steps . value . push ( msg )
stepIndex . value = steps . value . length - 1
}
// O(1) — 数组随机访问
async function runConstant ( ) {
const arr = Array . from ( { length : n . value } , ( ) => Math . floor ( Math . random ( ) * 100 ) + 1 )
arrayData . value = arr
addStep ( ` 生成包含 ${ n . value } 个元素的数组 ` )
await sleep ( 300 )
const idx = Math . floor ( Math . random ( ) * n . value )
highlights . value = { current : idx }
addStep ( ` 直接通过索引 arr[ ${ idx } ] 访问元素,执行 1 次操作 ` )
await sleep ( 600 )
opCount . value = 1
addStep ( ` ✅ 无论 n 多大,始终只需 1 次操作 → O(1) ` )
}
// O(log n) — 二分查找
async function runLogarithmic ( ) {
const arr = Array . from ( { length : n . value } , ( _ , i ) => i + 1 )
arrayData . value = arr
const target = Math . floor ( Math . random ( ) * n . value ) + 1
addStep ( ` 有序数组 [1.. ${ n . value } ],查找目标值 ${ target } ` )
await sleep ( 400 )
let left = 0 , right = n . value - 1
let count = 0
while ( left <= right ) {
count ++
const mid = Math . floor ( ( left + right ) / 2 )
highlights . value = { comparing : [ left , right ] , current : mid }
addStep ( ` 第 ${ count } 次:区间 [ ${ left + 1 } , ${ right + 1 } ],中间索引 ${ mid + 1 } (值= ${ arr [ mid ] } ) ` )
await sleep ( 500 )
if ( arr [ mid ] === target ) {
highlights . value = { sorted : [ mid ] , current : mid }
addStep ( ` ✅ 找到目标 ${ target } 在位置 ${ mid + 1 } ,共 ${ count } 次比较 ` )
opCount . value = count
return
} else if ( arr [ mid ] < target ) {
left = mid + 1
addStep ( ` ${ arr [ mid ] } < ${ target } ,搜右侧 ` )
} else {
right = mid - 1
addStep ( ` ${ arr [ mid ] } > ${ target } ,搜左侧 ` )
}
await sleep ( 400 )
}
addStep ( ` ❌ 未找到,共 ${ count } 次比较 ` )
opCount . value = count
}
// O(n) — 线性查找
async function runLinear ( ) {
const arr = Array . from ( { length : n . value } , ( ) => Math . floor ( Math . random ( ) * 100 ) )
arrayData . value = arr
const target = Math . floor ( Math . random ( ) * 100 )
addStep ( ` 数组长度= ${ n . value } ,查找目标值 ${ target } ` )
await sleep ( 400 )
let count = 0
for ( let i = 0 ; i < n . value ; i ++ ) {
count ++
highlights . value = { current : i }
addStep ( ` 第 ${ count } 次:比较 arr[ ${ i } ] = ${ arr [ i ] } ${ arr [ i ] === target ? '✅ 找到' : '' } ` )
await sleep ( 200 )
if ( arr [ i ] === target ) {
highlights . value = { sorted : [ i ] }
addStep ( ` ✅ 找到目标 ${ target } 在位置 ${ i + 1 } ,共 ${ count } 次比较 ` )
opCount . value = count
return
}
}
addStep ( ` ❌ 未找到,共 ${ count } 次比较 ` )
opCount . value = count
}
// O(n log n) — 归并排序分解过程
async function runNlogn ( ) {
const size = Math . min ( n . value , 32 )
const arr = Array . from ( { length : size } , ( ) => Math . floor ( Math . random ( ) * 100 ) )
arrayData . value = arr
addStep ( ` 数组 [ ${ arr . join ( ', ' ) } ] (n= ${ size } ) ` )
await sleep ( 400 )
let count = 0
async function mergeSort ( arr , left , right , depth = 0 ) {
if ( left >= right ) return
const mid = Math . floor ( ( left + right ) / 2 )
count ++
highlights . value = { range : [ left , right ] , current : mid }
addStep ( ` 分解 [ ${ left + 1 } .. ${ right + 1 } ] → 左[ ${ left + 1 } .. ${ mid + 1 } ] 右[ ${ mid + 2 } .. ${ right + 1 } ] ` )
await sleep ( 300 )
await mergeSort ( arr , left , mid , depth + 1 )
await mergeSort ( arr , mid + 1 , right , depth + 1 )
// 合并
const temp = [ ]
let i = left , j = mid + 1
while ( i <= mid && j <= right ) {
count ++
if ( arr [ i ] <= arr [ j ] ) temp . push ( arr [ i ++ ] )
else temp . push ( arr [ j ++ ] )
}
while ( i <= mid ) { temp . push ( arr [ i ++ ] ) ; count ++ }
while ( j <= right ) { temp . push ( arr [ j ++ ] ) ; count ++ }
for ( let k = left ; k <= right ; k ++ ) {
arr [ k ] = temp [ k - left ]
highlights . value = { sorted : Array . from ( { length : right + 1 } , ( _ , i ) => i ) . slice ( 0 , k + 1 ) }
}
addStep ( ` 合并完成 [ ${ left + 1 } .. ${ right + 1 } ] ` )
await sleep ( 300 )
}
await mergeSort ( arr , 0 , size - 1 )
arrayData . value = [ ... arr ]
const nlogn = Math . ceil ( size * Math . log2 ( size ) )
opCount . value = count
addStep ( ` ✅ 归并排序完成,总操作约 ${ count } 次,理论值 O(n·log₂n) ≈ ${ nlogn } ` )
}
// O(n²) — 冒泡排序
async function runQuadratic ( ) {
const size = Math . min ( n . value , 20 )
const arr = Array . from ( { length : size } , ( ) => Math . floor ( Math . random ( ) * 100 ) )
arrayData . value = arr
addStep ( ` 初始数组 [ ${ arr . join ( ', ' ) } ] (n= ${ size } ) ` )
await sleep ( 400 )
let count = 0
const len = arr . length
for ( let i = 0 ; i < len - 1 ; i ++ ) {
for ( let j = 0 ; j < len - 1 - i ; j ++ ) {
count ++
highlights . value = { comparing : [ j , j + 1 ] }
addStep ( ` 比较 arr[ ${ j } ]= ${ arr [ j ] } 和 arr[ ${ j + 1 } ]= ${ arr [ j + 1 ] } ` )
await sleep ( 200 )
if ( arr [ j ] > arr [ j + 1 ] ) {
[ arr [ j ] , arr [ j + 1 ] ] = [ arr [ j + 1 ] , arr [ j ] ]
highlights . value = { swapping : [ j , j + 1 ] }
addStep ( ` 交换 → [ ${ arr . join ( ', ' ) } ] ` )
await sleep ( 200 )
}
}
highlights . value = { sorted : Array . from ( { length : len } , ( _ , k ) => k ) . slice ( len - 1 - i ) }
}
arrayData . value = [ ... arr ]
const expected = size * ( size - 1 ) / 2
opCount . value = count
addStep ( ` ✅ 冒泡排序完成,共 ${ count } 次比较,理论值 n(n-1)/2 = ${ expected } ` )
}
// O(2ⁿ) — 递归斐波那契
async function runExponential ( ) {
const size = Math . min ( n . value , 10 ) // 限制,防止浏览器卡死
addStep ( ` 递归计算 Fibonacci( ${ size } ),展示递归调用树 ` )
await sleep ( 400 )
let count = 0
let callStack = [ ]
function fib ( k ) {
count ++
if ( k <= 1 ) {
addStep ( ` fib( ${ k } ) = ${ k } (基准情况) ` )
return k
}
addStep ( ` fib( ${ k } ) = fib( ${ k - 1 } ) + fib( ${ k - 2 } ) ` )
const a = fib ( k - 1 )
const b = fib ( k - 2 )
const result = a + b
addStep ( ` fib( ${ k } ) = ${ a } + ${ b } = ${ result } ` )
return result
}
const result = fib ( size )
const expected = Math . pow ( 2 , size )
opCount . value = count
addStep ( ` ✅ Fibonacci( ${ size } ) = ${ result } ,递归调用 ${ count } 次,理论 O(2ⁿ) ≈ ${ expected } ` )
}
// 单步前进
function stepForward ( ) {
if ( stepIndex . value < totalSteps . value - 1 ) {
stepIndex . value ++
scrollToLogBottom ( )
}
}
// 对比图表数据
const chartData = computed ( ( ) => {
const currentN = n . value
const items = [
{ label : 'O(1)' , count : 1 , color : '#22c55e' } ,
{ label : 'O(log n)' , count : Math . ceil ( Math . log2 ( currentN ) ) , color : '#3b82f6' } ,
{ label : 'O(n)' , count : currentN , color : '#f59e0b' } ,
{ label : 'O(n log n)' , count : Math . ceil ( currentN * Math . log2 ( currentN ) ) , color : '#f97316' } ,
{ label : 'O(n²)' , count : currentN * currentN , color : '#ef4444' } ,
{ label : 'O(2ⁿ)' , count : Math . min ( Math . pow ( 2 , currentN ) , 999999 ) , color : '#dc2626' }
]
const maxCount = Math . max ( ... items . map ( i => i . count ) , 1 )
return items . map ( item => ( {
... item ,
percent : Math . min ( ( item . count / maxCount ) * 100 , 100 )
} ) )
} )
< / script >
< style scoped >
. complexity - demo {
max - width : 1000 px ;
margin : 0 auto ;
padding : 0 24 px ;
}
. demo - header {
display : flex ;
gap : 16 px ;
align - items : flex - start ;
padding : 28 px 0 20 px ;
border - bottom : 1 px solid var ( -- border - color ) ;
margin - bottom : 24 px ;
}
. demo - icon {
font - size : 40 px ;
flex - shrink : 0 ;
}
. demo - title {
font - size : 26 px ;
font - weight : 800 ;
color : var ( -- text - primary ) ;
margin - bottom : 4 px ;
}
. demo - subtitle {
font - size : 14 px ;
color : var ( -- text - secondary ) ;
}
/* 控制面板 */
. ctrl - section {
background : var ( -- card - bg ) ;
border : 1 px solid var ( -- border - color ) ;
border - radius : 12 px ;
padding : 20 px ;
margin - bottom : 24 px ;
}
. ctrl - row {
display : flex ;
align - items : center ;
gap : 12 px ;
margin - bottom : 14 px ;
}
. ctrl - label {
font - size : 14 px ;
font - weight : 600 ;
color : var ( -- text - primary ) ;
min - width : 100 px ;
}
. n - slider {
flex : 1 ;
max - width : 300 px ;
accent - color : var ( -- primary - color ) ;
}
. n - value {
font - family : var ( -- mono ) ;
font - size : 18 px ;
font - weight : 700 ;
color : var ( -- primary - color ) ;
min - width : 40 px ;
}
. algo - select {
padding : 8 px 12 px ;
border - radius : 8 px ;
border : 1 px solid var ( -- border - color ) ;
background : var ( -- bg ) ;
color : var ( -- text - primary ) ;
font - size : 14 px ;
flex : 1 ;
max - width : 400 px ;
cursor : pointer ;
}
. ctrl - actions {
display : flex ;
gap : 10 px ;
}
. ctrl - btn {
padding : 8 px 20 px ;
border - radius : 8 px ;
border : 1 px solid var ( -- border - color ) ;
background : var ( -- bg ) ;
color : var ( -- text - primary ) ;
font - size : 14 px ;
font - weight : 600 ;
cursor : pointer ;
transition : all 0.15 s ;
}
. ctrl - btn : hover : not ( : disabled ) {
background : var ( -- hover - bg ) ;
border - color : var ( -- primary - color ) ;
}
. ctrl - btn : disabled {
opacity : 0.5 ;
cursor : not - allowed ;
}
. ctrl - btn : first - child {
background : var ( -- primary - color ) ;
color : white ;
border - color : var ( -- primary - color ) ;
}
. ctrl - btn : first - child : hover : not ( : disabled ) {
opacity : 0.9 ;
}
/* 算法说明 */
. info - section {
margin - bottom : 24 px ;
}
. algo - info - card {
background : var ( -- card - bg ) ;
border : 1 px solid var ( -- border - color ) ;
border - radius : 12 px ;
padding : 20 px ;
}
. algo - info - card h3 {
font - size : 18 px ;
font - weight : 700 ;
color : var ( -- text - primary ) ;
margin - bottom : 10 px ;
}
. complexity - badges {
display : flex ;
gap : 10 px ;
margin - bottom : 12 px ;
}
. badge {
font - size : 13 px ;
font - weight : 600 ;
padding : 4 px 12 px ;
border - radius : 6 px ;
}
. time - badge {
background : # eff6ff ;
color : # 2563 eb ;
}
. space - badge {
background : # f0fdf4 ;
color : # 16 a34a ;
}
. algo - info - card p {
font - size : 14 px ;
color : var ( -- text - secondary ) ;
line - height : 1.6 ;
margin - bottom : 10 px ;
}
. op - count {
font - size : 15 px ;
color : var ( -- text - primary ) ;
padding : 8 px 14 px ;
background : var ( -- hover - bg ) ;
border - radius : 8 px ;
}
. op - formula {
font - family : var ( -- mono ) ;
font - size : 13 px ;
color : var ( -- text - tertiary ) ;
margin - left : 8 px ;
}
/* 可视化区域 */
. visualization - area {
display : flex ;
flex - direction : column ;
gap : 20 px ;
}
. array - display {
background : var ( -- card - bg ) ;
border : 1 px solid var ( -- border - color ) ;
border - radius : 12 px ;
padding : 20 px ;
}
. array - label {
font - size : 13 px ;
font - weight : 600 ;
color : var ( -- text - secondary ) ;
margin - bottom : 12 px ;
}
. array - bars {
display : flex ;
align - items : flex - end ;
gap : 3 px ;
min - height : 220 px ;
}
. array - bar - wrapper {
flex : 1 ;
display : flex ;
align - items : flex - end ;
justify - content : center ;
min - width : 12 px ;
}
. array - bar {
width : 100 % ;
background : var ( -- primary - color ) ;
border - radius : 4 px 4 px 0 0 ;
transition : all 0.3 s ease ;
display : flex ;
align - items : flex - end ;
justify - content : center ;
min - height : 20 px ;
}
. bar - val {
font - size : 10 px ;
color : white ;
font - weight : 600 ;
padding : 2 px 0 ;
}
. bar - current {
background : # f59e0b ;
}
. bar - comparing {
background : # 60 a5fa ;
}
. bar - sorted {
background : # 22 c55e ;
}
/* 步骤日志 */
. step - log {
background : var ( -- card - bg ) ;
border : 1 px solid var ( -- border - color ) ;
border - radius : 12 px ;
overflow : hidden ;
}
. log - header {
padding : 12 px 16 px ;
font - size : 14 px ;
font - weight : 700 ;
color : var ( -- text - primary ) ;
border - bottom : 1 px solid var ( -- border - color ) ;
background : var ( -- hover - bg ) ;
}
. log - body {
max - height : 300 px ;
overflow - y : auto ;
padding : 8 px 0 ;
}
. log - line {
padding : 5 px 16 px ;
font - size : 13 px ;
font - family : var ( -- mono ) ;
color : var ( -- text - tertiary ) ;
line - height : 1.5 ;
border - left : 3 px solid transparent ;
}
. log - line . current {
color : var ( -- text - primary ) ;
background : var ( -- active - bg ) ;
border - left - color : var ( -- primary - color ) ;
}
. log - line . done {
color : var ( -- text - secondary ) ;
}
. log - step - num {
display : inline - block ;
min - width : 28 px ;
color : var ( -- text - tertiary ) ;
font - size : 11 px ;
}
/* 对比图表 */
. chart - section {
background : var ( -- card - bg ) ;
border : 1 px solid var ( -- border - color ) ;
border - radius : 12 px ;
padding : 20 px ;
}
. chart - header {
font - size : 16 px ;
font - weight : 700 ;
color : var ( -- text - primary ) ;
margin - bottom : 16 px ;
}
. chart - canvas {
display : flex ;
flex - direction : column ;
gap : 10 px ;
}
. chart - row {
display : flex ;
align - items : center ;
gap : 12 px ;
}
. chart - label {
min - width : 90 px ;
font - size : 13 px ;
font - weight : 600 ;
font - family : var ( -- mono ) ;
color : var ( -- text - primary ) ;
}
. chart - bar - track {
flex : 1 ;
height : 28 px ;
background : var ( -- hover - bg ) ;
border - radius : 6 px ;
overflow : hidden ;
}
. chart - bar {
height : 100 % ;
border - radius : 6 px ;
display : flex ;
align - items : center ;
padding - left : 8 px ;
transition : width 0.5 s ease ;
min - width : fit - content ;
}
. chart - bar - text {
font - size : 12 px ;
font - weight : 600 ;
color : white ;
white - space : nowrap ;
}
. nav - back {
margin : 32 px 0 ;
}
. back - link {
display : inline - block ;
color : var ( -- primary - color ) ;
text - decoration : none ;
font - weight : 600 ;
font - size : 14 px ;
}
. back - link : hover {
text - decoration : underline ;
}
@ media ( prefers - color - scheme : dark ) {
. time - badge { background : # 1 e3a5f ; color : # 60 a5fa ; }
. space - badge { background : # 14532 d ; color : # 4 ade80 ; }
}
< / style >