diff --git a/website/assets/default.webm b/website/assets/default.webm new file mode 100644 index 0000000..b35a7ea Binary files /dev/null and b/website/assets/default.webm differ diff --git a/website/css/animations.css b/website/css/animations.css deleted file mode 100644 index 8c75590..0000000 --- a/website/css/animations.css +++ /dev/null @@ -1,46 +0,0 @@ -@keyframes heroFloat { - from { transform: translateY(0); } - to { transform: translateY(-12px); } -} - -@keyframes marquee { - from { transform: translateX(0); } - to { transform: translateX(-33.33%); } -} - -@keyframes fadeInUp { - from { opacity: 0; transform: translateY(28px); } - to { opacity: 1; transform: translateY(0); } -} - -@keyframes textFloat { - 0%,100% { transform: rotateX(20deg) rotateY(-20deg) translateZ(0); } - 50% { transform: rotateX(25deg) rotateY(-15deg) translateZ(40px); } -} - -@keyframes bubbleFadeIn { - from { opacity: 0; transform: scale(0.9) translateY(16px); } - to { opacity: 1; transform: scale(1) translateY(0); } -} - -@keyframes cursorBlink { - 0%,100% { opacity: 1; } - 50% { opacity: 0; } -} - -@keyframes scrollCuePulse { - 0%,100% { opacity: 0.4; transform: scaleY(1); } - 50% { opacity: 0.8; transform: scaleY(1.15); } -} - -/* Scroll reveal classes */ -.reveal { - opacity: 0; - transform: translateY(24px); - transition: opacity 0.55s ease, transform 0.55s ease; -} - -.reveal.revealed { - opacity: 1; - transform: translateY(0); -} \ No newline at end of file diff --git a/website/css/components.css b/website/css/components.css index f0d8346..757312d 100644 --- a/website/css/components.css +++ b/website/css/components.css @@ -1,222 +1,150 @@ -/* Navigation */ -nav { - position: fixed; - top: 0; - width: 100%; - height: 64px; - background: rgba(13, 11, 14, 0.82); - backdrop-filter: blur(20px) saturate(160%); - border-bottom: 1px solid var(--border); - z-index: 100; - transition: border-color 0.3s; -} - -nav.scrolled { - border-bottom-color: var(--border-hover); -} - -.nav-content { - display: flex; - align-items: center; - justify-content: space-between; - height: 100%; -} - -.nav-logo { - display: flex; - align-items: center; - gap: 12px; - text-decoration: none; - color: var(--text-primary); - font-size: 1.25rem; - font-weight: 600; -} - -.nav-logo img { - height: 34px; -} - -.nav-links { - display: none; - gap: 24px; -} - -.nav-links a { - text-decoration: none; - color: var(--text-muted); - font-size: 0.8rem; - transition: color 0.2s; -} - -.nav-links a:hover { - color: var(--text-primary); -} - -.nav-actions { - display: none; - align-items: center; - gap: 16px; -} - -@media (min-width: 768px) { - .nav-links { display: flex; } - .nav-actions { display: flex; } -} - -/* Buttons */ .btn-primary { + background: var(--sky); + color: white; + border-radius: var(--radius-pill); + font-family: 'Plus Jakarta Sans', sans-serif; + font-weight: 700; + font-size: 0.85rem; + padding: 10px 24px; display: inline-flex; align-items: center; justify-content: center; - background: var(--pink); - color: #0D0B0E; - border-radius: var(--radius-pill); - font-family: 'Syne', sans-serif; - font-weight: 700; - font-size: clamp(0.85rem, 2.5vw, 1rem); - padding: clamp(12px, 3vw, 15px) clamp(24px, 5vw, 40px); - text-decoration: none; - transition: background 0.2s, transform 0.2s, box-shadow 0.2s; + transition: background 0.2s ease, transform 0.2s ease; } .btn-primary:hover { - background: #f9a8d4; + background: var(--sky-deep); transform: translateY(-1px); - box-shadow: var(--glow-sm); } -.btn-primary.small { - font-size: 0.85rem; - padding: 10px 24px; -} - -.btn-ghost { - display: inline-flex; - align-items: center; - gap: 8px; - font-family: 'Syne', sans-serif; - font-weight: 600; - font-size: 0.9rem; - color: var(--text-secondary); - text-decoration: none; - transition: color 0.2s; +.btn-large { + padding: 14px 32px; + font-size: 1rem; } -.btn-ghost:hover { - color: var(--text-primary); -} - -.btn-ghost .arrow { - transition: transform 0.2s; +.card { + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-sm); + padding: 32px; + transition: box-shadow 0.3s ease, border-color 0.3s ease, transform 0.3s ease; } -.btn-ghost:hover .arrow { - transform: translateX(3px); +.card:hover { + box-shadow: var(--shadow-md), var(--shadow-sky); + border-color: var(--sky-border-md); + transform: translateY(-4px); } -.btn-outline { +.chip { + background: var(--sky-faint); + border: 1px solid var(--sky-border); + color: var(--sky-deep); + border-radius: var(--radius-sm); + padding: 4px 10px; + font-size: var(--text-xs); + font-weight: 600; display: inline-flex; align-items: center; - border: 1px solid var(--border); - color: var(--text-secondary); - padding: 8px 16px; - border-radius: var(--radius-sm); - text-decoration: none; - font-size: 0.8rem; - transition: border-color 0.2s, color 0.2s; } -.btn-outline:hover { - border-color: var(--border-hover); - color: var(--pink); +.container { + max-width: var(--max-w); + margin: 0 auto; + padding: 0 24px; } -/* Badges & Chips */ -.badge-os { +.section-label { + color: var(--sky); + font-weight: 700; font-size: 0.72rem; - color: var(--text-muted); - border: 1px solid var(--border); - border-radius: var(--radius-sm); - padding: 6px 14px; - display: inline-block; + text-transform: uppercase; + letter-spacing: 0.12em; + margin-bottom: 12px; + display: block; } -.chip { - background: var(--bg-surface); - border: 1px solid var(--border); - border-radius: var(--radius-sm); - padding: 6px 12px; - font-family: 'Syne', sans-serif; - font-weight: 500; - font-size: 0.8rem; - color: var(--text-secondary); +/* Navbar */ +.nav { + position: fixed; + top: 0; + left: 0; + right: 0; + height: 70px; + background: rgba(243, 250, 255, 0.88); + backdrop-filter: blur(16px) saturate(140%); + border-bottom: 1px solid var(--border); + z-index: 1000; + transition: box-shadow 0.3s ease; } -.badge-mode-soft { - background: rgba(244,114,182,0.12); - border: 1px solid var(--border-pink); - color: var(--pink); - padding: 4px 10px; - border-radius: var(--radius-pill); +.nav--scrolled { + box-shadow: var(--shadow-sm); } -.badge-license { - color: #FCD34D; - border: 1px solid rgba(252,211,77,0.3); - padding: 4px 10px; - border-radius: var(--radius-sm); +.nav-container { + display: flex; + align-items: center; + justify-content: space-between; + height: 100%; } -code { - background: var(--bg-surface); - color: var(--pink); - padding: 2px 8px; - border-radius: var(--radius-sm); - font-size: 0.85em; +.nav-logo { + display: flex; + align-items: center; + gap: 12px; } -/* Cards */ -.card { - background: var(--bg-surface); - border: 1px solid var(--border); - border-radius: var(--radius-md); - padding: 24px; - transition: border-color 0.3s, box-shadow 0.3s, transform 0.3s; +.nav-logo span { + font-family: 'Fraunces', serif; + font-weight: 300; + font-size: 1.25rem; + color: var(--text-primary); } -.card:hover { - border-color: var(--border-hover); - box-shadow: var(--glow-xs); - transform: translateY(-3px); +.nav-links { + display: flex; + gap: 32px; +} + +.nav-link { + color: var(--text-muted); + transition: color 0.2s ease; } -.card-raised { - background: var(--bg-raised); +.nav-link:hover { + color: var(--text-primary); } -/* Stats */ -.stats-strip { - display: inline-flex; +.nav-actions { + display: flex; align-items: center; - gap: 16px; - opacity: 0; - animation: fadeInUp 0.6s ease forwards 0.6s; + gap: 24px; } -.stat { +.nav-stat { + font-size: 0.8rem; + font-weight: 500; + color: var(--text-secondary); display: flex; align-items: center; - gap: 6px; + gap: 4px; } -.stat-icon { - font-size: 0.9em; +.icon { + width: 1em; + height: 1em; + display: inline-block; + vertical-align: middle; + position: relative; + top: -1px; + fill: currentColor; } -.stat-value { - color: var(--text-secondary); -} +.icon--lg { font-size: 1.2em; } +.icon--sm { font-size: 0.9em; } -.stat-divider { - color: var(--text-muted); -} \ No newline at end of file +@media (max-width: 768px) { + .nav-links { display: none; } +} diff --git a/website/css/enhancements.css b/website/css/enhancements.css new file mode 100644 index 0000000..6b58911 --- /dev/null +++ b/website/css/enhancements.css @@ -0,0 +1,184 @@ +/* Time-of-Day Dark Theme */ +html.theme-dark { + --bg-base: #0A1118; + --bg-layer: #0D1821; + --bg-surface: #111F2C; + --bg-raised: #162838; + --bg-ink-wash: #05080C; + --text-primary: #F3FAFF; + --text-secondary:#BEE0F5; + --text-muted: #3E6A89; + --border: rgba(59, 158, 217, 0.15); + --border-mid: rgba(59, 158, 217, 0.3); + --shadow-sm: 0 4px 12px rgba(0,0,0,0.5); + --shadow-md: 0 8px 24px rgba(0,0,0,0.6); + --sky-border: rgba(59, 158, 217, 0.25); + --sky-border-md: rgba(59, 158, 217, 0.55); + --sky-faint: #0c1f30; + --sky-pale: #112940; +} + +/* Scroll-Synced Text Highlighting */ +.scroll-sync-text { + color: var(--text-muted); + background: linear-gradient(to right, var(--text-primary) 50%, var(--text-muted) 50%); + background-size: 200% 100%; + background-position: calc(100% - clamp(0, (var(--p, 0) - var(--sync-start, 0)) / (var(--sync-end, 1) - var(--sync-start, 0)), 1) * 100%) 0; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + display: inline-block; + transition: background-position 0.1s linear; +} + +/* Chapter Progress Dots */ +.progress-dots { + position: fixed; + right: 24px; + top: 50%; + transform: translateY(-50%); + display: flex; + flex-direction: column; + gap: 16px; + z-index: 1000; +} +.progress-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--border-mid); + transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); +} +.progress-dot.active { + background: var(--sky); + transform: scale(1.8); +} + +/* Sticky CTA */ +.sticky-cta { + position: fixed; + bottom: 32px; + left: 50%; + transform: translateX(-50%) translateY(100px); + z-index: 999; + opacity: 0; + pointer-events: none; + transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1); +} +.sticky-cta.visible { + transform: translateX(-50%) translateY(0); + opacity: 1; + pointer-events: auto; +} + +/* Settings Hotspots */ +.mock-settings { + position: relative; + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 24px; + box-shadow: var(--shadow-sm); + margin-top: 60px; + width: 100%; + max-width: 600px; + height: 180px; + margin-left: auto; + margin-right: auto; +} +.mock-settings-header { + height: 24px; + border-bottom: 1px solid var(--border); + margin-bottom: 16px; + display: flex; + gap: 8px; + align-items: center; +} +.mock-settings-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--border-mid); +} +.hotspot { + position: absolute; + width: 16px; + height: 16px; + background: var(--sky); + border-radius: 50%; + cursor: pointer; + animation: pulse 2s infinite; +} +.hotspot::after { + content: attr(data-tooltip); + position: absolute; + bottom: 150%; + left: 50%; + transform: translateX(-50%) translateY(10px); + background: rgba(255, 255, 255, 0.85); + backdrop-filter: blur(20px); + border: 1px solid var(--border); + color: var(--text-primary); + padding: 8px 12px; + border-radius: var(--radius-sm); + font-size: 0.75rem; + white-space: nowrap; + font-weight: 600; + opacity: 0; + pointer-events: none; + transition: all 0.3s ease; + box-shadow: var(--shadow-sm); +} +html.theme-dark .hotspot::after { + background: rgba(0, 0, 0, 0.85); + color: white; +} +.hotspot:hover::after { + opacity: 1; + transform: translateX(-50%) translateY(0); +} +@keyframes pulse { + 0% { box-shadow: 0 0 0 0 rgba(59, 158, 217, 0.7); } + 70% { box-shadow: 0 0 0 10px rgba(59, 158, 217, 0); } + 100% { box-shadow: 0 0 0 0 rgba(59, 158, 217, 0); } +} + +/* Cursor Particles */ +.cursor-particle { + position: fixed; + width: 4px; + height: 4px; + background: var(--sky); + border-radius: 50%; + pointer-events: none; + z-index: 9999; + animation: particleFade 0.8s forwards ease-out; +} +@keyframes particleFade { + 0% { transform: translate(-50%, -50%) scale(1); opacity: 0.8; } + 100% { transform: translate(-50%, -50%) scale(3); opacity: 0; } +} + +/* Floating Testimonial Quotes */ +.floating-quote { + position: absolute; + background: rgba(255, 255, 255, 0.5); + backdrop-filter: blur(10px); + border: 1px solid var(--border); + padding: 16px 24px; + border-radius: var(--radius-lg); + font-style: italic; + font-family: 'Fraunces', serif; + color: var(--text-secondary); + font-size: 0.9rem; + z-index: 0; + opacity: 0.6; + animation: floatQuote 20s infinite alternate ease-in-out; + pointer-events: none; +} +html.theme-dark .floating-quote { + background: rgba(0, 0, 0, 0.3); +} +@keyframes floatQuote { + 0% { transform: translate(0, 0) rotate(-2deg); } + 100% { transform: translate(20px, -30px) rotate(2deg); } +} diff --git a/website/css/layout.css b/website/css/layout.css deleted file mode 100644 index 48255ec..0000000 --- a/website/css/layout.css +++ /dev/null @@ -1,56 +0,0 @@ -.container { - width: 100%; - max-width: var(--max-w); - margin: 0 auto; - padding: 0 1.5rem; -} - -section { - padding: var(--section-pad); -} - -.bg-layer { - background-color: var(--bg-layer); -} - -.bg-surface { - background-color: var(--bg-surface); -} - -/* Utility layout classes */ -.flex { display: flex; } -.flex-col { display: flex; flex-direction: column; } -.items-center { align-items: center; } -.justify-center { justify-content: center; } -.justify-between { justify-content: space-between; } -.gap-2 { gap: 0.5rem; } -.gap-4 { gap: 1rem; } -.gap-6 { gap: 1.5rem; } -.gap-8 { gap: 2rem; } -.gap-12 { gap: 3rem; } - -.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)); } - -.text-center { text-align: center; } -.text-right { text-align: right; } - -.mx-auto { margin-left: auto; margin-right: auto; } -.mt-8 { margin-top: 2rem; } -.mt-12 { margin-top: 3rem; } -.mb-8 { margin-bottom: 2rem; } -.mb-12 { margin-bottom: 3rem; } - -@media (min-width: 768px) { - .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\:flex-row { flex-direction: row; } - .md\:text-left { text-align: left; } -} - -@media (min-width: 1024px) { - .lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } - .lg\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } -} \ No newline at end of file diff --git a/website/css/main.css b/website/css/main.css index 7c4bc70..e482167 100644 --- a/website/css/main.css +++ b/website/css/main.css @@ -1,676 +1,325 @@ -@import './variables.css'; @import './reset.css'; +@import './variables.css'; @import './typography.css'; -@import './layout.css'; @import './components.css'; -@import './animations.css'; - -/* --- Global Utilities & Overrides --- */ - -/* Scrollbar styling for a consistent dark theme */ -::-webkit-scrollbar { - width: 10px; - height: 10px; -} - -::-webkit-scrollbar-track { - background: var(--bg-void); -} - -::-webkit-scrollbar-thumb { - background: var(--bg-surface); - border-radius: var(--radius-pill); - border: 2px solid var(--bg-void); -} - -::-webkit-scrollbar-thumb:hover { - background: var(--text-muted); -} - -/* Selection color */ -::selection { - background: var(--pink-glow); - color: var(--text-primary); -} +@import './scroll-story.css'; +@import './simulator.css'; +@import './enhancements.css'; -/* --- Section Specific Styles --- */ - -/* Hero Specific */ -#hero { - position: relative; - min-height: 100svh; - display: flex; - align-items: center; - justify-content: center; - background-image: repeating-linear-gradient(0deg, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 1px, transparent 1px, transparent 48px); +html { + scroll-behavior: smooth; + scroll-snap-type: y proximity; + overflow-x: hidden; } -#hero::before { - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -60%); - width: 600px; - height: 400px; - background: radial-gradient(ellipse at center, rgba(244,114,182,0.07) 0%, transparent 70%); - pointer-events: none; - z-index: 0; +/* Hide scrollbar for Chrome, Safari and Opera */ +*::-webkit-scrollbar { + display: none; } -.hero-content { - position: relative; - z-index: 1; - max-width: 700px; - display: flex; - flex-direction: column; - align-items: center; +/* Hide scrollbar for IE, Edge and Firefox */ +* { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ } -.hero-logo { - height: 160px; - filter: drop-shadow(0 20px 50px rgba(244,114,182,0.3)); - animation: heroFloat 4s ease-in-out infinite alternate; - transition: transform 0.3s ease, filter 0.3s ease; - margin-bottom: 2rem; +main { + width: 100%; } -@media (max-width: 768px) { - .hero-logo { - height: 120px; - } +.act-wrapper { + scroll-snap-align: start; } -.hero-logo:hover { - transform: scale(1.04) translateY(-12px); - filter: drop-shadow(0 25px 60px rgba(244,114,182,0.4)); +footer { + background: var(--bg-layer); + border-top: 1px solid var(--border); + padding: 80px 0 40px; } -.scroll-cue { - position: absolute; - bottom: 0; - left: 50%; - transform: translateX(-50%); - width: 1px; - height: 60px; - background: linear-gradient(to bottom, var(--pink-border), transparent); - animation: scrollCuePulse 2s ease-in-out infinite; - transform-origin: top; - transition: opacity 0.5s; -} - -.scroll-cue.hidden { - opacity: 0; -} - -/* Ticker Strip */ -.ticker-wrap { - height: 40px; - background: var(--bg-void); - border-top: 1px solid var(--border-pink); - border-bottom: 1px solid var(--border-pink); - overflow: hidden; - display: flex; - align-items: center; +.footer-grid { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + gap: 48px; + margin-bottom: 60px; } -.ticker { - display: flex; - white-space: nowrap; - animation: marquee 26s linear infinite; - font-family: 'Syne', sans-serif; +.footer-col h4 { + font-family: 'Plus Jakarta Sans', sans-serif; font-weight: 700; font-size: 0.72rem; text-transform: uppercase; - letter-spacing: 0.14em; - color: var(--text-muted); -} - -.ticker-item { - padding: 0 2rem; -} - -.ticker-sep { - color: var(--pink); + letter-spacing: 0.12em; + margin-bottom: 24px; + color: var(--text-primary); } -/* Overlay Preview */ -.overlay-preview-card { - position: relative; - width: 100%; - max-width: 860px; - aspect-ratio: 16/9; - border-radius: var(--radius-lg); - overflow: hidden; - border: 1px solid var(--border); - margin: 0 auto; - container-type: inline-size; +.footer-links { + display: flex; + flex-direction: column; + gap: 12px; } -.overlay-layer-0 { - position: absolute; - inset: 0; - background: linear-gradient(135deg, #0A0807 0%, #180D14 40%, #0A0B14 100%); +.footer-link { + font-size: 0.88rem; + color: var(--text-secondary); + transition: color 0.2s ease; } -.overlay-layer-0::after { - content: ''; - position: absolute; - inset: 0; - background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.05'/%3E%3C/svg%3E"); - pointer-events: none; +.footer-link:hover { + color: var(--sky); } -.overlay-layer-1 { - position: absolute; - inset: 0; +.footer-bottom { + border-top: 1px solid var(--border); + padding-top: 32px; display: flex; + justify-content: space-between; align-items: center; - justify-content: center; - perspective: 1000px; + font-size: 0.78rem; + color: var(--text-muted); } -.overlay-layer-1-text { - font-family: 'Cormorant Garamond', serif; - font-weight: 700; - font-size: 20cqi; - color: var(--text-primary); - opacity: 0.09; - transform-style: preserve-3d; - animation: textFloat 6s ease-in-out infinite; - text-shadow: 1px 1px 0 rgba(240,234,245,0.4), - 2px 2px 0 rgba(240,234,245,0.4), - 3px 3px 0 rgba(240,234,245,0.4), - 4px 4px 0 rgba(240,234,245,0.4), - 5px 5px 0 rgba(240,234,245,0.4); +.stats-strip { + display: flex; + align-items: baseline; + gap: 32px; + margin: 40px 0; } -.overlay-layer-2 { - position: absolute; - top: 8%; - left: 6%; - width: 23.5cqi; - height: 23.5cqi; - border-radius: 50%; - background: rgba(255, 255, 255, 0.09); - backdrop-filter: blur(28px) saturate(160%); - -webkit-backdrop-filter: blur(28px) saturate(160%); - border: 1px solid rgba(255, 255, 255, 0.18); - box-shadow: 0 32px 64px -12px rgba(0,0,0,0.7), inset 0 1px 0 rgba(255,255,255,0.12); +.stat { display: flex; flex-direction: column; align-items: center; - justify-content: center; - animation: bubbleFadeIn 1.2s cubic-bezier(0.16, 1, 0.3, 1) forwards; -} - -.bubble-time { - font-family: 'Cormorant Garamond', serif; - font-weight: 600; - font-size: 5.6cqi; - color: var(--text-primary); - line-height: 1; -} - -.bubble-title { - font-family: 'Syne', sans-serif; - font-weight: 600; - font-size: 1.3cqi; - text-transform: uppercase; - letter-spacing: 0.15em; - opacity: 0.75; - margin-top: 0.5cqi; -} - -.bubble-sub { - font-family: 'Syne', sans-serif; - font-weight: 400; - font-size: 1.1cqi; - opacity: 0.45; - margin-top: 0.3cqi; } -.bubble-skip { - position: absolute; - bottom: 2.8cqi; - font-family: 'Syne', sans-serif; - font-weight: 600; - font-size: 1.2cqi; - text-transform: uppercase; - letter-spacing: 0.1em; - padding: 0.5cqi 1.4cqi; - border-radius: var(--radius-pill); - border: 1px solid rgba(255,255,255,0.2); - background: transparent; +.stat [data-stat] { + font-family: 'Fraunces', serif; + font-weight: 300; + font-size: 2.4rem; color: var(--text-primary); - cursor: pointer; - transition: background 0.2s, border-color 0.2s; -} - -.bubble-skip:hover { - background: rgba(255,255,255,0.1); - border-color: rgba(255,255,255,0.4); -} - -.overlay-layer-3 { - position: absolute; - bottom: 1.8cqi; - right: 1.8cqi; -} - -.overlay-layer-3 .badge-mode-soft { - font-size: 1.2cqi; - padding: 0.5cqi 1.2cqi; -} - -/* How It Works */ -.step-item { - border-left: 2px solid var(--border); - padding: 0 0 0 24px; - transition: border-left-color 0.3s; -} - -.step-item:hover { - border-left-color: var(--pink); -} - -.step-number { - font-family: 'Cormorant Garamond', serif; - font-weight: 400; - font-size: 4rem; - color: var(--text-muted); - line-height: 1; - margin-bottom: 1rem; -} - -.step-title { - font-family: 'Cormorant Garamond', serif; - font-weight: 600; - font-size: 1.4rem; - color: var(--text-primary); - margin-bottom: 0.5rem; -} - -.step-connector { - display: none; - flex: 1; - border-top: 1px dashed var(--border); - position: relative; - margin: 0 2rem; - margin-top: 2rem; /* align with number */ -} - -.step-connector::after { - content: '►'; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - color: var(--pink); - font-size: 0.8rem; - background: var(--bg-layer); - padding: 0 4px; -} - -@media (min-width: 768px) { - .steps-row { - display: flex; - align-items: flex-start; - } - .step-connector { - display: block; - } -} - -/* Features */ -.features-grid { - display: grid; - grid-template-columns: 1fr; - gap: 1.5rem; -} - -@media (min-width: 768px) { - .features-grid { - grid-template-columns: repeat(2, 1fr); - } - .md\:col-span-2 { - grid-column: span 2; - } -} - -@media (min-width: 1024px) { - .features-grid { - grid-template-columns: repeat(3, 1fr); - } - .lg\:col-span-2 { - grid-column: span 2; - } - .lg\:col-span-3 { - grid-column: span 3; - } } -.feature-icon-area { - font-family: 'Syne', sans-serif; +.stat-label { + font-family: 'Plus Jakarta Sans', sans-serif; font-weight: 600; font-size: 0.7rem; text-transform: uppercase; - color: var(--pink); - letter-spacing: 0.12em; - margin-bottom: 1rem; -} - -.settings-table { - width: 100%; - margin-top: 1.5rem; - background: var(--bg-raised); - border-radius: var(--radius-sm); - overflow: hidden; - border-left: 2px solid var(--pink); -} - -.settings-table-row { - display: flex; - padding: 8px 12px; - border-bottom: 1px solid var(--border); - font-size: 0.8rem; -} - -.settings-table-row:last-child { - border-bottom: none; -} - -.settings-table-col-1 { - width: 35%; - color: var(--text-primary); -} - -.settings-table-col-2 { - width: 25%; - color: var(--text-primary); - font-family: 'JetBrains Mono', monospace; -} - -.settings-table-col-3 { - width: 40%; color: var(--text-muted); + letter-spacing: 0.1em; } -/* Modes */ -.modes-divider { - display: none; - width: 1px; - background: var(--border); - position: absolute; - top: 0; - bottom: 0; - left: 50%; - transform: translateX(-50%); -} - -.modes-divider img { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 24px; - height: 24px; - background: var(--bg-base); - padding: 4px; -} - -@media (min-width: 768px) { - .modes-divider { - display: block; - } -} - -.card-hard-mode { - border-color: var(--border-pink); - box-shadow: var(--glow-xs); +.stat-sep { + color: var(--border-mid); + font-size: 1.5rem; } -/* Open Source */ -.oss-stats { - display: flex; - gap: 1rem; - margin-top: 2rem; - margin-bottom: 2rem; - flex-wrap: wrap; +.oss-stats-row { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; + margin: 32px 0; } -.oss-stat { - background: var(--bg-raised); +.oss-stat-card { + background: var(--bg-layer); border: 1px solid var(--border); border-radius: var(--radius-md); - padding: 20px 24px; - flex: 1; - min-width: 140px; + padding: 24px; + text-align: center; } .oss-stat-value { - font-family: 'Cormorant Garamond', serif; - font-weight: 600; + font-family: 'Fraunces', serif; + font-weight: 300; font-size: 3rem; color: var(--text-primary); line-height: 1; - margin-bottom: 0.5rem; + margin-bottom: 8px; } .oss-stat-label { - font-family: 'Syne', sans-serif; + font-family: 'Plus Jakarta Sans', sans-serif; font-weight: 600; font-size: 0.7rem; text-transform: uppercase; color: var(--text-muted); + letter-spacing: 0.1em; } -.terminal-card { - background: #080608; - border: 1px solid var(--border); +/* Terminal */ +.terminal { + background: var(--bg-ink-wash); + border: 1px solid rgba(255,255,255,0.07); border-radius: var(--radius-md); overflow: hidden; - box-shadow: var(--shadow-deep); + box-shadow: var(--shadow-md); + position: relative; } .terminal-header { - background: var(--bg-surface); - padding: 8px 16px; + background: rgba(255,255,255,0.05); + padding: 12px 16px; display: flex; align-items: center; - border-bottom: 1px solid var(--border); + gap: 8px; } -.terminal-dots { +.terminal-copy-btn { + margin-left: auto; + background: rgba(255, 255, 255, 0.1); + color: white; + padding: 4px 10px; + border-radius: 4px; + font-size: 0.65rem; + font-family: 'Plus Jakarta Sans', sans-serif; + font-weight: 600; display: flex; + align-items: center; gap: 6px; - margin-right: 16px; + transition: all 0.2s ease; +} + +.terminal-copy-btn:hover { + background: var(--sky); +} + +.terminal-copy-btn.copied { + background: #22C55E; } -.dot { - width: 10px; - height: 10px; - border-radius: 50%; +/* Parallax Layers */ +.parallax-layer { + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; } -.dot.red { background: #EF4444; } -.dot.yellow { background: #EAB308; } -.dot.green { background: #22C55E; } + +.dot { width: 10px; height: 10px; border-radius: 50%; } +.dot-red { background: #EF4444; } +.dot-yellow { background: #EAB308; } +.dot-green { background: #22C55E; } .terminal-title { - font-family: 'Syne', sans-serif; - font-weight: 500; - font-size: 0.78rem; - color: var(--text-muted); + margin-left: 8px; + font-family: 'JetBrains Mono', monospace; + font-size: 0.7rem; + color: rgba(255,255,255,0.5); } .terminal-body { - padding: 16px; + padding: 24px; font-family: 'JetBrains Mono', monospace; font-size: 0.82rem; - color: var(--text-secondary); - line-height: 1.5; - min-height: 220px; + color: rgba(255,255,255,0.9); + min-height: 300px; + line-height: 1.6; } -.term-prompt { color: var(--pink); } -.term-success { color: #86EFAC; } -.term-cursor { +.t-prompt { color: var(--sky); } +.t-success { color: #86EFAC; } +.t-cursor { display: inline-block; width: 8px; - height: 1em; - background: var(--text-secondary); - vertical-align: middle; - animation: cursorBlink 1s step-end infinite; + height: 15px; + background: white; + margin-left: 4px; + animation: cursorBlink 1s infinite; } -/* Install */ -.install-timeline { - display: flex; - flex-direction: column; - gap: 2rem; +@keyframes cursorBlink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } } -.install-step { - display: flex; - gap: 1.5rem; +/* Features Grid */ +.features-grid { + display: grid; + grid-template-columns: 1.2fr 1fr; + gap: 24px; } -.install-marker { +.features-col-stacked { display: flex; flex-direction: column; - align-items: center; + gap: 24px; } -.install-circle { - width: 32px; - height: 32px; - border: 1px solid var(--border-pink); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - color: var(--pink); - font-family: 'Cormorant Garamond', serif; - font-weight: 600; - font-size: 1.1rem; -} - -.install-line { - width: 1px; - flex: 1; - background: var(--border); - margin-top: 8px; -} - -.req-card { - background: var(--bg-surface); - border-left: 2px solid var(--pink); - border-radius: 0 var(--radius-sm) var(--radius-sm) 0; - padding: 12px 16px; - font-family: 'Syne', sans-serif; - font-weight: 400; - font-size: 0.88rem; - color: var(--text-secondary); -} - -/* Download Section */ -#download { - background: var(--bg-surface); - border-top: 1px solid var(--border-pink); -} - -.btn-large { - font-size: clamp(0.95rem, 3vw, 1.1rem); - padding: clamp(14px, 3.5vw, 18px) clamp(28px, 6vw, 48px); -} - -/* Footer */ -footer { - background: var(--bg-void); - border-top: 1px solid var(--border); - padding: 4rem 1.5rem 2rem; -} - -.footer-grid { - display: grid; - grid-template-columns: 1fr; - gap: 2rem; - max-width: var(--max-w); - margin: 0 auto; -} - -@media (min-width: 768px) { - .footer-grid { - grid-template-columns: 2fr 1fr 1fr; - } +.feature-card .category { + color: var(--sky); + font-weight: 700; + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 0.12em; + margin-bottom: 8px; + display: block; } -.footer-logo { - display: flex; - align-items: center; - gap: 12px; - font-family: 'Cormorant Garamond', serif; +.feature-card h3 { + font-size: 1.3rem; font-weight: 600; - font-size: 1.2rem; - color: var(--text-primary); - text-decoration: none; - margin-bottom: 0.5rem; + margin-bottom: 12px; } -.footer-header { - font-family: 'Syne', sans-serif; - font-weight: 700; - font-size: 0.7rem; - text-transform: uppercase; - letter-spacing: 0.1em; - color: var(--text-muted); - margin-bottom: 1rem; +.feature-card p { + font-size: 0.9rem; } -.footer-links { +.settings-table { + background: var(--bg-layer); + border-radius: var(--radius-sm); + padding: 16px; + margin-top: 20px; display: flex; flex-direction: column; - gap: 0.5rem; + gap: 12px; } -.footer-links a, .footer-links span { - color: var(--text-muted); - font-size: 0.85rem; - text-decoration: none; - transition: color 0.2s; +.settings-row { + display: flex; + justify-content: space-between; + font-size: 0.82rem; } -.footer-links a:hover { - color: var(--text-primary); -} +.settings-key { font-weight: 600; color: var(--text-primary); } +.settings-val { color: var(--sky-deep); } +.settings-range { color: var(--text-muted); font-size: 0.75rem; } -.footer-bottom { - max-width: var(--max-w); - margin: 0 auto; - margin-top: 4rem; - padding-top: 2rem; - border-top: 1px solid var(--border); - text-align: center; - font-size: 0.78rem; - color: var(--text-muted); +@media (max-width: 1024px) { + .features-grid { grid-template-columns: 1fr; } + .footer-grid { grid-template-columns: 1fr 1fr; } + .act-v-layout { gap: 40px !important; } } -/* --- Interactive Cursor & Ambient Lighting --- */ -@media (pointer: fine) { - body { - cursor: url('../assets/Cat_Cursor.png'), auto; - } +@media (max-width: 768px) { + .footer-grid { grid-template-columns: 1fr; } + .footer-bottom { flex-direction: column; gap: 20px; text-align: center; } + .oss-stats-row { grid-template-columns: 1fr; } - a, button, [role="button"], input, select, textarea, .card, .chip { - cursor: url('../assets/Cat_Pointer.png'), pointer !important; - } - - #ambient-glow { - position: fixed; - inset: 0; - pointer-events: none; - z-index: 9998; - background: radial-gradient(800px circle at var(--mouse-x, 50vw) var(--mouse-y, 50vh), rgba(244, 114, 182, 0.06), transparent 40%); - transition: opacity 0.5s ease; + .terminal-container { display: none !important; } + .floating-quote { display: none !important; } + .mock-settings { display: none !important; } + + /* Natural mobile spacing reductions */ + .act { + padding: 0 16px !important; } -} \ No newline at end of file + + div[style*="margin-top: 60px"] { margin-top: 24px !important; } + div[style*="margin-top: 40px"] { margin-top: 20px !important; } + div[style*="gap: 80px"] { gap: 24px !important; } + div[style*="gap: 32px"] { gap: 12px !important; } + div[style*="gap: 24px"] { gap: 12px !important; } + + h2[style*="margin-bottom: 24px"] { margin-bottom: 12px !important; font-size: 1.8rem !important; } + h2[style*="margin: 24px 0"] { margin: 12px 0 !important; font-size: 1.8rem !important; } + + /* Interlude specific overrides */ + div[style*="font-size: var(--text-2xl)"] { font-size: 1.6rem !important; line-height: 1.3 !important; } + div[style*="margin: 24px auto"] { margin: 12px auto !important; } + + .card { padding: 12px 16px !important; } + .feature-card h3 { font-size: 1.1rem !important; margin-bottom: 4px !important; } + .feature-card p { font-size: 0.8rem !important; margin-bottom: 0 !important; line-height: 1.4; } +} diff --git a/website/css/reset.css b/website/css/reset.css index e637720..bf0bca1 100644 --- a/website/css/reset.css +++ b/website/css/reset.css @@ -1,57 +1,11 @@ -*, *::before, *::after { - box-sizing: border-box; -} - -body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol { - margin: 0; - padding: 0; -} - -ul[role='list'], ol[role='list'] { - list-style: none; -} - -html:focus-within { - scroll-behavior: smooth; -} - -html, body { - overflow-x: hidden; -} - -body { - min-height: 100vh; - text-rendering: optimizeSpeed; - line-height: 1.5; - background-color: var(--bg-base); - color: var(--text-primary); - -webkit-font-smoothing: antialiased; -} - -a:not([class]) { - text-decoration-skip-ink: auto; -} - -img, picture { - max-width: 100%; - display: block; -} - -input, button, textarea, select { - font: inherit; -} - -@media (prefers-reduced-motion: reduce) { - html:focus-within { - scroll-behavior: auto; - } - - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } -} \ No newline at end of file +/* Minimal modern reset */ +*, *::before, *::after { box-sizing: border-box; } +* { margin: 0; padding: 0; } +body { line-height: 1.5; -webkit-font-smoothing: antialiased; } +img, picture, video, canvas, svg { display: block; max-width: 100%; } +input, button, textarea, select { font: inherit; } +p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; } +a { text-decoration: none; color: inherit; } +ul { list-style: none; } +button { background: none; border: none; cursor: pointer; padding: 0; } +html { scroll-behavior: smooth; } diff --git a/website/css/scroll-story.css b/website/css/scroll-story.css new file mode 100644 index 0000000..673758f --- /dev/null +++ b/website/css/scroll-story.css @@ -0,0 +1,272 @@ +/* Pinned chapter layout */ +.act-wrapper { + position: relative; + width: 100%; +} + +.act { + position: sticky; + top: 0; + height: 100vh; + width: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +/* Animations driven by --p (0 to 1 progress) */ + +.fade-up { + opacity: calc((var(--p, 0) - var(--start, 0)) / (var(--end, 1) - var(--start, 0))); + transform: translateY(calc((1 - clamp(0, (var(--p, 0) - var(--start, 0)) / (var(--end, 1) - var(--start, 0)), 1)) * 30px)); + pointer-events: none; +} + +.fade-up.visible { + pointer-events: auto; +} + +/* Specific Act Styles */ + +/* Act 1 */ +.act1-bg { + position: absolute; + inset: 0; + background: radial-gradient(circle at center, var(--bg-surface) 0%, var(--bg-base) 100%); + opacity: calc(1 - var(--p, 0)); +} + +/* Act 3 Overlay Simulation */ +.overlay-sim { + position: absolute; + inset: 0; + background: var(--bg-ink-wash); + opacity: calc((var(--p, 0) - 0.2) / 0.2); + z-index: 5; +} + +.blur-desktop { + position: absolute; + inset: 0; + background-image: linear-gradient(rgba(255,255,255,0.05) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,0.05) 1px, transparent 1px); + background-size: 40px 40px; + filter: blur(calc(clamp(0, (var(--p, 0) - 0.2) / 0.3, 1) * 40px)); + z-index: 4; +} + +/* Break Bubble */ +.break-bubble { + width: 320px; + height: 320px; + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(30px) saturate(180%); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 50%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + box-shadow: 0 32px 64px -12px rgba(0, 0, 0, 0.6); + position: relative; + text-align: center; + color: white; + z-index: 10; + transform: translateY(calc((1 - clamp(0, (var(--p, 0) - 0.35) / 0.3, 1)) * 100px)) scale(calc(0.8 + 0.2 * clamp(0, (var(--p, 0) - 0.35) / 0.3, 1))); + opacity: calc((var(--p, 0) - 0.35) / 0.2); +} + +.break-bubble .timer-display { + font-size: 3.8rem; + font-weight: 800; + font-variant-numeric: tabular-nums; + letter-spacing: -0.04em; + background: linear-gradient(180deg, #fff 0%, rgba(255,255,255,0.7) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.break-bubble .status-text { + font-size: 0.85rem; + font-weight: 600; + opacity: 0.8; + text-transform: uppercase; + letter-spacing: 0.15em; + margin-bottom: 4px; +} + +.break-bubble .work-status { + font-size: 0.65rem; + opacity: 0.55; + margin-top: 8px; +} + +.break-bubble .skip-btn { + background: rgba(255,255,255,0.15); + border: 1px solid rgba(255,255,255,0.3); + color: white; + padding: 8px 28px; + border-radius: 50px; + font-size: 0.78rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-top: 24px; +} + +.bg-3d-text { + position: absolute; + font-size: 15vw; + font-weight: 900; + color: white; + opacity: calc(clamp(0, (var(--p, 0) - 0.4) / 0.2, 1) * 0.12); + text-transform: uppercase; + transform: rotateX(20deg) rotateY(-20deg); + text-shadow: 1px 1px 0 rgba(255,255,255,0.08), 2px 2px 0 rgba(255,255,255,0.07), 0 8px 30px rgba(0,0,0,0.3); + animation: textFloat 8s ease-in-out infinite; + pointer-events: none; + z-index: 6; +} + +@keyframes textFloat { + 0%, 100% { transform: rotateX(20deg) rotateY(-20deg) translateZ(0); } + 50% { transform: rotateX(25deg) rotateY(-14deg) translateZ(40px); } +} + +@keyframes textRotate { + from { transform: rotateX(20deg) rotateY(0deg); } + to { transform: rotateX(20deg) rotateY(360deg); } +} + +@keyframes textPulse { + 0%, 100% { transform: rotateX(20deg) rotateY(-20deg) scale(1); opacity: 0.12; } + 50% { transform: rotateX(20deg) rotateY(-20deg) scale(1.1); opacity: 0.2; } +} + +@keyframes float { + from { transform: translateY(0); } + to { transform: translateY(-10px); } +} + +.floating { + animation: float 4s ease-in-out infinite alternate; +} + +/* Reveal utility */ +.reveal { + opacity: 0; + transform: translateY(24px); + transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1), transform 0.6s cubic-bezier(0.16, 1, 0.3, 1); +} + +.reveal.visible { + opacity: 1; + transform: translateY(0); +} + +.paused-anim * { + animation-play-state: paused !important; +} + +/* Scroll Hint */ +.scroll-hint { + position: absolute; + bottom: 40px; + left: 50%; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + opacity: 1; + transition: opacity 0.5s ease, transform 0.5s ease; + z-index: 100; +} + +.scroll-hint span { + font-family: 'Plus Jakarta Sans', sans-serif; + font-weight: 600; + font-size: 0.72rem; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--text-muted); +} + +.mouse { + width: 24px; + height: 40px; + border: 2px solid var(--border-mid); + border-radius: 20px; + position: relative; +} + +.wheel { + width: 4px; + height: 8px; + background: var(--sky); + border-radius: 2px; + position: absolute; + top: 6px; + left: 50%; + transform: translateX(-50%); + animation: scrollWheel 2s infinite; +} + +@keyframes scrollWheel { + 0% { transform: translateX(-50%) translateY(0); opacity: 1; } + 100% { transform: translateX(-50%) translateY(15px); opacity: 0; } +} + +/* Footer Waves */ +.footer-waves { + position: absolute; + inset: 0; + overflow: hidden; + z-index: -1; +} + +.wave { + position: absolute; + background: var(--sky-faint); + opacity: 0.3; + transition: transform calc(1s * (1 - var(--p, 0))) ease-out; +} + +.wave--bottom { + bottom: 0; + left: 0; + right: 0; + height: 40vh; + transform: translateY(calc((1 - var(--p, 0)) * 100%)); + border-radius: 100% 100% 0 0; +} + +.wave--left { + top: 0; + bottom: 0; + left: 0; + width: 30vw; + transform: translateX(calc((1 - var(--p, 0)) * -100%)); + border-radius: 0 100% 100% 0; +} + +.wave--right { + top: 0; + bottom: 0; + right: 0; + width: 30vw; + transform: translateX(calc((1 - var(--p, 0)) * 100%)); + border-radius: 100% 0 0 100%; +} + +@media (prefers-reduced-motion: reduce) { + .act, .act-wrapper { height: auto !important; position: relative !important; } + .act { padding: 80px 20px; } + .fade-up { opacity: 1 !important; transform: none !important; } + .reveal { opacity: 1 !important; transform: none !important; } + .overlay-sim, .blur-desktop { display: none; } + .break-bubble { opacity: 1 !important; transform: none !important; margin: 40px 0; } + * { animation: none !important; } +} diff --git a/website/css/simulator.css b/website/css/simulator.css new file mode 100644 index 0000000..d1aa7a4 --- /dev/null +++ b/website/css/simulator.css @@ -0,0 +1,177 @@ +.sim-layout-container { + display: flex; + align-items: center; + justify-content: space-between; + gap: 40px; + width: 100%; + height: 100%; + z-index: 10; +} + +.sim-preview-area { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.simulator-ui { + width: 320px; + background: rgba(255, 255, 255, 0.08); + backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: var(--radius-lg); + padding: 24px; + color: white; + opacity: calc((var(--p, 0) - 0.3) / 0.2); + pointer-events: auto; +} + +.sim-select { + width: 100%; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + color: white; + padding: 8px; + border-radius: 4px; + font-size: 0.8rem; + outline: none; + cursor: var(--cursor-pointer); +} + +.sim-select option { + background: #0D2137; /* Match bg-ink-wash */ + color: white; +} + +@media (max-width: 1024px) { + .sim-layout-container { + flex-direction: column; + justify-content: center; + padding: 100px 20px; + height: auto; + min-height: 100vh; + } + + .simulator-ui { + width: 100%; + max-width: 400px; + opacity: 1; + margin-top: 20px; + } +} + +.sim-group { + margin-bottom: 20px; +} + +.sim-label { + display: block; + font-size: 0.65rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.1em; + color: rgba(255, 255, 255, 0.5); + margin-bottom: 12px; +} + +.sim-controls { + display: flex; + flex-direction: column; + gap: 12px; +} + +.sim-control-row { + display: flex; + align-items: center; + justify-content: space-between; +} + +.sim-btn-group { + display: flex; + background: rgba(255, 255, 255, 0.05); + border-radius: var(--radius-sm); + padding: 4px; +} + +.sim-btn { + padding: 6px 12px; + font-size: 0.75rem; + font-weight: 600; + border-radius: 4px; + transition: all 0.2s ease; + color: rgba(255, 255, 255, 0.6); +} + +.sim-btn.active { + background: var(--sky); + color: white; +} + +input[type="range"] { + width: 100%; + accent-color: var(--sky); +} + +.sim-value { + font-family: 'JetBrains Mono', monospace; + font-size: 0.75rem; + color: var(--sky-pale); +} + +.video-bg { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + opacity: 1; + z-index: 2; +} + +.sim-divider { + height: 1px; + background: rgba(255, 255, 255, 0.1); + margin: 16px 0; +} + +.sim-sub-label { + font-size: 0.7rem; + color: rgba(255, 255, 255, 0.4); + width: 60px; +} + +.sim-context-group { + transition: all 0.3s ease; +} + +/* Bubble Content Scaling */ +.bubble-content { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: relative; + /* Use container queries or a simple scale factor in JS */ +} + +.break-bubble .timer-display { + font-size: 3.8rem; /* Will be scaled by JS */ + line-height: 1; +} + +.break-bubble .status-text { + font-size: 0.85rem; +} + +.break-bubble .work-status { + font-size: 0.65rem; +} + +.break-bubble .skip-btn { + font-size: 0.78rem; + padding: 8px 24px; +} diff --git a/website/css/typography.css b/website/css/typography.css index d014d67..2e5b82c 100644 --- a/website/css/typography.css +++ b/website/css/typography.css @@ -1,45 +1,51 @@ -@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;0,700;1,400;1,600&family=Syne:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,600;1,9..144,300;1,9..144,600&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap'); + +:root { + --text-xs: clamp(0.7rem, 0.9vw, 0.78rem); + --text-sm: clamp(0.8rem, 1.1vw, 0.88rem); + --text-base: clamp(0.9rem, 1.4vw, 1rem); + --text-lg: clamp(1.05rem, 1.8vw, 1.3rem); + --text-xl: clamp(1.4rem, 2.8vw, 2rem); + --text-2xl: clamp(2rem, 4.5vw, 3.5rem); + --text-hero: clamp(3rem, 8vw, 7rem); +} body { - font-family: 'Syne', sans-serif; + font-family: 'Plus Jakarta Sans', sans-serif; + font-size: var(--text-base); font-weight: 400; line-height: 1.65; letter-spacing: 0.01em; + color: var(--text-secondary); + background: var(--bg-base); + cursor: var(--cursor); } -h1, h2, h3, h4, h5, h6, .serif { - font-family: 'Cormorant Garamond', serif; +a, button, [role="button"], .card, .btn-primary { + cursor: var(--cursor-pointer); } -code, pre, .mono { - font-family: 'JetBrains Mono', monospace; +h1, h2, h3, h4, h5, h6 { + font-family: 'Fraunces', serif; + font-variation-settings: 'opsz' 72; + font-weight: 300; + color: var(--text-primary); + line-height: 1.2; } -/* Specific type treatments based on guidelines */ -.headline-hero { - font-size: var(--text-hero); - line-height: 0.92; - font-weight: 400; +h1 italic, h2 italic, h3 italic { + font-style: italic; } -.headline-section { - font-size: var(--text-2xl); +code, pre { + font-family: 'JetBrains Mono', monospace; font-weight: 400; } -.headline-card { - font-size: 1.2rem; - font-weight: 600; -} - -.ui-label { - font-family: 'Syne', sans-serif; +.label, .nav-link, .badge { + font-family: 'Plus Jakarta Sans', sans-serif; font-weight: 600; + font-size: var(--text-xs); text-transform: uppercase; - letter-spacing: 0.08em; + letter-spacing: 0.1em; } - -.stat-number { - font-family: 'Cormorant Garamond', serif; - font-weight: 600; -} \ No newline at end of file diff --git a/website/css/variables.css b/website/css/variables.css index f5592b7..262fe57 100644 --- a/website/css/variables.css +++ b/website/css/variables.css @@ -1,59 +1,40 @@ :root { - /* ─── THE ONE ACCENT ───────────────────────────── */ - --pink: #F472B6; /* cat's nose — PRIMARY accent, used sparingly */ - --pink-light: #FBCFE8; /* inner ear blush — for tints, hover overlays */ - --pink-deep: #BE185D; /* deeper rose — pressed states, depth */ - --pink-glow: rgba(244, 114, 182, 0.2); - --pink-border: rgba(244, 114, 182, 0.3); - - /* ─── WARM DARK BASE ───────────────────────────── */ - /* Note: use warm near-black — NOT cold blue-black. - Add a tiny hint of red-brown to every bg tone. */ - --bg-void: #08070A; /* deepest — almost pure black */ - --bg-base: #0D0B0E; /* page background */ - --bg-layer: #141118; /* section alt bg */ - --bg-surface: #1B1820; /* cards, inputs */ - --bg-raised: #221F2A; /* hover, elevated */ - - /* ─── BORDERS ──────────────────────────────────── */ - --border: rgba(255, 255, 255, 0.07); - --border-hover: rgba(244, 114, 182, 0.35); - --border-pink: rgba(244, 114, 182, 0.2); + /* ─── BACKGROUNDS ──────────────────────────────── */ + --bg-base: #F3FAFF; + --bg-layer: #E9F5FD; + --bg-surface: #FFFFFF; + --bg-raised: #F8FCFF; + --bg-ink-wash: #0D2137; + + /* ─── THE ACCENT ───────────────────────────────── */ + --sky: #3B9ED9; + --sky-deep: #1B6FA5; + --sky-pale: #BEE0F5; + --sky-faint: #DCEEFA; + --sky-border: rgba(59, 158, 217, 0.25); + --sky-border-md: rgba(59, 158, 217, 0.55); /* ─── TEXT ─────────────────────────────────────── */ - --text-primary: #F0EAF5; /* warm ivory-white, not cold white */ - --text-secondary: #8E8098; /* muted warm grey-violet */ - --text-muted: #4D4557; /* near-invisible placeholder text */ - - /* ─── STRUCTURAL WHITES (from logo body) ──────── */ - --cat-white: #FFFFFF; - --ink: #1E293B; /* cat outline — only used for decorative logo elements */ - - /* ─── TYPOGRAPHY ───────────────────────────────── */ - --text-xs: clamp(0.7rem, 1vw, 0.8rem); - --text-sm: clamp(0.8rem, 1.2vw, 0.9rem); - --text-base: clamp(0.9rem, 1.5vw, 1.05rem); - --text-lg: clamp(1.1rem, 2vw, 1.35rem); - --text-xl: clamp(1.5rem, 3vw, 2.2rem); - --text-2xl: clamp(2.2rem, 5vw, 3.8rem); - --text-hero: clamp(3rem, 9vw, 7.5rem); + --text-primary: #0D2137; + --text-secondary: #3E6A89; + --text-muted: #8BAFC7; + --text-on-sky: #FFFFFF; + + /* ─── STRUCTURAL ───────────────────────────────── */ + --border: rgba(59, 158, 217, 0.12); + --border-mid: rgba(59, 158, 217, 0.25); + --shadow-sm: 0 1px 4px rgba(13, 33, 55, 0.06), 0 2px 12px rgba(13, 33, 55, 0.04); + --shadow-md: 0 4px 20px rgba(13, 33, 55, 0.08), 0 2px 8px rgba(13, 33, 55, 0.05); + --shadow-sky: 0 4px 24px rgba(59, 158, 217, 0.18); + + /* ─── CURSOR ───────────────────────────────────── */ + --cursor: url('../assets/Cat_Cursor.png'), auto; + --cursor-pointer: url('../assets/Cat_Pointer.png'), pointer; /* ─── SPACING & SHAPE ──────────────────────────── */ --radius-sm: 6px; - --radius-md: 12px; - --radius-lg: 20px; - --radius-xl: 36px; + --radius-md: 14px; + --radius-lg: 22px; --radius-pill: 9999px; - - --max-w: 1140px; - --section-pad: clamp(5rem, 9vw, 9rem) 1.5rem; - - /* ─── GLOW & SHADOW ────────────────────────────── */ - --glow-xs: 0 0 12px var(--pink-glow); - --glow-sm: 0 0 24px var(--pink-glow); - --glow-md: 0 0 48px var(--pink-glow), 0 0 96px rgba(244,114,182,0.08); - --glow-hero: 0 0 80px rgba(244,114,182,0.18), 0 0 160px rgba(244,114,182,0.06); - - --shadow-card: 0 2px 24px rgba(0,0,0,0.6), 0 1px 3px rgba(0,0,0,0.5); - --shadow-deep: 0 20px 60px rgba(0,0,0,0.7); -} \ No newline at end of file + --max-w: 1100px; +} diff --git a/website/index.html b/website/index.html index b7f91c4..8b391ba 100644 --- a/website/index.html +++ b/website/index.html @@ -3,1043 +3,1011 @@ - PauseCat — Increase productivity with paws-itive breaks - - + PauseCat — Breathe. Stretch. Work better. - + - - - - -
-
-
- - - - - - - Windows 10 / 11 + +
+ Loading + +
+
+
- -

- Your screen
can wait. -

- -

- PauseCat lives in your Windows system tray and enforces break - intervals — blurring your screen, counting down a glassmorphic - overlay, and resuming only when it's done.
- Built in Rust. Under 1.5 MB. Always watching. -

- - +
+ +
+ +
+
+
+
- - - -

- "Increase productivity with paws-itive breaks" -

- -
-
- - - - - - - stars + --:-- -- +
- · -
- - - - - - - - - downloads -
- · -
- - - - - - - - latest +
+

+ Tuesday. +

-
- -

- PauseCat_Installer.msi · - - · Apache 2.0 · Requires Windows 10/11 + WebView2 -

-
-
-
- - -
-
-
- TAKE A BREAK BUILT IN RUST - WINDOWS NATIVE - APACHE 2.0 - < 1.5 MB - OPEN SOURCE - -
-
- TAKE A BREAK BUILT IN RUST - WINDOWS NATIVE - APACHE 2.0 - < 1.5 MB - OPEN SOURCE - -
-
- TAKE A BREAK BUILT IN RUST - WINDOWS NATIVE - APACHE 2.0 - < 1.5 MB - OPEN SOURCE - -
-
-
- - -
-
-
- THE BREAK EXPERIENCE -
-

- "When it's time, PauseCat takes over." -

- -
-
-
-
PAUSE
-
-
-
04:58
-
TAKE A BREAK
-
You've worked 1h 0m
- -
-
-
SOFT MODE
-
-
- -
-
- - - - Hardware Accelerated + You haven't moved in 2 hours. +

-
- +

- - - Soft Mode — Skippable + Your eyes haven't left the screen. +

-
- +

- - - Theme-Aware -

-
-
-
- - -
-
-
- HOW IT WORKS -
-

- "Dead simple to set up." -

- -
-
-
01
-

Install

-

- "One MSI. That's it." -

-

- Download PauseCat_Installer.msi from GitHub Releases. - The WiX installer checks for the WebView2 runtime and registers - autostart. Run it, find the cat in your tray. + Your last glass of water was at noon.

-
- -
-
02
-

Configure

-

- "Right-click. Settings. Done." -

-

- Set work interval (5 min – 4 hours), break duration, break mode - (Soft or Hard), custom messages, and 3D text style. Every setting - live-previews inside the settings window. +

+

+ Something needs to change.

-
-
-
03
-

Work. Break. Repeat.

-

- "PauseCat handles the rest." -

-

- Interval fires → GDI captures your screen → Rust blurs it → - WebView2 break overlay appears. Lock your screen? Timer pauses. - Playing music? PauseCat pauses it. Sleep? Timer resumes when - you're back. -

+ -
+
-
- -
-
-

- Everything you'd engineer yourself.
- Already done. -

+ +
+
+
+ -
-
-
FULL CONFIG
-

- "Deep Customization" -

-

- Soft or Hard mode. Custom break messages, randomized. 3D text - behind the overlay: rotation X/Y/Z, float/rotate/swing/pulse - animation, opacity, depth, glow. Bubble: size, position, opacity. +

+

Meet PauseCat.

+
+ +
+

+ An open-source break reminder for Windows.
Quiet, precise, + and shaped like a cat.

+
-
-
-
Work Interval
-
60 min
-
5 min – 4 hours
-
-
-
Break Duration
-
5 min
-
1 min – 2 hours
+
+
+
+ + + Stars
-
-
Mode
-
Soft
-
Soft / Hard
+ +
+ + Downloads
-
-
Autostart
-
On
-
On / Off
+ +
+ v1.1.2 + Latest
-
-
OVERLAY ENGINE
-

"GDI Blur Background"

-

- Win32 GDI BitBlt screen capture + a pure Rust Gaussian blur. - Instantaneous, zero-flicker. The background you see during a break - is your actual screen, not a screenshot-of-a-screenshot. -

-
- -
-
HARDWARE UI
-

"WebView2-Accelerated Overlay"

-

- The break bubble runs inside a WebView2 host — the Chromium engine - powering Edge. Smooth animations, true - backdrop-filter, hardware-accelerated rendering. -

-
- -
-
SESSION AWARE
-

"Intelligent Timer Pausing"

-

- WTS session events: screen lock → timer pauses. Wake from sleep → - timer resumes. PBT power events for suspend/resume. It never - counts idle time as work time. -

-
- -
-
MEDIA CONTROL
-

"SMTC Integration"

-

- Windows System Media Transport Controls. Break starts → active - media pauses. Break ends → media resumes. Your Spotify keeps - playing context, not state. -

-
- -
-
LIGHTWEIGHT
-

"< 1.5 MB Binary"

-

- opt-level = 'z', fat LTO, single codegen unit, UPX - LZMA compression. The installer is smaller than most browser - extension icons. +

+

+ + Windows 10 / 11 | Apache 2.0 | < 2.5 MB

+ + Download for Windows
-
+
-
- - -
-
-

- "How hard should it push?" -

- -
-
-
SOFT MODE
-

- "Gentle nudge" -

-

- The overlay appears with a countdown and a Skip button. You're an - adult — PauseCat reminds, never locks you out. -

-

- Default mode. Good for most users. -

-
- -
-
- HARD MODE -
-

- "Full enforcement" -

-

- No Skip button. No dismiss. The countdown runs to zero. You set - the interval — PauseCat holds you to it. -

-

- For users who know they'll skip soft breaks every time. -

-
+ +
+
+
-
- SETTINGS.JSON -
-
-{
-  "behavior": {
-    "mode": "Hard",
-    "interval": 3600,
-    "break": 300
-  },
-  "overlay": {
-    "blur_radius": 45,
-    "theme": "Dark"
-  }
-}
-

- Easily configured via the tray UI. -

+ Built in Rust. + + Under 2.5 MB. + + Always watching. +
+
+ + Windows + Apache 2.0 + v1.1.2 +
-
+
- - -
-
-
-
- APACHE 2.0 -

- "Fully open. Read every line." -

-

- Clean Rust architecture: state machine, GDI capture, Gaussian - blur, WebView2 host, WinHTTP update engine — all on GitHub. Fork - it, audit it, improve it. -

- -
-
-
-
GitHub Stars
-
-
-
-
Total Downloads
+ +
+
+ +
+
PAUSE
+ +
+
+
+
+
Breathe In...
+
01:58
+
You've worked for 1 hour
+ +
-
-
-
Latest Version
+ +
+

+ Hardware-accelerated. Fullscreen.
This is the actual + PauseCat experience. +

- + +
+ Overlay Style +
+ + +
+
-
-
-
-
-
-
+ +
+
+ +
+ + +
+
-
pausecat — PowerShell
-
-
- -
-
-
-
-
- -
-
-

- "One file. One click. Running." -

+ + -
-
-
-
1
-
-
-
-

Download from GitHub Releases

-

- [PauseCat_Installer.msi] -

-
-
+ + +
+ Break Mode +
+ + +
+
-
-
-
2
-
-
-
-

Run PauseCat_Installer.msi

-

- WiX handles: WebView2 runtime check, autostart registry entry, - file installation. -

-
-
+
+ Bubble UI +
+ + + +
+
+ + + +
+
-
-
-
3
-
-
-

- Find the cat in your system tray -

-

- Right-click → Settings to configure your intervals, mode, and - overlay style. +

+ Mirroring %AppData%\PauseCat\config.json

-
- -
- Windows 10 or 11 (64-bit)
- WebView2 Runtime (auto-installed by WiX)
- ~1.5 MB disk space -
+
+
-
- - -
-
-

- "Built by one. Improved by many." -

- - + +
- - - Submit a PR - -

- Branch: feature/<desc> or - fix/<desc>. Conventional commits. Main is - read-only. -

-
- Read CONTRIBUTING.md + +

Work. Break. Repeat.

+

+ GDI capture → Rust blur → WebView2 overlay. Timer pauses + automatically on screen lock. +

+
-
-
+ +
+ -
+
+
-
+          
-# Prerequisites: Rust stable · WiX Toolset v4 · WebView2 Runtime - -git clone https://github.com/0xarchit/pauseCat -cd pauseCat -cargo run # development -cargo test # test suite -
-
-
- - - -
-
- Logo -

- "Your next break is overdue." -

-

- Free. Open source. Already running on Windows machines that need it. -

+ "Finally, a break app that doesn't feel like malware." +
+
+ "I actually look forward to taking breaks now." +
+
+ "Under 2.5MB and built in Rust. Perfect." +
- - - - - - - - Download for Windows - +
+ APACHE 2.0 +

+ Built in Rust. Fully Open. +

+
+ +
+
+
+ OVERLAY ENGINE +

GDI Blur

+

+ Win32 GDI BitBlt capture, blurred via a pure + Rust Gaussian implementation. Zero flicker. +

+
+
+
+ HARDWARE UI +

WebView2 Overlay

+

+ Hardware accelerated Chromium engine. +

+
+
+ SESSION AWARE +

Intelligent Timer

+

+ WTS lock → pause. Wake → resume. +

+
+
+
-

- · Apache 2.0 · Windows 10/11 · < - 1.5 MB -

+
+
+
+
+
+
+ pausecat — PowerShell + +
+
+ +
+
+
+
+ +
- - - + Download for Windows (2.5MB) + + + + + + + diff --git a/website/js/github-stats.js b/website/js/github-stats.js index 96629f7..a8e153f 100644 --- a/website/js/github-stats.js +++ b/website/js/github-stats.js @@ -1,98 +1,65 @@ -const REPO = '0xarchit/pauseCat'; -const CACHE_KEY = 'pausecat_gh_stats'; -const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes +const REPO = '0xarchit/pauseCat'; +const API = 'https://api.github.com/repos/' + REPO; +const CACHE = 'pausecat_stats_v1'; +const TTL = 5 * 60 * 1000; // 5 minutes -const FALLBACKS = { +const FALLBACK = { stars: '—', downloads: '—', - version: '—', - msiUrl: 'https://github.com/0xarchit/pauseCat/releases/latest', + version: 'v1.1.2', + msiUrl: `https://github.com/${REPO}/releases/latest`, }; -function formatNumber(n) { +function fmt(n) { if (typeof n !== 'number') return '—'; - if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; - return n.toLocaleString(); + return n >= 1000 ? (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k' : String(n); } -function applyStats(stats) { +function applyStats(s) { document.querySelectorAll('[data-stat="stars"]') - .forEach(el => el.textContent = stats.stars); + .forEach(el => { el.textContent = s.stars; }); document.querySelectorAll('[data-stat="downloads"]') - .forEach(el => el.textContent = stats.downloads); + .forEach(el => { el.textContent = s.downloads; }); document.querySelectorAll('[data-stat="version"]') - .forEach(el => el.textContent = stats.version); - - // Update all download links to point to the real MSI asset - if (stats.msiUrl) { + .forEach(el => { el.textContent = s.version; }); + if (s.msiUrl) { document.querySelectorAll('[data-action="download"]') - .forEach(el => el.href = stats.msiUrl); + .forEach(el => { el.href = s.msiUrl; }); } } -async function fetchStats() { - // ── 1. Check cache ───────────────────────────────────────────── +export async function fetchStats() { try { - const cached = JSON.parse(localStorage.getItem(CACHE_KEY) || 'null'); - if (cached && Date.now() - cached.ts < CACHE_TTL_MS) { - applyStats(cached.data); - return; + const cached = JSON.parse(localStorage.getItem(CACHE)); + if (cached && Date.now() - cached.ts < TTL) { + applyStats(cached.data); return; } - } catch (_) { /* corrupt cache — ignore, re-fetch */ } + } catch (_) {} - // ── 2. Apply fallbacks immediately so page never shows empty ─── - applyStats(FALLBACKS); + applyStats(FALLBACK); - // ── 3. Fetch in parallel ─────────────────────────────────────── try { - const headers = { 'Accept': 'application/vnd.github+json' }; - - const [repoRes, latestRes, allReleasesRes] = await Promise.all([ - fetch(`https://api.github.com/repos/${REPO}`, { headers }), - fetch(`https://api.github.com/repos/${REPO}/releases/latest`, { headers }), - fetch(`https://api.github.com/repos/${REPO}/releases?per_page=100`, { headers }), + const H = { Accept: 'application/vnd.github+json' }; + const [rR, lR, aR] = await Promise.all([ + fetch(API, { headers: H }), + fetch(`${API}/releases/latest`, { headers: H }), + fetch(`${API}/releases?per_page=100`, { headers: H }), ]); + if (!rR.ok || !lR.ok || !aR.ok) throw new Error('API error'); + const [repo, latest, all] = await Promise.all([rR.json(), lR.json(), aR.json()]); - if (!repoRes.ok || !latestRes.ok || !allReleasesRes.ok) { - throw new Error('One or more API requests failed'); - } - - const [repo, latest, allReleases] = await Promise.all([ - repoRes.json(), - latestRes.json(), - allReleasesRes.json(), - ]); - - // ── Stars ──────────────────────────────────────────────────── - const stars = formatNumber(repo.stargazers_count); - - // ── Version ────────────────────────────────────────────────── - const version = latest.tag_name || FALLBACKS.version; - - // ── Total downloads: sum every asset across every release ──── - const totalDownloads = Array.isArray(allReleases) - ? allReleases.reduce((total, release) => - total + (release.assets || []).reduce((sum, asset) => - sum + (asset.download_count || 0), 0), 0) - : 0; - const downloads = formatNumber(totalDownloads); - - // ── MSI download URL from latest release ──────────────────── - const msiAsset = (latest.assets || []).find(a => a.name.endsWith('.msi')); - const msiUrl = msiAsset?.browser_download_url || FALLBACKS.msiUrl; + const stars = fmt(repo.stargazers_count); + const version = latest.tag_name ?? FALLBACK.version; + const totalDL = all.reduce((t, r) => + t + (r.assets ?? []).reduce((s, a) => s + (a.download_count ?? 0), 0), 0); + const downloads = fmt(totalDL); + const msiAsset = (latest.assets ?? []).find(a => a.name.endsWith('.msi')); + const msiUrl = msiAsset?.browser_download_url ?? FALLBACK.msiUrl; const stats = { stars, downloads, version, msiUrl }; - - // ── 4. Update DOM ───────────────────────────────────────────── applyStats(stats); - - // ── 5. Cache result ────────────────────────────────────────── - localStorage.setItem(CACHE_KEY, JSON.stringify({ ts: Date.now(), data: stats })); - + localStorage.setItem(CACHE, JSON.stringify({ ts: Date.now(), data: stats })); } catch (err) { - // Fallbacks already applied in step 2 — nothing more to do - console.warn('[PauseCat] GitHub stats unavailable:', err.message); + console.warn('[PauseCat] Stats unavailable:', err.message); } } - -export { fetchStats }; \ No newline at end of file diff --git a/website/js/interactive.js b/website/js/interactive.js deleted file mode 100644 index 46d44cf..0000000 --- a/website/js/interactive.js +++ /dev/null @@ -1,20 +0,0 @@ -export function initInteractive() { - if (!window.matchMedia('(pointer: fine)').matches) return; - - // Create ambient lighting overlay - const ambientGlow = document.createElement('div'); - ambientGlow.id = 'ambient-glow'; - document.body.appendChild(ambientGlow); - - window.addEventListener('mousemove', (e) => { - // Global background ambient light tracking - document.documentElement.style.setProperty('--mouse-x', `${e.clientX}px`); - document.documentElement.style.setProperty('--mouse-y', `${e.clientY}px`); - }); - - ambientGlow.style.opacity = '0'; - - window.addEventListener('mousemove', () => { - ambientGlow.style.opacity = '1'; - }, { once: true }); -} \ No newline at end of file diff --git a/website/js/main.js b/website/js/main.js index 28b36db..b6f6342 100644 --- a/website/js/main.js +++ b/website/js/main.js @@ -1,13 +1,81 @@ import { fetchStats } from './github-stats.js'; -import { initScrollReveal } from './scroll-reveal.js'; -import { initNav } from './nav.js'; +import { initScrollEngine } from './scroll-engine.js'; +import { initStoryChapters, initSystemTime, initSimulator } from './story-chapters.js'; import { initTypewriter } from './typewriter.js'; -import { initInteractive } from './interactive.js'; document.addEventListener('DOMContentLoaded', () => { - fetchStats(); - initScrollReveal(); - initNav(); - initTypewriter(); - initInteractive(); -}); \ No newline at end of file + const loader = document.getElementById('loader'); + const loaderBar = document.getElementById('loader-progress'); + const loaderBarBg = document.getElementById('loader-bar-bg'); + const startBtn = document.getElementById('start-btn'); + + let progress = 0; + const interval = setInterval(() => { + progress += Math.random() * 25; + if (progress >= 100) { + progress = 100; + clearInterval(interval); + + // Loading complete: Show Start Button + setTimeout(() => { + loaderBarBg.style.display = 'none'; + startBtn.style.opacity = '1'; + startBtn.style.pointerEvents = 'auto'; + startBtn.style.transform = 'translateY(0)'; + }, 400); + } + loaderBar.style.width = `${progress}%`; + }, 80); + + startBtn.addEventListener('click', () => { + // This click event satisfies the browser's User Gesture requirement for Audio + loader.style.opacity = '0'; + setTimeout(() => { + loader.style.display = 'none'; + + // Initialize everything only AFTER the user has engaged + fetchStats(); + initSystemTime(); + initScrollEngine(); + initStoryChapters(); + initSimulator(); + initTypewriter(); + }, 600); + }); + + // Time-of-Day Theme (Dark mode after 7PM, before 6AM) + const hour = new Date().getHours(); + if (hour >= 19 || hour < 6) { + document.documentElement.classList.add('theme-dark'); + } + + // OS Detection Fallback + const isMacOrLinux = /Mac|Linux/i.test(navigator.userAgent || navigator.platform); + if (isMacOrLinux) { + document.querySelectorAll('[data-action="download"]').forEach(btn => { + const textSpan = btn.querySelector('[data-text="download"]'); + if (textSpan) textSpan.textContent = 'Windows Only — View GitHub'; + btn.href = 'https://github.com/0xarchit/pauseCat'; + btn.style.background = 'var(--bg-ink-wash)'; + btn.style.color = 'white'; + }); + } + + // Terminal Copy Functionality + const copyBtn = document.getElementById('terminal-copy'); + if (copyBtn) { + copyBtn.addEventListener('click', () => { + const command = 'git clone https://github.com/0xarchit/pauseCat'; + navigator.clipboard.writeText(command).then(() => { + const span = copyBtn.querySelector('span'); + const oldText = span.textContent; + span.textContent = 'Copied!'; + copyBtn.classList.add('copied'); + setTimeout(() => { + span.textContent = oldText; + copyBtn.classList.remove('copied'); + }, 2000); + }); + }); + } +}); diff --git a/website/js/nav.js b/website/js/nav.js deleted file mode 100644 index bad2ebf..0000000 --- a/website/js/nav.js +++ /dev/null @@ -1,14 +0,0 @@ -export function initNav() { - const nav = document.querySelector('nav'); - - window.addEventListener('scroll', () => { - if (window.scrollY > 60) { - nav.classList.add('scrolled'); - } else { - nav.classList.remove('scrolled'); - } - }); - - // Add mobile menu logic here if needed later. - // Currently sticking to the core desktop spec but ready for extension. -} \ No newline at end of file diff --git a/website/js/scroll-engine.js b/website/js/scroll-engine.js new file mode 100644 index 0000000..af6a13e --- /dev/null +++ b/website/js/scroll-engine.js @@ -0,0 +1,140 @@ +export function initScrollEngine() { + const acts = document.querySelectorAll('.act-wrapper'); + const nav = document.querySelector('.nav'); + const dots = document.querySelectorAll('.progress-dot'); + const stickyCta = document.getElementById('sticky-cta'); + + let audioCtx = null; + const initAudio = () => { + if (audioCtx) return; + audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + if (audioCtx.state === 'suspended') audioCtx.resume(); + }; + + // Multiple triggers to unlock audio on all browsers + ['click', 'pointerdown', 'touchstart', 'keydown'].forEach(evt => + document.addEventListener(evt, initAudio, { once: true }) + ); + + initAudio(); // Call immediately since we are now triggered by the Start button + + function playTick(isDown = true) { + if (!audioCtx) initAudio(); // Try one last time + if (!audioCtx || audioCtx.state === 'suspended') { + audioCtx?.resume(); + if (audioCtx?.state === 'suspended') return; // Browser still blocking + } + + const osc = audioCtx.createOscillator(); + const gain = audioCtx.createGain(); + osc.connect(gain); + gain.connect(audioCtx.destination); + + osc.type = 'sine'; + const freq = isDown ? 900 : 600; + + osc.frequency.setValueAtTime(freq, audioCtx.currentTime); + osc.frequency.exponentialRampToValueAtTime(freq / 3, audioCtx.currentTime + 0.12); + + // Increased volume for better audibility + gain.gain.setValueAtTime(0.08, audioCtx.currentTime); + gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.12); + + osc.start(); + osc.stop(audioCtx.currentTime + 0.12); + } + + let activeIndex = -1; + let lastScrollY = window.scrollY; + + function onScroll() { + const isScrollingDown = window.scrollY > lastScrollY; + lastScrollY = window.scrollY; + + // Nav shadow + if (window.scrollY > 60) { + nav?.classList.add('nav--scrolled'); + } else { + nav?.classList.remove('nav--scrolled'); + } + + // Act progress + acts.forEach(wrapper => { + const rect = wrapper.getBoundingClientRect(); + const total = wrapper.offsetHeight - window.innerHeight; + const scrolled = -rect.top; + const progress = Math.max(0, Math.min(1, scrolled / total)); + const act = wrapper.querySelector('.act'); + + if (act) { + act.style.setProperty('--p', progress.toFixed(4)); + + // Parallax depth + const layer = act.querySelector('.parallax-layer'); + if (layer) { + layer.style.transform = `translateY(${progress * 15}vh)`; + } + } + + // Hide scroll hint + if (wrapper.dataset.hero === "true") { + const hint = wrapper.querySelector('.scroll-hint'); + if (hint) { + const hintOpacity = Math.max(0, 1 - (scrolled / 300)); + hint.style.opacity = hintOpacity; + hint.style.transform = `translateX(-50%) translateY(${scrolled * 0.2}px)`; + } + } + }); + + // Active dot tracking via viewport center + let currentActiveIndex = -1; + acts.forEach((wrapper, index) => { + const rect = wrapper.getBoundingClientRect(); + const centerY = window.innerHeight / 2; + if (rect.top <= centerY && rect.bottom >= centerY) { + currentActiveIndex = index; + } + }); + + if (currentActiveIndex !== -1 && currentActiveIndex !== activeIndex) { + activeIndex = currentActiveIndex; + dots.forEach((d, i) => d.classList.toggle('active', i === activeIndex)); + playTick(isScrollingDown); + } + + // Sticky CTA + if (stickyCta) { + if (window.scrollY > window.innerHeight * 2.5) { + stickyCta.classList.add('visible'); + } else { + stickyCta.classList.remove('visible'); + } + } + + // Reveal on scroll + const reveals = document.querySelectorAll('.reveal'); + reveals.forEach(el => { + const rect = el.getBoundingClientRect(); + const isVisible = rect.top < window.innerHeight * 0.85; + if (isVisible) el.classList.add('visible'); + }); + } + + // Animation Throttling (Optimization 1) + const actObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + const act = entry.target.querySelector('.act'); + if (entry.isIntersecting) { + act?.classList.remove('paused-anim'); + } else { + act?.classList.add('paused-anim'); + } + }); + }, { threshold: 0.05 }); + + acts.forEach(act => actObserver.observe(act)); + + window.addEventListener('scroll', onScroll, { passive: true }); + onScroll(); +} diff --git a/website/js/scroll-reveal.js b/website/js/scroll-reveal.js deleted file mode 100644 index b7c69a8..0000000 --- a/website/js/scroll-reveal.js +++ /dev/null @@ -1,26 +0,0 @@ -export function initScrollReveal() { - const observer = new IntersectionObserver(entries => { - entries.forEach((entry, i) => { - if (entry.isIntersecting) { - // slight stagger if multiple elements enter at once - entry.target.style.transitionDelay = `${i * 90}ms`; - entry.target.classList.add('revealed'); - observer.unobserve(entry.target); - } - }); - }, { threshold: 0.12 }); - - document.querySelectorAll('.reveal').forEach(el => observer.observe(el)); - - // Also handle the hero scroll cue visibility - const cue = document.querySelector('.scroll-cue'); - if (cue) { - const handleScroll = () => { - if (window.scrollY > 60) { - cue.classList.add('hidden'); - window.removeEventListener('scroll', handleScroll); - } - }; - window.addEventListener('scroll', handleScroll); - } -} \ No newline at end of file diff --git a/website/js/story-chapters.js b/website/js/story-chapters.js new file mode 100644 index 0000000..8fab9be --- /dev/null +++ b/website/js/story-chapters.js @@ -0,0 +1,240 @@ +export function initStoryChapters() { + const demoTimer = document.getElementById('demo-timer'); + const demoStatus = document.getElementById('demo-status'); + + const messages = [ + "Take a deep breath", + "Stretch your body", + "Rest your eyes for a moment", + "Time for a quick water break" + ]; + + let msgIndex = 0; + let timerSeconds = 118; // 01:58 + + function updateTimer() { + if (timerSeconds < 0) return; + + const mins = Math.floor(timerSeconds / 60); + const secs = timerSeconds % 60; + if (demoTimer) { + demoTimer.textContent = `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`; + } + + timerSeconds--; + + if (timerSeconds < 0) { + // COMPLETION STATE + if (demoStatus) { + demoStatus.textContent = "Congrats! Break Completed."; + demoStatus.style.color = "var(--sky)"; + demoStatus.style.opacity = "1"; + } + const bubble = document.getElementById('sim-bubble'); + if (bubble) bubble.style.boxShadow = "0 0 40px var(--sky)"; + clearInterval(interval); + clearInterval(msgInterval); + + // Auto-reset after 5 seconds + setTimeout(() => { + timerSeconds = 118; // 01:58 + if (demoStatus) { + demoStatus.style.color = ""; + demoStatus.style.opacity = "0.8"; + msgIndex = 0; + demoStatus.textContent = messages[msgIndex]; + } + if (bubble) bubble.style.boxShadow = "0 32px 64px -12px rgba(0, 0, 0, 0.6)"; + interval = setInterval(updateTimer, 1000); + msgInterval = setInterval(rotateMessage, 5000); + }, 5000); + } + } + + function rotateMessage() { + if (!demoStatus) return; + demoStatus.style.opacity = '0'; + setTimeout(() => { + msgIndex = (msgIndex + 1) % messages.length; + demoStatus.textContent = messages[msgIndex]; + demoStatus.style.opacity = '0.8'; + }, 500); + } + + // Only run when Simulator Act is in view + const actSim = document.getElementById('act-simulator'); + let interval; + let msgInterval; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + if (!interval) interval = setInterval(updateTimer, 1000); + if (!msgInterval) msgInterval = setInterval(rotateMessage, 5000); + } else { + clearInterval(interval); + clearInterval(msgInterval); + interval = null; + msgInterval = null; + } + }); + }, { threshold: 0.1 }); + + if (actSim) observer.observe(actSim); +} + +export function initSystemTime() { + const timeEl = document.getElementById('system-time'); + const dayEl = document.getElementById('system-day'); + if (!timeEl || !dayEl) return; + + function update() { + const now = new Date(); + + // Time + let hours = now.getHours(); + const minutes = now.getMinutes(); + const ampm = hours >= 12 ? 'PM' : 'AM'; + hours = hours % 12; + hours = hours ? hours : 12; + const strMinutes = minutes < 10 ? '0' + minutes : minutes; + timeEl.innerHTML = `${hours}:${strMinutes} ${ampm}`; + + // Day + const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + const dayName = days[now.getDay()]; + dayEl.innerHTML = `${dayName}.`; + } + + update(); + setInterval(update, 1000 * 60); // Update every minute +} + +export function initSimulator() { + const bubble = document.getElementById('sim-bubble'); + const skipBtn = document.getElementById('sim-skip-btn'); + const text3d = document.getElementById('sim-3d-text'); + const video = document.getElementById('sim-video'); + + const opacityInput = document.getElementById('sim-opacity'); + const opacityVal = document.getElementById('val-opacity'); + const sizeInput = document.getElementById('sim-size'); + const sizeVal = document.getElementById('val-size'); + const styleSelect = document.getElementById('sim-text-style'); + const textContentInput = document.getElementById('sim-text-content'); + const videoVolumeInput = document.getElementById('sim-video-volume'); + const videoVolumeVal = document.getElementById('val-video-volume'); + + const modeBtns = document.querySelectorAll('.sim-btn[data-mode]'); + const styleBtns = document.querySelectorAll('.sim-btn[data-style-select]'); + + const mediaSettings = document.getElementById('settings-media'); + const textSettings = document.getElementById('settings-text'); + + if (!bubble) return; + + // Style Toggle (Media vs Text) + styleBtns.forEach(btn => { + btn.addEventListener('click', () => { + styleBtns.forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + const style = btn.dataset.styleSelect; + + if (style === 'Text') { + video.style.display = 'none'; + text3d.style.display = 'block'; + mediaSettings.style.display = 'none'; + textSettings.style.display = 'block'; + } else { + video.style.display = 'block'; + text3d.style.display = 'none'; + mediaSettings.style.display = 'block'; + textSettings.style.display = 'none'; + } + }); + }); + + // Mode Toggle (Soft vs Hard) + modeBtns.forEach(btn => { + btn.addEventListener('click', () => { + modeBtns.forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + const mode = btn.dataset.mode; + skipBtn.style.visibility = (mode === 'Hard') ? 'hidden' : 'visible'; + }); + }); + + // Proportional Scaling Function + function updateBubbleUI() { + const size = sizeInput.value; + const ratio = size / 320; // 320 is our design base size + + bubble.style.width = `${size}px`; + bubble.style.height = `${size}px`; + sizeVal.textContent = `${size}px`; + + const timer = bubble.querySelector('.timer-display'); + const status = bubble.querySelector('.status-text'); + const work = bubble.querySelector('.work-status'); + const skip = bubble.querySelector('.skip-btn'); + + timer.style.fontSize = `${ratio * 3.8}rem`; + status.style.fontSize = `${ratio * 0.85}rem`; + work.style.fontSize = `${ratio * 0.65}rem`; + work.style.marginTop = `${ratio * 8}px`; + skip.style.fontSize = `${ratio * 0.78}rem`; + skip.style.padding = `${ratio * 8}px ${ratio * 24}px`; + skip.style.marginTop = `${ratio * 24}px`; + } + + sizeInput.addEventListener('input', updateBubbleUI); + + // Opacity + opacityInput.addEventListener('input', (e) => { + const val = e.target.value; + bubble.style.backgroundColor = `rgba(255, 255, 255, ${val})`; + opacityVal.textContent = val; + }); + + // 3D Text Content + textContentInput.addEventListener('input', (e) => { + text3d.textContent = e.target.value || "PAUSE"; + }); + + // Video Volume + videoVolumeInput.addEventListener('input', (e) => { + const val = e.target.value; + video.volume = val; + videoVolumeVal.textContent = `${Math.round(val * 100)}%`; + }); + + // 3D Text Style + styleSelect.addEventListener('change', (e) => { + const style = e.target.value; + text3d.style.animation = 'none'; + text3d.offsetHeight; // reflow + + if (style === 'float') text3d.style.animation = 'textFloat 8s ease-in-out infinite'; + else if (style === 'rotate') text3d.style.animation = 'textRotate 10s linear infinite'; + else if (style === 'pulse') text3d.style.animation = 'textPulse 4s ease-in-out infinite'; + else text3d.style.transform = 'rotateX(20deg) rotateY(-20deg)'; + }); + + // Initial call + updateBubbleUI(); + text3d.style.display = 'none'; // Default is Media + + // Cursor Particles in Simulator + const actSim = document.getElementById('act-simulator'); + if (actSim) { + actSim.addEventListener('mousemove', (e) => { + if (Math.random() > 0.3) return; // Throttle particle creation + const p = document.createElement('div'); + p.className = 'cursor-particle'; + p.style.left = e.clientX + 'px'; + p.style.top = e.clientY + 'px'; + document.body.appendChild(p); + setTimeout(() => p.remove(), 800); + }); + } +} diff --git a/website/js/typewriter.js b/website/js/typewriter.js index fa36a25..d244dff 100644 --- a/website/js/typewriter.js +++ b/website/js/typewriter.js @@ -1,91 +1,71 @@ export function initTypewriter() { - const terminalBody = document.querySelector('.terminal-body'); - if (!terminalBody) return; + const terminal = document.querySelector('.terminal-body'); + if (!terminal) return; - const script = [ - { type: 'prompt', text: 'git clone https://github.com/0xarchit/pauseCat' }, - { type: 'output', text: 'Cloning into \'pauseCat\'...' }, - { type: 'prompt', text: 'cd pauseCat' }, - { type: 'output', text: 'Read the code. Audit the architecture.' }, - { type: 'output', text: 'To run, download the MSI from Releases.' } + const lines = [ + { text: 'PS C:\\> ', type: 'prompt' }, + { text: 'git clone https://github.com/0xarchit/pauseCat', type: 'text' }, + { text: '\nPS C:\\> ', type: 'prompt' }, + { text: 'cd pauseCat', type: 'text' }, + { text: '\nPS C:\\> ', type: 'prompt' }, + { text: 'cargo build --release', type: 'text' }, + { text: '\n\nCompiling pausecat v1.1.2', type: 'text' }, + { text: '\nFinished release [optimized] in 18.4s', type: 'success' }, + { text: '\n\nPS C:\\> ', type: 'prompt' }, + { text: '.\\target\\release\\pausecat.exe', type: 'text' }, + { text: '\n[PauseCat] Tray initialized. Timer started. ', type: 'text' }, + { text: '█', type: 'cursor' } ]; - let lineIndex = 0; - let charIndex = 0; - let currentLineEl = null; - let isTyping = false; + let lineIdx = 0; + let charIdx = 0; + terminal.innerHTML = ''; - // Clear existing static content if any, except for setting up structure - terminalBody.innerHTML = ''; - - const createLine = (type) => { - const div = document.createElement('div'); - if (type === 'prompt') { - const ps = document.createElement('span'); - ps.className = 'term-prompt'; - ps.textContent = 'PS C:\\> '; - div.appendChild(ps); - } - return div; - }; - - const cursor = document.createElement('span'); - cursor.className = 'term-cursor'; + function type() { + if (lineIdx >= lines.length) return; - const typeNextChar = () => { - if (lineIndex >= script.length) { - terminalBody.appendChild(cursor); - return; + const line = lines[lineIdx]; + if (line.type === 'cursor') { + const span = document.createElement('span'); + span.className = 't-cursor'; + terminal.appendChild(span); + return; } - const currentLine = script[lineIndex]; + if (charIdx === 0) { + const span = document.createElement('span'); + if (line.type === 'prompt') span.className = 't-prompt'; + if (line.type === 'success') span.className = 't-success'; + terminal.appendChild(span); + } - if (charIndex === 0) { - if (currentLineEl && currentLineEl.contains(cursor)) { - currentLineEl.removeChild(cursor); - } - currentLineEl = createLine(currentLine.type); - terminalBody.appendChild(currentLineEl); - if (currentLine.type !== 'output' && currentLine.type !== 'success') { - currentLineEl.appendChild(cursor); - } + const currentSpan = terminal.lastChild; + const char = line.text[charIdx]; + + if (char === '\n') { + terminal.appendChild(document.createElement('br')); + } else { + currentSpan.textContent += char; } - if (currentLine.type === 'prompt') { - // Type out prompt character by character - if (charIndex < currentLine.text.length) { - const textNode = document.createTextNode(currentLine.text.charAt(charIndex)); - currentLineEl.insertBefore(textNode, cursor); - charIndex++; - setTimeout(typeNextChar, 40); - } else { - // Line finished - charIndex = 0; - lineIndex++; - setTimeout(typeNextChar, 200); - } + charIdx++; + if (charIdx >= line.text.length) { + charIdx = 0; + lineIdx++; + setTimeout(type, 200); } else { - // Instantly show output lines - const span = document.createElement('span'); - if (currentLine.type === 'success') { - span.className = 'term-success'; - } - span.textContent = currentLine.text; - currentLineEl.appendChild(span); - - charIndex = 0; - lineIndex++; - setTimeout(typeNextChar, 300); + setTimeout(type, 35); } - }; + } const observer = new IntersectionObserver((entries) => { - if (entries[0].isIntersecting && !isTyping) { - isTyping = true; - setTimeout(typeNextChar, 500); - observer.disconnect(); - } + entries.forEach(entry => { + if (entry.isIntersecting) { + type(); + observer.unobserve(terminal); + } + }); }, { threshold: 0.5 }); - observer.observe(terminalBody); -} \ No newline at end of file + observer.observe(terminal); +}