diff --git a/.vercelignore b/.vercelignore new file mode 100644 index 0000000..03f6826 --- /dev/null +++ b/.vercelignore @@ -0,0 +1,62 @@ +# Vercel Ignore File +# Optimize deployment by excluding unnecessary files + +# Development files +.git/ +.gitignore +.DS_Store +.vscode/ +.idea/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Hugo development files +resources/_gen/ +.hugo_build.lock +hugo_stats.json + +# Node modules (if any) +node_modules/ +package-lock.json +yarn.lock + +# Build artifacts (will be regenerated) +public/ + +# Development scripts +dev.sh +verify-urls.sh + +# Documentation +readme.md +README.md +*.md + +# Temporary files +*.tmp +*.temp +.cache/ + +# OS generated files +Thumbs.db +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db + +# IDE files +*.swp +*.swo +*~ +.vscode/ +.idea/ + +# Hugo archetypes (not needed for production) +archetypes/ + +# Scripts (except build.sh) +scripts/ +!build.sh diff --git a/assets/css/base.css b/assets/css/base.css new file mode 100644 index 0000000..7188d3f --- /dev/null +++ b/assets/css/base.css @@ -0,0 +1,319 @@ +/* + * Base Styles - Reset, Typography, Global Elements + * Modern CSS Architecture - Senior Frontend Developer Approach + */ + +/* ===== CSS RESET & NORMALIZATION ===== */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + font-size: 16px; + line-height: 1.5; + -webkit-text-size-adjust: 100%; + scroll-behavior: smooth; + overflow-x: hidden; +} + +body { + font-family: var(--font-family-base); + font-size: var(--font-size-base); + line-height: var(--line-height-base); + color: var(--color-text); + background-color: var(--color-background); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + margin: 0; + overflow-x: hidden; +} + +/* ===== TYPOGRAPHY SCALE ===== */ +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-family-heading); + line-height: var(--line-height-tight); + margin-bottom: var(--space-4); + color: var(--color-text); + font-weight: 700; +} + +h1 { + font-size: var(--font-size-5xl); + letter-spacing: -0.02em; +} + +h2 { + font-size: var(--font-size-4xl); + letter-spacing: -0.01em; +} + +h3 { + font-size: var(--font-size-3xl); +} + +h4 { + font-size: var(--font-size-2xl); +} + +h5 { + font-size: var(--font-size-xl); +} + +h6 { + font-size: var(--font-size-lg); +} + +p { + margin-bottom: var(--space-4); + color: var(--color-text); +} + +/* ===== LINKS ===== */ +a { + color: var(--color-primary); + text-decoration: none; + transition: color var(--transition-fast) ease-in-out; +} + +a:hover, +a:focus { + color: var(--color-primary-hover); + text-decoration: underline; +} + +a:focus { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} + +/* ===== LISTS ===== */ +ul, ol { + margin-bottom: var(--space-4); + padding-left: var(--space-6); +} + +li { + margin-bottom: var(--space-2); +} + +/* ===== IMAGES ===== */ +img { + max-width: 100%; + height: auto; + display: block; +} + +/* ===== FORM ELEMENTS ===== */ +button { + font-family: inherit; + font-size: inherit; + line-height: inherit; + cursor: pointer; + border: none; + background: none; + padding: 0; +} + +input, textarea, select { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +/* ===== SELECTION ===== */ +::selection { + background-color: var(--color-primary); + color: var(--color-white); +} + +::-moz-selection { + background-color: var(--color-primary); + color: var(--color-white); +} + +/* ===== SCROLLBAR STYLING ===== */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--color-surface); +} + +::-webkit-scrollbar-thumb { + background: var(--color-border); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--color-text-muted); +} + +/* ===== ACCESSIBILITY ===== */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + +/* Enhanced focus management */ +.js-focus-visible :focus:not(.focus-visible) { + outline: none; +} + +:focus-visible { + outline: 2px solid var(--color-primary); + outline-offset: 2px; + border-radius: var(--radius-sm); +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + :root { + --color-border: #000000; + --color-text: #000000; + --color-background: #ffffff; + } + + [data-theme="dark"] { + --color-border: #ffffff; + --color-text: #ffffff; + --color-background: #000000; + } +} + +/* Screen reader only content */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* Skip link for keyboard navigation */ +.skip-link { + position: absolute; + top: -40px; + left: 6px; + background: var(--color-primary); + color: var(--color-white); + padding: var(--space-2) var(--space-3); + text-decoration: none; + border-radius: var(--radius-base); + font-weight: 500; + z-index: 10000; + transition: top var(--transition-fast) var(--ease-out); +} + +.skip-link:focus { + top: 6px; + outline: 2px solid var(--color-white); + outline-offset: 2px; +} + +/* Improve text selection contrast */ +::selection { + background-color: var(--color-primary); + color: var(--color-white); + text-shadow: none; +} + +::-moz-selection { + background-color: var(--color-primary); + color: var(--color-white); + text-shadow: none; +} + +/* Ensure interactive elements have sufficient size */ +button, +a, +input, +select, +textarea { + min-height: 44px; + min-width: 44px; +} + +/* Improve link accessibility */ +a:not([class]) { + text-decoration: underline; + text-decoration-skip-ink: auto; + text-underline-offset: 0.3em; +} + +a:not([class]):hover, +a:not([class]):focus { + text-decoration-thickness: 2px; +} + +/* Print styles for accessibility */ +@media print { + *, + *::before, + *::after { + background: transparent !important; + color: black !important; + box-shadow: none !important; + text-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]::after { + content: " (" attr(href) ")"; + } + + abbr[title]::after { + content: " (" attr(title) ")"; + } + + pre { + white-space: pre-wrap !important; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + thead { + display: table-header-group; + } + + tr, + img { + page-break-inside: avoid; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } +} diff --git a/assets/css/components.css b/assets/css/components.css new file mode 100644 index 0000000..f1c2fb2 --- /dev/null +++ b/assets/css/components.css @@ -0,0 +1,639 @@ +/* + * Component Styles - Reusable UI Components + * BEM Methodology with Design System Approach + */ + +/* ===== SITE HEADER ===== */ +.site-header { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: var(--z-fixed); + background: var(--glass-bg); + backdrop-filter: var(--glass-backdrop); + -webkit-backdrop-filter: var(--glass-backdrop); + border-bottom: 1px solid var(--color-border); + transition: + background-color var(--transition-fast) var(--ease-in-out), + box-shadow var(--transition-fast) var(--ease-in-out), + padding var(--transition-fast) var(--ease-in-out); +} + +.site-header__container { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-3) var(--space-8); + position: relative; + max-width: var(--container-max-width); + margin: 0 auto; + height: var(--header-height); +} + +.site-header--scrolled { + background: rgba(248, 247, 243, 0.98); + box-shadow: var(--shadow-md); +} + +[data-theme="dark"] .site-header { + background: rgba(10, 10, 10, 0.95); +} + +[data-theme="dark"] .site-header--scrolled { + background: rgba(10, 10, 10, 0.98); +} + +/* ===== LOGO ===== */ +.logo { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + font-family: var(--font-family-heading); + font-weight: 700; + font-size: var(--font-size-lg); + letter-spacing: 0.02em; + color: var(--color-text); + text-decoration: none; + transition: none !important; + z-index: 1; +} + +.logo:hover { + color: var(--color-primary); + text-decoration: none; +} + +/* ===== THEME TOGGLE ===== */ +.theme-toggle { + background: transparent; + border: none; + padding: var(--space-1) var(--space-2); + cursor: pointer; + font-family: var(--font-family-heading); + font-size: var(--font-size-xs); + font-weight: 500; + color: var(--color-text); + border-radius: var(--radius-base); + transition: + color var(--transition-fast) var(--ease-in-out), + background-color var(--transition-fast) var(--ease-in-out); +} + +.theme-toggle:hover { + color: var(--color-primary); + background-color: var(--color-primary-50); +} + +.theme-toggle:focus { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} + +[data-theme="dark"] .theme-toggle:hover { + color: var(--color-accent); + background-color: rgba(255, 215, 0, 0.1); +} + +.theme-text { + transition: none !important; +} + + + +/* ===== MAIN SITE LINK ===== */ +.main-site-link { + font-family: var(--font-family-heading); + font-weight: 500; + font-size: var(--font-size-xs); + color: var(--color-text); + text-decoration: none; + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-base); + border: 1px solid var(--color-border); + transition: + color var(--transition-fast) var(--ease-in-out), + border-color var(--transition-fast) var(--ease-in-out), + background-color var(--transition-fast) var(--ease-in-out); +} + +.main-site-link:hover { + color: var(--color-primary); + border-color: var(--color-primary); + background-color: var(--color-primary-50); + text-decoration: none; +} + +.main-site-link:focus { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} + +.theme-text--light { + display: none; +} + +.theme-text--dark { + display: inline; +} + +[data-theme="dark"] .theme-text--light { + display: inline; +} + +[data-theme="dark"] .theme-text--dark { + display: none; +} + +[data-theme="dark"] .main-site-link:hover { + color: var(--color-accent); + background-color: rgba(255, 215, 0, 0.1); +} + +/* ===== HERO SECTION ===== */ +.hero { + text-align: center; + padding: var(--space-20) 0; + margin-top: var(--header-height); +} + +.hero__headline { + font-family: var(--font-family-heading); + font-size: clamp(var(--font-size-4xl), 8vw, var(--font-size-7xl)); + line-height: var(--line-height-tight); + text-transform: uppercase; + margin: 0; + font-weight: 700; + color: var(--color-text); +} + +.hero__interactive-o { + display: inline-block; + width: 1em; + height: 0.4em; + background: linear-gradient(120deg, var(--color-accent), var(--color-primary)); + border-radius: var(--radius-full); + vertical-align: middle; + margin: 0 -0.05em; + position: relative; + top: -0.1em; +} + +.hero__interactive-arrow { + display: inline-block; + width: 0.8em; + height: 0.8em; + background: linear-gradient(160deg, var(--color-primary), #6DD5FA); + clip-path: polygon(0% 0%, 100% 0%, 100% 50%, 50% 100%, 0% 50%); + vertical-align: middle; + position: relative; + top: 0.1em; + margin-left: -0.2em; +} + +/* ===== POST LIST ===== */ +.post-list { + margin-top: var(--space-16); +} + +.post-item { + padding: var(--space-6) 0; + border-bottom: 1px solid var(--color-border); + transition: + border-color var(--transition-fast) var(--ease-in-out), + transform var(--transition-fast) var(--ease-out); +} + +.post-item:last-child { + border-bottom: none; +} + +.post-item:hover { + transform: translateY(-2px); +} + +.post-item__header { + margin-bottom: var(--space-3); +} + +.post-item__title { + font-family: var(--font-family-heading); + font-size: var(--font-size-2xl); + margin: 0 0 var(--space-2) 0; + line-height: var(--line-height-tight); +} + +.post-item__link { + color: var(--color-text); + text-decoration: none; + transition: color var(--transition-fast) var(--ease-in-out); +} + +.post-item__link:hover { + color: var(--color-primary); +} + +.post-item__meta { + display: flex; + align-items: center; + gap: var(--space-2); + flex-wrap: wrap; + color: var(--color-text-muted); + font-size: var(--font-size-sm); + margin-bottom: var(--space-2); +} + +.post-item__date { + color: var(--color-text-muted); +} + +.post-item__tags { + display: flex; + gap: var(--space-1); + flex-wrap: wrap; +} + +.post-item__tag { + background: var(--color-gray-100); + color: var(--color-text-muted); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-sm); + font-size: var(--font-size-xs); + font-weight: 500; +} + +[data-theme="dark"] .post-item__tag { + background: var(--color-gray-800); + color: var(--color-text-muted); +} + +.post-item__excerpt { + color: var(--color-text-muted); + line-height: var(--line-height-relaxed); + margin-top: var(--space-2); +} + +/* ===== POST CONTENT ===== */ +.post { + max-width: var(--content-max-width); + margin: var(--space-16) auto; + padding-top: var(--header-height); +} + +.post__header { + margin-bottom: var(--space-8); + text-align: center; + border-bottom: 1px solid var(--color-border); + padding-bottom: var(--space-8); +} + +.post__title { + font-family: var(--font-family-heading); + font-size: clamp(var(--font-size-3xl), 6vw, var(--font-size-5xl)); + margin-bottom: var(--space-4); + color: var(--color-text); +} + +.post__meta { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-4); + flex-wrap: wrap; + color: var(--color-text-muted); + font-size: var(--font-size-sm); + margin-bottom: var(--space-4); +} + +.post__meta-separator { + opacity: 0.5; +} + +.post__description { + font-size: var(--font-size-lg); + color: var(--color-text-muted); + font-style: italic; + max-width: 600px; + margin: 0 auto; + line-height: var(--line-height-relaxed); +} + +.post__content { + font-size: var(--font-size-lg); + line-height: var(--line-height-relaxed); +} + +.post__content h2, +.post__content h3, +.post__content h4 { + margin-top: var(--space-8); + margin-bottom: var(--space-4); +} + +.post__content p { + margin-bottom: var(--space-6); +} + +.post__content img { + border-radius: var(--radius-md); + box-shadow: var(--shadow-md); + margin: var(--space-6) 0; +} + +.post__content figure { + margin: var(--space-8) 0; + text-align: center; +} + +.post__content figcaption { + margin-top: var(--space-2); + font-size: var(--font-size-sm); + color: var(--color-text-muted); + font-style: italic; +} + +.post__date { + color: var(--color-text-muted); +} + +.post__reading-time { + color: var(--color-text-muted); +} + +.post__categories { + display: flex; + gap: var(--space-1); + flex-wrap: wrap; +} + +.post__category-link { + color: var(--color-primary); + text-decoration: none; + font-weight: 500; + transition: color var(--transition-fast) var(--ease-in-out); +} + +.post__category-link:hover { + color: var(--color-primary-hover); +} + +.post__footer { + margin-top: var(--space-12); +} + +/* ===== TAGS ===== */ +.post__tags { + margin: var(--space-8) 0; + padding: var(--space-6) 0; + border-top: 1px solid var(--color-border); +} + +.post__tags-title { + margin: 0 0 var(--space-4) 0; + font-size: var(--font-size-lg); + color: var(--color-text); +} + +.post__tags-list { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); +} + +.tag { + display: inline-block; + background: var(--color-primary); + color: var(--color-white); + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-full); + text-decoration: none; + font-size: var(--font-size-sm); + font-weight: 500; + transition: + transform var(--transition-fast) var(--ease-out), + background-color var(--transition-fast) var(--ease-in-out); +} + +.tag:hover { + transform: translateY(-2px); + background-color: var(--color-primary-hover); + color: var(--color-white); + text-decoration: none; +} + +/* ===== SOCIAL SHARING ===== */ +.social-sharing { + margin: var(--space-8) 0; + padding: var(--space-6) 0; + border-top: 1px solid var(--color-border); + text-align: center; +} + +.social-sharing__title { + margin: 0 0 var(--space-4) 0; + font-size: var(--font-size-lg); + color: var(--color-text); +} + +.social-sharing__buttons { + display: flex; + justify-content: center; + gap: var(--space-4); + flex-wrap: wrap; +} + +.social-btn { + display: inline-block; + padding: var(--space-3) var(--space-4); + border-radius: var(--radius-md); + text-decoration: none; + font-size: var(--font-size-sm); + font-weight: 500; + transition: + opacity var(--transition-fast) var(--ease-in-out), + transform var(--transition-fast) var(--ease-out); +} + +.social-btn:hover { + opacity: 0.8; + transform: translateY(-1px); + text-decoration: none; +} + +.social-btn--twitter { + background: #1DA1F2; + color: var(--color-white); +} + +.social-btn--linkedin { + background: #0077B5; + color: var(--color-white); +} + +/* ===== POST NAVIGATION ===== */ +.post-nav { + margin: var(--space-8) 0; + padding: var(--space-6) 0; + border-top: 1px solid var(--color-border); +} + +.post-nav__container { + display: flex; + justify-content: space-between; + gap: var(--space-8); +} + +.post-nav__item { + flex: 1; + min-width: 200px; +} + +.post-nav__item--next { + text-align: right; +} + +.post-nav__label { + margin: 0 0 var(--space-2) 0; + font-size: var(--font-size-xs); + text-transform: uppercase; + color: var(--color-text-muted); + letter-spacing: 0.05em; +} + +.post-nav__link { + color: var(--color-primary); + text-decoration: none; + font-weight: 500; + transition: color var(--transition-fast) var(--ease-in-out); +} + +.post-nav__link:hover { + color: var(--color-primary-hover); + text-decoration: underline; +} + +/* ===== BUTTONS ===== */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: var(--space-3) var(--space-6); + font-family: var(--font-family-base); + font-size: var(--font-size-base); + font-weight: 500; + line-height: 1; + text-decoration: none; + border: none; + border-radius: var(--radius-md); + cursor: pointer; + transition: + background-color var(--transition-fast) var(--ease-in-out), + color var(--transition-fast) var(--ease-in-out), + transform var(--transition-fast) var(--ease-out), + box-shadow var(--transition-fast) var(--ease-in-out); +} + +.btn:hover { + transform: translateY(-1px); + text-decoration: none; +} + +.btn:focus { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} + +.btn--primary { + background-color: var(--color-primary); + color: var(--color-white); +} + +.btn--primary:hover { + background-color: var(--color-primary-hover); + color: var(--color-white); + box-shadow: var(--shadow-lg); +} + +.btn--secondary { + background-color: var(--color-surface); + color: var(--color-text); + border: 1px solid var(--color-border); +} + +.btn--secondary:hover { + background-color: var(--color-gray-50); + color: var(--color-text); +} + +.btn--ghost { + background-color: transparent; + color: var(--color-primary); +} + +.btn--ghost:hover { + background-color: var(--color-primary-50); + color: var(--color-primary); +} + +/* ===== PAGINATION ===== */ +.pagination { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-4); + margin: var(--space-12) 0; + padding: var(--space-6) 0; + border-top: 1px solid var(--color-border); +} + +.pagination__link { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-4); + background-color: var(--color-surface); + color: var(--color-primary); + text-decoration: none; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + font-weight: 500; + transition: + background-color var(--transition-fast) var(--ease-in-out), + border-color var(--transition-fast) var(--ease-in-out), + transform var(--transition-fast) var(--ease-out); +} + +.pagination__link:hover { + background-color: var(--color-primary-50); + border-color: var(--color-primary); + transform: translateY(-1px); + text-decoration: none; +} + +.pagination__link--prev { + /* Specific styles for previous link if needed */ +} + +.pagination__link--next { + /* Specific styles for next link if needed */ +} + +.pagination__info { + color: var(--color-text-muted); + font-size: var(--font-size-sm); + font-weight: 500; +} + +/* Mobile pagination */ +@media (max-width: 47.9375rem) { + .pagination { + flex-direction: column; + gap: var(--space-3); + } + + .pagination__link { + width: 100%; + justify-content: center; + } +} diff --git a/assets/css/layout.css b/assets/css/layout.css new file mode 100644 index 0000000..48244bf --- /dev/null +++ b/assets/css/layout.css @@ -0,0 +1,366 @@ +/* + * Layout System - Grid, Flexbox, Container, Positioning + * Mobile-First Responsive Design Approach + */ + +/* ===== GLOBAL LAYOUT SETUP ===== */ +body { + padding-top: var(--header-height); +} + +/* ===== CONTAINER SYSTEM ===== */ +.container { + max-width: var(--container-max-width); + margin: 0 auto; + padding: 0 var(--space-8); + width: 100%; +} + +.container--narrow { + max-width: var(--content-max-width); +} + +.container--wide { + max-width: 1400px; +} + +.container--fluid { + max-width: none; +} + +/* ===== FLEXBOX UTILITIES ===== */ +.flex { + display: flex; +} + +.flex-col { + flex-direction: column; +} + +.flex-row { + flex-direction: row; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.flex-nowrap { + flex-wrap: nowrap; +} + +.items-start { + align-items: flex-start; +} + +.items-center { + align-items: center; +} + +.items-end { + align-items: flex-end; +} + +.items-stretch { + align-items: stretch; +} + +.justify-start { + justify-content: flex-start; +} + +.justify-center { + justify-content: center; +} + +.justify-end { + justify-content: flex-end; +} + +.justify-between { + justify-content: space-between; +} + +.justify-around { + justify-content: space-around; +} + +.justify-evenly { + justify-content: space-evenly; +} + +.flex-1 { + flex: 1; +} + +.flex-auto { + flex: auto; +} + +.flex-none { + flex: none; +} + +/* ===== GRID SYSTEM ===== */ +.grid { + display: grid; +} + +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.grid-cols-auto { + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); +} + +.grid-cols-auto-sm { + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); +} + +.col-span-full { + grid-column: 1 / -1; +} + +.col-span-2 { + grid-column: span 2; +} + +.col-span-3 { + grid-column: span 3; +} + +.col-span-4 { + grid-column: span 4; +} + +/* Gap utilities */ +.gap-1 { gap: var(--space-1); } +.gap-2 { gap: var(--space-2); } +.gap-3 { gap: var(--space-3); } +.gap-4 { gap: var(--space-4); } +.gap-5 { gap: var(--space-5); } +.gap-6 { gap: var(--space-6); } +.gap-8 { gap: var(--space-8); } +.gap-10 { gap: var(--space-10); } +.gap-12 { gap: var(--space-12); } + +/* ===== POSITIONING ===== */ +.relative { + position: relative; +} + +.absolute { + position: absolute; +} + +.fixed { + position: fixed; +} + +.sticky { + position: sticky; +} + +.static { + position: static; +} + +/* Position utilities */ +.top-0 { top: 0; } +.right-0 { right: 0; } +.bottom-0 { bottom: 0; } +.left-0 { left: 0; } + +.inset-0 { + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +/* ===== DISPLAY UTILITIES ===== */ +.block { + display: block; +} + +.inline { + display: inline; +} + +.inline-block { + display: inline-block; +} + +.hidden { + display: none; +} + +/* ===== OVERFLOW ===== */ +.overflow-hidden { + overflow: hidden; +} + +.overflow-auto { + overflow: auto; +} + +.overflow-x-hidden { + overflow-x: hidden; +} + +.overflow-y-auto { + overflow-y: auto; +} + +/* ===== Z-INDEX ===== */ +.z-dropdown { z-index: var(--z-dropdown); } +.z-sticky { z-index: var(--z-sticky); } +.z-fixed { z-index: var(--z-fixed); } +.z-modal { z-index: var(--z-modal); } + +/* ===== RESPONSIVE BREAKPOINTS ===== */ +/* Mobile First - Start with mobile styles, then enhance for larger screens */ + +/* Small tablets and large phones (768px and up) */ +@media (min-width: 48rem) { + .container { + padding: 0 var(--space-10); + } + + /* Flexbox responsive utilities */ + .sm\:flex { + display: flex; + } + + .sm\:flex-row { + flex-direction: row; + } + + .sm\:flex-col { + flex-direction: column; + } + + .sm\:items-center { + align-items: center; + } + + .sm\:justify-between { + justify-content: space-between; + } + + /* Grid responsive utilities */ + .sm\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .sm\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .sm\:col-span-2 { + grid-column: span 2; + } + + /* Display utilities */ + .sm\:block { + display: block; + } + + .sm\:hidden { + display: none; + } +} + +/* Medium screens (1024px and up) */ +@media (min-width: 64rem) { + .container { + padding: 0 var(--space-12); + } + + /* Grid responsive utilities */ + .md\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .md\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .md\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .md\:col-span-2 { + grid-column: span 2; + } + + .md\:col-span-3 { + grid-column: span 3; + } + + /* Display utilities */ + .md\:block { + display: block; + } + + .md\:hidden { + display: none; + } +} + +/* Large screens (1280px and up) */ +@media (min-width: 80rem) { + .container { + padding: 0 var(--space-16); + } + + /* Grid responsive utilities */ + .lg\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .lg\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .lg\:grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + + /* Display utilities */ + .lg\:block { + display: block; + } + + .lg\:hidden { + display: none; + } +} + +/* Extra large screens (1440px and up) */ +@media (min-width: 90rem) { + .xl\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .xl\:grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + + .xl\:grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } +} diff --git a/assets/css/style.css b/assets/css/style.css index 8df3e96..48a2fc2 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -1,615 +1,219 @@ -/* --- Global Styles & Variables --- */ -/* --- Local Font Declarations --- */ -@font-face { - font-family: 'Satoshi'; - src: url('/fonts/Satoshi-Regular.otf') format('opentype'); - font-weight: 400; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Unbounded'; - src: url('/fonts/Unbounded-VariableFont_wght.ttf') format('truetype-variations'); - font-weight: 100 900; - font-style: normal; - font-display: swap; -} - -:root { - /* Light Theme Colors */ - --background-color: #F8F7F3; - --text-color: #1a1a1a; - --accent-color-yellow: #FFD700; - --accent-color-blue: #0099FF; - --font-body: 'Satoshi', sans-serif; - --font-heading: 'Unbounded', sans-serif; - - /* Additional Light Theme Colors */ - --surface-color: #ffffff; - --border-color: rgba(0, 0, 0, 0.1); - --shadow-color: rgba(0, 0, 0, 0.1); - --glass-bg: rgba(255, 255, 255, 0.2); - --glass-border: rgba(255, 255, 255, 0.3); - --post-border: #e0e0e0; -} - -/* Dark Theme Colors */ -[data-theme="dark"] { - --background-color: #0a0a0a; - --text-color: #f5f5f5; - --accent-color-yellow: #FFD700; - --accent-color-blue: #00B4FF; - --surface-color: #1a1a1a; - --border-color: rgba(255, 255, 255, 0.1); - --shadow-color: rgba(0, 0, 0, 0.3); - --glass-bg: rgba(255, 255, 255, 0.05); - --glass-border: rgba(255, 255, 255, 0.1); - --post-border: #333333; -} - -/* System Theme Detection */ -@media (prefers-color-scheme: dark) { - :root:not([data-theme]) { - --background-color: #0a0a0a; - --text-color: #f5f5f5; - --accent-color-yellow: #FFD700; - --accent-color-blue: #00B4FF; - --surface-color: #1a1a1a; - --border-color: rgba(255, 255, 255, 0.1); - --shadow-color: rgba(0, 0, 0, 0.3); - --glass-bg: rgba(255, 255, 255, 0.05); - --glass-border: rgba(255, 255, 255, 0.1); - --post-border: #333333; - } -} - -/* Optimized transitions for theme switching - only apply to specific properties */ -body, .site-header, .container { - transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease; -} - -/* NO transitions for text content and theme-sensitive elements */ -.theme-text, .logo, .hero-headline, h1, h2, h3, h4, h5, h6, p, a, .post-item { - transition: none !important; -} - -/* Fast transitions for interactive elements only */ -.theme-toggle { - transition: color 0.1s ease; -} - -.theme-toggle:hover { - transition: color 0.1s ease; -} - -body { - background-color: var(--background-color); - color: var(--text-color); - font-family: var(--font-body); - margin: 0; - padding-top: 80px; /* Account for fixed header */ -} - -.container { - max-width: 1200px; - margin: 0 auto; - padding: 0 40px; -} - -a { - color: var(--text-color); - text-decoration: none; +/* + * Legacy Styles - Table & Infographics Components + * + * These styles are maintained for specific components that haven't been + * fully migrated to the new modular system yet. + * + * Note: CSS files are now concatenated in Hugo template: + * theme.css → base.css → layout.css → components.css → utilities.css → style.css + */ + +/* ===== LEGACY SUPPORT & MIGRATION ===== */ +/* These styles maintain compatibility during migration */ + +/* Table styles for post content */ +.table-container { + margin: var(--space-8) 0; + overflow-x: auto; + border-radius: var(--radius-md); + border: 1px solid var(--color-border); } -/* --- Header & Navigation --- */ -.site-header { - display: flex; - justify-content: center; - align-items: center; - padding: 20px 40px; - position: fixed; - top: 0; - left: 0; - right: 0; - background: rgba(248, 247, 243, 0.95); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - z-index: 1000; - border-bottom: 1px solid var(--border-color); - transition: all 0.3s ease; +.post__content table { width: 100%; - max-width: 100vw; - box-sizing: border-box; -} - -/* Simplified header background - no pseudo-element overflow */ -.site-header::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: inherit; - backdrop-filter: inherit; - -webkit-backdrop-filter: inherit; - border-bottom: inherit; - z-index: -1; + border-collapse: collapse; + margin: 0; + background-color: var(--color-surface); + font-size: var(--font-size-sm); + min-width: 600px; } -/* Dark mode header adjustments */ -[data-theme="dark"] .site-header { - background: rgba(10, 10, 10, 0.95); +.post__content thead { + background-color: var(--color-surface); + border-bottom: 2px solid var(--color-border); } -/* Header scroll effect */ -.site-header.scrolled { - background: rgba(248, 247, 243, 0.98); - box-shadow: 0 2px 20px var(--shadow-color); - padding: 15px 40px; +.post__content th { + padding: var(--space-4) var(--space-6); + text-align: left; + font-weight: 600; + font-size: var(--font-size-sm); + color: var(--color-text); + border-right: 1px solid var(--color-border); } -[data-theme="dark"] .site-header.scrolled { - background: rgba(10, 10, 10, 0.98); +.post__content th:last-child { + border-right: none; } -/* Mobile header scroll adjustments */ -@media (max-width: 768px) { - .site-header.scrolled { - padding: 12px 20px; - height: 55px; - } +.post__content td { + padding: var(--space-4) var(--space-6); + border-bottom: 1px solid var(--color-border); + border-right: 1px solid var(--color-border); + vertical-align: top; + line-height: var(--line-height-relaxed); + color: var(--color-text); } -@media (max-width: 480px) { - .site-header.scrolled { - padding: 10px 15px; - height: 45px; - } +.post__content td:last-child { + border-right: none; } -/* Theme toggle with CSS-controlled text display */ -.theme-toggle { - background: transparent; - border: none; - padding: 8px 12px; - cursor: pointer; - font-family: var(--font-heading); - font-size: 14px; - font-weight: 500; - color: var(--text-color); - position: absolute; - left: 20px; - top: 50%; - transform: translateY(-50%); - transition: none !important; /* No transitions on the button itself */ - z-index: 1001; +.post__content tbody tr { + transition: background-color var(--transition-fast) var(--ease-in-out); } -.theme-toggle:hover { - color: var(--accent-color-blue); +.post__content tbody tr:hover { + background-color: rgba(0, 0, 0, 0.02); } -[data-theme="dark"] .theme-toggle:hover { - color: var(--accent-color-yellow); +[data-theme="dark"] .post__content tbody tr:hover { + background-color: rgba(255, 255, 255, 0.02); } -/* CSS-controlled theme text visibility - instant switching like logo */ -.theme-text { - transition: none !important; /* Force no transitions */ +.post__content tbody tr:last-child td { + border-bottom: none; } -.theme-text-light { - display: none; +.post__content tbody tr:nth-child(even) { + background-color: rgba(0, 0, 0, 0.01); } -.theme-text-dark { - display: inline; +[data-theme="dark"] .post__content tbody tr:nth-child(even) { + background-color: rgba(255, 255, 255, 0.01); } -[data-theme="dark"] .theme-text-light { - display: inline; +.post__content td:first-child { + font-weight: 500; } -[data-theme="dark"] .theme-text-dark { - display: none; +/* Mobile responsive tables */ +@media (max-width: 47.9375rem) { + .post__content table { + font-size: var(--font-size-xs); + } + + .post__content th, + .post__content td { + padding: var(--space-3) var(--space-4); + } + + .post__content th { + font-size: var(--font-size-xs); + } } -/* Focus states for accessibility */ -.theme-toggle:focus { - outline: 2px solid var(--accent-color-blue); - outline-offset: 2px; -} +[data-theme="dark"] .table-container { + border-color: var(--color-border); +} -[data-theme="dark"] .theme-toggle:focus { - outline-color: var(--accent-color-yellow); +/* ===== INFOGRAPHICS COMPONENT ===== */ +.infographics { + margin: var(--space-10) 0; } -/* Main site link styling */ -.main-site-link { - font-family: var(--font-heading); - font-size: 14px; - font-weight: 500; - color: var(--text-color); - text-decoration: none; - position: absolute; - right: 20px; /* Position on the right side */ - top: 50%; - transform: translateY(-50%); - padding: 8px 12px; - border-radius: 4px; - transition: color 0.1s ease, background-color 0.1s ease; - z-index: 1001; +.infograph-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: var(--space-4); } -.main-site-link:hover { - color: var(--accent-color-blue); - background-color: rgba(0, 153, 255, 0.1); +.chart-card.span-full { + grid-column: 1 / -1; } -[data-theme="dark"] .main-site-link:hover { - color: var(--accent-color-yellow); - background-color: rgba(255, 215, 0, 0.1); +.chart-card.span-full .chart-container { + height: 360px; } -/* Focus states for accessibility */ -.main-site-link:focus { - outline: 2px solid var(--accent-color-blue); - outline-offset: 2px; +.data-card, +.stats-card { + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-xl); + padding: var(--space-4) var(--space-5); + box-shadow: var(--shadow-md); + transition: + box-shadow var(--transition-fast) var(--ease-in-out), + transform var(--transition-fast) var(--ease-out); } -[data-theme="dark"] .main-site-link:focus { - outline-color: var(--accent-color-yellow); +.data-card:hover, +.stats-card:hover { + box-shadow: var(--shadow-lg); + transform: translateY(-2px); } -/* Center the logo */ -.logo { - font-weight: 700; - font-family: 'Unbounded', sans-serif; - font-size: 1.5rem; - letter-spacing: 1px; - position: absolute; - left: 50%; - transform: translateX(-50%); - color: var(--text-color); - text-decoration: none; - z-index: 999; +.data-card h3, +.chart-card__title { + margin: 0 0 var(--space-2) 0; + font-family: var(--font-family-heading); + font-size: var(--font-size-base); + opacity: 0.85; + color: var(--color-text); } -/* --- Hero Section (Homepage) --- */ -.hero-section { +.big-stat { + font-family: var(--font-family-heading); + font-weight: 800; + font-size: var(--font-size-5xl); + line-height: var(--line-height-none); text-align: center; - padding: 80px 0; + color: var(--color-primary); + margin-bottom: var(--space-1); } -.hero-headline { - font-family: 'Unbounded', sans-serif; - font-size: 5rem; /* Adjust as needed */ - line-height: 1.1; - text-transform: uppercase; - margin: 0; - font-weight: 700; -} - -/* --- Interactive Elements (CSS Version) --- */ -.interactive-o { - display: inline-block; - width: 1em; - height: 0.4em; - background: linear-gradient(120deg, var(--accent-color-yellow), var(--accent-color-blue)); - border-radius: 50px; - vertical-align: middle; - margin: 0 -0.05em; - position: relative; - top: -0.1em; +.big-stat-desc { + text-align: center; + margin-top: var(--space-1); + font-weight: 600; + opacity: 0.75; + color: var(--color-text-muted); + font-size: var(--font-size-sm); } -.interactive-arrow { - display: inline-block; - width: 0.8em; - height: 0.8em; - background: linear-gradient(160deg, var(--accent-color-blue), #6DD5FA); - clip-path: polygon(0% 0%, 100% 0%, 100% 50%, 50% 100%, 0% 50%); - vertical-align: middle; +.chart-container { position: relative; - top: 0.1em; - margin-left: -0.2em; -} - -/* --- Post List (For later) --- */ -.post-list { - margin-top: 60px; -} -.post-item { - padding: 20px 0; - border-bottom: 1px solid var(--post-border); -} -.post-item h2 a { - font-family: 'Unbounded', sans-serif; -} - -/* --- Single Post Content --- */ -.post-content { - max-width: 800px; - margin: 60px auto; -} -.post-content h1 { - font-family: 'Unbounded', sans-serif; - font-size: 3rem; -} -.post-content .content { - font-size: 1.1rem; - line-height: 1.7; - font-family: var(--font-body); -} - -/* --- Global Overflow Prevention --- */ -html, body { - max-width: 100vw; - overflow-x: hidden; -} - -/* --- Mobile Responsiveness --- */ -@media (max-width: 768px) { - body { - padding-top: 70px; /* Reduced header height */ - } - - .container { - padding: 0 20px; - max-width: 100%; - } - - .site-header { - padding: 15px 20px; - height: 60px; - justify-content: center; - position: fixed; - } - - .theme-toggle { - left: 15px; - font-size: 12px; - padding: 6px 10px; - transform: translateY(-50%); - } - - .main-site-link { - right: 15px; /* Position on the right side for mobile */ - font-size: 12px; - padding: 6px 10px; - } - - .logo { - font-size: 1.3rem; - position: relative; - left: 0; - transform: none; - } - - .hero-headline { - font-size: 3rem; - line-height: 1.2; - } - - .post-content h1 { - font-size: 2rem; - } -} - -@media (max-width: 480px) { - body { - padding-top: 60px; /* Even more compact */ - } - - .container { - padding: 0 15px; - max-width: 100%; - } - - .site-header { - padding: 12px 15px; - height: 50px; - min-height: 50px; - justify-content: center; - align-items: center; - flex-direction: row; - gap: 0; - } - - .theme-toggle { - left: 10px; - font-size: 11px; - padding: 5px 8px; - position: absolute; - top: 50%; - transform: translateY(-50%); - } - - .main-site-link { - right: 10px; /* Position on the right side for small mobile */ - font-size: 11px; - padding: 5px 8px; - } - - .logo { - font-size: 1.1rem; - position: relative; - left: 0; - transform: none; - margin: 0; - } - - .hero-headline { - font-size: 2.2rem; - line-height: 1.3; - word-break: break-word; - hyphens: auto; - } - - .post-content { - margin: 30px auto; - } - - .post-content h1 { - font-size: 1.6rem; - line-height: 1.3; - } -} - -/* --- Image Styles --- */ -.responsive-image, .simple-image { - max-width: 100%; - overflow: hidden; -} - -.responsive-image img, .simple-image img { width: 100%; - height: auto; - object-fit: cover; - transition: transform 0.3s ease; -} - -.responsive-image:hover img { - transform: scale(1.02); -} - -/* Ensure images don't break layout */ -.post-content img { - max-width: 100%; - height: auto; - border-radius: 6px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - margin: 1rem 0; + height: 240px; } -/* Figure styles for better image presentation */ -.post-content figure { - margin: 2rem 0; - text-align: center; -} - -.post-content figcaption { - margin-top: 0.5rem; - font-size: 0.9rem; - color: var(--text-color); - opacity: 0.7; - font-style: italic; -} - -/* Dark theme adjustments for images */ -[data-theme="dark"] .post-content img { - box-shadow: 0 2px 8px rgba(255, 255, 255, 0.1); -} - -/* Responsive image sizes */ -@media (max-width: 768px) { - .responsive-image, .simple-image { - margin: 1rem -1rem; /* Extend to screen edges on mobile */ +@media (min-width: 48rem) { + .chart-container { + height: 300px; } - - .responsive-image img, .simple-image img { - border-radius: 0; /* Remove border radius on mobile for full-width effect */ - } -} - -/* --- Table Styles --- */ -.table-container { - margin: 2rem 0; - overflow-x: auto; - border-radius: 6px; - border: 1px solid var(--border-color); } -.post-content table { - width: 100%; - border-collapse: collapse; - margin: 0; - background-color: var(--surface-color); - font-size: 0.95rem; - min-width: 600px; /* Ensures table doesn't get too cramped */ +/* ===== RESPONSIVE IMAGES ===== */ +.responsive-image, +.simple-image { + max-width: 100%; + overflow: hidden; + border-radius: var(--radius-md); } -.post-content thead { - background-color: var(--surface-color); - border-bottom: 2px solid var(--border-color); +.responsive-image img, +.simple-image img { + width: 100%; + height: auto; + object-fit: cover; + transition: transform var(--transition-base) var(--ease-out); } -.post-content th { - padding: 1rem 1.5rem; - text-align: left; - font-weight: 600; - font-size: 0.9rem; - color: var(--text-color); - border-right: 1px solid var(--border-color); - border: none; -} - -.post-content th:last-child { - border-right: none; -} - -.post-content td { - padding: 1rem 1.5rem; - border-bottom: 1px solid var(--border-color); - border-right: 1px solid var(--border-color); - vertical-align: top; - line-height: 1.5; - color: var(--text-color); -} - -.post-content td:last-child { - border-right: none; -} - -.post-content tbody tr { - transition: background-color 0.2s ease; -} - -.post-content tbody tr:hover { - background-color: rgba(0, 0, 0, 0.02); -} - -.post-content tbody tr:last-child td { - border-bottom: none; -} - -/* Clean alternating rows */ -.post-content tbody tr:nth-child(even) { - background-color: rgba(0, 0, 0, 0.01); -} - -[data-theme="dark"] .post-content tbody tr:nth-child(even) { - background-color: rgba(255, 255, 255, 0.01); -} - -[data-theme="dark"] .post-content tbody tr:hover { - background-color: rgba(255, 255, 255, 0.02); -} - -/* Remove special styling for first column */ -.post-content td:first-child { - font-weight: 500; -} - -/* Mobile responsive tables */ -@media (max-width: 768px) { - .post-content table { - font-size: 0.85rem; - } - - .post-content th, - .post-content td { - padding: 0.8rem 1rem; - } - - .post-content th { - font-size: 0.8rem; - } +.responsive-image:hover img { + transform: scale(1.02); +} + +/* Mobile responsive images */ +@media (max-width: 47.9375rem) { + .responsive-image, + .simple-image { + margin: var(--space-4) calc(-1 * var(--space-4)); + border-radius: 0; + } + + .responsive-image img, + .simple-image img { + border-radius: 0; + } } - -/* Dark theme table adjustments */ -[data-theme="dark"] .table-container { - border-color: rgba(255, 255, 255, 0.1); -} \ No newline at end of file diff --git a/assets/css/theme.css b/assets/css/theme.css new file mode 100644 index 0000000..936d552 --- /dev/null +++ b/assets/css/theme.css @@ -0,0 +1,231 @@ +/* + * Theme Variables & Color System + * Design Tokens Approach - Scalable & Maintainable + */ + +/* ===== FONT DECLARATIONS ===== */ +@font-face { + font-family: 'Satoshi'; + src: url('/fonts/Satoshi-Regular.otf') format('opentype'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Unbounded'; + src: url('/fonts/Unbounded-VariableFont_wght.ttf') format('truetype-variations'); + font-weight: 100 900; + font-style: normal; + font-display: swap; +} + +/* ===== DESIGN TOKENS - ROOT SYSTEM ===== */ +:root { + /* Typography Scale */ + --font-family-base: 'Satoshi', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + --font-family-heading: 'Unbounded', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + --font-family-mono: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace; + + /* Font Size Scale - Perfect Typography Ratio (1.25) */ + --font-size-xs: 0.75rem; /* 12px */ + --font-size-sm: 0.875rem; /* 14px */ + --font-size-base: 1rem; /* 16px */ + --font-size-lg: 1.125rem; /* 18px */ + --font-size-xl: 1.25rem; /* 20px */ + --font-size-2xl: 1.5rem; /* 24px */ + --font-size-3xl: 1.875rem; /* 30px */ + --font-size-4xl: 2.25rem; /* 36px */ + --font-size-5xl: 3rem; /* 48px */ + --font-size-6xl: 3.75rem; /* 60px */ + --font-size-7xl: 4.5rem; /* 72px */ + + /* Line Height Scale */ + --line-height-none: 1; + --line-height-tight: 1.25; + --line-height-snug: 1.375; + --line-height-normal: 1.5; + --line-height-relaxed: 1.625; + --line-height-loose: 2; + --line-height-base: var(--line-height-normal); + + /* Spacing Scale - 8pt Grid System */ + --space-1: 0.25rem; /* 4px */ + --space-2: 0.5rem; /* 8px */ + --space-3: 0.75rem; /* 12px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.25rem; /* 20px */ + --space-6: 1.5rem; /* 24px */ + --space-7: 1.75rem; /* 28px */ + --space-8: 2rem; /* 32px */ + --space-10: 2.5rem; /* 40px */ + --space-12: 3rem; /* 48px */ + --space-16: 4rem; /* 64px */ + --space-20: 5rem; /* 80px */ + --space-24: 6rem; /* 96px */ + + /* Border Radius Scale */ + --radius-sm: 0.125rem; /* 2px */ + --radius-base: 0.25rem; /* 4px */ + --radius-md: 0.375rem; /* 6px */ + --radius-lg: 0.5rem; /* 8px */ + --radius-xl: 0.75rem; /* 12px */ + --radius-2xl: 1rem; /* 16px */ + --radius-full: 9999px; + + /* Animation & Transitions */ + --transition-fast: 150ms; + --transition-base: 250ms; + --transition-slow: 350ms; + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in: cubic-bezier(0.4, 0, 1, 1); + + /* Z-Index Scale */ + --z-dropdown: 1000; + --z-sticky: 1020; + --z-fixed: 1030; + --z-modal-backdrop: 1040; + --z-modal: 1050; + --z-popover: 1060; + --z-tooltip: 1070; + + /* Layout Constraints */ + --container-max-width: 1200px; + --content-max-width: 800px; + --sidebar-width: 280px; + --header-height: 50px; + + /* Light Theme Colors */ + --color-white: #ffffff; + --color-black: #000000; + --color-gray-50: #f9fafb; + --color-gray-100: #f3f4f6; + --color-gray-200: #e5e7eb; + --color-gray-300: #d1d5db; + --color-gray-400: #9ca3af; + --color-gray-500: #6b7280; + --color-gray-600: #4b5563; + --color-gray-700: #374151; + --color-gray-800: #1f2937; + --color-gray-900: #111827; + + /* Brand Colors */ + --color-primary-50: #eff6ff; + --color-primary-100: #dbeafe; + --color-primary-200: #bfdbfe; + --color-primary-300: #93c5fd; + --color-primary-400: #60a5fa; + --color-primary-500: #3b82f6; + --color-primary-600: #2563eb; + --color-primary-700: #1d4ed8; + --color-primary-800: #1e40af; + --color-primary-900: #1e3a8a; + + --color-accent-50: #fffbeb; + --color-accent-100: #fef3c7; + --color-accent-200: #fde68a; + --color-accent-300: #fcd34d; + --color-accent-400: #fbbf24; + --color-accent-500: #f59e0b; + --color-accent-600: #d97706; + --color-accent-700: #b45309; + --color-accent-800: #92400e; + --color-accent-900: #78350f; + + /* Semantic Color Assignments - Light Theme */ + --color-background: #f8f7f3; + --color-surface: var(--color-white); + --color-surface-elevated: var(--color-white); + --color-text: #1a1a1a; + --color-text-muted: var(--color-gray-600); + --color-text-subtle: var(--color-gray-500); + --color-border: rgba(0, 0, 0, 0.1); + --color-border-subtle: rgba(0, 0, 0, 0.05); + --color-primary: #0099ff; + --color-primary-hover: #0088e6; + --color-accent: #ffd700; + --color-accent-hover: #e6c200; + --color-success: #10b981; + --color-warning: #f59e0b; + --color-error: #ef4444; + + /* Glass Effect */ + --glass-bg: rgba(255, 255, 255, 0.2); + --glass-border: rgba(255, 255, 255, 0.3); + --glass-backdrop: blur(10px); + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-base: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); +} + +/* ===== DARK THEME OVERRIDE ===== */ +[data-theme="dark"] { + /* Dark Theme Semantic Colors */ + --color-background: #0a0a0a; + --color-surface: #1a1a1a; + --color-surface-elevated: #2a2a2a; + --color-text: #f5f5f5; + --color-text-muted: var(--color-gray-400); + --color-text-subtle: var(--color-gray-500); + --color-border: rgba(255, 255, 255, 0.1); + --color-border-subtle: rgba(255, 255, 255, 0.05); + --color-primary: #00b4ff; + --color-primary-hover: #0099e6; + --color-accent: #ffd700; + --color-accent-hover: #ffed4a; + + /* Dark Glass Effect */ + --glass-bg: rgba(255, 255, 255, 0.05); + --glass-border: rgba(255, 255, 255, 0.1); + + /* Dark Shadows */ + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); + --shadow-base: 0 1px 3px 0 rgba(0, 0, 0, 0.4), 0 1px 2px 0 rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.3); +} + +/* ===== SYSTEM THEME DETECTION ===== */ +@media (prefers-color-scheme: dark) { + :root:not([data-theme]) { + --color-background: #0a0a0a; + --color-surface: #1a1a1a; + --color-surface-elevated: #2a2a2a; + --color-text: #f5f5f5; + --color-text-muted: var(--color-gray-400); + --color-text-subtle: var(--color-gray-500); + --color-border: rgba(255, 255, 255, 0.1); + --color-border-subtle: rgba(255, 255, 255, 0.05); + --color-primary: #00b4ff; + --color-primary-hover: #0099e6; + --color-accent: #ffd700; + --color-accent-hover: #ffed4a; + --glass-bg: rgba(255, 255, 255, 0.05); + --glass-border: rgba(255, 255, 255, 0.1); + } +} + +/* ===== THEME TRANSITION OPTIMIZATION ===== */ +body, +.site-header, +.container { + transition: + background-color var(--transition-fast) var(--ease-in-out), + border-color var(--transition-fast) var(--ease-in-out), + box-shadow var(--transition-fast) var(--ease-in-out); +} + +/* Prevent theme transition on text elements for instant switching */ +.theme-text, +.logo, +.hero-headline, +h1, h2, h3, h4, h5, h6, +p, a, .post-item { + transition: none !important; +} diff --git a/assets/css/utilities.css b/assets/css/utilities.css new file mode 100644 index 0000000..85363c0 --- /dev/null +++ b/assets/css/utilities.css @@ -0,0 +1,363 @@ +/* + * Utility Classes - Atomic CSS Approach + * Functional utilities for rapid development + */ + +/* ===== SPACING UTILITIES ===== */ +/* Margin utilities */ +.m-0 { margin: 0; } +.m-1 { margin: var(--space-1); } +.m-2 { margin: var(--space-2); } +.m-3 { margin: var(--space-3); } +.m-4 { margin: var(--space-4); } +.m-5 { margin: var(--space-5); } +.m-6 { margin: var(--space-6); } +.m-8 { margin: var(--space-8); } +.m-10 { margin: var(--space-10); } +.m-12 { margin: var(--space-12); } +.m-16 { margin: var(--space-16); } +.m-auto { margin: auto; } + +/* Margin top */ +.mt-0 { margin-top: 0; } +.mt-1 { margin-top: var(--space-1); } +.mt-2 { margin-top: var(--space-2); } +.mt-3 { margin-top: var(--space-3); } +.mt-4 { margin-top: var(--space-4); } +.mt-5 { margin-top: var(--space-5); } +.mt-6 { margin-top: var(--space-6); } +.mt-8 { margin-top: var(--space-8); } +.mt-10 { margin-top: var(--space-10); } +.mt-12 { margin-top: var(--space-12); } +.mt-16 { margin-top: var(--space-16); } + +/* Margin bottom */ +.mb-0 { margin-bottom: 0; } +.mb-1 { margin-bottom: var(--space-1); } +.mb-2 { margin-bottom: var(--space-2); } +.mb-3 { margin-bottom: var(--space-3); } +.mb-4 { margin-bottom: var(--space-4); } +.mb-5 { margin-bottom: var(--space-5); } +.mb-6 { margin-bottom: var(--space-6); } +.mb-8 { margin-bottom: var(--space-8); } +.mb-10 { margin-bottom: var(--space-10); } +.mb-12 { margin-bottom: var(--space-12); } +.mb-16 { margin-bottom: var(--space-16); } + +/* Margin left */ +.ml-0 { margin-left: 0; } +.ml-1 { margin-left: var(--space-1); } +.ml-2 { margin-left: var(--space-2); } +.ml-3 { margin-left: var(--space-3); } +.ml-4 { margin-left: var(--space-4); } +.ml-auto { margin-left: auto; } + +/* Margin right */ +.mr-0 { margin-right: 0; } +.mr-1 { margin-right: var(--space-1); } +.mr-2 { margin-right: var(--space-2); } +.mr-3 { margin-right: var(--space-3); } +.mr-4 { margin-right: var(--space-4); } +.mr-auto { margin-right: auto; } + +/* Margin x-axis */ +.mx-0 { margin-left: 0; margin-right: 0; } +.mx-1 { margin-left: var(--space-1); margin-right: var(--space-1); } +.mx-2 { margin-left: var(--space-2); margin-right: var(--space-2); } +.mx-3 { margin-left: var(--space-3); margin-right: var(--space-3); } +.mx-4 { margin-left: var(--space-4); margin-right: var(--space-4); } +.mx-auto { margin-left: auto; margin-right: auto; } + +/* Margin y-axis */ +.my-0 { margin-top: 0; margin-bottom: 0; } +.my-1 { margin-top: var(--space-1); margin-bottom: var(--space-1); } +.my-2 { margin-top: var(--space-2); margin-bottom: var(--space-2); } +.my-3 { margin-top: var(--space-3); margin-bottom: var(--space-3); } +.my-4 { margin-top: var(--space-4); margin-bottom: var(--space-4); } +.my-6 { margin-top: var(--space-6); margin-bottom: var(--space-6); } +.my-8 { margin-top: var(--space-8); margin-bottom: var(--space-8); } + +/* Padding utilities */ +.p-0 { padding: 0; } +.p-1 { padding: var(--space-1); } +.p-2 { padding: var(--space-2); } +.p-3 { padding: var(--space-3); } +.p-4 { padding: var(--space-4); } +.p-5 { padding: var(--space-5); } +.p-6 { padding: var(--space-6); } +.p-8 { padding: var(--space-8); } +.p-10 { padding: var(--space-10); } +.p-12 { padding: var(--space-12); } +.p-16 { padding: var(--space-16); } + +/* Padding top */ +.pt-0 { padding-top: 0; } +.pt-1 { padding-top: var(--space-1); } +.pt-2 { padding-top: var(--space-2); } +.pt-3 { padding-top: var(--space-3); } +.pt-4 { padding-top: var(--space-4); } +.pt-5 { padding-top: var(--space-5); } +.pt-6 { padding-top: var(--space-6); } +.pt-8 { padding-top: var(--space-8); } +.pt-16 { padding-top: var(--space-16); } + +/* Padding bottom */ +.pb-0 { padding-bottom: 0; } +.pb-1 { padding-bottom: var(--space-1); } +.pb-2 { padding-bottom: var(--space-2); } +.pb-3 { padding-bottom: var(--space-3); } +.pb-4 { padding-bottom: var(--space-4); } +.pb-5 { padding-bottom: var(--space-5); } +.pb-6 { padding-bottom: var(--space-6); } +.pb-8 { padding-bottom: var(--space-8); } +.pb-16 { padding-bottom: var(--space-16); } + +/* Padding x-axis */ +.px-0 { padding-left: 0; padding-right: 0; } +.px-1 { padding-left: var(--space-1); padding-right: var(--space-1); } +.px-2 { padding-left: var(--space-2); padding-right: var(--space-2); } +.px-3 { padding-left: var(--space-3); padding-right: var(--space-3); } +.px-4 { padding-left: var(--space-4); padding-right: var(--space-4); } +.px-6 { padding-left: var(--space-6); padding-right: var(--space-6); } +.px-8 { padding-left: var(--space-8); padding-right: var(--space-8); } + +/* Padding y-axis */ +.py-0 { padding-top: 0; padding-bottom: 0; } +.py-1 { padding-top: var(--space-1); padding-bottom: var(--space-1); } +.py-2 { padding-top: var(--space-2); padding-bottom: var(--space-2); } +.py-3 { padding-top: var(--space-3); padding-bottom: var(--space-3); } +.py-4 { padding-top: var(--space-4); padding-bottom: var(--space-4); } +.py-6 { padding-top: var(--space-6); padding-bottom: var(--space-6); } +.py-8 { padding-top: var(--space-8); padding-bottom: var(--space-8); } + +/* ===== TEXT UTILITIES ===== */ +.text-left { text-align: left; } +.text-center { text-align: center; } +.text-right { text-align: right; } +.text-justify { text-align: justify; } + +/* Font weights */ +.font-light { font-weight: 300; } +.font-normal { font-weight: 400; } +.font-medium { font-weight: 500; } +.font-semibold { font-weight: 600; } +.font-bold { font-weight: 700; } +.font-extrabold { font-weight: 800; } + +/* Font sizes */ +.text-xs { font-size: var(--font-size-xs); } +.text-sm { font-size: var(--font-size-sm); } +.text-base { font-size: var(--font-size-base); } +.text-lg { font-size: var(--font-size-lg); } +.text-xl { font-size: var(--font-size-xl); } +.text-2xl { font-size: var(--font-size-2xl); } +.text-3xl { font-size: var(--font-size-3xl); } +.text-4xl { font-size: var(--font-size-4xl); } +.text-5xl { font-size: var(--font-size-5xl); } + +/* Text colors */ +.text-primary { color: var(--color-primary); } +.text-accent { color: var(--color-accent); } +.text-muted { color: var(--color-text-muted); } +.text-subtle { color: var(--color-text-subtle); } +.text-white { color: var(--color-white); } +.text-black { color: var(--color-black); } + +/* Line heights */ +.leading-none { line-height: var(--line-height-none); } +.leading-tight { line-height: var(--line-height-tight); } +.leading-snug { line-height: var(--line-height-snug); } +.leading-normal { line-height: var(--line-height-normal); } +.leading-relaxed { line-height: var(--line-height-relaxed); } +.leading-loose { line-height: var(--line-height-loose); } + +/* Text decoration */ +.underline { text-decoration: underline; } +.no-underline { text-decoration: none; } +.line-through { text-decoration: line-through; } + +/* Text transform */ +.uppercase { text-transform: uppercase; } +.lowercase { text-transform: lowercase; } +.capitalize { text-transform: capitalize; } +.normal-case { text-transform: none; } + +/* Font style */ +.italic { font-style: italic; } +.not-italic { font-style: normal; } + +/* ===== BACKGROUND UTILITIES ===== */ +.bg-transparent { background-color: transparent; } +.bg-white { background-color: var(--color-white); } +.bg-gray-50 { background-color: var(--color-gray-50); } +.bg-gray-100 { background-color: var(--color-gray-100); } +.bg-gray-200 { background-color: var(--color-gray-200); } +.bg-primary { background-color: var(--color-primary); } +.bg-accent { background-color: var(--color-accent); } +.bg-surface { background-color: var(--color-surface); } + +/* ===== BORDER UTILITIES ===== */ +.border { border: 1px solid var(--color-border); } +.border-0 { border: 0; } +.border-2 { border: 2px solid var(--color-border); } +.border-t { border-top: 1px solid var(--color-border); } +.border-b { border-bottom: 1px solid var(--color-border); } +.border-l { border-left: 1px solid var(--color-border); } +.border-r { border-right: 1px solid var(--color-border); } + +/* Border radius */ +.rounded-none { border-radius: 0; } +.rounded-sm { border-radius: var(--radius-sm); } +.rounded { border-radius: var(--radius-base); } +.rounded-md { border-radius: var(--radius-md); } +.rounded-lg { border-radius: var(--radius-lg); } +.rounded-xl { border-radius: var(--radius-xl); } +.rounded-2xl { border-radius: var(--radius-2xl); } +.rounded-full { border-radius: var(--radius-full); } + +/* ===== SHADOW UTILITIES ===== */ +.shadow-none { box-shadow: none; } +.shadow-sm { box-shadow: var(--shadow-sm); } +.shadow { box-shadow: var(--shadow-base); } +.shadow-md { box-shadow: var(--shadow-md); } +.shadow-lg { box-shadow: var(--shadow-lg); } +.shadow-xl { box-shadow: var(--shadow-xl); } + +/* ===== WIDTH & HEIGHT UTILITIES ===== */ +.w-full { width: 100%; } +.w-auto { width: auto; } +.w-fit { width: fit-content; } +.w-1\/2 { width: 50%; } +.w-1\/3 { width: 33.333333%; } +.w-2\/3 { width: 66.666667%; } +.w-1\/4 { width: 25%; } +.w-3\/4 { width: 75%; } + +.h-full { height: 100%; } +.h-auto { height: auto; } +.h-fit { height: fit-content; } +.h-screen { height: 100vh; } + +.min-h-screen { min-height: 100vh; } +.min-h-full { min-height: 100%; } + +.max-w-none { max-width: none; } +.max-w-sm { max-width: 640px; } +.max-w-md { max-width: 768px; } +.max-w-lg { max-width: 1024px; } +.max-w-xl { max-width: 1280px; } +.max-w-2xl { max-width: 1536px; } +.max-w-full { max-width: 100%; } +.max-w-content { max-width: var(--content-max-width); } +.max-w-container { max-width: var(--container-max-width); } + +/* ===== OPACITY UTILITIES ===== */ +.opacity-0 { opacity: 0; } +.opacity-25 { opacity: 0.25; } +.opacity-50 { opacity: 0.5; } +.opacity-75 { opacity: 0.75; } +.opacity-100 { opacity: 1; } + +/* ===== TRANSFORM UTILITIES ===== */ +.transform { transform: translateZ(0); } +.scale-95 { transform: scale(0.95); } +.scale-100 { transform: scale(1); } +.scale-105 { transform: scale(1.05); } +.scale-110 { transform: scale(1.1); } + +.translate-y-1 { transform: translateY(var(--space-1)); } +.translate-y-2 { transform: translateY(var(--space-2)); } +.-translate-y-1 { transform: translateY(calc(-1 * var(--space-1))); } +.-translate-y-2 { transform: translateY(calc(-1 * var(--space-2))); } + +/* ===== TRANSITION UTILITIES ===== */ +.transition-none { transition: none; } +.transition { transition: all var(--transition-base) var(--ease-in-out); } +.transition-colors { transition: color var(--transition-fast) var(--ease-in-out), background-color var(--transition-fast) var(--ease-in-out); } +.transition-transform { transition: transform var(--transition-fast) var(--ease-out); } +.transition-opacity { transition: opacity var(--transition-fast) var(--ease-in-out); } + +/* ===== CURSOR UTILITIES ===== */ +.cursor-pointer { cursor: pointer; } +.cursor-not-allowed { cursor: not-allowed; } +.cursor-default { cursor: default; } + +/* ===== OVERFLOW UTILITIES ===== */ +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.text-ellipsis { text-overflow: ellipsis; } +.text-clip { text-overflow: clip; } + +/* ===== RESPONSIVE UTILITIES ===== */ +@media (max-width: 47.9375rem) { + .mobile\:hidden { display: none; } + .mobile\:block { display: block; } + .mobile\:text-center { text-align: center; } + .mobile\:text-sm { font-size: var(--font-size-sm); } + .mobile\:px-4 { padding-left: var(--space-4); padding-right: var(--space-4); } + .mobile\:py-2 { padding-top: var(--space-2); padding-bottom: var(--space-2); } +} + +@media (min-width: 48rem) { + .tablet\:block { display: block; } + .tablet\:hidden { display: none; } + .tablet\:text-left { text-align: left; } + .tablet\:text-base { font-size: var(--font-size-base); } +} + +@media (min-width: 64rem) { + .desktop\:block { display: block; } + .desktop\:hidden { display: none; } +} + +/* ===== FOCUS STATES ===== */ +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring:focus { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} + +/* ===== INTERACTION STATES ===== */ +.hover\:bg-gray-50:hover { + background-color: var(--color-gray-50); +} + +.hover\:text-primary:hover { + color: var(--color-primary); +} + +.hover\:scale-105:hover { + transform: scale(1.05); +} + +.hover\:shadow-lg:hover { + box-shadow: var(--shadow-lg); +} + +/* ===== ASPECT RATIO UTILITIES ===== */ +.aspect-square { + aspect-ratio: 1 / 1; +} + +.aspect-video { + aspect-ratio: 16 / 9; +} + +.aspect-4\/3 { + aspect-ratio: 4 / 3; +} + +/* ===== OBJECT FIT UTILITIES ===== */ +.object-contain { object-fit: contain; } +.object-cover { object-fit: cover; } +.object-fill { object-fit: fill; } +.object-none { object-fit: none; } +.object-scale-down { object-fit: scale-down; } diff --git a/assets/js/main.js b/assets/js/main.js index 037ad80..0eff50a 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,263 +1,321 @@ -// Lightweight performance-optimized JavaScript -(function() { - 'use strict'; +/** + * Main Application Entry Point + * Modern ES6+ Module Architecture with Senior Frontend Best Practices + */ + +import { ThemeManager } from './modules/theme.js'; +import { PerformanceManager } from './modules/performance.js'; +import { NavigationManager } from './modules/navigation.js'; +import { waitForDOM, dispatchCustomEvent } from './modules/utils.js'; + +/** + * Main Application Class + * Orchestrates all modules and handles application lifecycle + */ +class App { + constructor() { + this.modules = new Map(); + this.initialized = false; + this.version = '2.0.0'; - // Performance: Use requestAnimationFrame for smooth animations - const raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame; - - // Performance: Debounce function for scroll events - function debounce(func, wait) { - let timeout; - return function executedFunction(...args) { - const later = () => { - clearTimeout(timeout); - func(...args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; + // Initialize immediately for critical functionality + this.initImmediate(); + } + + /** + * Initialize critical functionality immediately (theme management) + */ + initImmediate() { + // Theme must be initialized immediately to prevent FOUC + try { + this.modules.set('theme', new ThemeManager()); + console.log('🎨 Theme manager initialized'); + } catch (error) { + console.error('Failed to initialize theme manager:', error); } - - // Performance: Throttle function for resize events - function throttle(func, limit) { - let inThrottle; - return function() { - const args = arguments; - const context = this; - if (!inThrottle) { - func.apply(context, args); - inThrottle = true; - setTimeout(() => inThrottle = false, limit); - } - }; + } + + /** + * Initialize application after DOM is ready + */ + async init() { + if (this.initialized) { + console.warn('App already initialized'); + return; } - - // Performance: Intersection Observer for lazy loading - const observerOptions = { - root: null, - rootMargin: '0px', - threshold: 0.1 - }; - - // Performance: Preload critical resources - function preloadCriticalResources() { - const criticalResources = [ - '/fonts/inter.woff2', - '/fonts/unbounded.woff2' - ]; - - criticalResources.forEach(resource => { - const link = document.createElement('link'); - link.rel = 'preload'; - link.as = resource.endsWith('.css') ? 'style' : 'font'; - link.href = resource; - link.crossOrigin = 'anonymous'; - document.head.appendChild(link); - }); + + try { + console.log(`🚀 Initializing App v${this.version}`); + + // Wait for DOM to be ready + await waitForDOM(); + + // Initialize modules in order of priority + await this.initializeModules(); + + // Setup global event listeners + this.setupGlobalEvents(); + + // Mark as initialized + this.initialized = true; + + // Dispatch app ready event + dispatchCustomEvent('app:ready', { version: this.version }); + + console.log('✅ App initialization complete'); + + } catch (error) { + console.error('❌ App initialization failed:', error); + this.handleInitializationError(error); } - - // Performance: Optimize font loading - function optimizeFontLoading() { - if ('fonts' in document) { - Promise.all([ - document.fonts.load('400 1em Inter'), - document.fonts.load('700 1em Unbounded') - ]).then(() => { - document.documentElement.classList.add('fonts-loaded'); - }); + } + + /** + * Initialize all application modules + */ + async initializeModules() { + const moduleInitializers = [ + { name: 'performance', class: PerformanceManager, critical: true }, + { name: 'navigation', class: NavigationManager, critical: true } + ]; + + for (const { name, class: ModuleClass, critical } of moduleInitializers) { + try { + console.log(`🔧 Initializing ${name} module...`); + const instance = new ModuleClass(); + this.modules.set(name, instance); + console.log(`✅ ${name} module initialized`); + } catch (error) { + console.error(`❌ Failed to initialize ${name} module:`, error); + + if (critical) { + throw new Error(`Critical module ${name} failed to initialize`); } + } } + } + + /** + * Setup global application event listeners + */ + setupGlobalEvents() { + // Handle page visibility changes + document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); - // Performance: Smooth scroll for anchor links - function initSmoothScroll() { - const links = document.querySelectorAll('a[href^="#"]'); - links.forEach(link => { - link.addEventListener('click', function(e) { - const targetId = this.getAttribute('href'); - if (targetId.length > 1) { - e.preventDefault(); - const target = document.querySelector(targetId); - if (target) { - target.scrollIntoView({ - behavior: 'smooth', - block: 'start' - }); - } - } - }); - }); - } + // Handle online/offline status + window.addEventListener('online', this.handleOnline.bind(this)); + window.addEventListener('offline', this.handleOffline.bind(this)); - // Performance: Add loading states - function initLoadingStates() { - const elements = document.querySelectorAll('.hero-headline, .post-item'); - elements.forEach(element => { - element.classList.add('loading'); - }); - - // Use Intersection Observer for progressive loading - const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - entry.target.classList.remove('loading'); - entry.target.classList.add('loaded'); - observer.unobserve(entry.target); - } - }); - }, observerOptions); - - elements.forEach(element => { - observer.observe(element); - }); - } + // Handle global errors + window.addEventListener('error', this.handleGlobalError.bind(this)); + window.addEventListener('unhandledrejection', this.handleUnhandledRejection.bind(this)); + + // Handle page unload + window.addEventListener('beforeunload', this.handleBeforeUnload.bind(this)); - // Performance: Optimize images - function optimizeImages() { - const images = document.querySelectorAll('img'); - images.forEach(img => { - // Add loading="lazy" for images below the fold - if (img.getBoundingClientRect().top > window.innerHeight) { - img.loading = 'lazy'; - } - - // Add error handling - img.addEventListener('error', function() { - this.style.display = 'none'; - }); - }); + // Page load complete + window.addEventListener('load', this.handlePageLoad.bind(this)); + } + + /** + * Handle page visibility changes + */ + handleVisibilityChange() { + if (document.hidden) { + console.log('📱 Page hidden'); + dispatchCustomEvent('app:hidden'); + } else { + console.log('👁️ Page visible'); + dispatchCustomEvent('app:visible'); } + } + + /** + * Handle online status + */ + handleOnline() { + console.log('🌐 Connection restored'); + dispatchCustomEvent('app:online'); + document.documentElement.classList.remove('offline'); + document.documentElement.classList.add('online'); + } + + /** + * Handle offline status + */ + handleOffline() { + console.log('📡 Connection lost'); + dispatchCustomEvent('app:offline'); + document.documentElement.classList.remove('online'); + document.documentElement.classList.add('offline'); + } + + /** + * Handle global JavaScript errors + * @param {ErrorEvent} event - Error event + */ + handleGlobalError(event) { + console.error('Global error:', event.error); - // Performance: Service Worker registration (if available) - function registerServiceWorker() { - if ('serviceWorker' in navigator) { - window.addEventListener('load', () => { - navigator.serviceWorker.register('/sw.js') - .then(registration => { - console.log('SW registered: ', registration); - }) - .catch(registrationError => { - console.log('SW registration failed: ', registrationError); - }); - }); - } + // Report to analytics if available + if (window.gtag) { + gtag('event', 'exception', { + 'description': event.error.message, + 'fatal': false + }); } + } + + /** + * Handle unhandled promise rejections + * @param {PromiseRejectionEvent} event - Promise rejection event + */ + handleUnhandledRejection(event) { + console.error('Unhandled promise rejection:', event.reason); - // Optimized theme management - instant switching - function initThemeManager() { - const html = document.documentElement; - - // Get current theme immediately - let currentTheme = localStorage.getItem('theme') || - (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); - - // Theme toggle click handler - optimized for instant switching - function setupThemeToggle() { - const themeToggle = document.getElementById('theme-toggle'); - if (themeToggle) { - themeToggle.addEventListener('click', function() { - // Toggle theme instantly - currentTheme = currentTheme === 'dark' ? 'light' : 'dark'; - - // Apply theme change immediately - no delays or transitions - html.setAttribute('data-theme', currentTheme); - localStorage.setItem('theme', currentTheme); - - // Force repaint for instant visual change - html.offsetHeight; - }); - } - } - - // Listen for system theme changes - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) { - if (!localStorage.getItem('theme')) { - currentTheme = e.matches ? 'dark' : 'light'; - html.setAttribute('data-theme', currentTheme); - html.offsetHeight; // Force repaint - } - }); - - // Setup toggle immediately and on DOM ready - setupThemeToggle(); - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', setupThemeToggle); - } + // Report to analytics if available + if (window.gtag) { + gtag('event', 'exception', { + 'description': `Unhandled Promise: ${event.reason}`, + 'fatal': false + }); } + } + + /** + * Handle page unload + */ + handleBeforeUnload() { + console.log('📤 Page unloading'); - // Performance: Analytics optimization - function optimizeAnalytics() { - // Defer analytics loading - const analyticsScript = document.querySelector('script[src*="vercel.com/analytics"]'); - if (analyticsScript) { - analyticsScript.setAttribute('data-defer', 'true'); + // Cleanup modules + this.modules.forEach((module, name) => { + if (typeof module.cleanup === 'function') { + try { + module.cleanup(); + } catch (error) { + console.error(`Error cleaning up ${name} module:`, error); } - } + } + }); - // Performance: Memory management - function cleanup() { - // Clean up event listeners when page unloads - window.addEventListener('beforeunload', () => { - // Cleanup code here if needed - }); - } + dispatchCustomEvent('app:beforeunload'); + } + + /** + * Handle page load complete + */ + handlePageLoad() { + console.log('🎯 Page load complete'); + document.documentElement.classList.add('page-loaded'); + dispatchCustomEvent('app:loaded'); - // Performance: Sticky header scroll effect - function initStickyHeader() { - const header = document.querySelector('.site-header'); - if (header) { - window.addEventListener('scroll', throttle(() => { - if (window.scrollY > 50) { - header.classList.add('scrolled'); - } else { - header.classList.remove('scrolled'); - } - }, 10)); - } + // Report performance metrics + const performance = this.getModule('performance'); + if (performance && typeof performance.reportPerformanceMetrics === 'function') { + // Performance metrics will be reported by the performance module } + } + + /** + * Handle initialization errors + * @param {Error} error - Initialization error + */ + handleInitializationError(error) { + // Show user-friendly error message + console.error('Application failed to initialize properly'); - // Performance: Initialize everything when DOM is ready - function init() { - // Preload critical resources - preloadCriticalResources(); - - // Optimize font loading - optimizeFontLoading(); - - // Initialize smooth scroll - initSmoothScroll(); - - // Initialize loading states - initLoadingStates(); - - // Initialize sticky header - initStickyHeader(); - - // Optimize images - optimizeImages(); - - // Register service worker - registerServiceWorker(); - - // Optimize analytics - optimizeAnalytics(); - - // Setup cleanup - cleanup(); - - // Performance: Mark page as loaded - window.addEventListener('load', () => { - document.documentElement.classList.add('page-loaded'); - }); - } + // Try to initialize in degraded mode + this.initializeDegradedMode(); + } + + /** + * Initialize application in degraded mode (fallback) + */ + initializeDegradedMode() { + console.log('🔄 Initializing in degraded mode'); - // Initialize theme manager immediately (don't wait for DOM) - initThemeManager(); + // Basic functionality only + document.documentElement.classList.add('degraded-mode'); - // Performance: Use DOMContentLoaded for faster initialization - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', init); - } else { - init(); + // Ensure theme still works + if (!this.modules.has('theme')) { + try { + this.modules.set('theme', new ThemeManager()); + } catch (error) { + console.error('Even theme failed in degraded mode:', error); + } } - -})(); \ No newline at end of file + } + + /** + * Get module instance + * @param {string} name - Module name + * @returns {Object|null} Module instance + */ + getModule(name) { + return this.modules.get(name) || null; + } + + /** + * Check if module is loaded + * @param {string} name - Module name + * @returns {boolean} Whether module is loaded + */ + hasModule(name) { + return this.modules.has(name); + } + + /** + * Get application version + * @returns {string} Application version + */ + getVersion() { + return this.version; + } + + /** + * Get initialization status + * @returns {boolean} Whether app is initialized + */ + isInitialized() { + return this.initialized; + } + + /** + * Reload application + */ + reload() { + console.log('🔄 Reloading application'); + window.location.reload(); + } + + /** + * Debug information + * @returns {Object} Debug information + */ + debug() { + return { + version: this.version, + initialized: this.initialized, + modules: Array.from(this.modules.keys()), + userAgent: navigator.userAgent, + url: window.location.href, + timestamp: new Date().toISOString() + }; + } +} + +// Initialize application +const app = new App(); + +// Make app available globally for debugging +window.app = app; + +// Start initialization +app.init().catch(error => { + console.error('Critical app initialization failure:', error); +}); + +// Export for potential module usage +export default app; \ No newline at end of file diff --git a/assets/js/modules/charts.js b/assets/js/modules/charts.js new file mode 100644 index 0000000..874cfde --- /dev/null +++ b/assets/js/modules/charts.js @@ -0,0 +1,483 @@ +/** + * Charts Module + * Modern, accessible, and theme-aware chart management + */ + +export class ChartManager { + constructor(container) { + this.container = container; + this.charts = new Map(); + this.chartConfig = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + labels: { + usePointStyle: true, + padding: 20, + font: { + family: 'Satoshi', + size: 14 + } + } + }, + tooltip: { + enabled: true, + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleFont: { + family: 'Satoshi', + size: 14, + weight: 'bold' + }, + bodyFont: { + family: 'Satoshi', + size: 13 + }, + cornerRadius: 8, + padding: 12 + } + }, + animation: { + duration: 800, + easing: 'easeOutCubic' + }, + interaction: { + intersect: false, + mode: 'index' + } + }; + + this.colors = { + primary: '#0099ff', + secondary: '#ffd700', + success: '#10b981', + danger: '#ef4444', + warning: '#f59e0b', + purple: '#8b5cf6', + gray: '#6b7280' + }; + } + + /** + * Initialize chart manager + */ + async init() { + try { + // Load Chart.js dynamically + await this.loadChartJS(); + + // Setup intersection observer for performance + this.setupIntersectionObserver(); + + // Listen for theme changes + this.setupThemeListener(); + + } catch (error) { + console.error('Failed to initialize ChartManager:', error); + } + } + + /** + * Load Chart.js library dynamically + */ + async loadChartJS() { + if (window.Chart) return Promise.resolve(); + + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.min.js'; + script.onload = resolve; + script.onerror = reject; + document.head.appendChild(script); + }); + } + + /** + * Setup intersection observer for lazy loading + */ + setupIntersectionObserver() { + if (!('IntersectionObserver' in window)) { + this.initializeCharts(); + return; + } + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + this.initializeCharts(); + observer.unobserve(entry.target); + } + }); + }, { + threshold: 0.2, + rootMargin: '50px' + }); + + observer.observe(this.container); + } + + /** + * Initialize all charts in the container + */ + initializeCharts() { + const canvases = this.container.querySelectorAll('canvas[data-chart-type]'); + + canvases.forEach(canvas => { + const chartType = canvas.dataset.chartType; + const chartId = canvas.id; + + if (this.charts.has(chartId)) return; // Already initialized + + try { + const chart = this.createChart(canvas, chartType); + if (chart) { + this.charts.set(chartId, chart); + } + } catch (error) { + console.error(`Failed to create ${chartType} chart:`, error); + } + }); + } + + /** + * Create chart based on type + * @param {HTMLCanvasElement} canvas - Canvas element + * @param {string} type - Chart type + * @returns {Chart|null} Chart instance + */ + createChart(canvas, type) { + const ctx = canvas.getContext('2d'); + const themeColors = this.getThemeColors(); + + switch (type) { + case 'memory': + return this.createMemoryChart(ctx, themeColors); + case 'addiction': + return this.createAddictionChart(ctx, themeColors); + case 'platforms': + return this.createPlatformChart(ctx, themeColors); + default: + console.warn(`Unknown chart type: ${type}`); + return null; + } + } + + /** + * Create memory performance chart + */ + createMemoryChart(ctx, themeColors) { + return new Chart(ctx, { + type: 'bar', + data: { + labels: ['Light Multitaskers', 'Heavy Multitaskers'], + datasets: [{ + label: 'Working Memory Score (%)', + data: [85, 60], + backgroundColor: [ + this.colors.primary + '99', // 60% opacity + this.colors.danger + '99' + ], + borderColor: [ + this.colors.primary, + this.colors.danger + ], + borderWidth: 2, + borderRadius: 6, + borderSkipped: false + }] + }, + options: { + ...this.chartConfig, + layout: { + padding: { + top: 20, + right: 20, + bottom: 20, + left: 20 + } + }, + scales: { + x: { + grid: { + display: false + }, + ticks: { + color: themeColors.text, + font: { + family: 'Satoshi', + size: 12, + weight: '500' + } + } + }, + y: { + beginAtZero: true, + max: 100, + grid: { + color: themeColors.grid, + lineWidth: 1 + }, + ticks: { + stepSize: 20, + color: themeColors.text, + font: { + family: 'Satoshi', + size: 11 + }, + callback: function(value) { + return value + '%'; + } + } + } + }, + plugins: { + ...this.chartConfig.plugins, + tooltip: { + ...this.chartConfig.plugins.tooltip, + callbacks: { + label: function(context) { + return `${context.dataset.label}: ${context.parsed.y}%`; + } + } + } + } + } + }); + } + + /** + * Create addiction awareness chart + */ + createAddictionChart(ctx, themeColors) { + return new Chart(ctx, { + type: 'doughnut', + data: { + labels: ['Find social media "addicting"', 'Do not find it addicting'], + datasets: [{ + data: [82, 18], + backgroundColor: [ + this.colors.danger + 'CC', // 80% opacity + themeColors.muted + '80' // 50% opacity + ], + borderColor: [ + this.colors.danger, + themeColors.muted + ], + borderWidth: 2, + hoverBorderWidth: 3 + }] + }, + options: { + ...this.chartConfig, + cutout: '60%', + layout: { + padding: 20 + }, + plugins: { + ...this.chartConfig.plugins, + legend: { + display: true, + position: 'bottom', + labels: { + usePointStyle: true, + padding: 20, + color: themeColors.text, + font: { + family: 'Satoshi', + size: 12, + weight: '500' + } + } + }, + tooltip: { + ...this.chartConfig.plugins.tooltip, + callbacks: { + label: function(context) { + return `${context.label}: ${context.parsed}%`; + } + } + } + } + } + }); + } + + /** + * Create platform usage chart + */ + createPlatformChart(ctx, themeColors) { + return new Chart(ctx, { + type: 'bar', + data: { + labels: ['WhatsApp', 'Instagram', 'Facebook'], + datasets: [{ + label: 'Usage among internet users (%)', + data: [80.8, 77.9, 67.8], + backgroundColor: [ + this.colors.success + '99', + this.colors.purple + '99', + this.colors.primary + '99' + ], + borderColor: [ + this.colors.success, + this.colors.purple, + this.colors.primary + ], + borderWidth: 2, + borderRadius: 6, + borderSkipped: false + }] + }, + options: { + ...this.chartConfig, + indexAxis: 'y', + layout: { + padding: { + top: 20, + right: 30, + bottom: 20, + left: 20 + } + }, + scales: { + x: { + beginAtZero: true, + max: 100, + grid: { + color: themeColors.grid + }, + ticks: { + color: themeColors.text, + font: { + family: 'Satoshi', + size: 11 + }, + callback: function(value) { + return value + '%'; + } + } + }, + y: { + grid: { + display: false + }, + ticks: { + color: themeColors.text, + font: { + family: 'Satoshi', + size: 12, + weight: '500' + } + } + } + }, + plugins: { + ...this.chartConfig.plugins, + tooltip: { + ...this.chartConfig.plugins.tooltip, + callbacks: { + label: function(context) { + return `${context.dataset.label}: ${context.parsed.x}%`; + } + } + } + } + } + }); + } + + /** + * Get theme-aware colors + */ + getThemeColors() { + const isDark = document.documentElement.getAttribute('data-theme') === 'dark' || + (!document.documentElement.getAttribute('data-theme') && + window.matchMedia('(prefers-color-scheme: dark)').matches); + + return { + text: isDark ? '#e5e7eb' : '#374151', + muted: isDark ? '#9ca3af' : '#6b7280', + grid: isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)', + background: isDark ? '#1a1a1a' : '#ffffff' + }; + } + + /** + * Setup theme change listener + */ + setupThemeListener() { + // Listen for theme changes + document.addEventListener('themechange', () => { + this.updateChartsTheme(); + }); + + // Also listen for attribute changes on document element + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') { + this.updateChartsTheme(); + } + }); + }); + + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-theme'] + }); + } + + /** + * Update chart colors for theme changes + */ + updateChartsTheme() { + const themeColors = this.getThemeColors(); + + this.charts.forEach((chart) => { + // Update axis colors + if (chart.options.scales) { + Object.keys(chart.options.scales).forEach(scaleKey => { + const scale = chart.options.scales[scaleKey]; + if (scale.ticks) { + scale.ticks.color = themeColors.text; + } + if (scale.grid) { + scale.grid.color = themeColors.grid; + } + }); + } + + // Update legend colors + if (chart.options.plugins?.legend?.labels) { + chart.options.plugins.legend.labels.color = themeColors.text; + } + + // Update the chart + chart.update('none'); // No animation for theme changes + }); + } + + /** + * Destroy all charts + */ + destroy() { + this.charts.forEach((chart) => { + chart.destroy(); + }); + this.charts.clear(); + } + + /** + * Get chart instance by ID + */ + getChart(chartId) { + return this.charts.get(chartId); + } + + /** + * Get all chart instances + */ + getAllCharts() { + return Array.from(this.charts.values()); + } +} diff --git a/assets/js/modules/navigation.js b/assets/js/modules/navigation.js new file mode 100644 index 0000000..df69ea0 --- /dev/null +++ b/assets/js/modules/navigation.js @@ -0,0 +1,298 @@ +/** + * Navigation Module + * Handles site navigation, smooth scrolling, and header behavior + */ + +import { throttle, smoothScrollTo } from './utils.js'; + +export class NavigationManager { + constructor() { + this.header = document.querySelector('.site-header'); + this.lastScrollY = window.scrollY; + this.scrollThreshold = 50; + + this.init(); + } + + /** + * Initialize navigation functionality + */ + init() { + this.setupSmoothScrolling(); + this.setupHeaderScroll(); + this.setupKeyboardNavigation(); + this.setupMobileNavigation(); + } + + /** + * Setup smooth scrolling for anchor links + */ + setupSmoothScrolling() { + // Handle anchor links + const anchorLinks = document.querySelectorAll('a[href^="#"]'); + anchorLinks.forEach(link => { + link.addEventListener('click', this.handleAnchorClick.bind(this)); + }); + + // Handle hash changes + window.addEventListener('hashchange', this.handleHashChange.bind(this)); + + // Handle initial hash on page load + if (window.location.hash) { + setTimeout(() => this.handleHashChange(), 100); + } + } + + /** + * Handle anchor link clicks + * @param {Event} event - Click event + */ + handleAnchorClick(event) { + const href = event.currentTarget.getAttribute('href'); + if (href.length <= 1) return; // Skip empty or just "#" links + + const targetId = href.substring(1); + const target = document.getElementById(targetId); + + if (target) { + event.preventDefault(); + + // Calculate offset for fixed header + const headerHeight = this.header ? this.header.offsetHeight : 0; + const offset = headerHeight + 20; // Add some padding + + smoothScrollTo(target, { offset }); + + // Update URL without triggering scroll + if (history.pushState) { + history.pushState(null, null, href); + } else { + window.location.hash = href; + } + } + } + + /** + * Handle hash changes + */ + handleHashChange() { + const hash = window.location.hash; + if (hash) { + const target = document.querySelector(hash); + if (target) { + const headerHeight = this.header ? this.header.offsetHeight : 0; + const offset = headerHeight + 20; + smoothScrollTo(target, { offset }); + } + } + } + + /** + * Setup header scroll behavior + */ + setupHeaderScroll() { + if (!this.header) return; + + const handleScroll = throttle(() => { + const currentScrollY = window.scrollY; + + // Add/remove scrolled class + if (currentScrollY > this.scrollThreshold) { + this.header.classList.add('site-header--scrolled'); + } else { + this.header.classList.remove('site-header--scrolled'); + } + + this.lastScrollY = currentScrollY; + }, 10); + + window.addEventListener('scroll', handleScroll, { passive: true }); + } + + /** + * Setup keyboard navigation + */ + setupKeyboardNavigation() { + // Handle escape key for modals/dropdowns + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape') { + this.closeAllDropdowns(); + } + }); + + // Skip to main content link for accessibility + this.createSkipLink(); + } + + /** + * Create skip to main content link for accessibility + */ + createSkipLink() { + const skipLink = document.createElement('a'); + skipLink.href = '#main'; + skipLink.className = 'skip-link sr-only'; + skipLink.textContent = 'Skip to main content'; + skipLink.style.cssText = ` + position: absolute; + top: -40px; + left: 6px; + background: var(--color-primary); + color: white; + padding: 8px; + text-decoration: none; + border-radius: 4px; + z-index: 10000; + transition: top 0.3s; + `; + + skipLink.addEventListener('focus', () => { + skipLink.style.top = '6px'; + }); + + skipLink.addEventListener('blur', () => { + skipLink.style.top = '-40px'; + }); + + skipLink.addEventListener('click', (event) => { + event.preventDefault(); + const main = document.querySelector('#main, main, [role="main"]'); + if (main) { + main.focus(); + main.scrollIntoView({ behavior: 'smooth' }); + } + }); + + document.body.insertBefore(skipLink, document.body.firstChild); + } + + /** + * Setup mobile navigation (if needed) + */ + setupMobileNavigation() { + // Mobile menu toggle functionality can be added here + // For now, the current design doesn't need a mobile menu + + // Add touch-friendly enhancements + this.addTouchEnhancements(); + } + + /** + * Add touch enhancements for mobile devices + */ + addTouchEnhancements() { + // Add touch delay removal + if ('ontouchstart' in window) { + document.body.classList.add('touch-device'); + + // Improve touch target sizes + const style = document.createElement('style'); + style.textContent = ` + .touch-device button, + .touch-device a, + .touch-device input, + .touch-device select { + min-height: 44px; + min-width: 44px; + } + `; + document.head.appendChild(style); + } + } + + /** + * Close all dropdown menus + */ + closeAllDropdowns() { + const dropdowns = document.querySelectorAll('.dropdown.open, .menu.open'); + dropdowns.forEach(dropdown => { + dropdown.classList.remove('open'); + }); + } + + /** + * Get current scroll position + * @returns {number} Current scroll Y position + */ + getScrollPosition() { + return window.scrollY || document.documentElement.scrollTop; + } + + /** + * Check if header is in scrolled state + * @returns {boolean} Whether header is scrolled + */ + isHeaderScrolled() { + return this.header && this.header.classList.contains('site-header--scrolled'); + } + + /** + * Scroll to top of page + * @param {boolean} smooth - Whether to use smooth scrolling + */ + scrollToTop(smooth = true) { + if (smooth) { + window.scrollTo({ + top: 0, + behavior: 'smooth' + }); + } else { + window.scrollTo(0, 0); + } + } + + /** + * Get navigation height (useful for offset calculations) + * @returns {number} Navigation height in pixels + */ + getNavigationHeight() { + return this.header ? this.header.offsetHeight : 0; + } + + /** + * Highlight active navigation item based on current page + */ + highlightActiveNavItem() { + const currentPath = window.location.pathname; + const navLinks = document.querySelectorAll('.nav-link, .menu-item a'); + + navLinks.forEach(link => { + const linkPath = new URL(link.href).pathname; + if (linkPath === currentPath) { + link.classList.add('active'); + link.setAttribute('aria-current', 'page'); + } else { + link.classList.remove('active'); + link.removeAttribute('aria-current'); + } + }); + } + + /** + * Update page title and meta description + * @param {string} title - New page title + * @param {string} description - New meta description + */ + updatePageMeta(title, description) { + if (title) { + document.title = title; + } + + if (description) { + let metaDesc = document.querySelector('meta[name="description"]'); + if (!metaDesc) { + metaDesc = document.createElement('meta'); + metaDesc.name = 'description'; + document.head.appendChild(metaDesc); + } + metaDesc.content = description; + } + } + + /** + * Cleanup navigation event listeners + */ + cleanup() { + // Remove event listeners if component is destroyed + // This is useful for SPA-like behavior + } +} diff --git a/assets/js/modules/performance.js b/assets/js/modules/performance.js new file mode 100644 index 0000000..5d32732 --- /dev/null +++ b/assets/js/modules/performance.js @@ -0,0 +1,297 @@ +/** + * Performance Optimization Module + * Handles lazy loading, image optimization, and performance monitoring + */ + +export class PerformanceManager { + constructor() { + this.observerOptions = { + root: null, + rootMargin: '50px', + threshold: 0.1 + }; + + this.init(); + } + + /** + * Initialize performance optimizations + */ + init() { + this.optimizeFonts(); + this.setupLazyLoading(); + this.setupImageOptimization(); + this.setupIntersectionObserver(); + this.monitorPerformance(); + } + + /** + * Optimize font loading + */ + optimizeFonts() { + if ('fonts' in document) { + Promise.all([ + document.fonts.load('400 1em Satoshi'), + document.fonts.load('700 1em Unbounded') + ]).then(() => { + document.documentElement.classList.add('fonts-loaded'); + }).catch(error => { + console.warn('Font loading failed:', error); + }); + } + } + + /** + * Setup lazy loading for images + */ + setupLazyLoading() { + // Use native lazy loading where supported + if ('loading' in HTMLImageElement.prototype) { + const images = document.querySelectorAll('img:not([loading])'); + images.forEach(img => { + // Add lazy loading to images below the fold + if (img.getBoundingClientRect().top > window.innerHeight) { + img.loading = 'lazy'; + } + }); + } else { + // Fallback for browsers without native lazy loading + this.setupIntersectionObserverLazyLoading(); + } + } + + /** + * Setup intersection observer for lazy loading fallback + */ + setupIntersectionObserverLazyLoading() { + const images = document.querySelectorAll('img[data-src]'); + if (images.length === 0) return; + + const imageObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const img = entry.target; + img.src = img.dataset.src; + img.removeAttribute('data-src'); + imageObserver.unobserve(img); + } + }); + }, this.observerOptions); + + images.forEach(img => imageObserver.observe(img)); + } + + /** + * Optimize images + */ + setupImageOptimization() { + const images = document.querySelectorAll('img'); + images.forEach(img => { + // Add error handling + img.addEventListener('error', this.handleImageError); + + // Add loading state + img.addEventListener('load', this.handleImageLoad); + }); + } + + /** + * Handle image loading errors + * @param {Event} event - Error event + */ + handleImageError(event) { + const img = event.target; + console.warn('Image failed to load:', img.src); + + // Hide broken images or show placeholder + img.style.display = 'none'; + + // Optionally, add a placeholder or retry logic + if (img.dataset.fallback) { + img.src = img.dataset.fallback; + } + } + + /** + * Handle successful image loading + * @param {Event} event - Load event + */ + handleImageLoad(event) { + const img = event.target; + img.classList.add('loaded'); + } + + /** + * Setup intersection observer for animations and loading states + */ + setupIntersectionObserver() { + const elements = document.querySelectorAll('.hero__headline, .post-item, .data-card'); + if (elements.length === 0) return; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('in-view'); + observer.unobserve(entry.target); + } + }); + }, this.observerOptions); + + elements.forEach(element => { + element.classList.add('pre-load'); + observer.observe(element); + }); + } + + /** + * Monitor performance metrics + */ + monitorPerformance() { + // Monitor Core Web Vitals + this.measureCLS(); + this.measureFID(); + this.measureLCP(); + + // Monitor page load performance + this.measurePageLoad(); + } + + /** + * Measure Cumulative Layout Shift (CLS) + */ + measureCLS() { + if ('LayoutShiftObserver' in window) { + let clsValue = 0; + const observer = new LayoutShiftObserver((entries) => { + for (const entry of entries) { + if (!entry.hadRecentInput) { + clsValue += entry.value; + } + } + }); + observer.observe({ type: 'layout-shift', buffered: true }); + + // Report CLS after page is loaded + window.addEventListener('beforeunload', () => { + if (clsValue > 0.1) { + console.warn(`High CLS detected: ${clsValue}`); + } + }); + } + } + + /** + * Measure First Input Delay (FID) + */ + measureFID() { + if ('PerformanceObserver' in window) { + const observer = new PerformanceObserver((list) => { + for (const entry of list.getEntries()) { + if (entry.processingStart && entry.startTime) { + const fid = entry.processingStart - entry.startTime; + if (fid > 100) { + console.warn(`High FID detected: ${fid}ms`); + } + } + } + }); + observer.observe({ type: 'first-input', buffered: true }); + } + } + + /** + * Measure Largest Contentful Paint (LCP) + */ + measureLCP() { + if ('PerformanceObserver' in window) { + const observer = new PerformanceObserver((list) => { + const entries = list.getEntries(); + const lastEntry = entries[entries.length - 1]; + if (lastEntry.startTime > 2500) { + console.warn(`High LCP detected: ${lastEntry.startTime}ms`); + } + }); + observer.observe({ type: 'largest-contentful-paint', buffered: true }); + } + } + + /** + * Measure page load performance + */ + measurePageLoad() { + window.addEventListener('load', () => { + // Mark page as fully loaded + document.documentElement.classList.add('page-loaded'); + + // Measure performance timing + if ('performance' in window && 'timing' in performance) { + const timing = performance.timing; + const loadTime = timing.loadEventEnd - timing.navigationStart; + const domReady = timing.domContentLoadedEventEnd - timing.navigationStart; + + if (loadTime > 3000) { + console.warn(`Slow page load: ${loadTime}ms`); + } + + // Optional: Send metrics to analytics + this.reportPerformanceMetrics({ + loadTime, + domReady, + firstPaint: timing.responseStart - timing.navigationStart + }); + } + }); + } + + /** + * Report performance metrics (override in analytics module) + * @param {Object} metrics - Performance metrics + */ + reportPerformanceMetrics(metrics) { + // This can be overridden by analytics module + if (window.gtag) { + gtag('event', 'page_load_time', { + 'custom_parameter': metrics.loadTime + }); + } + } + + /** + * Preload critical resources + * @param {Array} resources - Array of resource URLs + */ + preloadResources(resources = []) { + const defaultResources = [ + '/fonts/Satoshi-Regular.otf', + '/fonts/Unbounded-VariableFont_wght.ttf' + ]; + + [...defaultResources, ...resources].forEach(resource => { + const link = document.createElement('link'); + link.rel = 'preload'; + link.crossOrigin = 'anonymous'; + + if (resource.endsWith('.css')) { + link.as = 'style'; + } else if (resource.includes('font')) { + link.as = 'font'; + link.type = resource.endsWith('.woff2') ? 'font/woff2' : 'font/otf'; + } else if (resource.endsWith('.js')) { + link.as = 'script'; + } + + link.href = resource; + document.head.appendChild(link); + }); + } + + /** + * Cleanup performance observers + */ + cleanup() { + // Called on page unload if needed + if (this.observer) { + this.observer.disconnect(); + } + } +} diff --git a/assets/js/modules/theme.js b/assets/js/modules/theme.js new file mode 100644 index 0000000..99cdc39 --- /dev/null +++ b/assets/js/modules/theme.js @@ -0,0 +1,172 @@ +/** + * Theme Management Module + * Handles dark/light theme switching with optimized performance + */ + +export class ThemeManager { + constructor() { + this.html = document.documentElement; + this.storageKey = 'theme'; + this.prefersDarkQuery = window.matchMedia('(prefers-color-scheme: dark)'); + + this.init(); + } + + /** + * Initialize theme system + */ + init() { + // Get current theme immediately + this.currentTheme = this.getCurrentTheme(); + + // Apply theme immediately if not already set + if (!this.html.hasAttribute('data-theme')) { + this.applyTheme(this.currentTheme, false); + } + + // Setup theme toggle + this.setupThemeToggle(); + + // Listen for system theme changes + this.setupSystemThemeListener(); + } + + /** + * Get current theme from storage or system preference + * @returns {string} 'light' or 'dark' + */ + getCurrentTheme() { + const stored = localStorage.getItem(this.storageKey); + if (stored) return stored; + + return this.prefersDarkQuery.matches ? 'dark' : 'light'; + } + + /** + * Apply theme to document + * @param {string} theme - 'light' or 'dark' + * @param {boolean} save - Whether to save to localStorage + */ + applyTheme(theme, save = true) { + // Remove any existing theme attribute + this.html.removeAttribute('data-theme'); + + // Force reflow to ensure clean state + this.html.offsetHeight; + + // Apply new theme (only if dark, light is default) + if (theme === 'dark') { + this.html.setAttribute('data-theme', 'dark'); + } + + // Force another reflow for instant visual change + this.html.offsetHeight; + + // Save to localStorage + if (save) { + localStorage.setItem(this.storageKey, theme); + } + + this.currentTheme = theme; + + // Dispatch custom event for other modules to listen + this.dispatchThemeChangeEvent(theme); + } + + /** + * Toggle between light and dark theme + */ + toggle() { + const newTheme = this.currentTheme === 'dark' ? 'light' : 'dark'; + this.applyTheme(newTheme); + } + + /** + * Setup theme toggle button + */ + setupThemeToggle() { + const setupToggle = () => { + const themeToggle = document.getElementById('theme-toggle'); + if (themeToggle && !themeToggle._themeHandlerAttached) { + themeToggle.addEventListener('click', () => this.toggle()); + themeToggle._themeHandlerAttached = true; + + // Add keyboard support + themeToggle.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + this.toggle(); + } + }); + } + }; + + // Setup immediately and on DOM ready + setupToggle(); + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', setupToggle); + } + } + + /** + * Listen for system theme changes + */ + setupSystemThemeListener() { + this.prefersDarkQuery.addEventListener('change', (e) => { + // Only auto-switch if user hasn't manually set a preference + if (!localStorage.getItem(this.storageKey)) { + const newTheme = e.matches ? 'dark' : 'light'; + this.applyTheme(newTheme, false); + } + }); + } + + /** + * Dispatch theme change event + * @param {string} theme - The new theme + */ + dispatchThemeChangeEvent(theme) { + const event = new CustomEvent('themechange', { + detail: { theme }, + bubbles: true + }); + document.dispatchEvent(event); + } + + /** + * Get current theme + * @returns {string} Current theme ('light' or 'dark') + */ + getTheme() { + return this.currentTheme; + } + + /** + * Check if dark mode is active + * @returns {boolean} + */ + isDarkMode() { + return this.currentTheme === 'dark'; + } + + /** + * Set theme programmatically + * @param {string} theme - 'light' or 'dark' + */ + setTheme(theme) { + if (theme === 'light' || theme === 'dark') { + this.applyTheme(theme); + } else { + console.warn(`Invalid theme: ${theme}. Use 'light' or 'dark'.`); + } + } + + /** + * Reset theme to system preference + */ + resetToSystem() { + localStorage.removeItem(this.storageKey); + const systemTheme = this.prefersDarkQuery.matches ? 'dark' : 'light'; + this.applyTheme(systemTheme, false); + } +} diff --git a/assets/js/modules/utils.js b/assets/js/modules/utils.js new file mode 100644 index 0000000..adfb3fe --- /dev/null +++ b/assets/js/modules/utils.js @@ -0,0 +1,293 @@ +/** + * Utility Functions Module + * Common helper functions and utilities + */ + +/** + * Debounce function to limit function calls + * @param {Function} func - Function to debounce + * @param {number} wait - Wait time in milliseconds + * @param {boolean} immediate - Execute on leading edge + * @returns {Function} Debounced function + */ +export function debounce(func, wait, immediate = false) { + let timeout; + return function executedFunction(...args) { + const later = () => { + timeout = null; + if (!immediate) func.apply(this, args); + }; + const callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(this, args); + }; +} + +/** + * Throttle function to limit function execution frequency + * @param {Function} func - Function to throttle + * @param {number} limit - Time limit in milliseconds + * @returns {Function} Throttled function + */ +export function throttle(func, limit) { + let inThrottle; + return function(...args) { + if (!inThrottle) { + func.apply(this, args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + }; +} + +/** + * Get browser support information + * @returns {Object} Browser support object + */ +export function getBrowserSupport() { + return { + intersectionObserver: 'IntersectionObserver' in window, + performanceObserver: 'PerformanceObserver' in window, + webp: (() => { + const canvas = document.createElement('canvas'); + return canvas.toDataURL ? canvas.toDataURL('image/webp').indexOf('webp') > -1 : false; + })(), + lazyLoading: 'loading' in HTMLImageElement.prototype, + customElements: 'customElements' in window, + cssCustomProperties: CSS.supports('color', 'var(--test)'), + serviceWorker: 'serviceWorker' in navigator + }; +} + +/** + * Safely parse JSON with error handling + * @param {string} jsonString - JSON string to parse + * @param {*} fallback - Fallback value if parsing fails + * @returns {*} Parsed object or fallback + */ +export function safeJsonParse(jsonString, fallback = null) { + try { + return JSON.parse(jsonString); + } catch (error) { + console.warn('JSON parsing failed:', error); + return fallback; + } +} + +/** + * Get element's offset relative to document + * @param {Element} element - Target element + * @returns {Object} Offset object with top and left properties + */ +export function getElementOffset(element) { + const rect = element.getBoundingClientRect(); + return { + top: rect.top + window.pageYOffset, + left: rect.left + window.pageXOffset, + width: rect.width, + height: rect.height + }; +} + +/** + * Check if element is in viewport + * @param {Element} element - Target element + * @param {number} threshold - Visibility threshold (0-1) + * @returns {boolean} Whether element is in viewport + */ +export function isInViewport(element, threshold = 0) { + const rect = element.getBoundingClientRect(); + const windowHeight = window.innerHeight || document.documentElement.clientHeight; + const windowWidth = window.innerWidth || document.documentElement.clientWidth; + + const vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0); + const horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0); + + if (threshold === 0) { + return vertInView && horInView; + } + + const visibleHeight = Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0); + const visibleWidth = Math.min(rect.right, windowWidth) - Math.max(rect.left, 0); + const visibleArea = visibleHeight * visibleWidth; + const totalArea = rect.height * rect.width; + + return (visibleArea / totalArea) >= threshold; +} + +/** + * Smooth scroll to element + * @param {Element|string} target - Target element or selector + * @param {Object} options - Scroll options + */ +export function smoothScrollTo(target, options = {}) { + const element = typeof target === 'string' ? document.querySelector(target) : target; + if (!element) return; + + const defaultOptions = { + behavior: 'smooth', + block: 'start', + inline: 'nearest', + offset: 0 + }; + + const finalOptions = { ...defaultOptions, ...options }; + + if (finalOptions.offset) { + const elementPosition = getElementOffset(element).top; + const offsetPosition = elementPosition - finalOptions.offset; + + window.scrollTo({ + top: offsetPosition, + behavior: finalOptions.behavior + }); + } else { + element.scrollIntoView({ + behavior: finalOptions.behavior, + block: finalOptions.block, + inline: finalOptions.inline + }); + } +} + +/** + * Create and dispatch custom event + * @param {string} eventName - Event name + * @param {*} detail - Event detail data + * @param {Element} target - Target element (default: document) + */ +export function dispatchCustomEvent(eventName, detail = null, target = document) { + const event = new CustomEvent(eventName, { + detail, + bubbles: true, + cancelable: true + }); + target.dispatchEvent(event); +} + +/** + * Wait for DOM content to be loaded + * @returns {Promise} Promise that resolves when DOM is ready + */ +export function waitForDOM() { + return new Promise(resolve => { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', resolve, { once: true }); + } else { + resolve(); + } + }); +} + +/** + * Wait for all images to load + * @param {Element} container - Container element (default: document) + * @returns {Promise} Promise that resolves when all images are loaded + */ +export function waitForImages(container = document) { + const images = container.querySelectorAll('img'); + const promises = Array.from(images).map(img => { + return new Promise((resolve) => { + if (img.complete) { + resolve(); + } else { + img.addEventListener('load', resolve, { once: true }); + img.addEventListener('error', resolve, { once: true }); + } + }); + }); + + return Promise.all(promises); +} + +/** + * Format file size in human readable format + * @param {number} bytes - File size in bytes + * @param {number} decimals - Number of decimal places + * @returns {string} Formatted file size + */ +export function formatFileSize(bytes, decimals = 2) { + if (bytes === 0) return '0 Bytes'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +} + +/** + * Generate random ID + * @param {number} length - ID length + * @returns {string} Random ID + */ +export function generateId(length = 8) { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; +} + +/** + * Copy text to clipboard + * @param {string} text - Text to copy + * @returns {Promise} Success status + */ +export async function copyToClipboard(text) { + try { + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(text); + return true; + } else { + // Fallback for older browsers + const textArea = document.createElement('textarea'); + textArea.value = text; + textArea.style.position = 'fixed'; + textArea.style.opacity = '0'; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + const successful = document.execCommand('copy'); + document.body.removeChild(textArea); + return successful; + } + } catch (error) { + console.error('Failed to copy text:', error); + return false; + } +} + +/** + * Escape HTML special characters + * @param {string} text - Text to escape + * @returns {string} Escaped text + */ +export function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + +/** + * Get CSS custom property value + * @param {string} property - CSS custom property name + * @param {Element} element - Element to get property from (default: document.documentElement) + * @returns {string} Property value + */ +export function getCSSCustomProperty(property, element = document.documentElement) { + return getComputedStyle(element).getPropertyValue(property).trim(); +} + +/** + * Set CSS custom property value + * @param {string} property - CSS custom property name + * @param {string} value - Property value + * @param {Element} element - Element to set property on (default: document.documentElement) + */ +export function setCSSCustomProperty(property, value, element = document.documentElement) { + element.style.setProperty(property, value); +} diff --git a/build.sh b/build.sh index 93137e9..458e2d3 100755 --- a/build.sh +++ b/build.sh @@ -1,16 +1,148 @@ #!/bin/bash -# Production build script -# This builds the site with production URLs for deployment +# Enhanced Build Script for Production Deployment +# Senior Frontend Developer Optimizations -echo "🏗️ Building site for production..." -echo "🌐 Using production baseURL: https://blog.nischalskanda.tech/" -echo "" +set -e # Exit on any error + +echo "🚀 Starting enhanced production build..." + +# Color codes for better output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" +} + +print_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +print_error() { + echo -e "${RED}❌ $1${NC}" +} + +# Check if Hugo is installed +if ! command -v hugo &> /dev/null; then + print_error "Hugo is not installed. Please install Hugo first." + exit 1 +fi + +# Get Hugo version +HUGO_VERSION=$(hugo version) +print_status "Using $HUGO_VERSION" + +# Clean previous build +print_status "Cleaning previous build..." +rm -rf public/ +rm -rf resources/_gen/ 2>/dev/null || true +print_success "Build directory cleaned" -# Clean and build for production with explicit baseURL -hugo --cleanDestinationDir --minify --baseURL="https://blog.nischalskanda.tech/" +# Validate content before building +print_status "Validating content..." +if hugo --quiet --buildDrafts=false --buildFuture=false --renderToMemory; then + print_success "Content validation passed" +else + print_error "Content validation failed" + exit 1 +fi +# Run Hugo build with optimizations +print_status "Building site with Hugo optimizations..." + +# Check if we're in a CI environment (like Vercel) +if [ -n "$CI" ] || [ -n "$VERCEL" ]; then + print_status "Detected CI/Vercel environment - using optimized build" + hugo \ + --minify \ + --gc \ + --cleanDestinationDir \ + --baseURL="https://blog.nischalskanda.tech/" +else + print_status "Local development build" + hugo \ + --minify \ + --gc \ + --cleanDestinationDir \ + --enableGitInfo \ + --templateMetrics \ + --templateMetricsHints \ + --printMemoryUsage \ + --printPathWarnings \ + --printUnusedTemplates \ + --baseURL="https://blog.nischalskanda.tech/" +fi + +# Check build success +if [ $? -eq 0 ]; then + print_success "Hugo build completed successfully" +else + print_error "Hugo build failed" + exit 1 +fi + +# Post-build optimizations +print_status "Running post-build optimizations..." + +# Count generated files +FILE_COUNT=$(find public/ -type f | wc -l) +print_status "Generated $FILE_COUNT files" + +# Calculate total size +TOTAL_SIZE=$(du -sh public/ | cut -f1) +print_status "Total build size: $TOTAL_SIZE" + +# Check for large files that might need optimization +print_status "Checking for large files..." +LARGE_FILES=$(find public/ -type f -size +1M 2>/dev/null || true) +if [ ! -z "$LARGE_FILES" ]; then + print_warning "Large files detected (>1MB):" + echo "$LARGE_FILES" | while read file; do + if [ ! -z "$file" ]; then + size=$(du -h "$file" | cut -f1) + echo " - $file ($size)" + fi + done +else + print_success "No unusually large files detected" +fi + +# Validate critical files exist +print_status "Validating critical files..." +CRITICAL_FILES=("public/index.html" "public/sitemap.xml" "public/robots.txt") +for file in "${CRITICAL_FILES[@]}"; do + if [ -f "$file" ]; then + print_success "$(basename "$file") exists" + else + print_error "Critical file missing: $file" + exit 1 + fi +done + +# Performance hints +print_status "Performance optimization summary:" +echo " 📦 CSS files: $(find public/ -name "*.css" 2>/dev/null | wc -l)" +echo " 📜 JS files: $(find public/ -name "*.js" 2>/dev/null | wc -l)" +echo " 🖼️ Images: $(find public/ \( -name "*.jpg" -o -name "*.png" -o -name "*.webp" -o -name "*.avif" \) 2>/dev/null | wc -l)" +echo " 📄 HTML files: $(find public/ -name "*.html" 2>/dev/null | wc -l)" + +# Final success message +print_success "🎉 Enhanced production build completed successfully!" +echo "" +echo "📊 Build Summary:" +echo " 📁 Output directory: ./public/" +echo " 🌐 Base URL: https://blog.nischalskanda.tech/" +echo " 📈 Total files: $FILE_COUNT" +echo " 💾 Total size: $TOTAL_SIZE" +echo " ⏱️ Build completed at: $(date)" echo "" -echo "✅ Production build complete!" -echo "📁 Files are ready in ./public/ directory" -echo "🚀 Deploy the ./public/ directory to your hosting provider" +echo "🚀 Ready for deployment!" diff --git a/content/posts/.DS_Store b/content/posts/.DS_Store index c04fde4..090962f 100644 Binary files a/content/posts/.DS_Store and b/content/posts/.DS_Store differ diff --git a/content/posts/brain_rot/images/thumbnail.jpg b/content/posts/brain_rot/images/thumbnail.jpg new file mode 100644 index 0000000..f61707a Binary files /dev/null and b/content/posts/brain_rot/images/thumbnail.jpg differ diff --git a/content/posts/brain_rot/index.md b/content/posts/brain_rot/index.md new file mode 100644 index 0000000..a017ef5 --- /dev/null +++ b/content/posts/brain_rot/index.md @@ -0,0 +1,115 @@ +--- +title: "The Dopamine Brain Rot" +date: 2025-08-10T10:30:00+05:30 +draft: false +description: "A blunt, research-backed rant about how short-form content hijacks your reward system—and practical habits to reclaim your focus." +tags: ["Brain Health", "Cognitive Science", "Technology", "Product Design"] +categories: ["Health", "Technology"] +--- +{{< img src="images/thumbnail.jpg" alt="brain cluttered with all kinds of social media" caption="Everything you watch on internet is SHIT" >}} +**99% of you will not make it to the end of this post.** + +So, we need to talk about *brain rot*. No, not the zombie kind. The TikTok, Instagram, YouTube Shorts, Spotify, 2x speed podcast kind. + +You know that thing where your attention span is now shorter than a reel about "7 hacks to wake up at 5 AM"? Yeah. That. + +Turns out, we’ve successfully trained our brains to crave dopamine like it’s sugar at a kindergarten birthday party. And guess what’s fuelling this craving? Our beloved apps. Our Gen Z-approved, aesthetically pleasing, algorithm-controlled, snack-sized content playgrounds. + +**80% of you just clicked away.** For the 20% still here, buckle up. + +--- + +## What Even *Is* Dopamine Brain Rot? + +Imagine your brain as a squirrel. Now imagine throwing that squirrel 100 acorns per minute, each shinier than the last. That’s what dopamine overload feels like. It’s not your fault. It’s literally how your brain’s reward system works—**a chemical high-five for anything stimulating**. + +But when you constantly get that high-five from 6-second reels, playlist hopping, or switching between five apps in 3 minutes, your brain starts to say: + +> "Hmm... why read a book when I can doomscroll for 3 hours and call it a 'mental health break'?" + +This isn’t just a vibe issue. It’s a *neurochemical war*. + +**Only 10% of you are still reading.** Legends. + +--- + +## Numbers? You Want Numbers? + +* A study published in **Nature** says overstimulation from social media shortens attention span and reduces our brain’s ability to retain info. +* The **average Gen Z** attention span is now **less than 8 seconds**, officially beating the goldfish. +* **47%** of Gen Z reports feeling *mentally exhausted* after extended TikTok/Instagram sessions, according to the APA. + +Congrats, we’re the first generation to scroll ourselves into burnout before breakfast. + +**Down to 5% now.** The real ones. + +{{< infographics items="stats,memory" >}} + +--- + +## Okay But Is It *Just* TikTok’s Fault? + +Nope. Your Spotify playlist addiction isn’t innocent either. + +Switching songs mid-chorus? Skipping podcasts because the host paused for breath? Yeah, that’s dopamine junkie behavior. + +Even productivity tools like Notion or habit trackers? Guilty. We chase the *feeling* of being productive, not the result. Checking things off gives you a dopamine squirt. But finishing an actual project? Too long. NEXT. + +**3% left. Y’all are built different.** + +--- + +## And the Side Effects Are... Real Bad + +* **Chronic dissatisfaction**: Nothing hits anymore. +* **Anxiety**: Your brain’s on constant ping-alert. +* **Mental fatigue**: Browser tabs open in your head. Forever. +* **Creative block**: Consuming 10,000 things, producing none. + +Even rest feels like a task you have to optimize. + +**Still here? You’re the 1% this post was meant for.** + +--- + +{{< infographics items="addiction,platforms" >}} + +## So… What the Hell Do We Do? + +We fight back. With small, boring habits. + +1. **Dopamine detoxes that aren’t cringe** + + * Not "live in the woods"—just leave your phone in another room. Survive 10 minutes of boredom. +2. **Mono-task like a medieval peasant** + + * One tab. One task. One brain. +3. **Reward boredom** + + * That’s when the good ideas creep in. +4. **Create before you consume** + + * Write. Draw. Think. Just don’t start with someone else’s hot take. +5. **Sleep. Like, actual sleep.** + + * You don’t need to earn rest, you need to protect it. + +--- + +## Final Thoughts (Before You Scroll Again) + +We’re not broken. But we are being hacked. + +Dopamine isn’t the villain. It’s the messenger. And right now, the message is: *you might be entertaining yourself into creative extinction.* + +Close this tab. Stare at a wall. Let your brain remember boredom. + +**Your mind deserves better than reels, refreshes, and recycled sound bites.** + +Let it rest. Let it rot less. + +Let it *live*. + +— Nischal ✌️ + + diff --git a/dev.sh b/dev.sh index d8d3f11..2c86141 100755 --- a/dev.sh +++ b/dev.sh @@ -1,18 +1,37 @@ #!/bin/bash -# Development server script for local development -# This ensures all URLs point to localhost instead of production +# Enhanced Development Server Script +# Optimized for local development with hot reloading + +set -e # Exit on any error echo "🚀 Starting Hugo development server..." echo "📍 Server will be available at http://localhost:1313/" echo "🔗 All links will use localhost URLs for proper local development" +echo "🔥 Hot reloading enabled for instant updates" echo "" +# Clean any previous build artifacts for fresh start +echo "🧹 Cleaning previous build artifacts..." +rm -rf public/ 2>/dev/null || true +rm -rf resources/_gen/ 2>/dev/null || true + +echo "🔧 Starting development server with enhanced features..." + +# Start Hugo development server with optimized settings hugo server \ --buildDrafts \ --buildFuture \ + --buildExpired \ --baseURL="http://localhost:1313" \ --bind="127.0.0.1" \ - --port=1313 + --port=1313 \ + --navigateToChanged \ + --watch \ + --livereload \ + --disableFastRender=false \ + --gc \ + --cleanDestinationDir +echo "" echo "👋 Development server stopped" diff --git a/hugo.toml b/hugo.toml index 12942ce..ca657a9 100644 --- a/hugo.toml +++ b/hugo.toml @@ -43,12 +43,15 @@ canonifyURLs = true googleAnalytics = "" # Add your GA4 tracking ID vercelAnalytics = true # Enable Vercel Analytics -# Performance - lazyLoading = true - -# Build performance +# Performance & Build Optimization [build] writeStats = true + includeFiles = [ + "assets/**", + "layouts/**", + "content/**", + "static/**" + ] [minify] disableCSS = false @@ -57,36 +60,117 @@ canonifyURLs = true disableJSON = false disableSVG = false disableXML = false + minifyOutput = true + # Advanced minification settings + [minify.tdewolff] + [minify.tdewolff.css] + keepCSS2 = true + precision = 0 + [minify.tdewolff.html] + keepComments = false + keepConditionalComments = true + keepDefaultAttrVals = true + keepDocumentTags = true + keepEndTags = true + keepQuotes = false + keepWhitespace = false + [minify.tdewolff.js] + keepVarNames = false + precision = 0 + [minify.tdewolff.json] + precision = 0 + [minify.tdewolff.svg] + precision = 1 + [minify.tdewolff.xml] + keepWhitespace = false + +# Enhanced Caching Strategy [caches] [caches.getjson] - maxAge = "10m" + dir = ":cacheDir/:project" + maxAge = "1h" [caches.getcsv] - maxAge = "10m" + dir = ":cacheDir/:project" + maxAge = "1h" + [caches.getresource] + dir = ":cacheDir/:project" + maxAge = "24h" [caches.images] - maxAge = "8760h" # 1 year + dir = ":resourceDir/_gen" + maxAge = "8760h" # 1 year - images rarely change [caches.assets] + dir = ":resourceDir/_gen" + maxAge = "720h" # 30 days - assets may change more frequently + [caches.modules] + dir = ":cacheDir/modules" maxAge = "8760h" # 1 year - + +# Advanced Image Processing [imaging] - # Optimized image settings for performance + # High quality settings with performance optimization quality = 85 - resampleFilter = "Lanczos" + resampleFilter = "CatmullRom" # Better quality than Lanczos for most images hint = "photo" anchor = "Smart" + bgColor = "#ffffff" - # Generate WebP versions for better performance + # WebP generation for modern browsers [imaging.webp] quality = 85 + lossless = false + + # AVIF generation for cutting-edge performance + [imaging.avif] + quality = 80 + speed = 5 # Balance between compression and encoding speed + +# Security configuration optimized for Vercel +[security] + enableInlineShortcodes = false + [security.exec] + allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^npx$', '^postcss$', '^git$'] + osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+|(XDG_)?CACHE_HOME|YARN_CACHE_FOLDER)$'] + [security.funcs] + getenv = ['^HUGO_', '^CI$', '^VERCEL_', '^NEXT_PUBLIC_'] + [security.http] + methods = ['(?i)GET|POST'] + urls = ['.*'] [markup] [markup.goldmark] [markup.goldmark.renderer] unsafe = true + hardWraps = false + xhtml = false + [markup.goldmark.parser] + autoHeadingID = true + autoHeadingIDType = "github" + [markup.goldmark.parser.attribute] + block = false + title = true + [markup.goldmark.extensions] + definitionList = true + footnote = true + linkify = true + linkifyProtocol = "https" + strikethrough = true + table = true + taskList = true + typographer = true [markup.highlight] style = "github" lineNos = true + lineNumbersInTable = false + noClasses = false codeFences = true + guessSyntax = false + hl_Lines = "" + anchorLineNos = false + [markup.tableOfContents] + endLevel = 3 + ordered = false + startLevel = 2 # Menu configuration for better navigation [menu] diff --git a/hugo_stats.json b/hugo_stats.json index 2b1bbd1..3b9809b 100644 --- a/hugo_stats.json +++ b/hugo_stats.json @@ -7,11 +7,13 @@ "body", "br", "button", + "canvas", "code", "div", "em", "figcaption", "figure", + "footer", "h1", "h2", "h3", @@ -47,29 +49,74 @@ ], "classes": [ "author-bio", + "big-stat", + "big-stat-desc", + "chart-card", + "chart-card__title", + "chart-container", "container", - "content", - "hero-headline", - "hero-section", - "interactive-arrow", - "interactive-o", + "data-card", + "hero", + "hero__headline", + "hero__interactive-arrow", + "hero__interactive-o", + "infograph-grid", + "infographics", "logo", "main-site-link", - "post-content", - "post-description", - "post-header", + "pagination", + "pagination__info", + "pagination__link", + "pagination__link--next", + "pagination__link--prev", + "post", "post-item", + "post-item__date", + "post-item__excerpt", + "post-item__header", + "post-item__link", + "post-item__meta", + "post-item__tag", + "post-item__tags", + "post-item__title", "post-list", - "post-meta", - "post-navigation", - "post-tags", + "post-nav", + "post-nav__container", + "post-nav__item", + "post-nav__item--next", + "post-nav__label", + "post-nav__link", + "post__categories", + "post__category-link", + "post__content", + "post__date", + "post__description", + "post__footer", + "post__header", + "post__meta", + "post__meta-separator", + "post__reading-time", + "post__tags", + "post__tags-list", + "post__tags-title", + "post__title", "responsive-image", "site-header", + "site-header__container", + "social-btn", + "social-btn--linkedin", + "social-btn--twitter", "social-sharing", + "social-sharing__buttons", + "social-sharing__title", + "span-full", + "sr-only", + "stats-card", "table-container", + "tag", "theme-text", - "theme-text-dark", - "theme-text-light", + "theme-text--dark", + "theme-text--light", "theme-toggle" ], "ids": [ @@ -79,19 +126,38 @@ "4-unplug-before-you-crash", "5-make-rules-to-keep-ai-in-check", "6-move-your-butt-to-save-your-brain", + "addiction-description", + "addictionChart-1754840264", + "and-the-side-effects-are-real-bad", "conclusion", + "final-thoughts-before-you-scroll-again", "final-word", + "focus-desc", + "hero-headline", + "infographics-1754840264", "introduction", + "main", + "memory-description", + "memoryChart-1754840264", "my-product-designer-take-aka-why-did-this-feature-even-exist", + "numbers-you-want-numbers", + "okay-but-is-it-just-tiktoks-fault", + "platform-description", + "platformChart-1754840264", + "posts-heading", + "productivity-desc", "references", "saving-our-brains-before-theyre-toast", "so-how-the-heck-do-you-protect-yourself", + "so-what-the-hell-do-we-do", "the-mit-study-chatgpts-lowkey-brain-robbery", "the-numbers-that-make-you-wince", "the-sad-funny-tale-of-our-brains-demise", "the-tech-designers-perspective", "the-wider-tragedy-were-all-in-this-mess", "theme-toggle", + "usage-desc", + "what-even-is-dopamine-brain-rot", "when-even-sam-altman-says-dont-do-it", "where-the-internet-lost-its-mind-pcmag-update", "you-should-never-share-anything-personal-with-chatgpt" diff --git a/resources/_gen/images/posts/ai_brain_impact/images/brain-ai-comparison_hu_7942114c0d212f96.jpg b/layouts/404.html similarity index 100% rename from resources/_gen/images/posts/ai_brain_impact/images/brain-ai-comparison_hu_7942114c0d212f96.jpg rename to layouts/404.html diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index ff5a92c..7635652 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -5,7 +5,13 @@ {{ partial "head-seo.html" . }} - {{ $style := resources.Get "css/style.css" | resources.Minify | resources.Fingerprint }} + {{ $theme := resources.Get "css/theme.css" }} + {{ $base := resources.Get "css/base.css" }} + {{ $layout := resources.Get "css/layout.css" }} + {{ $components := resources.Get "css/components.css" }} + {{ $utilities := resources.Get "css/utilities.css" }} + {{ $legacy := resources.Get "css/style.css" }} + {{ $style := slice $theme $base $layout $components $utilities $legacy | resources.Concat "css/bundle.css" | resources.Minify | resources.Fingerprint }} @@ -35,29 +41,44 @@ {{ end }} -
- - -
- - {{ block "main" . }}{{ end }} -
-
+ + + Main Site + + + + + +
+ {{ block "main" . }}{{ end }} +
{{ $js := resources.Get "js/main.js" | resources.Minify | resources.Fingerprint }} - + {{ $analyticsTest := resources.Get "js/analytics-test.js" | resources.Minify }} diff --git a/layouts/_default/list.html b/layouts/_default/list.html index ee9a7cc..505712a 100644 --- a/layouts/_default/list.html +++ b/layouts/_default/list.html @@ -1,17 +1,60 @@ {{ define "main" }} -
-

- THE ONLY BLOGS YOU
- WOULD LOVE TO READ +
+

+ THE ONLY BLOGS YOU
+ WOULD LOVE TO READ

-

+ -
+
+

Blog Posts

{{ range .Paginator.Pages }} -
-

{{ .Title }}

- +
+
+

+ +

+
+ + {{- if .Params.tags }} +
+ {{- range $index, $tag := first 3 .Params.tags }} + #{{ $tag }} + {{- end }} +
+ {{- end }} +
+
+ {{- if .Description }} +
+ {{ .Description }} +
+ {{- end }}
{{ end }} -
+ + {{ if gt .Paginator.TotalPages 1 }} + + {{ end }} + {{ end }} diff --git a/layouts/_default/single.html b/layouts/_default/single.html index 210a588..3954bb3 100644 --- a/layouts/_default/single.html +++ b/layouts/_default/single.html @@ -1,29 +1,31 @@ {{ define "main" }} -
-
-

{{ .Title }}

-