2026-01-18 10:24:35 +08:00
< template >
< div class = "frontend-evolution-demo" >
<!-- Modern Timeline -- >
< div class = "timeline-container" >
2026-02-18 17:38:10 +08:00
< div class = "timeline-track" / >
2026-01-18 12:21:49 +08:00
< button
v-for = "(stage, index) in stages"
2026-01-18 10:24:35 +08:00
:key = "index"
class = "timeline-node"
2026-01-18 12:21:49 +08:00
: class = "{
active: currentStage === index,
passed: currentStage > index
}"
2026-01-18 10:24:35 +08:00
@click ="currentStage = index"
>
< div class = "node-dot" >
2026-02-18 17:38:10 +08:00
< div class = "inner-dot" / >
2026-01-18 10:24:35 +08:00
< / div >
< div class = "node-content" >
< span class = "year-badge" > { { stage . year } } < / span >
< span class = "node-label" > { { stage . label } } < / span >
< / div >
< / button >
< / div >
< div class = "content-wrapper" >
2026-02-18 17:38:10 +08:00
< transition
name = "fade-slide"
mode = "out-in"
>
< div
:key = "currentStage"
class = "stage-content"
>
2026-01-18 10:24:35 +08:00
< div class = "header-section" >
< h3 >
2026-02-18 17:38:10 +08:00
< span class = "stage-index" > { { indexToRoman ( currentStage + 1 ) } } . < / span >
2026-01-18 10:24:35 +08:00
{ { stages [ currentStage ] . title } }
< / h3 >
< p > { { stages [ currentStage ] . desc } } < / p >
< / div >
< div class = "visualization-grid" >
<!-- Code Editor -- >
< div class = "mac-window code-window" >
< div class = "window-bar" >
< div class = "traffic-lights" >
2026-02-18 17:38:10 +08:00
< span class = "light red" / >
< span class = "light yellow" / >
< span class = "light green" / >
2026-01-18 10:24:35 +08:00
< / div >
2026-01-18 12:21:49 +08:00
< div class = "window-title" >
{ { stages [ currentStage ] . codeTitle } }
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< div class = "editor-content" >
< pre > < code > { { stages [ currentStage ] . code } } < / code > < / pre >
< / div >
< / div >
<!-- Diagram View -- >
< div class = "mac-window diagram-window" >
< div class = "window-bar" >
2026-02-18 17:38:10 +08:00
< div class = "window-title" >
Architecture Pattern
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< div class = "diagram-canvas" >
<!-- Stage 0 : Static -- >
2026-02-18 17:38:10 +08:00
< div
v-if = "currentStage === 0"
class = "diagram static"
>
2026-01-18 10:24:35 +08:00
< div class = "flow-stack" >
< div class = "concept-box html" >
< span class = "icon" > 📄 < / span > HTML ( Content )
< / div >
2026-02-18 17:38:10 +08:00
< div class = "flow-arrow" >
↓
< / div >
2026-01-18 10:24:35 +08:00
< div class = "concept-box browser" >
< span class = "icon" > 🌍 < / span > Browser ( Display )
< / div >
< / div >
2026-02-18 17:38:10 +08:00
< div class = "side-note" >
Server sends complete HTML
< / div >
2026-01-18 10:24:35 +08:00
< / div >
<!-- Stage 1 : jQuery -- >
2026-02-18 17:38:10 +08:00
< div
v-if = "currentStage === 1"
class = "diagram jquery"
>
2026-01-18 10:24:35 +08:00
< div class = "concept-box dom" >
< span class = "icon" > 🌳 < / span > DOM Tree
< / div >
< div class = "chaos-arrows" >
2026-02-18 17:38:10 +08:00
< svg
viewBox = "0 0 100 60"
class = "chaos-svg"
>
2026-01-18 12:21:49 +08:00
< path
d = "M10,10 Q50,5 90,10"
class = "arrow-path"
marker -end = " url ( # arrowhead ) "
/ >
< path
d = "M90,50 Q50,55 10,50"
class = "arrow-path"
marker -end = " url ( # arrowhead ) "
/ >
< path
d = "M20,20 Q50,40 80,20"
class = "arrow-path dashed"
marker -end = " url ( # arrowhead ) "
/ >
2026-01-18 10:24:35 +08:00
< / svg >
< span class = "label-action" > Direct Manipulation < / span >
< span class = "label-event" > Events < / span >
< / div >
< div class = "concept-box js" >
< span class = "icon" > 🍝 < / span > jQuery / JS
< / div >
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
<!-- SVG Marker Definition -- >
2026-01-18 12:21:49 +08:00
< svg style = "position: absolute; width: 0; height: 0" >
2026-01-18 10:24:35 +08:00
< defs >
2026-01-18 12:21:49 +08:00
< marker
id = "arrowhead"
markerWidth = "10"
markerHeight = "7"
refX = "9"
refY = "3.5"
orient = "auto"
>
2026-02-18 17:38:10 +08:00
< polygon
points = "0 0, 10 3.5, 0 7"
fill = "#666"
/ >
2026-01-18 10:24:35 +08:00
< / marker >
< / defs >
< / svg >
< / div >
<!-- Stage 2 : MVC -- >
2026-02-18 17:38:10 +08:00
< div
v-if = "currentStage === 2"
class = "diagram mvc"
>
2026-01-18 10:24:35 +08:00
< div class = "mvc-triangle" >
2026-02-18 17:38:10 +08:00
< div class = "concept-box model" >
Model
< / div >
< div class = "concept-box view" >
View
< / div >
< div class = "concept-box controller" >
Controller
< / div >
2026-01-18 12:21:49 +08:00
2026-01-18 10:24:35 +08:00
<!-- Connecting Lines -- >
2026-02-18 17:38:10 +08:00
< div class = "line m-v" / >
< div class = "line v-c" / >
< div class = "line c-m" / >
< / div >
< div class = "mvc-desc" >
Two - way Binding
2026-01-18 10:24:35 +08:00
< / div >
< / div >
<!-- Stage 3 : Component -- >
2026-02-18 17:38:10 +08:00
< div
v-if = "currentStage === 3"
class = "diagram component"
>
2026-01-18 10:24:35 +08:00
< div class = "comp-structure" >
< div class = "comp-box root" >
< span class = "comp-label" > App < / span >
< div class = "comp-children" >
2026-02-18 17:38:10 +08:00
< div class = "comp-box header" >
Header
< / div >
2026-01-18 10:24:35 +08:00
< div class = "comp-box list" >
ProductList
< div class = "comp-children row" >
2026-02-18 17:38:10 +08:00
< div class = "comp-box item" >
Item
< / div >
< div class = "comp-box item" >
Item
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< / div >
< / div >
< / div >
< / div >
2026-02-18 17:38:10 +08:00
< div class = "flow-pill" >
State ➔ UI = f ( State )
< / div >
2026-01-18 10:24:35 +08:00
< / div >
< / div >
< / div >
< / div >
< / div >
< / transition >
< / div >
< / div >
< / template >
< script setup >
import { ref } from 'vue'
const currentStage = ref ( 0 )
const indexToRoman = ( num ) => {
const map = { 1 : 'I' , 2 : 'II' , 3 : 'III' , 4 : 'IV' }
return map [ num ] || num
}
const stages = [
{
year : '1990s' ,
label : 'Static Web' ,
title : 'The Static Era' ,
desc : 'Web pages were just digital documents. The server sent HTML, and the browser rendered it. Want new content? Refresh the whole page.' ,
codeTitle : 'index.html' ,
code : ` <html>
<body>
<h1>Hello World</h1>
<p>Static content served by server.</p>
</body>
</html> `
} ,
{
year : '2005+' ,
label : 'jQuery Era' ,
title : 'Imperative DOM' ,
desc : 'JS directly manipulated the DOM. "Find that button, add a click listener, change that div\'s color". Logic became tangled like "spaghetti".' ,
codeTitle : 'script.js' ,
code : ` $ ('#btn').click(function() {
// Find & Modify directly
$ ('.box').show();
$ ('.text').text('Loading...');
// Callback hell...
$ .ajax('/api', function(data) {
$ ('.content').html(data);
});
}); `
} ,
{
year : '2010+' ,
label : 'MVC/MVVM' ,
title : 'Framework Era' ,
desc : 'Separation of concerns. Data (Model) and View were separated. Two-way data binding (like in AngularJS) was magic but performance-heavy.' ,
codeTitle : 'controller.js' ,
code : ` $ scope.user = { name: 'Bob' };
// Magic: Data changes -> View updates
$ scope.updateName = function() {
$ scope.user.name = 'Alice';
}; `
} ,
{
year : '2013+' ,
label : 'Component' ,
title : 'Component Era' ,
desc : 'UI is broken into independent "Lego blocks" (Components). Declarative: You define "What it looks like given State X", framework handles the "How".' ,
codeTitle : 'ProductCard.vue' ,
code : ` <template>
<div class="card">
<h3>{{ product.name }}</h3>
<button @click="buy">Buy</button>
</div>
</template>
<script>
// State driven
export default {
props: ['product'],
methods: { buy() { ... } }
}
< \ /script> `
}
]
< / script >
< style scoped >
. frontend - evolution - demo {
border - radius : 16 px ;
background : var ( -- vp - c - bg ) ;
2026-01-18 12:21:49 +08:00
box - shadow : 0 8 px 30 px rgba ( 0 , 0 , 0 , 0.05 ) ;
2026-01-18 10:24:35 +08:00
border : 1 px solid var ( -- vp - c - divider ) ;
overflow : hidden ;
margin : 2 rem 0 ;
2026-01-18 12:21:49 +08:00
font - family :
- apple - system , BlinkMacSystemFont , 'Segoe UI' , Roboto , Helvetica , Arial ,
sans - serif ;
2026-01-18 10:24:35 +08:00
}
/* --- Timeline --- */
. timeline - container {
padding : 2 rem 1 rem 1 rem ;
background : linear - gradient ( to bottom , var ( -- vp - c - bg - soft ) , var ( -- vp - c - bg ) ) ;
display : flex ;
justify - content : space - between ;
position : relative ;
border - bottom : 1 px solid var ( -- vp - c - divider ) ;
}
. timeline - track {
position : absolute ;
top : 2.5 rem ; /* Center with dots */
left : 3 rem ;
right : 3 rem ;
height : 2 px ;
background : var ( -- vp - c - divider ) ;
z - index : 0 ;
}
. timeline - node {
position : relative ;
z - index : 1 ;
background : transparent ;
border : none ;
display : flex ;
flex - direction : column ;
align - items : center ;
cursor : pointer ;
padding : 0 ;
width : 25 % ;
transition : all 0.3 s ease ;
opacity : 0.6 ;
}
. timeline - node : hover {
opacity : 0.9 ;
}
2026-01-18 12:21:49 +08:00
. timeline - node . active ,
. timeline - node . passed {
2026-01-18 10:24:35 +08:00
opacity : 1 ;
}
. node - dot {
width : 16 px ;
height : 16 px ;
border - radius : 50 % ;
background : var ( -- vp - c - bg ) ;
border : 2 px solid var ( -- vp - c - text - 3 ) ;
margin - bottom : 0.8 rem ;
display : flex ;
align - items : center ;
justify - content : center ;
transition : all 0.3 s cubic - bezier ( 0.175 , 0.885 , 0.32 , 1.275 ) ;
}
. inner - dot {
width : 0 ;
height : 0 ;
border - radius : 50 % ;
background : var ( -- vp - c - brand ) ;
transition : all 0.3 s ;
}
. timeline - node . active . node - dot {
border - color : var ( -- vp - c - brand ) ;
transform : scale ( 1.3 ) ;
box - shadow : 0 0 0 4 px var ( -- vp - c - bg - soft ) ;
}
. timeline - node . active . inner - dot {
width : 8 px ;
height : 8 px ;
}
. timeline - node . passed . node - dot {
border - color : var ( -- vp - c - brand ) ;
background : var ( -- vp - c - brand ) ;
}
. node - content {
text - align : center ;
display : flex ;
flex - direction : column ;
align - items : center ;
gap : 0.2 rem ;
}
. year - badge {
font - size : 0.75 rem ;
font - family : var ( -- vp - font - family - mono ) ;
background : var ( -- vp - c - bg - alt ) ;
padding : 2 px 6 px ;
border - radius : 4 px ;
color : var ( -- vp - c - text - 2 ) ;
}
. node - label {
font - size : 0.9 rem ;
font - weight : 600 ;
color : var ( -- vp - c - text - 1 ) ;
}
/* --- Content --- */
. content - wrapper {
padding : 2 rem ;
min - height : 400 px ;
}
. header - section {
text - align : center ;
margin - bottom : 2 rem ;
max - width : 600 px ;
margin - left : auto ;
margin - right : auto ;
}
. header - section h3 {
font - size : 1.5 rem ;
margin - bottom : 0.5 rem ;
background : linear - gradient ( 120 deg , var ( -- vp - c - brand ) , # 8 b5cf6 ) ;
- webkit - background - clip : text ;
- webkit - text - fill - color : transparent ;
}
. stage - index {
color : var ( -- vp - c - text - 3 ) ;
- webkit - text - fill - color : var ( -- vp - c - text - 3 ) ;
margin - right : 0.5 rem ;
font - weight : normal ;
}
. header - section p {
font - size : 1 rem ;
color : var ( -- vp - c - text - 2 ) ;
line - height : 1.6 ;
}
/* --- Visualization Grid --- */
. visualization - grid {
display : grid ;
grid - template - columns : 1 fr 1 fr ;
gap : 2 rem ;
align - items : stretch ;
}
@ media ( max - width : 768 px ) {
. visualization - grid {
grid - template - columns : 1 fr ;
}
}
. mac - window {
border - radius : 12 px ;
2026-01-18 12:21:49 +08:00
border : 1 px solid rgba ( 0 , 0 , 0 , 0.1 ) ;
box - shadow : 0 10 px 30 px rgba ( 0 , 0 , 0 , 0.1 ) ;
2026-01-18 10:24:35 +08:00
overflow : hidden ;
display : flex ;
flex - direction : column ;
background : white ;
transition : transform 0.3 s ease ;
}
. mac - window : hover {
transform : translateY ( - 5 px ) ;
}
. code - window {
background : # 1 e1e2e ; /* Dark theme */
}
. diagram - window {
background : var ( -- vp - c - bg - alt ) ;
}
. window - bar {
padding : 0.8 rem 1 rem ;
2026-01-18 12:21:49 +08:00
background : rgba ( 255 , 255 , 255 , 0.05 ) ; /* Transparent on dark */
border - bottom : 1 px solid rgba ( 255 , 255 , 255 , 0.1 ) ;
2026-01-18 10:24:35 +08:00
display : flex ;
align - items : center ;
position : relative ;
}
. diagram - window . window - bar {
background : white ;
2026-01-18 12:21:49 +08:00
border - bottom : 1 px solid rgba ( 0 , 0 , 0 , 0.05 ) ;
2026-01-18 10:24:35 +08:00
}
. traffic - lights {
display : flex ;
gap : 6 px ;
}
. light {
width : 10 px ;
height : 10 px ;
border - radius : 50 % ;
}
2026-01-18 12:21:49 +08:00
. light . red {
background : # ff5f56 ;
}
. light . yellow {
background : # ffbd2e ;
}
. light . green {
background : # 27 c93f ;
}
2026-01-18 10:24:35 +08:00
. window - title {
position : absolute ;
left : 0 ;
right : 0 ;
text - align : center ;
font - size : 0.8 rem ;
color : # 9 ca3af ;
font - family : var ( -- vp - font - family - mono ) ;
}
. diagram - window . window - title {
color : var ( -- vp - c - text - 2 ) ;
font - weight : 600 ;
}
. editor - content {
2026-02-14 20:23:34 +08:00
padding : 0.75 rem ;
2026-01-18 10:24:35 +08:00
overflow : auto ;
flex : 1 ;
}
. editor - content pre {
margin : 0 ;
background : transparent ;
padding : 0 ;
}
. editor - content code {
font - family : 'Fira Code' , 'Menlo' , monospace ;
font - size : 0.85 rem ;
line - height : 1.5 ;
color : # a6accd ;
}
. diagram - canvas {
padding : 2 rem ;
flex : 1 ;
display : flex ;
align - items : center ;
justify - content : center ;
min - height : 250 px ;
position : relative ;
}
/* --- Diagram Specifics --- */
. concept - box {
background : white ;
2026-01-18 12:21:49 +08:00
border : 1 px solid rgba ( 0 , 0 , 0 , 0.1 ) ;
2026-01-18 10:24:35 +08:00
padding : 0.8 rem 1.2 rem ;
2026-02-14 20:23:34 +08:00
border - radius : 6 px ;
2026-01-18 12:21:49 +08:00
box - shadow : 0 4 px 6 px rgba ( 0 , 0 , 0 , 0.05 ) ;
2026-01-18 10:24:35 +08:00
font - weight : 600 ;
font - size : 0.9 rem ;
display : flex ;
align - items : center ;
gap : 0.5 rem ;
z - index : 2 ;
}
2026-01-18 12:21:49 +08:00
. icon {
font - size : 1.2 rem ;
}
2026-01-18 10:24:35 +08:00
/* Static Diagram */
. diagram . static . flow - stack {
display : flex ;
flex - direction : column ;
align - items : center ;
gap : 1 rem ;
}
. side - note {
margin - top : 1 rem ;
font - size : 0.8 rem ;
color : var ( -- vp - c - text - 3 ) ;
2026-01-18 12:21:49 +08:00
background : rgba ( 0 , 0 , 0 , 0.05 ) ;
2026-01-18 10:24:35 +08:00
padding : 4 px 8 px ;
border - radius : 4 px ;
}
/* jQuery Diagram */
. diagram . jquery {
flex - direction : column ;
gap : 1 rem ;
width : 100 % ;
}
. chaos - arrows {
position : relative ;
height : 80 px ;
width : 100 % ;
display : flex ;
align - items : center ;
justify - content : center ;
}
. chaos - svg {
position : absolute ;
top : 0 ;
left : 0 ;
width : 100 % ;
height : 100 % ;
overflow : visible ;
}
. arrow - path {
fill : none ;
stroke : # 9 ca3af ;
stroke - width : 2 ;
}
. arrow - path . dashed {
stroke - dasharray : 4 ;
}
2026-01-18 12:21:49 +08:00
. label - action ,
. label - event {
2026-01-18 10:24:35 +08:00
font - size : 0.75 rem ;
background : white ;
padding : 2 px 4 px ;
border - radius : 4 px ;
z - index : 1 ;
2026-01-18 12:21:49 +08:00
box - shadow : 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.1 ) ;
}
. label - action {
transform : translate ( - 20 px , - 10 px ) ;
}
. label - event {
transform : translate ( 20 px , 10 px ) ;
2026-01-18 10:24:35 +08:00
}
/* MVC Diagram */
. diagram . mvc {
flex - direction : column ;
gap : 1 rem ;
}
. mvc - triangle {
position : relative ;
width : 200 px ;
height : 160 px ;
}
2026-01-18 12:21:49 +08:00
. mvc - triangle . model {
position : absolute ;
top : 0 ;
left : 50 % ;
transform : translateX ( - 50 % ) ;
}
. mvc - triangle . view {
position : absolute ;
bottom : 0 ;
left : 0 ;
}
. mvc - triangle . controller {
position : absolute ;
bottom : 0 ;
right : 0 ;
}
2026-01-18 10:24:35 +08:00
. line {
position : absolute ;
background : # cbd5e1 ;
z - index : 1 ;
}
. line . m - v {
height : 2 px ;
width : 110 px ;
top : 45 % ;
left : 20 px ;
transform : rotate ( 60 deg ) ;
}
. line . v - c {
height : 2 px ;
width : 100 px ;
bottom : 20 px ;
left : 50 px ;
}
. line . c - m {
height : 2 px ;
width : 110 px ;
top : 45 % ;
right : 20 px ;
transform : rotate ( - 60 deg ) ;
}
. mvc - desc {
margin - top : 1 rem ;
font - size : 0.85 rem ;
font - weight : bold ;
color : var ( -- vp - c - brand ) ;
}
/* Component Diagram */
. diagram . component {
flex - direction : column ;
gap : 1.5 rem ;
}
. comp - structure {
display : flex ;
justify - content : center ;
}
. comp - box {
background : white ;
border : 2 px solid # 3 b82f6 ;
border - radius : 6 px ;
padding : 6 px ;
font - size : 0.8 rem ;
text - align : center ;
box - shadow : 0 4 px 0 rgba ( 59 , 130 , 246 , 0.2 ) ;
}
. comp - box . root {
background : # eff6ff ;
display : flex ;
flex - direction : column ;
gap : 8 px ;
padding : 10 px ;
}
. comp - label {
font - weight : bold ;
color : # 1 e40af ;
}
. comp - children {
display : flex ;
gap : 8 px ;
justify - content : center ;
}
. comp - children . row {
margin - top : 4 px ;
}
2026-01-18 12:21:49 +08:00
. comp - box . header {
background : # dbeafe ;
border - style : dashed ;
}
. comp - box . list {
background : # dbeafe ;
}
. comp - box . item {
background : # bfdbfe ;
font - size : 0.7 rem ;
padding : 4 px ;
}
2026-01-18 10:24:35 +08:00
. flow - pill {
background : linear - gradient ( 90 deg , # 3 b82f6 , # 8 b5cf6 ) ;
color : white ;
padding : 0.5 rem 1.5 rem ;
border - radius : 20 px ;
font - weight : bold ;
font - size : 0.9 rem ;
box - shadow : 0 4 px 10 px rgba ( 59 , 130 , 246 , 0.3 ) ;
}
/* Transitions */
. fade - slide - enter - active ,
. fade - slide - leave - active {
transition : all 0.4 s ease ;
}
. fade - slide - enter - from {
opacity : 0 ;
transform : translateY ( 20 px ) ;
}
. fade - slide - leave - to {
opacity : 0 ;
transform : translateY ( - 20 px ) ;
}
2026-01-18 12:21:49 +08:00
< / style >