PageSourceSearch

https://tempestphp.com/

html tempestphp.com collected 2026-10-03 19:28:03 UTC 52,907 bytes, 802 lines download raw bytes

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>)-&gt;<span class="hl-property">to</span>(<span class="hl-type">Book</span>::<span class="hl-keyword">class</span>)-&gt;<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&rsquo;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">&lt;<span class="hl-keyword">article</span>&gt;
303  &lt;<span class="hl-keyword">h1</span>&gt;{{ <span class="hl-variable">$book</span>-&gt;<span class="hl-property">title</span> }}&lt;/<span class="hl-keyword">h1</span>&gt;
304  {!! <span class="hl-variable">$book</span>-&gt;<span class="hl-property">body</span> !!}
305&lt;/<span class="hl-keyword">article</span>&gt;</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">&lt;<span class="hl-keyword">x-base</span> <span class="hl-property">:title</span>=&quot;<span class="hl-variable">$this</span>-&gt;<span class="hl-property">seo</span>-&gt;<span class="hl-property">title</span>&quot;&gt;
358  &lt;<span class="hl-keyword">ul</span>&gt;
359    &lt;<span class="hl-keyword">li</span> <span class="hl-property">:foreach</span>=&quot;<span class="hl-variable">$this</span>-&gt;<span class="hl-property">books</span> <span class="hl-keyword">as</span> <span class="hl-variable">$book</span>&quot;&gt;
360      &lt;<span class="hl-keyword">span</span>&gt;{{ <span class="hl-variable">$book</span>-&gt;<span class="hl-property">title</span> }}&lt;/<span class="hl-keyword">span</span>&gt;
361
362      &lt;<span class="hl-keyword">span</span> <span class="hl-property">:if</span>=&quot;<span class="hl-variable">$this</span>-&gt;<span class="hl-property">showDate</span>(<span class="hl-variable">$book</span>)&quot;&gt;
363        &lt;<span class="hl-keyword">x-badge</span> <span class="hl-property">variant</span>=&quot;outline&quot;&gt;
364          {{ <span class="hl-variable">$book</span>-&gt;<span class="hl-property">publishedAt</span> }}
365        &lt;/<span class="hl-keyword">x-badge</span>&gt;
366      &lt;/<span class="hl-keyword">span</span>&gt;
367    &lt;/<span class="hl-keyword">li</span>&gt;
368  &lt;/<span class="hl-keyword">ul</span>&gt;
369&lt;/<span class="hl-keyword">x-base</span>&gt;</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&rsquo;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    -&gt;<span class="hl-property">select</span>()
423    -&gt;<span class="hl-property">where</span>(<span class="hl-value">'title'</span>, <span class="hl-value">'Timeline Taxi'</span>)
424    -&gt;<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>)-&gt;<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&rsquo;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>-&gt;<span class="hl-property">isbn</span>-&gt;<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>-&gt;<span class="hl-property">console</span>-&gt;<span class="hl-property">error</span>(<span class="hl-value">&quot;No book found matching that title.&quot;</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>)-&gt;<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>-&gt;<span class="hl-property">repository</span>-&gt;<span class="hl-property">exists</span>(<span class="hl-variable">$book</span>-&gt;<span class="hl-property">isbn13</span>) <span class="hl-operator">&amp;&amp;</span> ! <span class="hl-variable">$force</span>) {
492            <span class="hl-variable">$this</span>-&gt;<span class="hl-property">console</span>-&gt;<span class="hl-property">info</span>(<span class="hl-value">&quot;Book already exists.&quot;</span>);
493            <span class="hl-keyword">return</span>;
494        }
495
496        <span class="hl-variable">$this</span>-&gt;<span class="hl-property">repository</span>-&gt;<span class="hl-property">save</span>(<span class="hl-variable">$book</span>);
497        <span class="hl-variable">$this</span>-&gt;<span class="hl-property">console</span>-&gt;<span class="hl-property">success</span>(<span class="hl-value">&quot;Synchronized {$book-&gt;title}.&quot;</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.