2026-03-06 04:55:42 +00:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" / >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" / >
< title > FabDash — Fabrication Project Dashboard< / title >
< link rel = "icon" href = "/logo.png" / >
< link rel = "preconnect" href = "https://fonts.googleapis.com" / >
< link href = "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap" rel = "stylesheet" / >
< style >
* , * :: before , * :: after { box-sizing : border-box ; margin : 0 ; padding : 0 ; }
: root {
--gold : #C9A84C ;
--gold-light : #E8C96A ;
--gold-muted : #8A6E2F ;
--bg : #111111 ;
--raised : #1A1A1A ;
--elevated : #242424 ;
--border : #2E2E2E ;
--text : #F5F5F5 ;
--muted : #888888 ;
--blue : #0EA5E9 ;
--amber : #F59E0B ;
--green : #22C55E ;
--red : #EF4444 ;
}
html { scroll-behavior : smooth ; }
body {
font-family : 'Inter' , system-ui , sans-serif ;
background : var ( - - bg ) ;
color : var ( - - text ) ;
overflow-x : hidden ;
}
/* ── Scrollbar ── */
:: -webkit-scrollbar { width : 6 px ; }
:: -webkit-scrollbar-track { background : var ( - - raised ) ; }
:: -webkit-scrollbar-thumb { background : var ( - - border ) ; border-radius : 3 px ; }
:: -webkit-scrollbar-thumb : hover { background : var ( - - gold - muted ) ; }
/* ── Nav ── */
nav {
position : fixed ;
top : 0 ; left : 0 ; right : 0 ;
z-index : 100 ;
display : flex ;
align-items : center ;
justify-content : space-between ;
padding : 0 2.5 rem ;
height : 64 px ;
background : rgba ( 17 , 17 , 17 , 0.88 ) ;
backdrop-filter : blur ( 12 px ) ;
border-bottom : 1 px solid var ( - - border ) ;
}
. nav-brand {
display : flex ;
align-items : center ;
gap : 10 px ;
text-decoration : none ;
}
. nav-brand img {
width : 32 px ; height : 32 px ;
object-fit : contain ;
}
. nav-brand span {
font-size : 1.1 rem ;
font-weight : 900 ;
letter-spacing : 0.12 em ;
color : var ( - - gold ) ;
}
. nav-cta {
display : inline-flex ;
align-items : center ;
gap : 6 px ;
background : var ( - - gold ) ;
color : #000 ;
font-weight : 700 ;
font-size : 0.82 rem ;
letter-spacing : 0.05 em ;
padding : 8 px 20 px ;
border-radius : 4 px ;
text-decoration : none ;
transition : background 0.2 s ;
}
. nav-cta : hover { background : var ( - - gold - light ) ; }
/* ── Hero ── */
. hero {
min-height : 100 vh ;
display : flex ;
flex-direction : column ;
align-items : center ;
justify-content : center ;
text-align : center ;
padding : 100 px 2 rem 4 rem ;
position : relative ;
overflow : hidden ;
}
. hero-bg {
position : absolute ;
inset : 0 ;
background :
radial-gradient ( ellipse 80 % 60 % at 50 % 0 % , rgba ( 201 , 168 , 76 , 0.10 ) 0 % , transparent 70 % ) ,
radial-gradient ( ellipse 50 % 40 % at 80 % 80 % , rgba ( 201 , 168 , 76 , 0.06 ) 0 % , transparent 60 % ) ;
pointer-events : none ;
}
/* parallax grid */
. hero-grid {
position : absolute ;
inset : 0 ;
background-image :
linear-gradient ( rgba ( 201 , 168 , 76 , 0.04 ) 1 px , transparent 1 px ) ,
linear-gradient ( 90 deg , rgba ( 201 , 168 , 76 , 0.04 ) 1 px , transparent 1 px ) ;
background-size : 60 px 60 px ;
pointer-events : none ;
transform : translateZ ( 0 ) ;
}
. hero-logo {
width : 100 px ; height : 100 px ;
object-fit : contain ;
margin-bottom : 1.5 rem ;
filter : drop-shadow ( 0 0 24 px rgba ( 201 , 168 , 76 , 0.5 ) ) ;
animation : float 4 s ease-in-out infinite ;
}
@ keyframes float {
0 % , 100 % { transform : translateY ( 0 ) ; }
50 % { transform : translateY ( -10 px ) ; }
}
. hero-eyebrow {
font-size : 0.72 rem ;
font-weight : 700 ;
letter-spacing : 0.2 em ;
color : var ( - - gold ) ;
text-transform : uppercase ;
margin-bottom : 1 rem ;
}
. hero-title {
font-size : clamp ( 2.6 rem , 6 vw , 4.8 rem ) ;
font-weight : 900 ;
line-height : 1.05 ;
letter-spacing : -0.02 em ;
max-width : 820 px ;
margin : 0 auto 1.25 rem ;
}
. hero-title em {
font-style : normal ;
color : var ( - - gold ) ;
}
. hero-sub {
font-size : clamp ( 1 rem , 2 vw , 1.2 rem ) ;
color : var ( - - muted ) ;
max-width : 560 px ;
margin : 0 auto 2.5 rem ;
line-height : 1.65 ;
}
. hero-actions {
display : flex ;
gap : 1 rem ;
align-items : center ;
justify-content : center ;
flex-wrap : wrap ;
}
. btn-primary {
display : inline-flex ;
align-items : center ;
gap : 8 px ;
background : var ( - - gold ) ;
color : #000 ;
font-weight : 700 ;
font-size : 0.9 rem ;
letter-spacing : 0.04 em ;
padding : 12 px 28 px ;
border-radius : 4 px ;
text-decoration : none ;
transition : background 0.2 s , box-shadow 0.2 s ;
}
. btn-primary : hover {
background : var ( - - gold - light ) ;
box-shadow : 0 0 20 px rgba ( 201 , 168 , 76 , 0.4 ) ;
}
. btn-ghost {
display : inline-flex ;
align-items : center ;
gap : 8 px ;
background : transparent ;
color : var ( - - text ) ;
font-weight : 600 ;
font-size : 0.9 rem ;
padding : 12 px 24 px ;
border : 1 px solid var ( - - border ) ;
border-radius : 4 px ;
text-decoration : none ;
transition : border-color 0.2 s , color 0.2 s ;
}
. btn-ghost : hover { border-color : var ( - - gold ) ; color : var ( - - gold ) ; }
/* ── Section base ── */
section {
padding : 6 rem 2 rem ;
}
. container {
max-width : 1120 px ;
margin : 0 auto ;
}
. section-label {
font-size : 0.7 rem ;
font-weight : 700 ;
letter-spacing : 0.22 em ;
text-transform : uppercase ;
color : var ( - - gold ) ;
margin-bottom : 0.75 rem ;
}
. section-title {
font-size : clamp ( 1.8 rem , 4 vw , 2.8 rem ) ;
font-weight : 800 ;
letter-spacing : -0.02 em ;
line-height : 1.1 ;
margin-bottom : 1 rem ;
}
. section-sub {
color : var ( - - muted ) ;
font-size : 1.05 rem ;
line-height : 1.65 ;
max-width : 560 px ;
}
/* ── Demo Preview ── */
. demo-section {
background : linear-gradient ( 180 deg , var ( - - bg ) 0 % , #0d0d0d 100 % ) ;
}
. demo-shell {
background : var ( - - raised ) ;
border : 1 px solid var ( - - border ) ;
border-radius : 8 px ;
overflow : hidden ;
box-shadow : 0 24 px 80 px rgba ( 0 , 0 , 0 , 0.6 ) , 0 0 0 1 px var ( - - border ) ;
margin-top : 3 rem ;
}
/* Window chrome */
. demo-topbar {
display : flex ;
align-items : center ;
gap : 8 px ;
padding : 12 px 16 px ;
background : #0f0f0f ;
border-bottom : 1 px solid var ( - - border ) ;
}
. dot { width : 10 px ; height : 10 px ; border-radius : 50 % ; }
. dot-r { background : #FF5F57 ; }
. dot-y { background : #FEBC2E ; }
. dot-g { background : #28C840 ; }
. demo-url {
flex : 1 ;
text-align : center ;
font-size : 0.72 rem ;
color : var ( - - muted ) ;
}
/* App layout mock */
. app-mock {
display : flex ;
height : 520 px ;
}
/* Sidebar */
. mock-sidebar {
width : 200 px ;
flex-shrink : 0 ;
background : #0f0f0f ;
border-right : 1 px solid var ( - - border ) ;
display : flex ;
flex-direction : column ;
padding : 14 px 0 ;
}
. sidebar-logo-row {
display : flex ;
align-items : center ;
gap : 8 px ;
padding : 0 14 px 14 px ;
border-bottom : 1 px solid var ( - - border ) ;
margin-bottom : 14 px ;
}
. sidebar-logo-row img {
width : 24 px ; height : 24 px ; object-fit : contain ;
}
. sidebar-wordmark {
font-size : 0.7 rem ;
font-weight : 900 ;
letter-spacing : 0.14 em ;
color : var ( - - gold ) ;
}
. sidebar-project {
padding : 0 10 px ;
margin-bottom : 4 px ;
}
. project-bar {
display : flex ;
align-items : center ;
gap : 8 px ;
padding : 7 px 8 px ;
border-radius : 4 px ;
background : var ( - - elevated ) ;
cursor : pointer ;
}
. project-dot {
width : 8 px ; height : 8 px ;
border-radius : 50 % ;
flex-shrink : 0 ;
}
. project-name {
font-size : 0.72 rem ;
font-weight : 600 ;
color : var ( - - text ) ;
white-space : nowrap ;
overflow : hidden ;
text-overflow : ellipsis ;
}
. project-count {
margin-left : auto ;
font-size : 0.62 rem ;
color : var ( - - muted ) ;
background : var ( - - raised ) ;
border-radius : 10 px ;
padding : 1 px 6 px ;
}
. deliverable-row {
display : flex ;
align-items : center ;
gap : 7 px ;
padding : 5 px 8 px 5 px 24 px ;
font-size : 0.67 rem ;
color : var ( - - muted ) ;
}
. badge {
font-size : 0.58 rem ;
font-weight : 600 ;
padding : 1 px 5 px ;
border-radius : 3 px ;
white-space : nowrap ;
}
. badge-upcoming { background : rgba ( 14 , 165 , 233 , 0.18 ) ; color : #38BDF8 ; }
. badge-progress { background : rgba ( 245 , 158 , 11 , 0.18 ) ; color : #FCD34D ; }
. badge-complete { background : rgba ( 34 , 197 , 94 , 0.18 ) ; color : #4ADE80 ; }
. badge-overdue { background : rgba ( 239 , 68 , 68 , 0.18 ) ; color : #F87171 ; }
/* Main calendar mock */
. mock-main {
flex : 1 ;
display : flex ;
flex-direction : column ;
overflow : hidden ;
}
. cal-header {
display : flex ;
align-items : center ;
justify-content : space-between ;
padding : 12 px 18 px ;
border-bottom : 1 px solid var ( - - border ) ;
background : var ( - - raised ) ;
}
. cal-title {
font-size : 0.85 rem ;
font-weight : 700 ;
letter-spacing : 0.02 em ;
}
. cal-nav {
display : flex ;
gap : 6 px ;
}
. cal-btn {
background : var ( - - elevated ) ;
border : 1 px solid var ( - - border ) ;
border-radius : 4 px ;
color : var ( - - text ) ;
font-size : 0.68 rem ;
font-weight : 600 ;
padding : 4 px 10 px ;
cursor : pointer ;
}
. cal-btn . active {
background : var ( - - gold ) ;
color : #000 ;
border-color : var ( - - gold ) ;
}
/* Calendar grid */
. cal-grid {
flex : 1 ;
display : flex ;
flex-direction : column ;
overflow : hidden ;
}
. cal-dow {
display : grid ;
grid-template-columns : repeat ( 7 , 1 fr ) ;
border-bottom : 1 px solid var ( - - border ) ;
}
. cal-dow-cell {
padding : 6 px ;
text-align : center ;
font-size : 0.6 rem ;
font-weight : 700 ;
letter-spacing : 0.08 em ;
color : var ( - - muted ) ;
text-transform : uppercase ;
}
. cal-weeks {
flex : 1 ;
display : flex ;
flex-direction : column ;
}
. cal-week {
flex : 1 ;
display : grid ;
grid-template-columns : repeat ( 7 , 1 fr ) ;
border-bottom : 1 px solid var ( - - border ) ;
}
. cal-week : last-child { border-bottom : none ; }
. cal-day {
border-right : 1 px solid var ( - - border ) ;
padding : 4 px 5 px ;
min-height : 0 ;
overflow : hidden ;
}
. cal-day : last-child { border-right : none ; }
. cal-day . other-month . day-num { color : #444 ; }
. cal-day . today . day-num {
background : var ( - - gold ) ;
color : #000 ;
border-radius : 50 % ;
width : 20 px ; height : 20 px ;
display : flex ; align-items : center ; justify-content : center ;
}
. day-num {
font-size : 0.6 rem ;
font-weight : 600 ;
color : var ( - - muted ) ;
margin-bottom : 3 px ;
width : 20 px ; height : 20 px ;
display : flex ; align-items : center ; justify-content : center ;
}
. cal-event {
font-size : 0.55 rem ;
font-weight : 600 ;
padding : 2 px 5 px ;
border-radius : 2 px ;
margin-bottom : 2 px ;
white-space : nowrap ;
overflow : hidden ;
text-overflow : ellipsis ;
color : #000 ;
}
/* ── Benefits ── */
. benefits-section {
background : linear-gradient ( 180 deg , #0d0d0d 0 % , var ( - - bg ) 100 % ) ;
}
. benefits-grid {
display : grid ;
grid-template-columns : repeat ( auto - fit , minmax ( 300 px , 1 fr ) ) ;
gap : 1.5 rem ;
margin-top : 3 rem ;
}
. benefit-card {
background : var ( - - raised ) ;
border : 1 px solid var ( - - border ) ;
border-radius : 6 px ;
padding : 1.75 rem ;
position : relative ;
overflow : hidden ;
transition : border-color 0.25 s , transform 0.25 s ;
}
. benefit-card :: before {
content : '' ;
position : absolute ;
top : 0 ; left : 0 ; right : 0 ;
height : 2 px ;
background : linear-gradient ( 90 deg , var ( - - gold ) , var ( - - gold - muted ) ) ;
opacity : 0 ;
transition : opacity 0.25 s ;
}
. benefit-card : hover {
border-color : var ( - - gold - muted ) ;
transform : translateY ( -3 px ) ;
}
. benefit-card : hover :: before { opacity : 1 ; }
. benefit-icon {
font-size : 1.8 rem ;
margin-bottom : 1 rem ;
line-height : 1 ;
}
. benefit-title {
font-size : 1 rem ;
font-weight : 700 ;
margin-bottom : 0.5 rem ;
}
. benefit-desc {
font-size : 0.875 rem ;
color : var ( - - muted ) ;
line-height : 1.65 ;
}
/* ── Heatmap Demo ── */
. heatmap-section {
background : var ( - - bg ) ;
}
. heatmap-wrap {
margin-top : 3 rem ;
background : var ( - - raised ) ;
border : 1 px solid var ( - - border ) ;
border-radius : 6 px ;
padding : 1.5 rem ;
}
. heatmap-header {
display : flex ;
align-items : center ;
justify-content : space-between ;
margin-bottom : 1 rem ;
}
. heatmap-title {
font-size : 0.8 rem ;
font-weight : 700 ;
letter-spacing : 0.06 em ;
text-transform : uppercase ;
color : var ( - - gold ) ;
}
. heatmap-stats {
display : flex ;
gap : 1.5 rem ;
}
. heatmap-stat { text-align : right ; }
. heatmap-stat-val {
font-size : 1.1 rem ;
font-weight : 800 ;
color : var ( - - gold ) ;
}
. heatmap-stat-lbl {
font-size : 0.62 rem ;
color : var ( - - muted ) ;
text-transform : uppercase ;
letter-spacing : 0.1 em ;
}
. heatmap-weeks {
display : flex ;
gap : 4 px ;
overflow-x : auto ;
padding-bottom : 4 px ;
}
. heatmap-week {
display : flex ;
flex-direction : column ;
gap : 3 px ;
flex-shrink : 0 ;
}
. heatmap-week-label {
font-size : 0.55 rem ;
color : var ( - - muted ) ;
text-align : center ;
height : 14 px ;
line-height : 14 px ;
}
. heatmap-cell {
width : 28 px ; height : 28 px ;
border-radius : 3 px ;
display : flex ;
align-items : center ;
justify-content : center ;
font-size : 0.58 rem ;
font-weight : 700 ;
color : #000 ;
}
. heatmap-cell . d0 { background : var ( - - elevated ) ; color : var ( - - muted ) ; }
. heatmap-cell . d1 { background : rgba ( 201 , 168 , 76 , 0.20 ) ; color : var ( - - gold ) ; }
. heatmap-cell . d2 { background : rgba ( 201 , 168 , 76 , 0.40 ) ; color : var ( - - gold - light ) ; }
. heatmap-cell . d3 { background : rgba ( 201 , 168 , 76 , 0.65 ) ; color : #1a1200 ; }
. heatmap-cell . d4 { background : var ( - - gold ) ; }
. heatmap-cell . d5 { background : var ( - - gold - light ) ; }
/* ── Features ── */
. features-section {
background : linear-gradient ( 180 deg , var ( - - bg ) 0 % , #0d0d0d 100 % ) ;
}
. features-grid {
display : grid ;
grid-template-columns : 1 fr 1 fr ;
gap : 4 rem ;
align-items : center ;
margin-top : 3 rem ;
}
@ media ( max-width : 768px ) {
. features-grid { grid-template-columns : 1 fr ; gap : 2 rem ; }
. app-mock { height : 420 px ; }
. mock-sidebar { width : 140 px ; }
nav { padding : 0 1 rem ; }
}
. feature-list {
list-style : none ;
display : flex ;
flex-direction : column ;
gap : 1.2 rem ;
}
. feature-item {
display : flex ;
gap : 1 rem ;
align-items : flex-start ;
}
. feature-icon-wrap {
width : 36 px ; height : 36 px ;
border-radius : 6 px ;
background : rgba ( 201 , 168 , 76 , 0.1 ) ;
border : 1 px solid rgba ( 201 , 168 , 76 , 0.2 ) ;
display : flex ; align-items : center ; justify-content : center ;
flex-shrink : 0 ;
font-size : 1 rem ;
}
. feature-text h4 {
font-size : 0.9 rem ;
font-weight : 700 ;
margin-bottom : 0.25 rem ;
}
. feature-text p {
font-size : 0.82 rem ;
color : var ( - - muted ) ;
line-height : 1.6 ;
}
/* Focus drawer mock */
. focus-mock {
background : var ( - - raised ) ;
border : 1 px solid var ( - - border ) ;
border-radius : 6 px ;
overflow : hidden ;
}
. focus-header {
padding : 12 px 16 px ;
border-bottom : 1 px solid var ( - - border ) ;
display : flex ;
align-items : center ;
justify-content : space-between ;
}
. focus-header-title {
font-size : 0.8 rem ;
font-weight : 700 ;
}
. focus-project-dot {
width : 8 px ; height : 8 px ;
border-radius : 50 % ;
display : inline-block ;
margin-right : 6 px ;
}
. focus-timeline {
display : flex ;
gap : 0 ;
padding : 1.5 rem 1 rem ;
overflow-x : auto ;
}
. timeline-item {
display : flex ;
flex-direction : column ;
align-items : center ;
gap : 8 px ;
min-width : 110 px ;
}
. timeline-card {
background : var ( - - elevated ) ;
border : 1 px solid var ( - - border ) ;
border-radius : 4 px ;
padding : 8 px 10 px ;
width : 100 % ;
text-align : center ;
}
. timeline-card . active {
border-color : var ( - - gold ) ;
box-shadow : 0 0 10 px rgba ( 201 , 168 , 76 , 0.25 ) ;
}
. timeline-card-title {
font-size : 0.67 rem ;
font-weight : 600 ;
color : var ( - - text ) ;
margin-bottom : 4 px ;
}
. timeline-card-date {
font-size : 0.58 rem ;
color : var ( - - muted ) ;
}
. timeline-line {
display : flex ;
align-items : center ;
width : 100 % ;
position : relative ;
}
. timeline-dot {
width : 8 px ; height : 8 px ;
border-radius : 50 % ;
background : var ( - - gold ) ;
flex-shrink : 0 ;
position : relative ;
z-index : 1 ;
}
. timeline-connector {
flex : 1 ;
height : 1 px ;
background : var ( - - border ) ;
}
. timeline-item : last-child . timeline-connector { display : none ; }
/* ── How It Works ── */
. how-section {
background : #0d0d0d ;
}
. steps-grid {
display : grid ;
grid-template-columns : repeat ( auto - fit , minmax ( 220 px , 1 fr ) ) ;
gap : 2 rem ;
margin-top : 3 rem ;
counter-reset : steps ;
}
. step-card {
position : relative ;
padding-left : 0 ;
}
. step-num {
font-size : 3 rem ;
font-weight : 900 ;
color : rgba ( 201 , 168 , 76 , 0.12 ) ;
line-height : 1 ;
margin-bottom : 0.75 rem ;
font-variant-numeric : tabular-nums ;
}
. step-title {
font-size : 1 rem ;
font-weight : 700 ;
margin-bottom : 0.5 rem ;
}
. step-desc {
font-size : 0.875 rem ;
color : var ( - - muted ) ;
line-height : 1.65 ;
}
/* ── Docker CTA ── */
. docker-section {
background : var ( - - bg ) ;
}
. docker-box {
background : var ( - - raised ) ;
border : 1 px solid var ( - - border ) ;
border-radius : 8 px ;
padding : 2 rem 2 rem 1.5 rem ;
margin-top : 2 rem ;
}
. docker-box-label {
font-size : 0.65 rem ;
font-weight : 700 ;
letter-spacing : 0.16 em ;
text-transform : uppercase ;
color : var ( - - muted ) ;
margin-bottom : 0.75 rem ;
}
. code-block {
background : #0a0a0a ;
border : 1 px solid var ( - - border ) ;
border-radius : 4 px ;
padding : 1 rem 1.25 rem ;
font-family : 'JetBrains Mono' , 'Fira Code' , 'Courier New' , monospace ;
font-size : 0.8 rem ;
color : var ( - - text ) ;
overflow-x : auto ;
margin-bottom : 1 rem ;
white-space : pre ;
line-height : 1.7 ;
}
. code-block . c-comment { color : var ( - - muted ) ; }
. code-block . c-gold { color : var ( - - gold ) ; }
. code-block . c-green { color : #4ADE80 ; }
. code-block . c-blue { color : #60A5FA ; }
/* ── Footer ── */
footer {
background : #0a0a0a ;
border-top : 1 px solid var ( - - border ) ;
padding : 2.5 rem 2 rem ;
text-align : center ;
}
. footer-brand {
display : flex ;
align-items : center ;
justify-content : center ;
gap : 8 px ;
margin-bottom : 0.75 rem ;
}
. footer-brand img {
width : 24 px ; height : 24 px ; object-fit : contain ;
}
. footer-brand span {
font-size : 0.85 rem ;
font-weight : 900 ;
letter-spacing : 0.14 em ;
color : var ( - - gold ) ;
}
. footer-note {
font-size : 0.78 rem ;
color : var ( - - muted ) ;
}
. gold-divider {
width : 60 px ;
height : 2 px ;
background : linear-gradient ( 90 deg , transparent , var ( - - gold ) , transparent ) ;
margin : 2 rem auto ;
}
/* ── Parallax scroll effect ── */
. parallax-layer {
will-change : transform ;
}
< / style >
< / head >
< body >
<!-- Navigation -->
< nav >
< a class = "nav-brand" href = "#" >
< img src = "/logo.png" alt = "FabDash logo" onerror = "this.style.display='none'" / >
< span > FABDASH< / span >
< / a >
< a class = "nav-cta" href = "/" > Open App → < / a >
< / nav >
<!-- ══════════════════════════════════════════
HERO
══════════════════════════════════════════ -->
< section class = "hero" >
< div class = "hero-bg" > < / div >
< div class = "hero-grid parallax-layer" data-speed = "0.3" > < / div >
< img src = "/logo.png" class = "hero-logo" alt = "FabDash logo"
onerror = "this.style.display='none'" / >
< p class = "hero-eyebrow" > Fabrication Project Dashboard< / p >
< h1 class = "hero-title" >
Track every cut, weld,< br / > and deadline — < em > visually< / em >
< / h1 >
< p class = "hero-sub" >
FabDash is a self-hosted calendar dashboard built for fabrication shops.
Drag, drop, and deliver on time — no spreadsheet gymnastics required.
< / p >
< div class = "hero-actions" >
< a href = "/" class = "btn-primary" > Open Dashboard → < / a >
< a href = "#demo" class = "btn-ghost" > See Demo ↓ < / a >
< / div >
< / section >
<!-- ══════════════════════════════════════════
DEMO PREVIEW
══════════════════════════════════════════ -->
< section class = "demo-section" id = "demo" >
< div class = "container" >
< p class = "section-label" > Live Demo< / p >
< h2 class = "section-title" > Your shop, at a glance< / h2 >
< p class = "section-sub" >
Every active project, every deliverable, color-coded and laid out on an
interactive calendar — exactly as it looks inside FabDash.
< / p >
< div class = "demo-shell" >
<!-- Window chrome -->
< div class = "demo-topbar" >
< span class = "dot dot-r" > < / span >
< span class = "dot dot-y" > < / span >
< span class = "dot dot-g" > < / span >
< span class = "demo-url" > fabdash.local:8080< / span >
< / div >
<!-- App layout -->
< div class = "app-mock" >
<!-- Sidebar -->
< div class = "mock-sidebar" >
< div class = "sidebar-logo-row" >
< img src = "/logo.png" alt = "" onerror = "this.style.display='none'" / >
< span class = "sidebar-wordmark" > FABDASH< / span >
< / div >
<!-- Project: Tucson -->
< div class = "sidebar-project" >
< div class = "project-bar" >
< span class = "project-dot" style = "background:#E67E22" > < / span >
< span class = "project-name" > Tucson< / span >
< span class = "project-count" > 4< / span >
< / div >
< div class = "deliverable-row" >
< span style = "flex:1;font-size:0.64rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" > Cut Extrusion< / span >
< span class = "badge badge-progress" > Active< / span >
< / div >
< div class = "deliverable-row" >
< span style = "flex:1;font-size:0.64rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" > Fabricate Assembly< / span >
< span class = "badge badge-upcoming" > Soon< / span >
< / div >
< / div >
<!-- Project: Mesa -->
< div class = "sidebar-project" >
< div class = "project-bar" >
< span class = "project-dot" style = "background:#3B82F6" > < / span >
< span class = "project-name" > Mesa< / span >
< span class = "project-count" > 3< / span >
< / div >
< div class = "deliverable-row" >
< span style = "flex:1;font-size:0.64rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" > Weld Subframe< / span >
< span class = "badge badge-complete" > Done< / span >
< / div >
< div class = "deliverable-row" >
< span style = "flex:1;font-size:0.64rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" > Paint & Finish< / span >
< span class = "badge badge-progress" > Active< / span >
< / div >
< / div >
<!-- Project: Phoenix Shelter -->
< div class = "sidebar-project" >
< div class = "project-bar" >
< span class = "project-dot" style = "background:#8B5CF6" > < / span >
< span class = "project-name" > Phoenix Shelter< / span >
< span class = "project-count" > 5< / span >
< / div >
< div class = "deliverable-row" >
< span style = "flex:1;font-size:0.64rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" > Roof Panel Cuts< / span >
< span class = "badge badge-overdue" > Late< / span >
< / div >
< / div >
<!-- Project: Scottsdale -->
< div class = "sidebar-project" >
< div class = "project-bar" >
< span class = "project-dot" style = "background:#10B981" > < / span >
< span class = "project-name" > Scottsdale< / span >
< span class = "project-count" > 2< / span >
< / div >
< / div >
< / div >
<!-- Calendar -->
< div class = "mock-main" >
< div class = "cal-header" >
< span class = "cal-title" > March 2026< / span >
< div class = "cal-nav" >
< button class = "cal-btn" > ← < / button >
< button class = "cal-btn active" > Month< / button >
< button class = "cal-btn" > Week< / button >
< button class = "cal-btn" > Today< / button >
< button class = "cal-btn" > → < / button >
< / div >
< / div >
< div class = "cal-grid" >
< div class = "cal-dow" >
< div class = "cal-dow-cell" > Sun< / div >
< div class = "cal-dow-cell" > Mon< / div >
< div class = "cal-dow-cell" > Tue< / div >
< div class = "cal-dow-cell" > Wed< / div >
< div class = "cal-dow-cell" > Thu< / div >
< div class = "cal-dow-cell" > Fri< / div >
< div class = "cal-dow-cell" > Sat< / div >
< / div >
< div class = "cal-weeks" >
<!-- Week 1 (Mar 1 → Mar 7) — note: Mar 1 is Sunday 2026 -->
< div class = "cal-week" >
< div class = "cal-day" > < div class = "day-num" > 1< / div > < / div >
< div class = "cal-day" > < div class = "day-num" > 2< / div >
< div class = "cal-event" style = "background:#E67E22" > Cut Extrusion< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 3< / div > < / div >
< div class = "cal-day" > < div class = "day-num" > 4< / div >
< div class = "cal-event" style = "background:#3B82F6" > Weld Subframe< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 5< / div >
< div class = "cal-event" style = "background:#8B5CF6" > Roof Cuts< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 6< / div >
< div class = "cal-event" style = "background:#10B981" > Scottsdale< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 7< / div > < / div >
< / div >
<!-- Week 2 -->
< div class = "cal-week" >
< div class = "cal-day" > < div class = "day-num" > 8< / div > < / div >
< div class = "cal-day" > < div class = "day-num" > 9< / div >
< div class = "cal-event" style = "background:#E67E22" > Fab Assembly< / div >
< / div >
< div class = "cal-day today" > < div class = "day-num" > 10< / div >
< div class = "cal-event" style = "background:#3B82F6" > Paint & Finish< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 11< / div > < / div >
< div class = "cal-day" > < div class = "day-num" > 12< / div >
< div class = "cal-event" style = "background:#8B5CF6" > Panel Weld< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 13< / div >
< div class = "cal-event" style = "background:#E67E22" > QC Check< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 14< / div > < / div >
< / div >
<!-- Week 3 -->
< div class = "cal-week" >
< div class = "cal-day" > < div class = "day-num" > 15< / div > < / div >
< div class = "cal-day" > < div class = "day-num" > 16< / div >
< div class = "cal-event" style = "background:#10B981" > Install Prep< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 17< / div > < / div >
< div class = "cal-day" > < div class = "day-num" > 18< / div >
< div class = "cal-event" style = "background:#E67E22" > Powder Coat< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 19< / div >
< div class = "cal-event" style = "background:#3B82F6" > Final QC< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 20< / div > < / div >
< div class = "cal-day" > < div class = "day-num" > 21< / div > < / div >
< / div >
<!-- Week 4 -->
< div class = "cal-week" >
< div class = "cal-day" > < div class = "day-num" > 22< / div >
< div class = "cal-event" style = "background:#8B5CF6" > Glass Mount< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 23< / div > < / div >
< div class = "cal-day" > < div class = "day-num" > 24< / div >
< div class = "cal-event" style = "background:#E67E22" > Ship TUC-01< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 25< / div > < / div >
< div class = "cal-day" > < div class = "day-num" > 26< / div >
< div class = "cal-event" style = "background:#10B981" > Delivery< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 27< / div > < / div >
< div class = "cal-day" > < div class = "day-num" > 28< / div > < / div >
< / div >
<!-- Week 5 -->
< div class = "cal-week" >
< div class = "cal-day" > < div class = "day-num" > 29< / div > < / div >
< div class = "cal-day" > < div class = "day-num" > 30< / div >
< div class = "cal-event" style = "background:#3B82F6" > MSA Closeout< / div >
< / div >
< div class = "cal-day" > < div class = "day-num" > 31< / div > < / div >
< div class = "cal-day other-month" > < div class = "day-num" > 1< / div > < / div >
< div class = "cal-day other-month" > < div class = "day-num" > 2< / div > < / div >
< div class = "cal-day other-month" > < div class = "day-num" > 3< / div > < / div >
< div class = "cal-day other-month" > < div class = "day-num" > 4< / div > < / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / section >
<!-- ══════════════════════════════════════════
BENEFITS
══════════════════════════════════════════ -->
< section class = "benefits-section" id = "benefits" >
< div class = "container" >
< p class = "section-label" > Why FabDash< / p >
< h2 class = "section-title" > Built for the shop floor,< br / > not the boardroom< / h2 >
< p class = "section-sub" >
Spreadsheets break. Sticky notes fall. FabDash keeps every project visible
and every deadline accountable — in your browser, on your server.
< / p >
< div class = "benefits-grid" >
< div class = "benefit-card" >
< div class = "benefit-icon" > 🗓️< / div >
< h3 class = "benefit-title" > Drag-and-drop calendar< / h3 >
< p class = "benefit-desc" >
Reschedule deliverables by dragging events between days. Month, week,
and day views let you zoom in when deadlines stack up.
< / p >
< / div >
< div class = "benefit-card" >
< div class = "benefit-icon" > 🎨< / div >
< h3 class = "benefit-title" > Color-coded projects< / h3 >
< p class = "benefit-desc" >
Assign a distinct color to each job. At a glance you know which events
belong to which project — no key required.
< / p >
< / div >
< div class = "benefit-card" >
< div class = "benefit-icon" > 🌡️< / div >
< h3 class = "benefit-title" > Workload heatmap< / h3 >
< p class = "benefit-desc" >
A 20-week density grid reveals crunch periods before they sneak up on
your crew. Spot hot weeks and redistribute the load.
< / p >
< / div >
< div class = "benefit-card" >
< div class = "benefit-icon" > ⚡< / div >
< h3 class = "benefit-title" > Instant status tracking< / h3 >
< p class = "benefit-desc" >
Upcoming, In Progress, Completed, and Overdue statuses are
auto-computed and color-coded so nothing slips through the cracks.
< / p >
< / div >
< div class = "benefit-card" >
< div class = "benefit-icon" > 🔄< / div >
< h3 class = "benefit-title" > Undo any action< / h3 >
< p class = "benefit-desc" >
Deleted a deliverable by accident? A 30-second countdown toast lets you
reverse any change before it's permanent.
< / p >
< / div >
< div class = "benefit-card" >
< div class = "benefit-icon" > 🔒< / div >
< h3 class = "benefit-title" > 100% self-hosted< / h3 >
< p class = "benefit-desc" >
Your project data never leaves your network. Deploy with a single Docker
command and own everything — database and all.
< / p >
< / div >
< / div >
< / div >
< / section >
<!-- ══════════════════════════════════════════
HEATMAP DEMO
══════════════════════════════════════════ -->
< section class = "heatmap-section" >
< div class = "container" >
< p class = "section-label" > Workload Heatmap< / p >
< h2 class = "section-title" > See crunch before it hits< / h2 >
< p class = "section-sub" >
The 20-week heatmap shows deliverable density across your entire pipeline.
Gold-hot cells flag overloaded weeks so you can rebalance while there's
still time.
< / p >
< div class = "heatmap-wrap" >
< div class = "heatmap-header" >
< span class = "heatmap-title" > 20-Week Workload — March → July 2026< / span >
< div class = "heatmap-stats" >
< div class = "heatmap-stat" >
< div class = "heatmap-stat-val" > 18< / div >
< div class = "heatmap-stat-lbl" > Active< / div >
< / div >
< div class = "heatmap-stat" >
< div class = "heatmap-stat-val" > 4< / div >
< div class = "heatmap-stat-lbl" > Projects< / div >
< / div >
< div class = "heatmap-stat" >
< div class = "heatmap-stat-val" > 3< / div >
< div class = "heatmap-stat-lbl" > Overdue< / div >
< / div >
< / div >
< / div >
< div class = "heatmap-weeks" id = "heatmap" > < / div >
< / div >
< / div >
< / section >
<!-- ══════════════════════════════════════════
FEATURE DETAILS (Focus View)
══════════════════════════════════════════ -->
< section class = "features-section" id = "features" >
< div class = "container" >
< div class = "features-grid" >
<!-- Focus timeline mock -->
< div class = "focus-mock" >
< div class = "focus-header" >
< span class = "focus-header-title" >
< span class = "focus-project-dot" style = "background:#E67E22" > < / span >
Tucson — Focus View
< / span >
< span class = "badge badge-progress" > 4 deliverables< / span >
< / div >
< div class = "focus-timeline" >
< div class = "timeline-item" >
< div class = "timeline-card" >
< div class = "timeline-card-title" > Cut Extrusion< / div >
< div class = "timeline-card-date" > Mar 2< / div >
< div style = "margin-top:4px" > < span class = "badge badge-complete" > Done< / span > < / div >
< / div >
< div class = "timeline-line" >
< div class = "timeline-dot" style = "background:#22C55E" > < / div >
< div class = "timeline-connector" > < / div >
< / div >
< / div >
< div class = "timeline-item" >
< div class = "timeline-card active" >
< div class = "timeline-card-title" > Fab Assembly< / div >
< div class = "timeline-card-date" > Mar 9< / div >
< div style = "margin-top:4px" > < span class = "badge badge-progress" > Active< / span > < / div >
< / div >
< div class = "timeline-line" >
< div class = "timeline-dot" style = "background:#F59E0B" > < / div >
< div class = "timeline-connector" > < / div >
< / div >
< / div >
< div class = "timeline-item" >
< div class = "timeline-card" >
< div class = "timeline-card-title" > QC Check< / div >
< div class = "timeline-card-date" > Mar 13< / div >
< div style = "margin-top:4px" > < span class = "badge badge-upcoming" > Soon< / span > < / div >
< / div >
< div class = "timeline-line" >
< div class = "timeline-dot" style = "background:#0EA5E9" > < / div >
< div class = "timeline-connector" > < / div >
< / div >
< / div >
< div class = "timeline-item" >
< div class = "timeline-card" >
< div class = "timeline-card-title" > Ship TUC-01< / div >
< div class = "timeline-card-date" > Mar 24< / div >
< div style = "margin-top:4px" > < span class = "badge badge-upcoming" > Soon< / span > < / div >
< / div >
< div class = "timeline-line" >
< div class = "timeline-dot" > < / div >
< div class = "timeline-connector" > < / div >
< / div >
< / div >
< / div >
< / div >
<!-- Feature list -->
< div >
< p class = "section-label" > Focus View< / p >
< h2 class = "section-title" > Zoom in on one project< / h2 >
< p class = "section-sub" style = "margin-bottom:1.75rem" >
Click any project to slide up a horizontal timeline showing every
deliverable in order. Edit, drag, and update status without leaving
the view.
< / p >
< ul class = "feature-list" >
< li class = "feature-item" >
< div class = "feature-icon-wrap" > 🖱️< / div >
< div class = "feature-text" >
< h4 > Double-click to edit< / h4 >
< p > Double-click any deliverable card to open an inline editor — change the title, date, or status in one step.< / p >
< / div >
< / li >
< li class = "feature-item" >
< div class = "feature-icon-wrap" > ⌨️< / div >
< div class = "feature-text" >
< h4 > Keyboard shortcuts< / h4 >
< p > N to create, B to toggle the sidebar, ← → to navigate months, T to jump to today, Esc to close modals.< / p >
< / div >
< / li >
< li class = "feature-item" >
< div class = "feature-icon-wrap" > 📁< / div >
< div class = "feature-text" >
< h4 > Google Drive link< / h4 >
< p > Attach a Drive folder to each project so every drawing, spec sheet, and photo lives one click away.< / p >
< / div >
< / li >
< li class = "feature-item" >
< div class = "feature-icon-wrap" > 🖱️< / div >
< div class = "feature-text" >
< h4 > Right-click context menus< / h4 >
< p > Right-click any event or deliverable to edit, duplicate, change status, or delete — no menu diving.< / p >
< / div >
< / li >
< / ul >
< / div >
< / div >
< / div >
< / section >
<!-- ══════════════════════════════════════════
HOW IT WORKS
══════════════════════════════════════════ -->
< section class = "how-section" id = "how" >
< div class = "container" >
< p class = "section-label" > Getting Started< / p >
< h2 class = "section-title" > Up and running in minutes< / h2 >
< p class = "section-sub" >
One Docker command. No accounts, no SaaS subscriptions, no per-seat pricing.
Just your data on your hardware.
< / p >
< div class = "steps-grid" >
< div class = "step-card" >
< div class = "step-num" > 01< / div >
< h3 class = "step-title" > Pull the image< / h3 >
< p class = "step-desc" > Run < code style = "color:var(--gold)" > docker run< / code > with a volume mount for persistent storage. That's the entire installation.< / p >
< / div >
< div class = "step-card" >
< div class = "step-num" > 02< / div >
< h3 class = "step-title" > Create a project< / h3 >
< p class = "step-desc" > Hit < kbd style = "background:var(--elevated);border:1px solid var(--border);border-radius:3px;padding:1px 5px;font-size:0.8em" > N< / kbd > or click the + button. Name it, pick a color, add an optional Drive link.< / p >
< / div >
< div class = "step-card" >
< div class = "step-num" > 03< / div >
< h3 class = "step-title" > Add deliverables< / h3 >
< p class = "step-desc" > Each deliverable gets a title, a due date, and a status. They appear instantly on the calendar as color-coded events.< / p >
< / div >
< div class = "step-card" >
< div class = "step-num" > 04< / div >
< h3 class = "step-title" > Drag and deliver< / h3 >
< p class = "step-desc" > Reschedule by dragging events. Mark complete with a right-click. Watch the heatmap cool down as work ships.< / p >
< / div >
< / div >
< / div >
< / section >
<!-- ══════════════════════════════════════════
DOCKER CTA
══════════════════════════════════════════ -->
< section class = "docker-section" >
< div class = "container" style = "text-align:center" >
< p class = "section-label" > Self-Hosted< / p >
< h2 class = "section-title" > Deploy on your own hardware< / h2 >
< p class = "section-sub" style = "margin:1rem auto 0" >
FabDash ships as a single Docker container with an embedded SQLite database.
Works perfectly on Unraid, TrueNAS, a Raspberry Pi, or any Linux server.
< / p >
< div class = "docker-box" style = "max-width:680px;margin:2rem auto 0;text-align:left" >
< div class = "docker-box-label" > Quick Start< / div >
< div class = "code-block" > < span class = "c-comment" > # Pull and run FabDash< / span >
docker run -d \
--name fabdash \
-p < span class = "c-blue" > 8080< / span > :< span class = "c-blue" > 8080< / span > \
-v < span class = "c-green" > ./data< / span > :/app/data \
--restart unless-stopped \
fabdash:latest< / div >
< div class = "docker-box-label" style = "margin-top:1rem" > Then open< / div >
< div class = "code-block" > < span class = "c-gold" > http://your-server-ip:< span class = "c-blue" > 8080< / span > < / span > < / div >
< / div >
< div style = "margin-top:2.5rem" >
< a href = "/" class = "btn-primary" style = "font-size:1rem;padding:14px 36px" >
Open FabDash →
< / a >
< / div >
< / div >
< / section >
< div class = "gold-divider" > < / div >
<!-- ══════════════════════════════════════════
FOOTER
══════════════════════════════════════════ -->
< footer >
< div class = "footer-brand" >
< img src = "/logo.png" alt = "" onerror = "this.style.display='none'" / >
< span > FABDASH< / span >
< / div >
< p class = "footer-note" > Self-hosted fabrication dashboard — your shop, your data.< / p >
2026-03-06 04:59:40 +00:00
< a href = "https://github.com/jasonMPM/fabdash" target = "_blank" rel = "noopener"
style = "display:inline-flex;align-items:center;gap:6px;margin-top:0.75rem;font-size:0.78rem;color:var(--muted);text-decoration:none;transition:color 0.2s"
onmouseover = "this.style.color='var(--gold)'" onmouseout = "this.style.color='var(--muted)'" >
< svg width = "15" height = "15" viewBox = "0 0 24 24" fill = "currentColor" aria-hidden = "true" >
< path d = "M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" / >
< / svg >
github.com/jasonMPM/fabdash
< / a >
2026-03-06 04:55:42 +00:00
< / footer >
<!-- ══════════════════════════════════════════
SCRIPTS
══════════════════════════════════════════ -->
< script >
/* ── Heatmap generator ── */
( function ( ) {
const density = [ 0 , 1 , 0 , 2 , 3 , 1 , 0 , 2 , 4 , 5 , 3 , 2 , 1 , 0 , 2 , 3 , 4 , 2 , 1 , 0 ] ;
const labels = [
'Mar 1' , 'Mar 8' , 'Mar 15' , 'Mar 22' , 'Mar 29' ,
'Apr 5' , 'Apr 12' , 'Apr 19' , 'Apr 26' ,
'May 3' , 'May 10' , 'May 17' , 'May 24' , 'May 31' ,
'Jun 7' , 'Jun 14' , 'Jun 21' , 'Jun 28' ,
'Jul 5' , 'Jul 12'
] ;
const wrap = document . getElementById ( 'heatmap' ) ;
density . forEach ( ( d , i ) => {
const col = document . createElement ( 'div' ) ;
col . className = 'heatmap-week' ;
const lbl = document . createElement ( 'div' ) ;
lbl . className = 'heatmap-week-label' ;
lbl . textContent = labels [ i ] ;
col . appendChild ( lbl ) ;
for ( let row = 0 ; row < 5 ; row ++ ) {
const cell = document . createElement ( 'div' ) ;
const v = Math . max ( 0 , Math . min ( 5 , d + Math . round ( ( Math . random ( ) - 0.4 ) * 1.2 ) ) ) ;
cell . className = ` heatmap-cell d ${ v } ` ;
cell . textContent = v || '' ;
col . appendChild ( cell ) ;
}
wrap . appendChild ( col ) ;
} ) ;
} ) ( ) ;
/* ── Parallax on scroll ── */
( function ( ) {
const layers = document . querySelectorAll ( '.parallax-layer' ) ;
function onScroll ( ) {
const y = window . scrollY ;
layers . forEach ( el => {
const speed = parseFloat ( el . dataset . speed || 0.2 ) ;
el . style . transform = ` translateY( ${ y * speed } px) ` ;
} ) ;
}
window . addEventListener ( 'scroll' , onScroll , { passive : true } ) ;
} ) ( ) ;
/* ── Scroll-reveal ── */
( function ( ) {
const style = document . createElement ( 'style' ) ;
style . textContent = `
.reveal { opacity: 0; transform: translateY(28px); transition: opacity 0.55s ease, transform 0.55s ease; }
.reveal.visible { opacity: 1; transform: none; }
` ;
document . head . appendChild ( style ) ;
const targets = document . querySelectorAll (
'.benefit-card, .step-card, .feature-item, .focus-mock, .heatmap-wrap, .demo-shell, .docker-box'
) ;
targets . forEach ( ( el , i ) => {
el . classList . add ( 'reveal' ) ;
el . style . transitionDelay = ` ${ ( i % 4 ) * 80 } ms ` ;
} ) ;
const io = new IntersectionObserver ( entries => {
entries . forEach ( e => {
if ( e . isIntersecting ) { e . target . classList . add ( 'visible' ) ; io . unobserve ( e . target ) ; }
} ) ;
} , { threshold : 0.12 } ) ;
targets . forEach ( el => io . observe ( el ) ) ;
} ) ( ) ;
< / script >
< / body >
< / html >