1<html lang="en" class="flex flex-col h-dvh scroll-smooth"> 2<head> 3 <!-- Meta title --> 4 5 <title>Tempest, the PHP framework that gets out of your way</title> 6 7 <link rel="alternate" type="application/rss+xml" title="Tempest RSS" href="https://tempestphp.com/rss"> 8 9 <meta name="title" content="Tempest, the PHP framework that gets out of your way"> 10 <meta name="twitter:title" content="Tempest, the PHP framework that gets out of your way"> 11 <meta property="og:title" content="Tempest, the PHP framework that gets out of your way"> 12 <meta itemprop="name" content="Tempest, the PHP framework that gets out of your way"> 13 14 <meta charset="UTF-8"> 15 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 16 17 <!-- Meta description --> 18 19 <meta name="description" content="Tempest is a modern framework designed to enable developers to write as little framework-specific code as possible, so that they can focus on application code instead."> 20 <meta name="twitter:description" content="Tempest is a modern framework designed to enable developers to write as little framework-specific code as possible, so that they can focus on application code instead."> 21 <meta property="og:description" content="Tempest is a modern framework designed to enable developers to write as little framework-specific code as possible, so that they can focus on application code instead."> 22 <meta itemprop="description" content="Tempest is a modern framework designed to enable developers to write as little framework-specific code as possible, so that they can focus on application code instead."> 23 24 <!-- Meta image --> 25 <meta property="og:image" content="https://tempestphp.com/meta/home"> 26 <meta name="twitter:image" content="https://tempestphp.com/meta/home"> 27 <meta name="image" content="https://tempestphp.com/meta/home"> 28 <meta name="twitter:card" content="summary_large_image"> 29 <meta property="og:type" content="article"> 30 31 <!-- Favicon --> 32 <link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png"> 33 <link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png"> 34 <link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png"> 35 <link rel="manifest" href="/favicon/site.webmanifest"> 36 37 <!-- Vite tags --> 38 39 40
41<script type="module" src="/build/assets/main.entrypoint-BCjeSVPN.js"></script>
41<link rel="stylesheet" href="/build/assets/main-BK7cdzvE.css" />
41<script type="module" src="/build/assets/palette.entrypoint-DqmRhDKF.js"></script>
41 42 43 <!-- Dark mode --> 44
44<script> 45 function isDark() { 46 return localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches) 47 } 48 49 function applyTheme(theme = undefined) { 50 if (theme) { 51 localStorage.theme = theme 52 } 53 54 document.documentElement.classList.toggle('dark', isDark()) 55 document.dispatchEvent(new CustomEvent('theme-changed', {detail: {isDark: isDark()}})) 56 } 57 58 function toggleDarkMode() { 59 applyTheme(isDark() ? 'light' : 'dark') 60 } 61 62 applyTheme() 63 </script>
63 64 65 66</head> 67<body class="relative antialiased flex flex-col grow selection:bg-(--ui-primary)/20 selection:text-(--ui-primary) font-sans text-(--ui-text) bg-(--ui-bg) scheme-light dark:scheme-dark overflow-visible! pr-0! bg-[#f1f7ff] dark:bg-[black]"> 68<div class="absolute inset-0 bg-repeat pointer-events-none" style="background-image: url(/noise.svg)"> 69 <div id="command-palette"></div> 70</div> 71 72 73 74<!-- Header --> 75 76 77<div class="relative inset-x-0 z-[1] h-(--ui-header-height)"> 78 <header class="group transition-[top,border] z-[1] fixed top-4 data-[scrolling]:top-0 flex justify-center bg-[transparent] border-b border-transparent data-[scrolling]:border-(--ui-border) w-full duration-200 data-[scrolling]:backdrop-blur data-[scrolling]:bg-(--ui-bg)/75" id="header"> 79 <div class="grid grid-cols-[auto_1fr_auto] items-center gap-x-4 px-8 py-4 w-full 2xl:max-w-8xl lg:max-w-5xl xl:max-w-7xl"> 80 <!-- Left side --> 81 <div class="flex items-center gap-4"> 82 <a href="/" class="flex items-center gap-4"> 83 <!-- Logo --> 84 <div class="size-8"> 85 <img src="/img/tempest-logo.png" alt="Tempest logo" class="size-full"> 86 </div> 87 <span class="hidden lg:inline font-medium">Tempest</span> 88 </a> 89 90 <a class="hidden md:inline text-xs tracking-wide font-medium text-(--ui-text-muted) bg-(--ui-bg)/50 px-2 py-1 rounded-lg border border-(--ui-border)" href="https://github.com/tempestphp/tempest-framework/releases/v3.19.2"> 91 v3.19.2 </a> 92 </div> 93 94 <!-- Center --> 95 <div class="flex items-center gap-4 min-w-0 xl:ml-32"> 96 <button toggle-palette class="hidden sm:flex items-center gap-2 min-w-0 cursor-pointer rounded-lg border border-(--ui-border) bg-(--ui-bg)/50 dark:bg-transparent px-3 py-1.5 text-sm text-(--ui-text-muted) hover:bg-(--ui-bg-elevated) hover:border-(--ui-border-accented) transition group-data-[scrolling]:border-transparent group-data-[scrolling]:bg-transparent group-data-[scrolling]:hover:bg-(--ui-bg-elevated)"> 97 <label for="search" class="sr-only">Search</label> 98 99 100 101<svg class="size-4 shrink-0 text-(--ui-text-dimmed)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10a7 7 0 1 0 14 0a7 7 0 1 0-14 0m18 11l-6-6"/></svg> 102 <span class="truncate">Search docs, blog...</span> 103 <kbd class="ml-2 inline-flex items-center gap-0.5 rounded border border-(--ui-border) bg-(--ui-bg-muted) px-1.5 py-0.5 text-xs text-(--ui-text-dimmed) font-medium"> 104 / 105 </kbd> 106</button> 107 108 109 </div> 110 <!-- Right side --> 111 <div class="flex items-center gap-5 font-medium text-(--ui-text-muted)"> 112 <a class="transition hover:text-(--ui-text-highlighted) " href="https://tempestphp.com/community"> 113 Community 114 </a> 115 <a class="transition hover:text-(--ui-text-highlighted) " href="https://tempestphp.com/blog"> 116 Blog 117 </a> 118 <a class="transition hover:text-(--ui-text-highlighted) " href="https://tempestphp.com/documentation"> 119 <span class="sm:hidden">Docs</span> 120 <span class="hidden sm:inline">Documentation</span> 121 </a> 122 <a href="https://github.com/tempestphp/tempest-framework" class="transition hover:text-(--ui-text-highlighted) flex items-center gap-x-1.5 ml-4"> 123 124 125 126<svg class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19c-4.3 1.4-4.3-2.5-6-3m12 5v-3.5c0-1 .1-1.4-.5-2c2.8-.3 5.5-1.4 5.5-6a4.6 4.6 0 0 0-1.3-3.2a4.2 4.2 0 0 0-.1-3.2s-1.1-.3-3.5 1.3a12.3 12.3 0 0 0-6.2 0C6.5 2.8 5.4 3.1 5.4 3.1a4.2 4.2 0 0 0-.1 3.2A4.6 4.6 0 0 0 4 9.5c0 4.6 2.7 5.7 5.5 6c-.6.6-.6 1.2-.5 2V21"/></svg> 127 <span class="hidden lg:inline font-mono">2.3K</span> 128 </a> 129 </div> 130 </div> 131 </header> 132</div> 133 134 135
136<script> 137const header = document.getElementById('header') 138 139window.addEventListener('scroll', () => { 140 if (window.scrollY > 0) { 141 header.dataset.scrolling = true 142 } else { 143 delete header.dataset.scrolling 144 } 145}) 146</script>
146 147 148 149 150 151<main class="relative flex flex-col grow -mt-(--ui-header-height)"> 152 <!-- Hero. Full-bleed so the glow reaches the screen edges; the content 153 inside stays on the same container as every section below it. --> 154 <!-- Capped so the hero doesn't strand its content in the middle of a very 155 tall viewport; on a typical laptop this is just 100svh. --> 156 <section class="relative flex flex-col justify-center min-h-[min(100svh,56rem)] pt-24"> 157 <div class="hero-glow z-[-1] absolute inset-0 overflow-hidden pointer-events-none"></div> 158 <div class="container mx-auto px-6 grid grid-cols-1 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.05fr)] items-center gap-10 lg:gap-16"> 159 <!-- Copy --> 160 <div class="flex flex-col items-start"> 161 <h1 class="flex flex-col text-3xl md:text-4xl lg:text-5xl leading-none tracking-tighter"> 162 <span>The framework that</span> 163 <span class="text-(--ui-primary)">gets out of your way</span> 164 </h1> 165 <p class="mt-5 lg:mt-7 max-w-xl text-lg md:text-xl lg:text-2xl text-(--ui-text-toned) leading-snug"> 166 Focus on code that matters and brings value for the modern web. That's what Tempest is about. 167 </p> 168 <div class="mt-6 lg:mt-8 max-w-full flex flex-col xl:flex-row gap-4 items-center sm:items-start xl:items-center"> 169 170 171 172<button class="group flex items-center gap-x-3 max-w-full px-4 py-3 font-mono text-sm sm:text-base text-left rounded-xl border border-(--ui-border) bg-(--ui-bg)/60 hover:bg-(--ui-bg) hover:border-(--ui-border-accented) transition cursor-pointer" data-copy="#install-command-7f6fc114"> 173 174 175 176<svg class="shrink-0 size-5 text-(--ui-primary)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m8 9l3 3l-3 3m5 0h3"/><path d="M3 6a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></g></svg> 177 <span class="min-w-0 overflow-x-auto whitespace-nowrap text-(--ui-text-muted) group-hover:text-(--ui-text) transition" id="install-command-7f6fc114"> 178 composer create-project tempest/app 179 </span> 180 <span class="relative shrink-0 flex items-center justify-center size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-text) transition"> 181 182 183 184<svg class="absolute inset-0 size-full group-data-copied:opacity-0 transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M7 9.667A2.667 2.667 0 0 1 9.667 7h8.666A2.667 2.667 0 0 1 21 9.667v8.666A2.667 2.667 0 0 1 18.333 21H9.667A2.667 2.667 0 0 1 7 18.333z"/><path d="M4.012 16.737A2 2 0 0 1 3 15V5c0-1.1.9-2 2-2h10c.75 0 1.158.385 1.5 1"/></g></svg> 185 186 187 188<svg class="absolute inset-0 size-full opacity-0 group-data-copied:opacity-100 transition text-(--ui-success)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M18.333 6A3.667 3.667 0 0 1 22 9.667v8.666A3.667 3.667 0 0 1 18.333 22H9.667A3.667 3.667 0 0 1 6 18.333V9.667A3.667 3.667 0 0 1 9.667 6zM15 2c1.094 0 1.828.533 2.374 1.514a1 1 0 1 1-1.748.972C15.405 4.088 15.284 4 15 4H5c-.548 0-1 .452-1 1v9.998c0 .32.154.618.407.805l.1.065a1 1 0 1 1-.99 1.738A3 3 0 0 1 2 15V5c0-1.652 1.348-3 3-3zm1.293 9.293L13 14.585l-1.293-1.292a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414"/></svg> 189 </span> 190</button> 191 192 193 <a class="bg-(--ui-bg-inverted) text-(--ui-bg) hover:bg-(--ui-bg-inverted)/90 rounded-xl px-5 py-3 transition flex gap-2 items-center" href="https://tempestphp.com/documentation"> 194 <span class="grow whitespace-nowrap">Get started</span> 195 </a> 196 </div> 197 <!-- Proof --> 198 <ul class="hidden lg:flex flex-wrap items-center gap-x-5 gap-y-2 mt-6 lg:mt-8 font-mono text-sm text-(--ui-text-dimmed)"> 199 <li class="flex items-center gap-x-1.5"> 200 201 202 203<svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
203<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m12 17.75l-6.172 3.245l1.179-6.873l-5-4.867l6.9-1l3.086-6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z"/></svg> 204 <span>2.3K stars</span> 205 </li> 206 <li class="flex items-center gap-x-1.5"> 207 208 209 210<svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M6.5 7.5a1 1 0 1 0 2 0a1 1 0 1 0-2 0"/><path d="M3 6v5.172a2 2 0 0 0 .586 1.414l7.71 7.71a2.41 2.41 0 0 0 3.408 0l5.592-5.592a2.41 2.41 0 0 0 0-3.408l-7.71-7.71A2 2 0 0 0 11.172 3H6a3 3 0 0 0-3 3"/></g></svg> 211 <span>v3.19.2</span> 212 </li> 213 <li class="flex items-center gap-x-1.5"> 214 215 216 217<svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M2 12a10 9 0 1 0 20 0a10 9 0 1 0-20 0"/><path d="m5.5 15l.395-1.974L6.5 10h1.32a1 1 0 0 1 .986 1.164l-.167 1a1 1 0 0 1-.986.836H6m9.5 2l.395-1.974L16.5 10h1.32a1 1 0 0 1 .986 1.164l-.167 1a1 1 0 0 1-.986.836H16m-4-5.5L11 13m.6-3H14l-.5 3"/></g></svg> 218 <span>PHP 8.5+</span> 219 </li> 220 </ul> 221 </div> 222 <!-- Code --> 223 <div class="home min-w-0"> 224 <div class="home-code-block"> 225 <div class="code-title"><span class="code-title-dir">src/Books/</span><span class="code-title-name">BookController.php</span></div><pre class="language-php"><span class="hl-keyword">final</span> <span class="hl-keyword">readonly</span> <span class="hl-keyword">class</span> <span class="hl-type">BookController</span> 226{ 227 <span class="hl-injection"><span class="hl-attribute">#[<span class="hl-type">Get</span>(<span class="hl-value">'/books/{book}'</span>)]</span></span> 228 <span class="hl-keyword">public</span> <span class="hl-keyword">function</span> <span class="hl-property">show</span>(<span class="hl-injection"><span class="hl-type">Book</span> $book</span>): <span class="hl-type">View</span> 229 { 230 <span class="hl-keyword">return</span> <span class="hl-property">view</span>(<span class="hl-value">'book-show.view.php'</span>, <span class="hl-property">book</span>: <span class="hl-variable">$book</span>); 231 } 232 233 <span class="hl-injection"><span class="hl-attribute">#[<span class="hl-type">Post</span>(<span class="hl-value">'/books'</span>)]</span></span> 234 <span class="hl-keyword">public</span> <span class="hl-keyword">function</span> <span class="hl-property">create</span>(<span class="hl-injection"><span class="hl-type">CreateBookRequest</span> $request</span>): <span class="hl-type">Response</span> 235 { 236 <span class="hl-variable">$book</span> = <span class="hl-property">map</span>(<span class="hl-variable">$request</span>)-><span class="hl-property">to</span>(<span class="hl-type">Book</span>::<span class="hl-keyword">class</span>)-><span class="hl-property">save</span>(); 237 238 <span class="hl-keyword">return</span> <span class="hl-keyword">new</span> <span class="hl-type">Redirect</span>( 239 <span class="hl-property">uri</span>([<span class="hl-type">self</span>::<span class="hl-keyword">class</span>, <span class="hl-value">'show'</span>], <span class="hl-property">book</span>: <span class="hl-variable">$book</span>), 240 ); 241 } 242}</pre> </div> 243 </div> 244 </div> 245 <div class="bottom-0 absolute inset-x-0 hidden lg:flex justify-center items-center p-8"> 246 <a id="scroll-indicator" href="#features" class="flex flex-col items-center gap-2 text-(--ui-text-dimmed) hover:text-(--ui-text) transition"> 247 <span class="animate-pulse">Learn more</span> 248 249 250 251<svg class="size-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
251<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v14m6-6l-6 6m-6-6l6 6"/></svg> 252 </a> 253 </div> 254 </section> 255 256 <div class="container mx-auto"> 257 <!-- Features --> 258 <div id="features" class="scroll-mt-(--ui-header-height)"> 259 <!-- Discovery --> 260 261 262 263<section class="px-6 py-10 lg:py-16 tracking-tighter"> 264 <div class="flex flex-col gap-8 lg:gap-12"> 265 <!-- Copy --> 266 <div class="max-w-2xl"> 267 <h2 class="text-2xl md:text-3xl lg:text-4xl leading-tight text-(--ui-text-highlighted)"> 268 Zero-configuration with code discovery </h2> 269 <p class="mt-4 lg:mt-5 text-lg lg:text-xl text-(--ui-text-muted) leading-snug"> 270 Tempest scans your code and instantly registers routes, view components, console commands, middleware and more. It doesn’t need hand-holding; it just works. </p> 271 <!-- 272 Centred on small screens: the labels are long enough to reach the edge 273 of a phone-width column, so left-aligning leaves the shorter ones 274 looking ragged and wraps the longer ones untidily. 275 --> 276 <div class="mt-6 lg:mt-8 flex justify-center lg:justify-start"> 277 <!-- 278 Tracking is reset here: the section sets tracking-tighter, which suits 279 a heading but squashes a full sentence of button copy. 280 --> 281 <a class="group no-primary inline-flex items-center justify-center gap-x-2 rounded-lg px-4 py-2.5 font-medium tracking-normal text-center lg:text-left ring ring-inset ring-(--ui-border) text-(--ui-text) bg-(--ui-bg) hover:bg-(--ui-bg-elevated) hover:ring-(--ui-border-accented) focus:outline-hidden focus-visible:no-underline focus-visible:ring-2 focus-visible:ring-(--ui-border-inverted) transition" href="https://tempestphp.com/3.x/essentials/discovery"> 282 <span>Learn more about discovery</span> 283 284 285 286<svg class="size-4.5 shrink-0 text-(--ui-text-dimmed) group-hover:text-(--ui-text) group-hover:translate-x-0.5 transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14m-6 6l6-6m-6-6l6 6"/></svg> 287 </a> 288 </div> 289 </div> 290 <!-- Code --> 291 <div class="home min-w-0 tracking-normal home-code-columns"> 292 <div class="home-code-block"> 293 <div class="code-title"><span class="code-title-dir">src/Books/</span><span class="code-title-name">BookController.php</span></div><pre class="language-php"><span class="hl-keyword">final</span> <span class="hl-keyword">readonly</span> <span class="hl-keyword">class</span> <span class="hl-type">BookController</span> 294{ 295 <span class="hl-injection"><span class="hl-attribute">#[<span class="hl-type">Get</span>(<span class="hl-value">'/books'</span>)]</span></span> 296 <span class="hl-keyword">public</span> <span class="hl-keyword">function</span> <span class="hl-property">index</span>(): <span class="hl-type">View</span> 297 { 298 <span class="hl-keyword">return</span> <span class="hl-property">view</span>(<span class="hl-value">'./index.view.php'</span>); 299 } 300}</pre> </div> 301<div class="home-code-block"> 302 <div class="code-title"><span class="code-title-dir">src/Books/</span><span class="code-title-name">x-book.view.php</span></div><pre class="language-html"><<span class="hl-keyword">article</span>> 303 <<span class="hl-keyword">h1</span>>{{ <span class="hl-variable">$book</span>-><span class="hl-property">title</span> }}</<span class="hl-keyword">h1</span>> 304 {!! <span class="hl-variable">$book</span>-><span class="hl-property">body</span> !!} 305</<span class="hl-keyword">article</span>></pre> </div> 306<div class="home-code-block"> 307 <div class="code-title"><span class="code-title-dir">src/Books/</span><span class="code-title-name">BookObserver.php</span></div><pre class="language-php"><span class="hl-keyword">final</span> <span class="hl-keyword">readonly</span> <span class="hl-keyword">class</span> <span class="hl-type">BookObserver</span> 308{ 309 <span class="hl-attribute">#[<span class="hl-type">EventHandler</span>]</span> 310 <span class="hl-keyword">public</span> <span class="hl-keyword">function</span> <span class="hl-property">onBookPublished</span>(<span class="hl-injection"><span class="hl-type">BookPublished</span> $event</span>): <span class="hl-type">void</span> 311 {
312 <span class="hl-comment">// â¦</span> 313 } 314}</pre> </div> 315 </div> 316 </div> 317</section> 318 319 320 321 <!-- Template engine --> 322 323 324 325<section class="px-6 py-10 lg:py-16 tracking-tighter"> 326 <div class="grid grid-cols-1 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.25fr)] items-start gap-10 lg:gap-16"> 327 <!-- Copy --> 328 <div class="max-w-xl lg:sticky lg:top-[calc(var(--ui-header-height)+2rem)]"> 329 <h2 class="text-2xl md:text-3xl lg:text-4xl leading-tight text-(--ui-text-highlighted)"> 330 A refreshing new template engine </h2> 331 <p class="mt-4 lg:mt-5 text-lg lg:text-xl text-(--ui-text-muted) leading-snug"> 332 Tempest reimagines templating in PHP with a clean front-end engine, inspired by modern front-end frameworks. </p> 333<p class="mt-4 lg:mt-5 text-lg lg:text-xl text-(--ui-text-muted) leading-snug"> 334 Whether you love our modern syntax or prefer the battle-tested reliability of Blade and Twig, Tempest has you covered. </p> 335 <!-- 336 Centred on small screens: the labels are long enough to reach the edge 337 of a phone-width column, so left-aligning leaves the shorter ones 338 looking ragged and wraps the longer ones untidily. 339 --> 340 <div class="mt-6 lg:mt-8 flex justify-center lg:justify-start"> 341 <!-- 342 Tracking is reset here: the section sets tracking-tighter, which suits 343 a heading but squashes a full sentence of button copy. 344 --> 345 <a class="group no-primary inline-flex items-center justify-center gap-x-2 rounded-lg px-4 py-2.5 font-medium tracking-normal text-center lg:text-left ring ring-inset ring-(--ui-border) text-(--ui-text) bg-(--ui-bg) hover:bg-(--ui-bg-elevated) hover:ring-(--ui-border-accented) focus:outline-hidden focus-visible:no-underline focus-visible:ring-2 focus-visible:ring-(--ui-border-inverted) transition" href="https://tempestphp.com/3.x/essentials/views"> 346 <span>Read about tempest/view</span> 347 348 349 350<svg class="size-4.5 shrink-0 text-(--ui-text-dimmed) group-hover:text-(--ui-text) group-hover:translate-x-0.5 transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14m-6 6l6-6m-6-6l6 6"/></svg> 351 </a> 352 </div> 353 </div> 354 <!-- Code --> 355 <div class="home min-w-0 tracking-normal flex flex-col gap-4"> 356 <div class="home-code-block"> 357 <div class="code-title"><span class="code-title-dir">src/Books/</span><span class="code-title-name">index.view.php</span></div><pre class="language-html"><<span class="hl-keyword">x-base</span> <span class="hl-property">:title</span>="<span class="hl-variable">$this</span>-><span class="hl-property">seo</span>-><span class="hl-property">title</span>"> 358 <<span class="hl-keyword">ul</span>> 359 <<span class="hl-keyword">li</span> <span class="hl-property">:foreach</span>="<span class="hl-variable">$this</span>-><span class="hl-property">books</span> <span class="hl-keyword">as</span> <span class="hl-variable">$book</span>"> 360 <<span class="hl-keyword">span</span>>{{ <span class="hl-variable">$book</span>-><span class="hl-property">title</span> }}</<span class="hl-keyword">span</span>> 361 362 <<span class="hl-keyword">span</span> <span class="hl-property">:if</span>="<span class="hl-variable">$this</span>-><span class="hl-property">showDate</span>(<span class="hl-variable">$book</span>)"> 363 <<span class="hl-keyword">x-badge</span> <span class="hl-property">variant</span>="outline"> 364 {{ <span class="hl-variable">$book</span>-><span class="hl-property">publishedAt</span> }} 365 </<span class="hl-keyword">x-badge</span>> 366 </<span class="hl-keyword">span</span>> 367 </<span class="hl-keyword">li</span>> 368 </<span class="hl-keyword">ul</span>> 369</<span class="hl-keyword">x-base</span>></pre> </div> 370 </div> 371 </div> 372</section> 373 374 375 376 <!-- ORM --> 377 378 379 380<section class="px-6 py-10 lg:py-16 tracking-tighter"> 381 <div class="grid grid-cols-1 lg:grid-cols-[minmax(0,1.25fr)_minmax(0,1fr)] items-start gap-10 lg:gap-16"> 382 <!-- Copy --> 383 <div class="max-w-xl lg:order-2 lg:sticky lg:top-[calc(var(--ui-header-height)+2rem)]"> 384 <h2 class="text-2xl md:text-3xl lg:text-4xl leading-tight text-(--ui-text-highlighted)"> 385 A truly decoupled ORM </h2> 386 <p class="mt-4 lg:mt-5 text-lg lg:text-xl text-(--ui-text-muted) leading-snug"> 387 Models in Tempest embrace modern PHP and are designed to be decoupled from the database; they don’t even have to persist to the database and can be mapped to any kind of data source. </p> 388 <!-- 389 Centred on small screens: the labels are long enough to reach the edge 390 of a phone-width column, so left-aligning leaves the shorter ones 391 looking ragged and wraps the longer ones untidily. 392 --> 393 <div class="mt-6 lg:mt-8 flex justify-center lg:justify-start"> 394 <!-- 395 Tracking is reset here: the section sets tracking-tighter, which suits 396 a heading but squashes a full sentence of button copy. 397 --> 398 <a class="group no-primary inline-flex items-center justify-center gap-x-2 rounded-lg px-4 py-2.5 font-medium tracking-normal text-center lg:text-left ring ring-inset ring-(--ui-border) text-(--ui-text) bg-(--ui-bg) hover:bg-(--ui-bg-elevated) hover:ring-(--ui-border-accented) focus:outline-hidden focu
398s-visible:no-underline focus-visible:ring-2 focus-visible:ring-(--ui-border-inverted) transition" href="https://tempestphp.com/3.x/essentials/database"> 399 <span>Learn more about the ORM</span> 400 401 402 403<svg class="size-4.5 shrink-0 text-(--ui-text-dimmed) group-hover:text-(--ui-text) group-hover:translate-x-0.5 transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14m-6 6l6-6m-6-6l6 6"/></svg> 404 </a> 405 </div> 406 </div> 407 <!-- Code --> 408 <div class="home min-w-0 tracking-normal flex flex-col gap-4 lg:order-1"> 409 <div class="home-code-block"> 410 <div class="code-title"><span class="code-title-dir">src/Books/</span><span class="code-title-name">Book.php</span></div><pre class="language-php"><span class="hl-keyword">final</span> <span class="hl-keyword">class</span> <span class="hl-type">Book</span> 411{ 412 <span class="hl-injection"><span class="hl-attribute">#[<span class="hl-type">Length</span>(<span class="hl-property">min</span>: 1, <span class="hl-property">max</span>: 120)]</span></span> 413 <span class="hl-keyword">public</span> <span class="hl-type">string</span> <span class="hl-property">$title</span>; 414 415 <span class="hl-keyword">public</span> <span class="hl-type">?Author</span> <span class="hl-property">$author</span> = <span class="hl-keyword">null</span>; 416 417 <span class="hl-comment">/** <span class="hl-value">@var</span> <span class="hl-type">\App\Books\Chapter[] </span>*/</span> 418 <span class="hl-keyword">public</span> <span class="hl-type">array</span> <span class="hl-property">$chapters</span> = []; 419}</pre> </div> 420<div class="home-code-block"> 421 <pre class="language-php"><span class="hl-variable">$book</span> = <span class="hl-property">query</span>(<span class="hl-type">Book</span>::<span class="hl-keyword">class</span>) 422 -><span class="hl-property">select</span>() 423 -><span class="hl-property">where</span>(<span class="hl-value">'title'</span>, <span class="hl-value">'Timeline Taxi'</span>) 424 -><span class="hl-property">first</span>(); 425 426<span class="hl-comment">// â¦</span> 427 428<span class="hl-variable">$json</span> = <span class="hl-property">map</span>(<span class="hl-variable">$book</span>)-><span class="hl-property">toJson</span>();</pre> </div> 429 </div> 430 </div> 431</section> 432 433 434 435 <!-- Console --> 436 437 438 439<section class="px-6 py-10 lg:py-16 tracking-tighter"> 440 <div class="grid grid-cols-1 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.25fr)] items-start gap-10 lg:gap-16"> 441 <!-- Copy --> 442 <div class="max-w-xl lg:sticky lg:top-[calc(var(--ui-header-height)+2rem)]"> 443 <h2 class="text-2xl md:text-3xl lg:text-4xl leading-tight text-(--ui-text-highlighted)"> 444 Console applications reimagined </h2> 445 <p class="mt-4 lg:mt-5 text-lg lg:text-xl text-(--ui-text-muted) leading-snug"> 446 Console commands are automatically discovered and use PHP’s type system to define arguments and flags. </p> 447<p class="mt-4 lg:mt-5 text-lg lg:text-xl text-(--ui-text-muted) leading-snug"> 448 No need to search the documentation to remember the syntax, just write PHP. </p> 449 <!-- 450 Centred on small screens: the labels are long enough to reach the edge 451 of a phone-width column, so left-aligning leaves the shorter ones 452 looking ragged and wraps the longer ones untidily. 453 --> 454 <div class="mt-6 lg:mt-8 flex justify-center lg:justify-start"> 455 <!-- 456 Tracking is reset here: the section sets tracking-tighter, which suits 457 a heading but squashes a full sentence of button copy. 458 --> 459 <a class="group no-primary inline-flex items-center justify-center gap-x-2 rounded-lg px-4 py-2.5 font-medium tracking-normal text-center lg:text-left ring ring-inset ring-(--ui-border) text-(--ui-text) bg-(--ui-bg) hover:bg-(--ui-bg-elevated) hover:ring-(--ui-border-accented) focus:outline-hidden focus-visible:no-underline focus-visible:ring-2 focus-visible:ring-(--ui-border-inverted) transition" href="https://tempestphp.com/3.x/essentials/console-commands">
460 <span>Learn about console commands</span> 461 462 463 464<svg class="size-4.5 shrink-0 text-(--ui-text-dimmed) group-hover:text-(--ui-text) group-hover:translate-x-0.5 transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14m-6 6l6-6m-6-6l6 6"/></svg> 465 </a> 466 </div> 467 </div> 468 <!-- Code --> 469 <div class="home min-w-0 tracking-normal flex flex-col gap-4"> 470 <div class="home-code-block"> 471 <div class="code-title"><span class="code-title-dir">src/Books/</span><span class="code-title-name">FetchBookCommand.php</span></div><pre class="language-php"><span class="hl-keyword">final</span> <span class="hl-keyword">readonly</span> <span class="hl-keyword">class</span> <span class="hl-type">FetchBookCommand</span> 472{ 473 <span class="hl-keyword">public</span> <span class="hl-keyword">function</span> <span class="hl-property">__construct</span>(<span class="hl-injection"> 474 <span class="hl-keyword">private</span> <span class="hl-type">BookRepository</span> <span class="hl-property">$repository</span>, 475 <span class="hl-keyword">private</span> <span class="hl-type">Isbn</span> <span class="hl-property">$isbn</span>, 476 <span class="hl-keyword">private</span> <span class="hl-type">Console</span> <span class="hl-property">$console</span>, 477 </span>) {} 478 479 <span class="hl-injection"><span class="hl-attribute">#[<span class="hl-type">ConsoleCommand</span>(<span class="hl-property">description</span>: <span class="hl-value">'Sync a book by title'</span>)]</span></span> 480 <span class="hl-keyword">public</span> <span class="hl-keyword">function</span> <span class="hl-property">__invoke</span>(<span class="hl-injection"><span class="hl-type">string</span> $title, <span class="hl-type">bool</span> $force = <span class="hl-keyword">false</span></span>): <span class="hl-type">void</span> 481 { 482 <span class="hl-variable">$data</span> = <span class="hl-variable">$this</span>-><span class="hl-property">isbn</span>-><span class="hl-property">findByTitle</span>(<span class="hl-variable">$title</span>); 483 484 <span class="hl-keyword">if</span> (! <span class="hl-variable">$data</span>) { 485 <span class="hl-variable">$this</span>-><span class="hl-property">console</span>-><span class="hl-property">error</span>(<span class="hl-value">"No book found matching that title."</span>); 486 <span class="hl-keyword">return</span>; 487 } 488 489 <span class="hl-variable">$book</span> = <span class="hl-property">map</span>(<span class="hl-variable">$data</span>)-><span class="hl-property">to</span>(<span class="hl-type">Book</span>::<span class="hl-keyword">class</span>); 490 491 <span class="hl-keyword">if</span> (<span class="hl-variable">$this</span>-><span class="hl-property">repository</span>-><span class="hl-property">exists</span>(<span class="hl-variable">$book</span>-><span class="hl-property">isbn13</span>) <span class="hl-operator">&&</span> ! <span class="hl-variable">$force</span>) { 492 <span class="hl-variable">$this</span>-><span class="hl-property">console</span>-><span class="hl-property">info</span>(<span class="hl-value">"Book already exists."</span>); 493 <span class="hl-keyword">return</span>; 494 } 495 496 <span class="hl-variable">$this</span>-><span class="hl-property">repository</span>-><span class="hl-property">save</span>(<span class="hl-variable">$book</span>); 497 <span class="hl-variable">$this</span>-><span class="hl-property">console</span>-><span class="hl-property">success</span>(<span class="hl-value">"Synchronized {$book->title}."</span>); 498 } 499}</pre> </div> 500 </div> 501 </div> 502</section> 503 504 505 </div> 506 507 <!-- Everything else --> 508 <section class="px-6 md:py-12 lg:py-20 tracking-tighter"> 509 <div class="max-w-2xl"> 510 <h2 class="text-2xl md:text-3xl lg:text-4xl leading-tight text-(--ui-text-highlighted)"> 511 And much, much more. 512 </h2> 513 <p class="mt-4 lg:mt-5 text-lg lg:text-xl text-(--ui-text-muted) leading-snug"> 514 Everything an application needs is already in the box â no plugin hunting, no wiring, no ceremony. 515 </p> 516 </div> 517 <ul class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mt-10 tracking-normal"> 518 <li class="group relative p-0.5 rounded-lg border border-(--ui-border) bg-(--ui-bg-elevated)/30 hover:bg-(--ui-bg-elevated)/75 transition"> 519 <div class="h-full flex flex-col gap-y-2 p-4 rounded-md border border-dashed border-(--ui-border)"> 520 <a class="absolute inset-0 rounded-lg" href="https://tempestphp.com/3.x/essentials/container">
521 <span class="sr-only">Container</span> 522 </a> 523 <div class="flex items-center gap-x-2"> 524 525 526 527<svg class="size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-primary) transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m12 3l8 4.5v9L12 21l-8-4.5v-9zm0 9l8-4.5M12 12v9m0-9L4 7.5"/></svg> 528 <span class="font-medium text-(--ui-text-highlighted)">Container</span> 529 </div> 530 <p class="text-(--ui-text-muted) leading-snug">Autowiring that resolves your dependencies without a single binding.</p> 531 </div> 532</li> 533 534 535<li class="group relative p-0.5 rounded-lg border border-(--ui-border) bg-(--ui-bg-elevated)/30 hover:bg-(--ui-bg-elevated)/75 transition"> 536 <div class="h-full flex flex-col gap-y-2 p-4 rounded-md border border-dashed border-(--ui-border)"> 537 <a class="absolute inset-0 rounded-lg" href="https://tempestphp.com/3.x/features/validation"> 538 <span class="sr-only">Validation</span> 539 </a> 540 <div class="flex items-center gap-x-2"> 541 542 543 544<svg class="size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-primary) transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.46 20.846A12 12 0 0 1 3.5 6A12 12 0 0 0 12 3a12 12 0 0 0 8.5 3a12 12 0 0 1-.09 7.06M15 19l2 2l4-4"/></svg> 545 <span class="font-medium text-(--ui-text-highlighted)">Validation</span> 546 </div> 547 <p class="text-(--ui-text-muted) leading-snug">Attribute-based rules that validate and map in one step.</p> 548 </div> 549</li> 550 551 552<li class="group relative p-0.5 rounded-lg border border-(--ui-border) bg-(--ui-bg-elevated)/30 hover:bg-(--ui-bg-elevated)/75 transition"> 553 <div class="h-full flex flex-col gap-y-2 p-4 rounded-md border border-dashed border-(--ui-border)"> 554 <a class="absolute inset-0 rounded-lg" href="https://tempestphp.com/3.x/features/mapper"> 555 <span class="sr-only">Data mapping</span> 556 </a> 557 <div class="flex items-center gap-x-2"> 558 559 560 561<svg class="size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-primary) transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 6a3 3 0 1 0 6 0a3 3 0 0 0-6 0m18 5V8a2 2 0 0 0-2-2h-6l3 3m0-6l-3 3M3 13v3a2 2 0 0 0 2 2h6l-3-3m0 6l3-3m4 0a3 3 0 1 0 6 0a3 3 0 0 0-6 0"/></svg> 562 <span class="font-medium text-(--ui-text-highlighted)">Data mapping</span> 563 </div> 564 <p class="text-(--ui-text-muted) leading-snug">Turn arrays, JSON or requests into objects, and back again.</p> 565 </div> 566</li> 567 568 569<li class="group relative p-0.5 rounded-lg border border-(--ui-border) bg-(--ui-bg-elevated)/30 hover:bg-(--ui-bg-elevated)/75 transition"> 570 <div class="h-full flex flex-col gap-y-2 p-4 rounded-md border border-dashed border-(--ui-border)"> 571 <a class="absolute inset-0 rounded-lg" href="https://tempestphp.com/3.x/features/events"> 572 <span class="sr-only">Events</span> 573 </a> 574 <div class="flex items-center gap-x-2"> 575 576 577 578<svg class="size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-primary) transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M18.364 19.364a9 9 0 1 0-12.728 0"/><path d="M15.536 16.536a5 5 0 1 0-7.072 0"/><path d="M11 13a1 1 0 1 0 2 0a1 1 0 1 0-2 0"/></g></svg> 579 <span class="font-medium text-(--ui-text-highlighted)">Events</span> 580 </div> 581 <p class="text-(--ui-text-muted) leading-snug">Discovered event handlers, no listener registration needed.</p> 582 </div> 583</li> 584 585 586<li class="group relative p-0.5 rounded-lg border border-(--ui-border) bg-(--ui-bg-elevated)/30 hover:bg-(--ui-bg-elevated)/75 transition"> 587 <div class="h-full flex flex-col gap-y-2 p-4 rounded-md border border-dashed border-(--ui-border)"> 588 <a class="absolute inset-0 rounded-lg" href="https://tempestphp.com/3.x/features/command-bus">
589 <span class="sr-only">Command bus</span> 590 </a> 591 <div class="flex items-center gap-x-2"> 592 593 594 595<svg class="size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-primary) transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M3 19a2 2 0 1 0 4 0a2 2 0 1 0-4 0m4 0h3a2 2 0 0 0 2-2V9a2 2 0 0 1 2-2h7"/><path d="m18 4l3 3l-3 3"/></g></svg> 596 <span class="font-medium text-(--ui-text-highlighted)">Command bus</span> 597 </div> 598 <p class="text-(--ui-text-muted) leading-snug">Dispatch commands synchronously or to the background.</p> 599 </div> 600</li> 601 602 603<li class="group relative p-0.5 rounded-lg border border-(--ui-border) bg-(--ui-bg-elevated)/30 hover:bg-(--ui-bg-elevated)/75 transition"> 604 <div class="h-full flex flex-col gap-y-2 p-4 rounded-md border border-dashed border-(--ui-border)"> 605 <a class="absolute inset-0 rounded-lg" href="https://tempestphp.com/3.x/features/mail"> 606 <span class="sr-only">Mail</span> 607 </a> 608 <div class="flex items-center gap-x-2"> 609 610 611 612<svg class="size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-primary) transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M3 7a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="m3 7l9 6l9-6"/></g></svg> 613 <span class="font-medium text-(--ui-text-highlighted)">Mail</span> 614 </div> 615 <p class="text-(--ui-text-muted) leading-snug">Send mailables built from the same view components you already use.</p> 616 </div> 617</li> 618 619 620<li class="group relative p-0.5 rounded-lg border border-(--ui-border) bg-(--ui-bg-elevated)/30 hover:bg-(--ui-bg-elevated)/75 transition"> 621 <div class="h-full flex flex-col gap-y-2 p-4 rounded-md border border-dashed border-(--ui-border)"> 622 <a class="absolute inset-0 rounded-lg" href="https://tempestphp.com/3.x/features/authentication"> 623 <span class="sr-only">Authentication</span> 624 </a> 625 <div class="flex items-center gap-x-2"> 626 627 628 629<svg class="size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-primary) transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M5 13a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2z"/><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0-2 0m-3-5V7a4 4 0 1 1 8 0v4"/></g></svg> 630 <span class="font-medium text-(--ui-text-highlighted)">Authentication</span> 631 </div> 632 <p class="text-(--ui-text-muted) leading-snug">Sessions, permissions and OAuth providers out of the box.</p> 633 </div> 634</li> 635 636 637<li class="group relative p-0.5 rounded-lg border border-(--ui-border) bg-(--ui-bg-elevated)/30 hover:bg-(--ui-bg-elevated)/75 transition"> 638 <div class="h-full flex flex-col gap-y-2 p-4 rounded-md border border-dashed border-(--ui-border)"> 639 <a class="absolute inset-0 rounded-lg" href="https://tempestphp.com/3.x/features/static-pages"> 640 <span class="sr-only">Static pages</span> 641 </a> 642 <div class="flex items-center gap-x-2"> 643 644 645 646<svg class="size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-primary) transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M20.942 13.02a9 9 0 1 0-9.47 7.964M3.6 9h16.8M3.6 15h9.9"/><path d="M11.5 3a17 17 0 0 0 0 18m1-18c2 3.206 2.837 6.913 2.508 10.537M20 21l2-2l-2-2m-3 0l-2 2l2 2"/></g></svg> 647 <span class="font-medium text-(--ui-text-highlighted)">Static pages</span> 648 </div> 649 <p class="text-(--ui-text-muted) leading-snug">Generate a fully static site from the routes you already wrote.</p> 650 </div> 651</li> 652 653 654<li class="group relative p-0.5 rounded-lg border border-(--ui-border) bg-(--ui-bg-elevated)/30 hover:bg-(--ui-bg-elevated)/75 transition"> 655 <div class="h-full flex flex-col gap-y-2 p-4 rounded-md border border-dashed border-(--ui-border)"> 656 <a class="absolute inset-0 rounded-lg" href="https://tempestphp.com/3.x/essentials/testing">
657 <span class="sr-only">Testing</span> 658 </a> 659 <div class="flex items-center gap-x-2"> 660 661 662 663<svg class="size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-primary) transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3h6m-5 6h4m-4-6v6L6 20a.7.7 0 0 0 .5 1h11a.7.7 0 0 0 .5-1L14 9V3"/></svg> 664 <span class="font-medium text-(--ui-text-highlighted)">Testing</span> 665 </div> 666 <p class="text-(--ui-text-muted) leading-snug">First-class helpers for HTTP, console, mail and the container.</p> 667 </div> 668</li> 669 670 671 </ul> 672 </section> 673 674 <!-- Closing call to action --> 675 <section class="px-6 pt-4 pb-20 lg:pt-6 lg:pb-28 tracking-tighter"> 676 <div class="flex flex-col items-center gap-6 px-6 py-14 lg:py-20 text-center rounded-2xl border border-(--ui-border) bg-(--ui-bg)/40"> 677 <h2 class="max-w-xl text-2xl md:text-3xl lg:text-4xl leading-tight text-(--ui-text-highlighted)"> 678 Start building in one command. 679 </h2> 680 <p class="max-w-lg text-lg text-(--ui-text-muted) leading-snug tracking-normal"> 681 No scaffolding to wade through, no configuration to write. Just a project and your business logic. 682 </p> 683 <div class="max-w-full mt-2"> 684 685 686 687<button class="group flex items-center gap-x-3 max-w-full px-4 py-3 font-mono text-sm sm:text-base text-left rounded-xl border border-(--ui-border) bg-(--ui-bg)/60 hover:bg-(--ui-bg) hover:border-(--ui-border-accented) transition cursor-pointer" data-copy="#install-command-2f563392"> 688 689 690 691<svg class="shrink-0 size-5 text-(--ui-primary)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m8 9l3 3l-3 3m5 0h3"/><path d="M3 6a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></g></svg> 692 <span class="min-w-0 overflow-x-auto whitespace-nowrap text-(--ui-text-muted) group-hover:text-(--ui-text) transition" id="install-command-2f563392"> 693 composer create-project tempest/app 694 </span> 695 <span class="relative shrink-0 flex items-center justify-center size-5 text-(--ui-text-dimmed) group-hover:text-(--ui-text) transition"> 696 697 698 699<svg class="absolute inset-0 size-full group-data-copied:opacity-0 transition" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M7 9.667A2.667 2.667 0 0 1 9.667 7h8.666A2.667 2.667 0 0 1 21 9.667v8.666A2.667 2.667 0 0 1 18.333 21H9.667A2.667 2.667 0 0 1 7 18.333z"/><path d="M4.012 16.737A2 2 0 0 1 3 15V5c0-1.1.9-2 2-2h10c.75 0 1.158.385 1.5 1"/></g></svg> 700 701 702 703<svg class="absolute inset-0 size-full opacity-0 group-data-copied:opacity-100 transition text-(--ui-success)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M18.333 6A3.667 3.667 0 0 1 22 9.667v8.666A3.667 3.667 0 0 1 18.333 22H9.667A3.667 3.667 0 0 1 6 18.333V9.667A3.667 3.667 0 0 1 9.667 6zM15 2c1.094 0 1.828.533 2.374 1.514a1 1 0 1 1-1.748.972C15.405 4.088 15.284 4 15 4H5c-.548 0-1 .452-1 1v9.998c0 .32.154.618.407.805l.1.065a1 1 0 1 1-.99 1.738A3 3 0 0 1 2 15V5c0-1.652 1.348-3 3-3zm1.293 9.293L13 14.585l-1.293-1.292a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414"/></svg> 704 </span> 705</button> 706 707 708 </div> 709 <div class="flex flex-wrap justify-center items-center gap-2 mt-2 font-medium"> 710 <a class="bg-(--ui-bg-inverted) text-(--ui-bg) hover:bg-(--ui-bg-inverted)/90 rounded-xl px-5 py-2.5 transition" href="https://tempestphp.com/documentation"> 711 Read the documentation 712 </a> 713 <a class="flex items-center gap-x-2 rounded-xl px-4 py-2.5 text-(--ui-text) ring ring-inset ring-(--ui-border) hover:bg-(--ui-bg-elevated) transition" href="https://tempestphp.com/discord"> 714 715 716 717<svg class="size-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M8 12a1 1 0 1 0 2 0a1 1 0 0 0-2 0m6 0a1 1 0 1 0 2 0a1 1 0 0 0-2 0"/><path d="M15.5 17c0 1 1.5 3 2 3c1.5 0 2.833-1.667 3.5-3c.667-1.667.5-5.833-1.5-11.5c-1.457-1.015-3-1.34-4.5-1.5l-.972 1.923a11.9 11.9 0 0 0-4.053 0L9 4c-1.5.16-3.043.485-4.5 1.5c-2 5.667-2.167 9.833-1.5 11.5c.667 1.333 2 3 3.5 3c.5 0 2-2 2-3"/><path d="M7 16.5c3.5 1 6.5 1 10 0"/></g></svg>
718 <span>Join the Discord</span> 719 </a> 720 </div> 721 </div> 722 </section> 723 </div> 724 <!-- 725 Sits outside the container, like the hero's glow, so it reaches the screen 726 edges instead of being clipped to the content width. Pulled down past the 727 end of <main> by the footer's height plus its margin, otherwise the glow 728 stops on a hard line exactly where the footer starts. 729 --> 730 <div class="page-glow z-[-1] absolute inset-x-0 -bottom-14 h-[20rem] sm:h-[36rem] overflow-hidden pointer-events-none"></div> 731 </main> 732 733 734
735<script> 736 document.addEventListener('scroll', function () { 737 const indicator = document.getElementById('scroll-indicator') 738 if (!indicator) { 739 return 740 } 741 742 const scrolled = window.scrollY > 100 743 744 indicator.style.opacity = scrolled ? '0' : '1' 745 indicator.style.pointerEvents = scrolled ? 'none' : 'auto' 746 }) 747 </script>
747 748 749 750 751 752 753 754<footer class="flex justify-center items-center gap-4 mb-8"> 755 <a class="flex items-center gap-1 text-lg font-medium transition hover:text-(--ui-primary)" href="https://tempestphp.com/github"> 756 757 758 759<svg class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19c-4.3 1.4-4.3-2.5-6-3m12 5v-3.5c0-1 .1-1.4-.5-2c2.8-.3 5.5-1.4 5.5-6a4.6 4.6 0 0 0-1.3-3.2a4.2 4.2 0 0 0-.1-3.2s-1.1-.3-3.5 1.3a12.3 12.3 0 0 0-6.2 0C6.5 2.8 5.4 3.1 5.4 3.1a4.2 4.2 0 0 0-.1 3.2A4.6 4.6 0 0 0 4 9.5c0 4.6 2.7 5.7 5.5 6c-.6.6-.6 1.2-.5 2V21"/></svg> 760 </a> 761 <a class="flex items-center gap-1 text-lg font-medium transition hover:text-(--ui-primary)" href="https://tempestphp.com/bluesky/brent"> 762 763 764 765<svg class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6.335 5.144C4.681 3.945 2 3.017 2 5.97c0 .59.35 4.953.556 5.661C3.269 14.094 5.686 14.381 8 14c-4.045.665-4.889 3.208-2.667 5.41C6.363 20.428 7.246 21 8 21c2 0 3.134-2.769 3.5-3.5q.5-1 .5-1.5q0 .5.5 1.5c.366.731 1.5 3.5 3.5 3.5c.754 0 1.637-.571 2.667-1.59C20.889 17.207 20.045 14.664 16 14c2.314.38 4.73.094 5.444-2.369c.206-.708.556-5.072.556-5.661c0-2.953-2.68-2.025-4.335-.826C15.372 6.806 12.905 10.192 12 12c-.905-1.808-3.372-5.194-5.665-6.856"/></svg> 766 </a> 767 <a class="flex items-center gap-1 text-lg font-medium transition hover:text-(--ui-primary)" href="https://tempestphp.com/twitter/brent"> 768 769 770 771<svg class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m4 4l11.733 16H20L8.267 4zm0 16l6.768-6.768m2.46-2.46L20 4"/></svg> 772 </a> 773 <a href="/rss" class="flex items-center gap-1 text-lg font-medium transition hover:text-(--ui-primary)"> 774 775 776 777<svg class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 19a1 1 0 1 0 2 0a1 1 0 1 0-2 0M4 4a16 16 0 0 1 16 16M4 11a9 9 0 0 1 9 9"/></svg> 778 </a> 779 <button id="toggle-theme" class="relative size-6 cursor-pointer overflow-hidden transition hover:text-(--ui-primary)"> 780 781 782 783<svg class="absolute inset-0 dark:opacity-0 size-full dark:translate-y-full duration-200" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3h.393a7.5 7.5 0 0 0 7.92 12.446A9 9 0 1 1 12 2.992z"/></svg> 784 785 786 787<svg class="absolute inset-0 opacity-0 dark:opacity-100 size-full -translate-y-full dark:translate-y-0 duration-200" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12a4 4 0 1 0 8 0a4 4 0 1 0-8 0m-5 0h1m8-9v1m8 8h1m-9 8v1M5.6 5.6l.7.7m12.1-.7l-.7.7m0 11.4l.7.7m-12.1-.7l-.7.7"/></svg> 788 </button> 789</footer> 790 791 792
793<script> 794 document.getElementById('toggle-theme').addEventListener('click', () => { 795 toggleDarkMode() 796 }) 797</script>
797 798 799 800 801</body> 802</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.