diff --git a/public/css/app.css b/public/css/app.css new file mode 100644 index 0000000..0070693 --- /dev/null +++ b/public/css/app.css @@ -0,0 +1,1125 @@ + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + html { + scroll-behavior: smooth; + } + + body { + font-family: 'Space Grotesk', system-ui, sans-serif; + background: #09090b; + color: #d4d4d8; + line-height: 1.8; + font-size: 1.125rem; + } + + .container { + max-width: 800px; + margin: 0 auto; + padding: 3rem 2rem 6rem; + } + + .nav { + display: flex; + gap: 2rem; + margin-bottom: 4rem; + font-size: 0.875rem; + font-family: 'JetBrains Mono', monospace; + } + + .nav a { + color: #71717a; + text-decoration: none; + transition: color 0.2s; + } + + .nav a:hover { + color: #7A86E8; + } + + .nav-sep { + color: #3f3f46; + } + + h1 { + font-size: clamp(3rem, 10vw, 5rem); + font-weight: 700; + line-height: 1.1; + margin-bottom: 1rem; + color: #fafafa; + letter-spacing: -0.02em; + } + + h1 .highlight { + background: linear-gradient(135deg, #4F8FE8 0%, #60A5FA 35%, #38BDF8 65%, #4F8FE8 100%); + background-size: 200% 200%; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: gradientShift 3s ease infinite; + filter: drop-shadow(0 0 30px rgba(79, 143, 232, 0.6)); + } + + @keyframes gradientShift { + 0%, 100% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + } + + .subtitle { + font-size: 1.25rem; + color: #71717a; + margin-bottom: 3rem; + opacity: 0; + animation: fadeInUp 0.8s ease forwards 0.15s; + } + + h2 { + font-size: 1.75rem; + font-weight: 600; + margin-top: 4rem; + margin-bottom: 1.5rem; + color: #fafafa; + } + + h3 { + font-size: 1.25rem; + font-weight: 600; + color: #fafafa; + margin-top: 2rem; + margin-bottom: 0.75rem; + } + + p { + margin-bottom: 1.5rem; + } + + a { + color: #7A86E8; + text-decoration: underline; + text-underline-offset: 3px; + } + + a:hover { + text-decoration: none; + } + + hr { + border: none; + height: 1.5rem; + margin: 4rem 0; + display: flex; + align-items: center; + justify-content: center; + } + + hr::before { + content: '• • •'; + color: #3f3f46; + font-size: 1rem; + letter-spacing: 0.25rem; + } + + blockquote { + border-left: 3px solid #7A86E8; + padding-left: 1.5rem; + margin: 2rem 0; + font-style: italic; + color: #a1a1aa; + } + + code { + font-family: 'JetBrains Mono', monospace; + background: #18181b; + padding: 0.2rem 0.5rem; + border-radius: 4px; + font-size: 0.9em; + } + + pre { + font-family: 'JetBrains Mono', monospace; + background: #18181b; + padding: 1.5rem; + border-radius: 8px; + overflow-x: auto; + margin: 2rem 0; + font-size: 0.875rem; + line-height: 1.6; + } + + pre code { + background: none; + padding: 0; + } + + strong { + color: #fafafa; + font-weight: 600; + } + + .os-selector { + display: flex; + gap: 0.5rem; + margin: 1.5rem 0 1rem; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + -ms-overflow-style: none; + } + + .os-selector::-webkit-scrollbar { + display: none; + } + + .os-tab { + padding: 0.5rem 1rem; + background: transparent; + border: 1px solid #27272a; + border-radius: 6px; + color: #71717a; + font-family: 'JetBrains Mono', monospace; + font-size: 0.875rem; + cursor: pointer; + transition: all 0.2s; + white-space: nowrap; + } + + .os-tab:hover { + border-color: #3f3f46; + color: #a1a1aa; + } + + .os-tab.active { + background: #7A86E8; + border-color: #7A86E8; + color: #fff; + } + + .os-commands { + margin: 1rem 0; + } + + .os-commands.hidden, + .example-commands.hidden, + .badge-commands.hidden, + .hero-code.hidden { + display: none; + } + + .hero-code, + .hero-code * { + opacity: 1 !important; + transform: none !important; + transition: none !important; + animation: none !important; + } + + .command-step { + margin: 1rem 0; + } + + .step-label { + display: block; + font-size: 0.875rem; + color: #71717a; + margin-bottom: 0.5rem; + } + + .code-block { + position: relative; + } + + .code-block pre { + margin: 0; + } + + .copy-btn { + position: absolute; + top: 0.75rem; + right: 0.75rem; + padding: 0.4rem 0.6rem; + background: #27272a; + border: 1px solid #3f3f46; + border-radius: 4px; + color: #71717a; + font-family: 'JetBrains Mono', monospace; + font-size: 0.75rem; + cursor: pointer; + transition: all 0.2s; + } + + .copy-btn:hover { + background: #3f3f46; + color: #a1a1aa; + } + + .copy-btn.copied { + background: #7A86E8; + border-color: #7A86E8; + color: #fff; + } + + .directory { + margin: 3rem 0; + } + + .directory h3 { + margin-bottom: 1rem; + } + + .directory-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.5rem 2rem; + font-size: 0.9rem; + } + + .directory-grid a { + color: #71717a; + text-decoration: none; + transition: color 0.2s; + } + + .directory-grid a:hover { + color: #7A86E8; + } + + @media (max-width: 640px) { + .directory-grid { + grid-template-columns: 1fr; + } + } + + .video-embed { + position: relative; + width: 100%; + padding-bottom: 56.25%; + margin: 2rem 0; + border-radius: 8px; + overflow: hidden; + } + + .video-embed iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: none; + } + + /* Animated Terminal */ + .terminal-container { + margin: 3rem 0; + border-radius: 16px; + overflow: hidden; + background: rgba(15, 15, 20, 0.95); + backdrop-filter: blur(20px); + box-shadow: + 0 0 0 1px rgba(30, 40, 60, 0.3), + 0 25px 50px -12px rgba(0, 0, 0, 0.6); + border: 1px solid rgba(40, 50, 70, 0.4); + position: relative; + } + + .terminal-container::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(50, 60, 80, 0.5), transparent); + } + + .terminal-header { + background: rgba(20, 20, 28, 0.8); + padding: 0.875rem 1.25rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + } + + .terminal-title { + flex: 1; + text-align: center; + font-size: 0.75rem; + color: #52525b; + font-family: 'JetBrains Mono', monospace; + letter-spacing: 0.5px; + } + + .terminal-body { + padding: 1.5rem 1.75rem; + height: 320px; + font-family: 'JetBrains Mono', monospace; + font-size: 0.8125rem; + line-height: 1.7; + background: linear-gradient(180deg, rgba(15, 15, 20, 0.5) 0%, rgba(10, 10, 15, 0.8) 100%); + overflow-y: auto; + } + + .terminal-line { + margin-bottom: 0.125rem; + opacity: 0; + transform: translateY(5px); + animation: terminalFadeIn 0.3s ease forwards; + white-space: pre; + } + + .terminal-line.prompt { + color: #71717a; + } + + .terminal-line .dollar { + color: #27ca40; + margin-right: 0.5rem; + } + + .terminal-line .cmd { + color: #7A86E8; + font-weight: 500; + } + + .terminal-line .arg { + color: #fbbf24; + } + + .terminal-line.output { + color: #52525b; + } + + .terminal-line.success { + color: #34d399; + } + + .terminal-line.info { + color: #7A86E8; + } + + .terminal-line.ascii { + color: #7A86E8; + line-height: 1.15; + } + + .terminal-line .version { + color: #c084fc; + } + + .terminal-line .number { + color: #fbbf24; + } + + .terminal-line .path { + color: #38bdf8; + } + + .terminal-line .checkmark { + color: #34d399; + } + + .terminal-line .error-count { + color: #f87171; + } + + .terminal-line .progress { + color: #34d399; + } + + .terminal-cursor { + display: inline-block; + width: 8px; + height: 15px; + background: #7A86E8; + animation: cursorBlink 1.2s ease-in-out infinite; + vertical-align: middle; + margin-left: 2px; + border-radius: 1px; + } + + @keyframes cursorBlink { + 0%, 40% { opacity: 1; } + 50%, 90% { opacity: 0; } + 100% { opacity: 1; } + } + + @keyframes terminalFadeIn { + to { + opacity: 1; + transform: translateY(0); + } + } + + .terminal-tabs { + display: flex; + justify-content: center; + gap: 0.25rem; + padding: 0.75rem 1rem; + background: rgba(10, 10, 15, 0.9); + border-top: 1px solid rgba(255, 255, 255, 0.03); + } + + .terminal-tab { + padding: 0.5rem 1rem; + font-family: 'JetBrains Mono', monospace; + font-size: 0.75rem; + color: #52525b; + background: transparent; + border: 1px solid transparent; + border-radius: 6px; + cursor: pointer; + transition: all 0.25s ease; + } + + .terminal-tab:hover { + color: #a1a1aa; + background: rgba(255, 255, 255, 0.03); + } + + .terminal-tab.active { + color: #fff; + background: rgba(40, 50, 70, 0.4); + border-color: rgba(50, 60, 80, 0.5); + } + + .terminal-info { + padding: 1rem 1.5rem; + background: rgba(10, 10, 15, 0.95); + border-top: 1px solid rgba(255, 255, 255, 0.03); + text-align: center; + } + + .terminal-info a { + display: inline-flex; + align-items: center; + gap: 0.75rem; + color: #a1a1aa; + text-decoration: none; + transition: all 0.2s ease; + padding: 0.5rem 1rem; + border-radius: 8px; + } + + .terminal-info a:hover { + color: #fff; + background: rgba(40, 50, 70, 0.3); + } + + .terminal-info strong { + color: #fff; + font-size: 0.9375rem; + } + + .terminal-info span { + font-size: 0.8125rem; + color: #52525b; + } + + .terminal-info a:hover span { + color: #71717a; + } + + .terminal-info .arrow { + color: #7A86E8; + transition: transform 0.2s ease; + } + + .terminal-info a:hover .arrow { + transform: translateX(3px); + } + + @media (max-width: 640px) { + .terminal-body { + padding: 1rem; + font-size: 0.75rem; + height: 280px; + } + .terminal-tabs { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + -ms-overflow-style: none; + justify-content: flex-start; + padding: 0.75rem 0.5rem; + } + .terminal-tabs::-webkit-scrollbar { + display: none; + } + .terminal-tab { + padding: 0.4rem 0.75rem; + font-size: 0.7rem; + flex-shrink: 0; + } + .terminal-info { + padding: 0.75rem 1rem; + } + .terminal-info a { + flex-direction: column; + gap: 0.25rem; + } + } + + footer { + margin-top: 6rem; + padding-top: 2rem; + border-top: 1px solid #27272a; + font-size: 0.875rem; + color: #52525b; + font-family: 'JetBrains Mono', monospace; + } + + footer a { + color: #71717a; + text-decoration: none; + } + + footer a:hover { + color: #7A86E8; + } + + .footer-row { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; + } + + .footer-links { + display: flex; + gap: 1.5rem; + flex-wrap: wrap; + } + + @media (max-width: 640px) { + .nav { + gap: 1rem; + flex-wrap: wrap; + } + } + + /* ===== ANIMATIONS ===== */ + + /* Keyframes */ + @keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes blink { + 0%, 50% { opacity: 1; } + 51%, 100% { opacity: 0; } + } + + @keyframes glow { + 0%, 100% { box-shadow: 0 0 20px rgba(122, 134, 232, 0.1); } + 50% { box-shadow: 0 0 30px rgba(122, 134, 232, 0.25); } + } + + /* Hero entrance animations */ + .hero-title { + animation: fadeInUp 0.8s ease forwards; + } + + .hero-subtitle { + opacity: 0; + animation: fadeInUp 0.8s ease forwards 0.15s; + } + + .hero-text-1 { + opacity: 0; + animation: fadeInUp 0.8s ease forwards 0.3s; + } + + .hero-text-2 { + opacity: 0; + animation: fadeInUp 0.8s ease forwards 0.45s; + } + + .hero-code { + opacity: 0; + animation: fadeInUp 0.8s ease forwards 0.6s; + } + + /* PHP highlight */ + .highlight { + display: inline-block; + } + + /* Scroll reveal */ + .reveal { + opacity: 0; + transform: translateY(40px); + transition: opacity 0.7s cubic-bezier(0.4, 0, 0.2, 1), + transform 0.7s cubic-bezier(0.4, 0, 0.2, 1); + } + + .reveal.visible { + opacity: 1; + transform: translateY(0); + } + + + /* Share button enhanced hover */ + .share-btn { + position: relative; + overflow: hidden; + transform: translateY(0); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + } + + .share-btn:hover { + transform: translateY(-3px); + box-shadow: 0 8px 25px rgba(122, 134, 232, 0.25); + } + + .share-btn:active { + transform: translateY(-1px); + } + + /* OS tab animations */ + .os-tab { + transform: scale(1); + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + } + + .os-tab:hover:not(.active) { + transform: scale(1.05); + border-color: #52525b; + } + + .os-tab.active { + box-shadow: 0 4px 20px rgba(122, 134, 232, 0.4); + } + + /* Code block enhancements */ + pre { + position: relative; + transition: box-shadow 0.3s ease, transform 0.3s ease; + } + + pre:hover { + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4); + transform: translateY(-2px); + } + + /* Typing effect container */ + .code-typed pre { + height: 380px; + overflow: hidden; + } + + .typing-cursor { + display: inline-block; + width: 2px; + height: 1.1em; + background: #7A86E8; + margin-left: 2px; + animation: blink 0.8s infinite; + vertical-align: text-bottom; + } + + /* Nav link underline animation */ + .nav a { + position: relative; + } + + .nav a::after { + content: ''; + position: absolute; + bottom: -4px; + left: 0; + width: 0; + height: 2px; + background: #7A86E8; + transition: width 0.3s ease; + } + + .nav a:hover::after { + width: 100%; + } + + /* Video embed hover */ + .video-embed { + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + box-shadow 0.4s ease; + } + + .video-embed:hover { + transform: scale(1.02); + box-shadow: 0 25px 60px rgba(122, 134, 232, 0.2); + } + + /* Copy button pulse on hover */ + .copy-btn { + transition: all 0.2s ease; + } + + .copy-btn:hover { + transform: scale(1.05); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + + /* Section dividers fade in */ + hr { + opacity: 0; + transition: opacity 0.6s ease; + } + + hr.visible { + opacity: 1; + } + + /* Footer reveal */ + footer { + opacity: 0; + transform: translateY(20px); + transition: opacity 0.6s ease, transform 0.6s ease; + } + + footer.visible { + opacity: 1; + transform: translateY(0); + } + + /* Badge section */ + .badge-showcase { + display: flex; + justify-content: center; + margin: 2rem 0; + } + + .badge-preview { + display: inline-block; + transition: transform 0.3s ease; + } + + .badge-preview:hover { + transform: scale(1.05); + } + + .badge-preview svg { + display: block; + } + + .badge-code { + margin: 1.5rem 0; + } + + .badge-code pre { + font-size: 0.8rem; + word-break: break-all; + white-space: pre-wrap; + } + + .badge-preview-result { + margin: 1.5rem 0; + } + + .badge-preview-result a { + display: inline-block; + transition: transform 0.2s ease; + } + + .badge-preview-result a:hover { + transform: scale(1.1); + } + + .badge-preview-result img { + display: block; + } + + /* Floating badge CTA */ + .floating-cta { + position: fixed; + bottom: 2rem; + right: 2rem; + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.25rem; + background: linear-gradient(135deg, #7A86E8 0%, #60A5FA 100%); + border: none; + border-radius: 50px; + color: #fff; + font-family: 'JetBrains Mono', monospace; + font-size: 0.875rem; + font-weight: 600; + text-decoration: none; + cursor: pointer; + box-shadow: 0 4px 20px rgba(122, 134, 232, 0.4); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 1000; + animation: floatIn 0.6s ease forwards 1s; + opacity: 0; + transform: translateY(20px); + } + + @keyframes floatIn { + to { + opacity: 1; + transform: translateY(0); + } + } + + .floating-cta:hover { + transform: translateY(-3px) scale(1.05); + box-shadow: 0 8px 30px rgba(122, 134, 232, 0.6); + } + + .floating-cta:active { + transform: translateY(-1px) scale(1.02); + } + + .floating-cta svg { + width: 18px; + height: 18px; + fill: currentColor; + } + + .floating-cta-left { + position: fixed; + bottom: 2rem; + left: 2rem; + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.625rem 1rem; + background: rgba(24, 24, 27, 0.9); + backdrop-filter: blur(8px); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 50px; + color: #a1a1aa; + font-family: 'JetBrains Mono', monospace; + font-size: 0.8rem; + text-decoration: none; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 1000; + animation: floatIn 0.6s ease forwards 1.2s; + opacity: 0; + transform: translateY(20px); + } + + .floating-cta-left:hover { + background: rgba(39, 39, 42, 0.95); + border-color: rgba(122, 134, 232, 0.3); + color: #fff; + transform: translateY(-2px); + } + + .floating-cta-left svg { + width: 16px; + height: 16px; + fill: currentColor; + } + + @media (max-width: 640px) { + .container { + padding-bottom: 8rem; + } + .floating-cta { + bottom: 1rem; + right: 1rem; + padding: 0.6rem 1rem; + font-size: 0.8rem; + } + .floating-cta-left { + bottom: 1rem; + left: 1rem; + padding: 0.5rem 0.875rem; + font-size: 0.75rem; + } + } + + /* Share Modal */ + .share-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(4px); + z-index: 2000; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + } + + .share-modal-overlay.active { + opacity: 1; + visibility: visible; + } + + .share-modal { + background: #18181b; + border: 1px solid #27272a; + border-radius: 16px; + padding: 2rem; + max-width: 360px; + width: 90%; + transform: scale(0.9) translateY(20px); + transition: transform 0.3s ease; + } + + .share-modal-overlay.active .share-modal { + transform: scale(1) translateY(0); + } + + .share-modal h3 { + margin: 0 0 1.5rem 0; + font-size: 1.25rem; + text-align: center; + color: #fff; + } + + .share-modal-options { + display: flex; + flex-direction: column; + gap: 0.75rem; + } + + .share-modal-option { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.875rem 1rem; + background: #27272a; + border: 1px solid #3f3f46; + border-radius: 10px; + color: #fff; + font-family: 'JetBrains Mono', monospace; + font-size: 0.875rem; + text-decoration: none; + cursor: pointer; + transition: all 0.2s ease; + } + + .share-modal-option:hover { + background: #3f3f46; + border-color: #7A86E8; + transform: translateX(4px); + } + + .share-modal-option svg { + width: 20px; + height: 20px; + flex-shrink: 0; + } + + .share-modal-close { + position: absolute; + top: 1rem; + right: 1rem; + background: none; + border: none; + color: #71717a; + cursor: pointer; + padding: 0.5rem; + transition: color 0.2s ease; + } + + .share-modal-close:hover { + color: #fff; + } + + /* Reduce motion for accessibility */ + @media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + } + + /* Trusted by section */ + .trusted-section { + margin-top: 4rem; + text-align: center; + } + + .trusted-label { + font-size: 0.75rem; + color: #52525b; + text-transform: uppercase; + letter-spacing: 0.1em; + margin-bottom: 1.5rem; + font-family: 'JetBrains Mono', monospace; + } + + .trusted-logos { + display: flex; + align-items: center; + justify-content: center; + gap: 2rem; + flex-wrap: wrap; + } + + .trusted-logos svg { + height: 24px; + width: auto; + fill: #71717a; + transition: fill 0.2s ease; + } + + .trusted-logos svg:hover { + fill: #a1a1aa; + } + + @media (max-width: 640px) { + .trusted-logos { + gap: 1.5rem; + } + .trusted-logos svg { + height: 18px; + } + } + + .feature-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; + margin: 2rem 0; + } + + .feature-card { + border: 1px solid #27272a; + border-radius: 12px; + padding: 1.5rem; + background: rgba(24, 24, 27, 0.4); + transition: border-color 0.25s ease, transform 0.25s ease; + } + + .feature-card:hover { + border-color: #3f3f46; + transform: translateY(-3px); + } + + .feature-card h3 { + margin-top: 0; + margin-bottom: 0.5rem; + } + + .feature-card p { + font-size: 0.95rem; + color: #a1a1aa; + margin-bottom: 1rem; + } + + .feature-card pre { + margin: 0; + font-size: 0.8rem; + } + + @media (max-width: 640px) { + .feature-grid { + grid-template-columns: 1fr; + } + } diff --git a/public/js/app.js b/public/js/app.js new file mode 100644 index 0000000..bbdbaa1 --- /dev/null +++ b/public/js/app.js @@ -0,0 +1,371 @@ + // ===== SCROLL REVEAL ===== + const revealObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('visible'); + } + }); + }, { + threshold: 0.1, + rootMargin: '0px 0px -50px 0px' + }); + + document.querySelectorAll('.reveal, .stagger-children, hr, footer').forEach(el => { + revealObserver.observe(el); + }); + + // ===== TYPING EFFECT ===== + const codeToType = `final readonly class Book +{ + public function __construct( + public Status $status, + public string $title, + ) {} + + public function label(): string + { + return match ($this->status) { + Status::Draft => 'Working on it', + Status::Published => 'Ready to read', + }; + } +}`; + + const syntaxHighlight = (code) => { + return code + .replace(/\b(final|readonly|class|public|function|return|match)\b/g, '$1') + .replace(/\b(Status|Book|string)\b/g, '$1') + .replace(/(__construct|label)/g, '$1') + .replace(/(\$\w+)/g, '$1') + .replace(/(Status)::(Draft|Published)/g, '$1::$2') + .replace(/'([^']+)'/g, '\'$1\''); + }; + + const typedCodeEl = document.getElementById('typed-code'); + let charIndex = 0; + let typingStarted = false; + + const typeCode = () => { + if (charIndex < codeToType.length) { + const currentText = codeToType.substring(0, charIndex + 1); + typedCodeEl.innerHTML = syntaxHighlight(currentText) + ''; + charIndex++; + const delay = codeToType[charIndex - 1] === '\n' ? 25 : Math.random() * 8 + 4; + setTimeout(typeCode, delay); + } else { + typedCodeEl.innerHTML = syntaxHighlight(codeToType); + } + }; + + // Start typing when hero code block is visible + const heroCodeObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting && !typingStarted) { + typingStarted = true; + setTimeout(typeCode, 300); + } + }); + }, { threshold: 0.5 }); + + heroCodeObserver.observe(document.getElementById('hero-value-objects')); + + // ===== OS TABS ===== + document.querySelectorAll('.os-tab[data-os]').forEach(tab => { + tab.addEventListener('click', () => { + document.querySelectorAll('.os-tab[data-os]').forEach(t => t.classList.remove('active')); + document.querySelectorAll('.os-commands').forEach(c => c.classList.add('hidden')); + + tab.classList.add('active'); + document.getElementById('os-' + tab.dataset.os).classList.remove('hidden'); + }); + }); + + // ===== HERO CODE TABS ===== + document.querySelectorAll('.os-tab[data-hero]').forEach(tab => { + tab.addEventListener('click', () => { + document.querySelectorAll('.os-tab[data-hero]').forEach(t => t.classList.remove('active')); + document.querySelectorAll('.hero-code').forEach(c => c.classList.add('hidden')); + + tab.classList.add('active'); + document.getElementById('hero-' + tab.dataset.hero).classList.remove('hidden'); + }); + }); + + // ===== BADGE TABS ===== + let currentFormat = 'md'; + let currentStyle = 'flat-square'; + + const updateBadgeCode = () => { + const baseUrl = 'https://img.shields.io/badge/Why_PHP-in_2026-7A86E8'; + const imgUrl = `${baseUrl}?style=${currentStyle}&labelColor=18181b`; + const linkUrl = 'https://whyphp.dev'; + + // Update preview image + document.querySelector('#badge-preview-img img').src = imgUrl; + + // Generate code based on format + let code; + if (currentFormat === 'md') { + code = `[![Why PHP](${imgUrl})](${linkUrl})`; + } else { + code = `Why PHP in 2026`; + } + + document.getElementById('badge-code-output').textContent = code; + document.getElementById('badge-copy-btn').dataset.copy = code; + }; + + document.querySelectorAll('.os-tab[data-format]').forEach(tab => { + tab.addEventListener('click', () => { + document.querySelectorAll('.os-tab[data-format]').forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + currentFormat = tab.dataset.format; + updateBadgeCode(); + }); + }); + + document.querySelectorAll('.os-tab[data-style]').forEach(tab => { + tab.addEventListener('click', () => { + document.querySelectorAll('.os-tab[data-style]').forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + currentStyle = tab.dataset.style; + updateBadgeCode(); + }); + }); + + document.querySelectorAll('.copy-btn').forEach(btn => { + btn.addEventListener('click', async () => { + const text = btn.dataset.copy; + await navigator.clipboard.writeText(text); + btn.textContent = 'copied'; + btn.classList.add('copied'); + setTimeout(() => { + btn.textContent = 'copy'; + btn.classList.remove('copied'); + }, 1000); + }); + }); + + // ===== ANIMATED TERMINAL ===== + const terminalTools = { + laravel: { + name: 'Laravel', + desc: 'Rails/Next.js for PHP', + url: 'https://laravel.com', + lines: [ + { type: 'prompt', text: '$ laravel new myapp' }, + { type: 'output', text: '' }, + { type: 'ascii', text: ' _ _' }, + { type: 'ascii', text: ' | | | |' }, + { type: 'ascii', text: ' | | __ _ _ __ __ ___ _____| |' }, + { type: 'ascii', text: ' | | / _` | \'__/ _` \\ \\ / / _ \\ |' }, + { type: 'ascii', text: ' | |___| (_| | | | (_| |\\ V / __/ |' }, + { type: 'ascii', text: ' |______\\__,_|_| \\__,_| \\_/ \\___|_|' }, + { type: 'output', text: '' }, + { type: 'output', text: ' Creating a laravel/laravel project...' }, + { type: 'output', text: '' }, + { type: 'success', text: ' Application ready in [myapp].' }, + { type: 'success', text: ' Built with love.' }, + { type: 'cursor', text: '' } + ] + }, + symfony: { + name: 'Symfony', + desc: 'Build with confidence at any scale', + url: 'https://symfony.com', + lines: [ + { type: 'prompt', text: '$ symfony new myapp' }, + { type: 'output', text: '' }, + { type: 'ascii', text: ' * Creating a new Symfony project with Composer' }, + { type: 'ascii', text: ' * Setting up the project under Git version control' }, + { type: 'ascii', text: ' (running git init /Projects/myapp)' }, + { type: 'ascii', text: ' ' }, + { type: 'ascii', text: ' [OK] Your project is now ready in /Projects/myapp ' }, + { type: 'output', text: '' }, + { type: 'cursor', text: '' } + ] + }, + composer: { + name: 'Composer', + desc: 'Cargo/npm for PHP', + url: 'https://getcomposer.org', + lines: [ + { type: 'prompt', text: '$ composer require laravel/sanctum' }, + { type: 'output', text: '' }, + { type: 'output', text: ' ./composer.json has been updated' }, + { type: 'output', text: ' Running composer update laravel/sanctum' }, + { type: 'output', text: ' Loading composer repositories with package information' }, + { type: 'output', text: ' Updating dependencies' }, + { type: 'output', text: '' }, + { type: 'output', text: ' Lock file operations: 1 install, 0 updates, 0 removals' }, + { type: 'success', text: ' - Locking laravel/sanctum (v4.0.6)' }, + { type: 'output', text: '' }, + { type: 'success', text: ' Package laravel/sanctum installed successfully' }, + { type: 'cursor', text: '' } + ] + }, + phpstan: { + name: 'PHPStan', + desc: 'TypeScript-like type safety', + url: 'https://phpstan.org', + lines: [ + { type: 'prompt', text: '$ ./vendor/bin/phpstan analyse' }, + { type: 'output', text: '' }, + { type: 'output', text: ' 42/42 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%' }, + { type: 'output', text: '' }, + { type: 'output', text: ' ───────────────────────────────────────────────────' }, + { type: 'output', text: '' }, + { type: 'success', text: ' [OK] No errors' }, + { type: 'output', text: '' }, + { type: 'output', text: ' 💡 Tip: PHPStan is at level 9 — maximum strictness' }, + { type: 'cursor', text: '' } + ] + }, + pest: { + name: 'Pest', + desc: 'Jest-style testing', + url: 'https://pestphp.com', + lines: [ + { type: 'prompt', text: '$ ./vendor/bin/pest' }, + { type: 'output', text: '' }, + { type: 'output', text: ' PEST v4.0' }, + { type: 'output', text: '' }, + { type: 'success', text: ' it can create a book 0.02s' }, + { type: 'success', text: ' it can publish a book 0.01s' }, + { type: 'success', text: ' it validates required fields 0.01s' }, + { type: 'success', text: ' it belongs to an author 0.02s' }, + { type: 'output', text: '' }, + { type: 'success', text: ' Tests: 4 passed (12 assertions)' }, + { type: 'output', text: ' Duration: 0.06s' }, + { type: 'cursor', text: '' } + ] + }, + phpunit: { + name: 'PHPUnit', + desc: 'programmer-oriented testing framework', + url: 'https://phpunit.de/', + lines: [ + { type: 'prompt', text: '$ ./vendor/bin/phpunit --testdox' }, + { type: 'output', text: '' }, + { type: 'output', text: 'PHPUnit 12.5.4 by Sebastian Bergmann and contributors.' }, + { type: 'output', text: '' }, + { type: 'success', text: 'Book (Vendor\App\Tests\Unit\Domain\Model\Book)' }, + { type: 'success', text: ' it can create a book' }, + { type: 'success', text: ' it can publish a book' }, + { type: 'success', text: ' it validates required fields' }, + { type: 'success', text: ' it belongs to an author' }, + { type: 'output', text: '' }, + { type: 'success', text: 'Tests: 4, Assertions: 12, Skipped: 0' }, + { type: 'cursor', text: '' } + ] + }, + pint: { + name: 'Pint', + desc: 'Code style fixer', + url: 'https://laravel.com/docs/pint', + lines: [ + { type: 'prompt', text: '$ ./vendor/bin/pint' }, + { type: 'output', text: '' }, + { type: 'output', text: ' PINT v1.18' }, + { type: 'output', text: '' }, + { type: 'output', text: ' app/Models/Book.php fixed' }, + { type: 'output', text: ' app/Http/Controllers/BookController.php fixed' }, + { type: 'output', text: ' tests/Feature/BookTest.php fixed' }, + { type: 'output', text: '' }, + { type: 'success', text: ' 3 files fixed' }, + { type: 'cursor', text: '' } + ] + }, + rector: { + name: 'Rector', + desc: 'Automated refactoring', + url: 'https://getrector.com', + lines: [ + { type: 'prompt', text: '$ ./vendor/bin/rector --dry-run' }, + { type: 'output', text: '' }, + { type: 'output', text: ' 12/12 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%' }, + { type: 'output', text: '' }, + { type: 'output', text: ' 3 files would have been changed:' }, + { type: 'output', text: '' }, + { type: 'output', text: ' AddVoidReturnTypeWhereNoReturnRector' }, + { type: 'output', text: ' ReadOnlyClassRector' }, + { type: 'output', text: ' TypedPropertyFromStrictConstructorRector' }, + { type: 'output', text: '' }, + { type: 'success', text: ' [OK] Rector is done! 3 files with changes' }, + { type: 'cursor', text: '' } + ] + } + }; + + function renderTerminal(tab) { + const tool = terminalTools[tab]; + const output = document.getElementById('terminal-output'); + output.innerHTML = ''; + + tool.lines.forEach((line, index) => { + const div = document.createElement('div'); + div.className = 'terminal-line ' + line.type; + div.style.animationDelay = (index * 0.08) + 's'; + + if (line.type === 'cursor') { + div.classList.add('prompt'); + div.innerHTML = ''; + } else { + div.innerHTML = line.text; + } + + output.appendChild(div); + }); + + document.getElementById('terminal-link').href = tool.url; + document.getElementById('terminal-tool-name').textContent = tool.name; + document.getElementById('terminal-tool-desc').textContent = tool.desc; + } + + document.querySelectorAll('.terminal-tab').forEach(tab => { + tab.addEventListener('click', () => { + document.querySelectorAll('.terminal-tab').forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + renderTerminal(tab.dataset.terminal); + }); + }); + + // ===== SHARE MODAL ===== + const shareModal = document.getElementById('share-modal'); + const shareBtn = document.getElementById('share-btn'); + const modalShareText = 'Why PHP in 2026? TypeScript-level types, no build steps, immutability by default via readonly, enums, and more.'; + const modalShareUrl = 'https://whyphp.dev'; + + // Set share links + document.getElementById('modal-share-twitter').href = + `https://twitter.com/intent/tweet?text=${encodeURIComponent(modalShareText)}&url=${encodeURIComponent(modalShareUrl)}`; + document.getElementById('modal-share-bluesky').href = + `https://bsky.app/intent/compose?text=${encodeURIComponent(modalShareText + ' ' + modalShareUrl)}`; + document.getElementById('modal-share-linkedin').href = + `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(modalShareUrl)}`; + + shareBtn.addEventListener('click', () => { + shareModal.classList.add('active'); + }); + + shareModal.addEventListener('click', (e) => { + if (e.target === shareModal) { + shareModal.classList.remove('active'); + } + }); + + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + shareModal.classList.remove('active'); + } + }); + + document.getElementById('modal-share-copy').addEventListener('click', async () => { + await navigator.clipboard.writeText(modalShareUrl); + const btn = document.getElementById('modal-share-copy'); + btn.innerHTML = ' Copied!'; + setTimeout(() => { + btn.innerHTML = ' Copy link'; + shareModal.classList.remove('active'); + }, 1500); + }); diff --git a/resources/views/components/feature-card.blade.php b/resources/views/components/feature-card.blade.php new file mode 100644 index 0000000..7f087e4 --- /dev/null +++ b/resources/views/components/feature-card.blade.php @@ -0,0 +1,6 @@ +@props(['title', 'description']) +
+

{{ $title }}

+

{{ $description }}

+ {{ $slot }} +
diff --git a/resources/views/partials/badge.blade.php b/resources/views/partials/badge.blade.php new file mode 100644 index 0000000..667dee2 --- /dev/null +++ b/resources/views/partials/badge.blade.php @@ -0,0 +1,27 @@ +
+

Show Your Support

+ +

Add this badge to your README. Let the mass know.

+ +
+ + Why PHP in 2026 + +
+ +
+ + +
+ +
+ + + +
+ +
+ +
[![Why PHP](https://img.shields.io/badge/Why_PHP-in_2026-7A86E8?style=flat-square&labelColor=18181b)](https://whyphp.dev)
+
+
diff --git a/resources/views/partials/ecosystem.blade.php b/resources/views/partials/ecosystem.blade.php new file mode 100644 index 0000000..51e3837 --- /dev/null +++ b/resources/views/partials/ecosystem.blade.php @@ -0,0 +1,44 @@ +
+

Ecosystem

+ +

Modern PHP isn't just better. It's unrecognizable from its past.

+ +
+
+
~/projects/myapp
+
+
+
$ laravel new myapp
+
+
_ _
+
| | | |
+
| | __ _ _ __ __ ___ _____| |
+
| | / _` | '__/ _` \ \ / / _ \ |
+
| |___| (_| | | | (_| |\ V / __/ |
+
|______\__,_|_| \__,_| \_/ \___|_|
+
+
Creating a laravel/laravel project...
+
+
Application ready in [myapp].
+
Built with love.
+
+
+
+ + + + + + + + +
+ +
+
diff --git a/resources/views/partials/features.blade.php b/resources/views/partials/features.blade.php new file mode 100644 index 0000000..5e20a5e --- /dev/null +++ b/resources/views/partials/features.blade.php @@ -0,0 +1,47 @@ +
+

The features that make PHP shine

+ +

Not one trick. A whole toolkit for code that's type-safe, expressive, and a joy to maintain.

+ +
+ +
enum Status: string
+{
+    case Published = 'published';
+}
+
+ +
final class Book
+{
+    use HasTimestamps, HasSlug;
+}
+
+ +
$label = match ($status) {
+    Status::Draft => 'Draft',
+    Status::Published => 'Live',
+};
+
+ +
final readonly class Money
+{
+    public function __construct(
+        public int $amount,
+    ) {}
+}
+
+ +
new Book(
+    title: 'Modern PHP',
+    status: Status::Draft,
+);
+
+ +
$country = $user?->address?->country;
+
+ +
#[Route('/books', methods: ['GET'])]
+public function index(): View
+
+
+
diff --git a/resources/views/partials/floating.blade.php b/resources/views/partials/floating.blade.php new file mode 100644 index 0000000..b06d042 --- /dev/null +++ b/resources/views/partials/floating.blade.php @@ -0,0 +1,33 @@ + + + Edit this site + + + + +
+
+

Spread the word

+ +
+
diff --git a/resources/views/partials/footer.blade.php b/resources/views/partials/footer.blade.php new file mode 100644 index 0000000..656d250 --- /dev/null +++ b/resources/views/partials/footer.blade.php @@ -0,0 +1,9 @@ + diff --git a/resources/views/partials/head.blade.php b/resources/views/partials/head.blade.php new file mode 100644 index 0000000..126b26f --- /dev/null +++ b/resources/views/partials/head.blade.php @@ -0,0 +1,137 @@ + + + + + + Why PHP in 2026? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/views/partials/hero.blade.php b/resources/views/partials/hero.blade.php new file mode 100644 index 0000000..9f4e06c --- /dev/null +++ b/resources/views/partials/hero.blade.php @@ -0,0 +1,101 @@ +
+

Why PHP in 2026?

+ +

It's actually insanely good now.

+ +

PHP has come a long way. Modern PHP has JIT compilation, immutability via readonly, enums, attributes, and a type system that rivals TypeScript.

+ +

No build steps. No transpilation. No bundling. Just write code and deploy. That's the superpower everyone forgot about.

+ +
+ + + + + + +
+ +
+
+
+
+ + + + + + + + + + +
+
+ +
+

Trusted by thousands of companies around the world

+
+ + + + + + + + + + +
+
diff --git a/resources/views/partials/nav.blade.php b/resources/views/partials/nav.blade.php new file mode 100644 index 0000000..80c3ec2 --- /dev/null +++ b/resources/views/partials/nav.blade.php @@ -0,0 +1,11 @@ + diff --git a/resources/views/partials/start.blade.php b/resources/views/partials/start.blade.php new file mode 100644 index 0000000..778389a --- /dev/null +++ b/resources/views/partials/start.blade.php @@ -0,0 +1,68 @@ +
+

Start Today

+ +

One command. Zero config. Production-ready.

+ +
+ + + +
+ +
+
+ Install PHP +
+ +
/bin/bash -c "$(curl -fsSL https://php.new/install/mac)"
+
+
+
+ Create a Laravel app and run it +
+ +
laravel new my-app
+cd my-app
+composer run dev # visit http://localhost:8000
+
+
+
+ + + + +
diff --git a/resources/views/partials/watch.blade.php b/resources/views/partials/watch.blade.php new file mode 100644 index 0000000..290e860 --- /dev/null +++ b/resources/views/partials/watch.blade.php @@ -0,0 +1,10 @@ +
+

Ready to see modern PHP?

+ +

Type-safe. Expressive. Modern. It just works. Watch me explain it.

+ +
+ +
+ +
diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php index c181617..83323a0 100644 --- a/resources/views/welcome.blade.php +++ b/resources/views/welcome.blade.php @@ -1,1883 +1,36 @@ - - - - - - Why PHP in 2026? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @include('partials.head')
- - -
-

Why PHP in 2026?

- -

It's actually insanely good now.

- -

PHP has come a long way. Modern PHP has JIT compilation, immutability via readonly, enums, attributes, and a type system that rivals TypeScript.

- -

No build steps. No transpilation. No bundling. Just write code and deploy. That's the superpower everyone forgot about.

- -
- - - - -
- -
-
-
-
- - - - - - -
-
- -
-

Trusted by thousands of companies around the world

-
- - - - - - - - - - -
-
+ @include('partials.nav') + @include('partials.hero')
-
-

Ecosystem

- -

Modern PHP isn't just better. It's unrecognizable from its past.

- -
-
-
~/projects/myapp
-
-
-
$ laravel new myapp
-
-
_ _
-
| | | |
-
| | __ _ _ __ __ ___ _____| |
-
| | / _` | '__/ _` \ \ / / _ \ |
-
| |___| (_| | | | (_| |\ V / __/ |
-
|______\__,_|_| \__,_| \_/ \___|_|
-
-
Creating a laravel/laravel project...
-
-
Application ready in [myapp].
-
Built with love.
-
-
-
- - - - - - - - -
- -
-
+ @include('partials.features')
-
-

Ready to see modern PHP?

- -

Type-safe. Expressive. Modern. It just works. Watch me explain it.

- -
- -
- -
+ @include('partials.ecosystem')
-
-

Start Today

- -

One command. Zero config. Production-ready.

- -
- - - -
- -
-
- Install PHP -
- -
/bin/bash -c "$(curl -fsSL https://php.new/install/mac)"
-
-
-
- Create a Laravel app and run it -
- -
laravel new my-app
-cd my-app
-composer run dev # visit http://localhost:8000
-
-
-
- - - - -
+ @include('partials.watch')
-
-

Show Your Support

- -

Add this badge to your README. Let the mass know.

+ @include('partials.start') -
- - Why PHP in 2026 - -
- -
- - -
- -
- - - -
+
-
- -
[![Why PHP](https://img.shields.io/badge/Why_PHP-in_2026-7A86E8?style=flat-square&labelColor=18181b)](https://whyphp.dev)
-
-
+ @include('partials.badge') - + @include('partials.footer')
- - - Edit this site - - - - -
-
-

Spread the word

- -
-
- - + diff --git a/tests/Feature/WelcomePageTest.php b/tests/Feature/WelcomePageTest.php new file mode 100644 index 0000000..da908f6 --- /dev/null +++ b/tests/Feature/WelcomePageTest.php @@ -0,0 +1,25 @@ +get('/') + ->assertOk() + ->assertSee('The features that make PHP shine') + ->assertSee('Named arguments') + ->assertSee('Nullsafe') + ->assertSee('Attributes'); +}); + +test('the welcome page links to the features section', function () { + $this->get('/') + ->assertOk() + ->assertSee('href="#features"', false); +}); + +test('the welcome page includes enums and traits code tabs', function () { + $this->get('/') + ->assertOk() + ->assertSee('data-hero="enums"', false) + ->assertSee('data-hero="traits"', false) + ->assertSee('id="hero-enums"', false) + ->assertSee('id="hero-traits"', false); +});