diff --git a/src/Web/Blog/BlogPostTag.php b/src/Web/Blog/BlogPostTag.php index 05952c25..34e1c9d4 100644 --- a/src/Web/Blog/BlogPostTag.php +++ b/src/Web/Blog/BlogPostTag.php @@ -13,9 +13,9 @@ enum BlogPostTag: string public function getStyle(): string { return match ($this) { - self::THOUGHTS => 'ring-amber-200 text-amber-400', - self::RELEASE => 'ring-blue-200 text-blue-400', - self::TUTORIAL => 'ring-teal-200 text-teal-400', + self::THOUGHTS => 'bg-yellow-400/20 dark:bg-yellow-400/10 text-yellow-700 dark:text-yellow-400', + self::RELEASE => 'bg-blue-400/20 dark:bg-blue-400/10 text-blue-700 dark:text-blue-400', + self::TUTORIAL => 'bg-teal-400/20 dark:bg-teal-400/10 text-teal-700 dark:text-teal-400', }; } } diff --git a/src/Web/Blog/articles/2025-03-13-request-objects-in-tempest.md b/src/Web/Blog/articles/2025-03-13-request-objects-in-tempest.md index e3310825..32e5d8fc 100644 --- a/src/Web/Blog/articles/2025-03-13-request-objects-in-tempest.md +++ b/src/Web/Blog/articles/2025-03-13-request-objects-in-tempest.md @@ -1,5 +1,5 @@ --- -title: Request Objects in Tempest +title: Request objects in Tempest description: Why Tempest requests are super intuitive author: brent tag: Tutorial diff --git a/src/Web/Blog/articles/2025-03-30-about-route-attributes.md b/src/Web/Blog/articles/2025-03-30-about-route-attributes.md index 83cb8291..2c5d6d5d 100644 --- a/src/Web/Blog/articles/2025-03-30-about-route-attributes.md +++ b/src/Web/Blog/articles/2025-03-30-about-route-attributes.md @@ -1,5 +1,5 @@ --- -title: About Route Attributes +title: About route attributes description: Let's explore Tempest's route attributes in depth author: brent tag: Thoughts diff --git a/src/Web/Blog/articles/2025-05-08-beta-1.md b/src/Web/Blog/articles/2025-05-08-beta-1.md index 46f9e36e..9c8547f1 100644 --- a/src/Web/Blog/articles/2025-05-08-beta-1.md +++ b/src/Web/Blog/articles/2025-05-08-beta-1.md @@ -1,5 +1,5 @@ --- -title: Tempest is Beta +title: Tempest is beta description: | Today we release the first beta version of Tempest, the PHP framework for web and console apps that gets out of your way. It's one of the final steps towards a stable 1.0 release. We'll use this beta phase to fix bugs, and we're committed to not making any breaking changes anymore, apart from experimental features. author: brent diff --git a/src/Web/Blog/articles/2025-05-26-tempests-vision.md b/src/Web/Blog/articles/2025-05-26-tempests-vision.md index 91eb71bf..e0413a07 100644 --- a/src/Web/Blog/articles/2025-05-26-tempests-vision.md +++ b/src/Web/Blog/articles/2025-05-26-tempests-vision.md @@ -1,5 +1,5 @@ --- -title: Tempest's Vision +title: Tempest's vision description: What sets Tempest apart as a framework for modern PHP development. author: brent tag: Thoughts diff --git a/src/Web/Blog/articles/2025-07-28-tempest-view-updates.md b/src/Web/Blog/articles/2025-07-28-tempest-view-updates.md index 8f3f875e..a29652c0 100644 --- a/src/Web/Blog/articles/2025-07-28-tempest-view-updates.md +++ b/src/Web/Blog/articles/2025-07-28-tempest-view-updates.md @@ -1,5 +1,5 @@ --- -title: Major updates to Tempest View +title: Major updates to Tempest views description: Tempest 1.5 released with some major improvements to its templating engine author: brent tag: Release @@ -7,7 +7,7 @@ tag: Release Today we released Tempest version 1.5, which includes a bunch of improvements to [Tempest View](/docs/essentials/views), the templating engine that ships by default with the framework. Tempest also has support for Blade and Twig, but we designed Tempest View to take a unique approach to templating with PHP, and I must say: it looks excellent! (I might be biased.) -Designing a new language is hard, even if it's "only" a templating language, which is why we marked Tempest View as experimental when Tempest 1.0 released. This meant the package could still change over time, although we try to keep breaking changes at a minimum. +Designing a new language is hard, even if it's "only" a templating language, which is why we marked Tempest View as experimental when Tempest 1.0 released. This meant the package could still change over time, although we try to keep breaking changes at a minimum. With the release of Tempest 1.5, we did have to make a handful of breaking changes, but overall they shouldn't have a big impact. And I believe both changes are moving the language forward in the right direction. In this post, I want to highlight the new Tempest View features and explain why they needed a breaking change or two. @@ -34,11 +34,13 @@ And likewise, view components won't have access to variables from the outer scop ```html - + ``` There's one exception to this rule: variables defined by the view itself are directly accessible from within view components. This can be useful when you're using view components that are tied to one specific view, but extracted to a component to avoid code repetition. +:::code-group + ```html x-home-highlight.view.php
{!! $highlights[$name] !!} @@ -48,21 +50,23 @@ There's one exception to this rule: variables defined by the view itself are dir ``` -```php +```php app/HomeController.php final class HomeController { #[Get('/')] public function __invoke(HighlightRepository $highlightRepository): View { return view( - 'home.view.php', + './home.view.php', highlights: $highlightRepository->all(), ); } } ``` -Variable scoping now works by compiling view components to PHP closures instead of what we used to do: manage variable scope ourselves. Besides fixing some bugs, it also [simplified view component rendering significantly](https://github.com/tempestphp/tempest-framework/pull/1435), which is great! +::: + +Variable scoping now works by compiling view components to PHP closures instead of what we used to do: manage variable scope ourselves. Besides fixing some bugs, it also [simplified view component rendering significantly](https://github.com/tempestphp/tempest-framework/pull/1435), which is great! ## Installable view components @@ -126,7 +130,7 @@ $original = $session->getOriginalValueFor($name, $default);
``` -While this style might require some getting used to for some people, I think it is the right decision to make: class-based view components had a lot of compiler edge cases that we had to take into account, and often lead to subtle bugs when building new components. I do plan on writing an in-depth post on how to build reusable view components with Tempest soon. Stay tuned for that! +While this style might require some getting used to for some people, I think it is the right decision to make: class-based view components had a lot of compiler edge cases that we had to take into account, and often lead to subtle bugs when building new components. I do plan on writing an in-depth post on how to build reusable view components with Tempest soon. Stay tuned for that! ## Work in progress IDE support @@ -142,6 +146,6 @@ There is a lot of work to be done, but it's amazing to see this moving forward. ## What's next? -From the beginning I've said that IDE support is a must for any project to succeed. It now looks like there's a real chance of that happening, which is amazing. Besides IDE support, there are a couple of big features to tackle: I want Tempest to ship with some form of "standard component library" that people can use as a scaffold, we're looking into adding HTMX support (or something alike) to build async components, and we plan on making bridges for Laravel and Symfony so that you can use Tempest View in projects outside of Tempest as well. +From the beginning I've said that IDE support is a must for any project to succeed. It now looks like there's a real chance of that happening, which is amazing. Besides IDE support, there are a couple of big features to tackle: I want Tempest to ship with some form of "standard component library" that people can use as a scaffold, we're looking into adding HTMX support (or something alike) to build async components, and we plan on making bridges for Laravel and Symfony so that you can use Tempest View in projects outside of Tempest as well. -If you're inspired and interested to help out with any of these features, then you're more than welcome to [join the Tempest Discord](/discord) and take it from there. \ No newline at end of file +If you're inspired and interested to help out with any of these features, then you're more than welcome to [join the Tempest Discord](/discord) and take it from there. diff --git a/src/Web/Blog/articles/2025-11-10-route-decorators.md b/src/Web/Blog/articles/2025-11-10-route-decorators.md index 1e316b54..89d62730 100644 --- a/src/Web/Blog/articles/2025-11-10-route-decorators.md +++ b/src/Web/Blog/articles/2025-11-10-route-decorators.md @@ -1,5 +1,5 @@ --- -title: "Route Decorators in Tempest 2.8" +title: "Route decorators in Tempest 2.8" description: Taking a deep dive in a new Tempest feature author: brent tag: release @@ -70,7 +70,7 @@ While I really like attribute-based routing, grouping route behavior does feel - Tempest's default route attributes are represented by HTTP verbs: `#[Get]`, `#[Post]`, etc. Making admin variants for each verb might be tedious, so in my previous example I decided to use one `#[AdminRoute]`, where the verb would be specified manually. There's nothing stopping me from adding `#[AdminGet]`, `#[AdminPost]`, etc; but it doesn't feel super clean. - When you prefer to namespace admin-specific route attributes like `#[Admin\Get]`, and `#[Admin\Post]`, you end up with naming collisions between normal- and admin versions. I've always found those types of ambiguities to increase cognitive load while coding. - This approach doesn't really scale: say there are two types of route groups that require a specific middleware (`AuthMiddleware`, for example), then you end up making two or more route attributes, duplicating that logic of adding `AuthMiddleware` to both. -- Say you want nested route groups: one for admin routes and then one for book routes (with a `/admin/books` prefix), you end up with yet another variant called `#[AdminBookRoute]` attribute, not ideal. +- Say you want nested route groups: one for admin routes and then one for book routes (with a `/admin/books` prefix), you end up with yet another variant called `#[AdminBookRoute]` attribute, not ideal. So… what's the solution? I first looked at Symfony, which also uses attributes for routing: @@ -95,7 +95,7 @@ class BookAdminController extends AbstractController } ``` -I think Symfony's approach gets us halfway there: it has the benefit of being able to define "shared route behavior" on the controller level, but not across controllers. You could create abstract controllers like `AdminController` and `AdminBookController`, which doesn't scale horizontally when you want to combine multiple route groups, because PHP doesn't have multi-inheritance. On top of that, I also like Tempest's design of using HTTP verbs to model route attributes like `#[Get]` and `#[Post]`, which is missing with Symfony. All of that to say, I like Symfony's approach, but I feel like there's room for improvement. +I think Symfony's approach gets us halfway there: it has the benefit of being able to define "shared route behavior" on the controller level, but not across controllers. You could create abstract controllers like `AdminController` and `AdminBookController`, which doesn't scale horizontally when you want to combine multiple route groups, because PHP doesn't have multi-inheritance. On top of that, I also like Tempest's design of using HTTP verbs to model route attributes like `#[Get]` and `#[Post]`, which is missing with Symfony. All of that to say, I like Symfony's approach, but I feel like there's room for improvement. With the scene now being set, let's see the design we ended up with in Tempest. @@ -227,4 +227,4 @@ On top of adding the {b`Tempest\Router\RouteDecorator`} interface, I've also add - {b`Tempest\Router\WithoutMiddleware`}: which explicitely removes one or more middleware classes from the default middleware stack to all decorated routes. - {b`Tempest\Router\Stateless`}: which will remove all session and cookie related middleware from the decorated routes. -I really like the solution we ended up with. I think it combines the best of both worlds. Maybe you have some thoughts about it as well? [Join the Tempest Discord](/discord) to let us know! You can also read all the details of route decorators [in the docs](/2.x/essentials/routing#route-decorators-route-groups). \ No newline at end of file +I really like the solution we ended up with. I think it combines the best of both worlds. Maybe you have some thoughts about it as well? [Join the Tempest Discord](/discord) to let us know! You can also read all the details of route decorators [in the docs](/2.x/essentials/routing#route-decorators-route-groups). diff --git a/src/Web/Blog/index.view.php b/src/Web/Blog/index.view.php index e278fecc..c746136b 100644 --- a/src/Web/Blog/index.view.php +++ b/src/Web/Blog/index.view.php @@ -7,44 +7,46 @@ -
+
-
+
-

Blog

-

+

Blog

+

Read the latest news and announcements about Tempest, from framework updates to real-world applications and expert insights.

- -
    -
  • - -
    - {{ $post->title }} -

    {{ $post->description }}

    -
    -
    - - {{ str($post->tag->value)->title() }} - - - by {{ $post->author->getName() }} on {{ $post->createdAt->format('F d, Y') }} - -
    -
  • -
+
    +
  • +
    + +
    + {{ $post->title }} +

    {{ $post->description }}

    +
    +
    + + {{ str($post->tag->value)->title() }} + + + by {{ $post->author->getName() }} on {{ $post->createdAt->format('F d, Y') }} + +
    +
    +
  • +
diff --git a/src/Web/Blog/show.view.php b/src/Web/Blog/show.view.php index 051a7051..3a475a7c 100644 --- a/src/Web/Blog/show.view.php +++ b/src/Web/Blog/show.view.php @@ -10,25 +10,25 @@ :meta="$post->meta" > -
+
-
+ diff --git a/src/Web/Homepage/rain.entrypoint.ts b/src/Web/Homepage/rain.entrypoint.ts index ad2fe689..1bb61c36 100644 --- a/src/Web/Homepage/rain.entrypoint.ts +++ b/src/Web/Homepage/rain.entrypoint.ts @@ -23,7 +23,7 @@ const config = { baseRainCount: 2, color: 200, opacity: 1, - saturation: 50, + saturation: 10, lightness: 15, initialRainIntensity: 2, intensityChangeSpeed: 0.01, diff --git a/src/Web/Homepage/x-home-section.view.php b/src/Web/Homepage/x-home-section.view.php index 24848424..34d8a12f 100644 --- a/src/Web/Homepage/x-home-section.view.php +++ b/src/Web/Homepage/x-home-section.view.php @@ -1,27 +1,27 @@
-
- -
-
- - {{ $heading }} - -

- {{ $paragraph }} -

-
- -
- -
-
- {!! $this->codeBlocks[$snippet] !!} -
-
+
+ +
+
+ + {{ $heading }} + +

+ {{ $paragraph }} +

+
+
+ +
+
+ {!! $this->codeBlocks[$snippet] !!} +
+
+
diff --git a/src/Web/RedirectsController.php b/src/Web/RedirectsController.php index d082ea1c..532720b9 100644 --- a/src/Web/RedirectsController.php +++ b/src/Web/RedirectsController.php @@ -21,15 +21,33 @@ public function github(): Redirect return new Redirect('https://github.com/tempestphp/tempest-framework'); } - #[Get('/bluesky')] - public function bluesky(): Redirect + #[Get('/bluesky/brent')] + public function blueskyBrent(): Redirect { - return new Redirect('https://bsky.app/profile/brendt.bsky.social'); + return new Redirect('https://bsky.app/profile/stitcher.io'); } - #[Get('/twitter')] - public function twitter(): Redirect + #[Get('/twitter/brent')] + public function twitterBrent(): Redirect { return new Redirect('https://x.com/brendt_gd'); } + + #[Get('/bluesky/enzo')] + public function blueskyEnzo(): Redirect + { + return new Redirect('https://bsky.app/profile/innocenzi.dev'); + } + + #[Get('/twitter/enzo')] + public function twitterEnzo(): Redirect + { + return new Redirect('https://x.com/enzoinnocenzi'); + } + + #[Get('/twitter/aidan')] + public function twitterAidan(): Redirect + { + return new Redirect('https://x.com/HelloAidanCasey'); + } } diff --git a/src/Web/assets/main.entrypoint.css b/src/Web/assets/main.entrypoint.css index e76c12c4..2b2d491f 100644 --- a/src/Web/assets/main.entrypoint.css +++ b/src/Web/assets/main.entrypoint.css @@ -9,6 +9,7 @@ --container-8xl: 96rem; --font-display: "Kantumruy Pro Variable"; --font-sans: "Public Sans Variable"; + --font-mono: "JetBrains Mono Variable"; /* Falling leaves */ --color-leaf-1: var(--color-amber-500); @@ -57,13 +58,14 @@ @layer base { :root, .light { + font-size: 15px; --ui-header-height: --spacing(18); @media (width > 40rem) { --ui-header-height: --spacing(28); } - --ui-primary: var(--color-blue-400); + --ui-primary: hsl(212.1, 87.8%, 64.5%); --ui-secondary: var(--color-green-500); --ui-success: var(--color-green-500); --ui-info: var(--color-blue-500); @@ -89,7 +91,7 @@ } .dark { - --ui-primary: var(--color-blue-400); + --ui-primary: hsl(212.3, 100%, 74.5%); --ui-secondary: var(--color-neutral-400); --ui-success: var(--color-emerald-400); --ui-info: var(--color-blue-300); @@ -192,6 +194,59 @@ pre { } } -@source inline("ring-amber-200 text-amber-400"); -@source inline("ring-blue-200 text-blue-400"); -@source inline("ring-teal-200 text-teal-400"); +/* +|-------------------------------------------------------------------------- +| Fonts +|-------------------------------------------------------------------------- +*/ + +@font-face { + font-family: "JetBrains Mono Variable"; + font-style: normal; + font-display: swap; + font-weight: 100 800; + src: url(https://cdn.jsdelivr.net/fontsource/fonts/jetbrains-mono:vf@latest/latin-wght-normal.woff2) + format("woff2-variations"); + unicode-range: + U+0000-00FF, + U+0131, + U+0152-0153, + U+02BB-02BC, + U+02C6, + U+02DA, + U+02DC, + U+0304, + U+0308, + U+0329, + U+2000-206F, + U+20AC, + U+2122, + U+2191, + U+2193, + U+2212, + U+2215, + U+FEFF, + U+FFFD; +} + +/* +|-------------------------------------------------------------------------- +| Home page code blocks +|-------------------------------------------------------------------------- +*/ + +.home .code-block { + @apply flex flex-col gap-y-0 p-0.5 rounded-xl border border-(--ui-border) + bg-(--ui-bg-elevated)/50 overflow-hidden; + + &.named-code-block { + .code-block-name { + @apply mb-1.5 mt-1 px-2 font-mono text-(--ui-text-dimmed) text-sm; + } + } + + pre { + @apply border border-(--ui-border) rounded-[0.6rem] p-4 my-0 + bg-(--ui-bg-elevated)/50 overflow-x-auto; + } +} diff --git a/src/Web/assets/save-scroll.ts b/src/Web/assets/save-scroll.ts index 114234b5..e8863dc0 100644 --- a/src/Web/assets/save-scroll.ts +++ b/src/Web/assets/save-scroll.ts @@ -1,4 +1,4 @@ -document.addEventListener('DOMContentLoaded', () => { +function restoreScrollContainers() { const elements = document.querySelectorAll('[data-save-scroll]') elements.forEach((element) => { @@ -13,4 +13,28 @@ document.addEventListener('DOMContentLoaded', () => { localStorage.setItem(key, element.scrollTop.toString()) }) }) +} + +function scrollIntoView() { + const elements = document.querySelectorAll('[data-scroll-into-view]') + + elements.forEach((element) => { + const id = element.getAttribute('data-scroll-into-view') + if (!id) { + return + } + + const target = document.getElementById(id) + const outsideOfContainer = element.getBoundingClientRect().top < 0 + || element.getBoundingClientRect().bottom > window.innerHeight + + if (target && outsideOfContainer) { + target.scrollTo({ behavior: 'smooth', top: element.offsetTop }) + } + }) +} + +document.addEventListener('DOMContentLoaded', () => { + restoreScrollContainers() + scrollIntoView() }) diff --git a/src/Web/assets/typography.css b/src/Web/assets/typography.css index 2edf9f36..7a65cc91 100644 --- a/src/Web/assets/typography.css +++ b/src/Web/assets/typography.css @@ -76,10 +76,10 @@ } .code-group-tabs { - @apply flex gap-x-1 px-2 py-1 pt-1.5 overflow-x-auto; + @apply flex gap-x-1 px-2 py-1 pt-1.5 overflow-x-auto grow; .code-group-tab { - @apply px-3 whitespace-nowrap grow py-1.5 font-mono text-sm + @apply px-3 whitespace-nowrap py-1.5 font-mono text-sm text-left text-(--ui-text-dimmed) rounded-md hover:text-(--ui-text-highlighted) transition-colors cursor-pointer; diff --git a/src/Web/x-base-code.view.php b/src/Web/x-base-code.view.php index 9f680f38..9c9e95f8 100644 --- a/src/Web/x-base-code.view.php +++ b/src/Web/x-base-code.view.php @@ -41,7 +41,7 @@ - +
diff --git a/src/Web/x-base.view.php b/src/Web/x-base.view.php index 245b2229..2bb6857f 100644 --- a/src/Web/x-base.view.php +++ b/src/Web/x-base.view.php @@ -79,7 +79,7 @@ function toggleDarkMode() { - +
diff --git a/src/Web/x-footer.view.php b/src/Web/x-footer.view.php index 38f0ffd5..af9bddd2 100644 --- a/src/Web/x-footer.view.php +++ b/src/Web/x-footer.view.php @@ -6,22 +6,22 @@ ?> -
+ diff --git a/src/Web/x-header.view.php b/src/Web/x-header.view.php index bdaad6ce..68d7cb4d 100644 --- a/src/Web/x-header.view.php +++ b/src/Web/x-header.view.php @@ -36,7 +36,7 @@ class="group transition-[top,border] z-[1] fixed top-4 data-[scrolling]:top-0 fl
-