Fáze požadavku: 9. Blade & Livewire prezentace
Modul 4 · Lekce 1

Blade šablony, direktivy a automatická ochrana proti XSS

💡 Shrnutí lekce

Šablonovací systém Blade, kompilace do nativního PHP v storage/framework/views, eskapování výstupu {{ }} jako ochrana proti Cross-Site Scripting (XSS) a neescapovaný výstup {!! !!}.

Šablonovací engine Blade a jeho kompilace

Blade je nativní, neuvěřitelně rychlý šablonovací engine Laravelu. Na rozdíl od jiných PHP šablonovacích systémů (jako je např. Twig nebo Smarty) vás Blade neomezuje v používání čistého PHP kódu uvnitř šablon.

Všechny Blade šablony (.blade.php) jsou při prvním spuštění zkompilovány do čistého nativního PHP kódu a uloženy do adresáře storage/framework/views/. Při dalších požadavcích se již kód nekompiluje, ale přímo vykonává, což znamená nulový výkonnostní dopad na produkční běh.

Šablona (.blade.php) ──► Blade Kompilátor ──► Nativní PHP v storage/framework/views/
                                                          │
                                                    Rychlý běh

1. Zobrazení dat a neprůstřelná ochrana proti XSS

Nejčastější bezpečnostní zranitelností webových aplikací je Cross-Site Scripting (XSS) – situace, kdy útočník vloží do databáze škodlivý JavaScriptový kód <script>...</script>, který se následně spustí v prohlížeči ostatních návštěvníků.

Blade tuto hrozbu řeší automaticky:

A) Bezpečný escapovaný výpis {{ $data }}

Kdykoliv v Blade použijete dvojité složené závorky, Laravel hodnotu automaticky prožene nativní funkcí htmlspecialchars():

<h1>{{ $student->name }}</h1>
{{-- Pokud name obsahuje '<script>', vypíše se jako &lt;script&gt; a nespustí se! --}}

B) Neescapovaný surový výpis {!! $data !!}

Pouze v případě, kdy si jste 100% jisti, že obsah je bezpečný (například vyrenderovaný Markdown v naší aplikaci, který byl předem ošetřen):

<div class="prose">
    {!! $renderedHtml !!}
</div>

Varování: Zápis {!! !!} nikdy nepoužívejte pro neověřené vstupy zadané uživatelem!


2. Praktické Blade direktivy v našem projektu

Blade nabízí elegantní zkratky pro běžné PHP konstrukce, které udržují kód čistý a čitelný:

  • Podmínky: @if($lesson->lifecycle_phase), @elseif, @else, @endif, případně @empty($variable) a @isset($variable).
  • Cykly: @foreach($modules as $module), kde máte k dispozici magickou proměnnou $loop (např. $loop->first, $loop->last, $loop->iteration).
  • Ochrana formulářů: @csrf vygeneruje skryté input pole s CSRF tokenem chránící proti zfalšování požadavku mezi weby.
  • Vkládání assetů: @vite(['resources/css/app.css', 'resources/js/app.js']) automaticky zajistí správné cesty ke stylům a skriptům.

3. Rozbor layoutu a sekcí v resources/views/layouts/app.blade.php

V naší aplikaci slouží resources/views/layouts/app.blade.php jako centrální kostra celého HTML dokumentu. Obsahuje:

  1. Společné HTML hlavičky (<head>), načtení fontů a CSS assetů.
  2. Globální horní navigaci s logem, přepínačem modulů a anonymním profilem studenta.
  3. Flash alerty pro zobrazení zpráv ze session (@if(session('success'))).
  4. Vymezené místo pro hlavní obsah: {{ $slot ?? '' }} a @yield('content').
💻 Klíčová konstrukce z praxe
{{-- resources/views/layouts/app.blade.php (Ukázka práce s Blade v naší aplikaci) --}}
<!DOCTYPE html>
<html lang="cs" class="h-full bg-slate-950">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    {{-- Prevence CSRF útoků pro asynchronní JavaScript požadavky --}}
    <meta name="csrf-token" content="{{ csrf_token() }}">

    {{-- Bezpečný escapovaný výpis titulku s výchozí hodnotou --}}
    <title>{{ $title ?? 'Anatomie Laravelu 12 & Moderního PHP 8.4' }} — Živá výuková platforma</title>

    {{-- Integrace moderní asset pipeline přes Vite --}}
    @vite(['resources/css/app.css', 'resources/js/app.js'])
    @livewireStyles
</head>
<body class="min-h-full flex flex-col bg-slate-950 text-slate-100 antialiased">
    {{-- Flash zprávy se session notifikacemi --}}
    @if(session('success'))
        <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-4">
            <div class="rounded-xl bg-emerald-500/10 border border-emerald-500/20 p-4 text-emerald-400 text-sm">
                {{ session('success') }}
            </div>
        </div>
    @endif

    {{-- Hlavní obsah: Podpora jak pro komponentový styl ($slot), tak pro klasické sekce (@yield) --}}
    <main class="flex-1">
        {{ $slot ?? '' }}
        @yield('content')
    </main>

    @livewireScripts
</body>
</html>

🎯 Ověření znalostí z lekce

Kvíz otestuje pochopení architektury a reálného kódu probíraného v této lekci.

Otázka 1 z 3

Jaký je zásadní bezpečnostní rozdíl mezi syntaxí {{ $data }} a {!! $data !!} v Blade?

Otázka 2 z 3

Kde Laravel ukládá zkompilované Blade šablony a proč je tento přístup tak rychlý?

Otázka 3 z 3

K čemu slouží direktiva @csrf uvnitř webového formuláře?

Zodpovězeno: 0 / 3
🔬 Živá anatomie v této aplikaci
2 soubory
Na co se v kódu zaměřit: V resources/views/layouts/app.blade.php si prohlédni direktivy @vite a vyhodnocování flash zpráv @if(session('success')). Všimni si, že hlavní obsah je napojen duálně přes {{ $slot ?? '' }} a @yield('content').
cesta: resources/views/layouts/app.blade.php
261 řádků · 16.3 KB
Hlavní Blade layout aplikace s tmavým tématem, navigací, synchronizací profilu a Alpine.js reaktivitou.
1 <!DOCTYPE html>
2 <html lang="cs" class="h-full bg-slate-950">
3 <head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="csrf-token" content="{{ csrf_token() }}">
7
8 <title>{{ $title ?? 'Anatomie Laravelu 12 & Moderního PHP 8.4' }} — Živá výuková platforma</title>
9 <meta name="description" content="Interaktivní výuková platforma Anatomie Laravelu 12 & Moderního PHP 8.4 na reálné živě běžící aplikaci.">
10
11 <link rel="preconnect" href="https://fonts.bunny.net">
12 <link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600,700|jetbrains-mono:400,500,600&display=swap" rel="stylesheet" />
13
14 @if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot')))
15 @vite(['resources/css/app.css', 'resources/js/app.js'])
16 @endif
17 @livewireStyles
18 </head>
19 <body class="min-h-full flex flex-col bg-slate-950 text-slate-100 antialiased selection:bg-indigo-500 selection:text-white font-sans">
20 @php
21 $student = $currentStudent ?? (app()->bound(\App\Services\StudentProgressService::class) ? app(\App\Services\StudentProgressService::class)->resolveProfile(request()->cookie(\App\Services\StudentProgressService::COOKIE_NAME ?? 'agy_student_token')) : null);
22 @endphp
23
24 {{-- Horní navigace --}}
25 <header class="sticky top-0 z-50 border-b border-slate-800/80 bg-slate-950/90 backdrop-blur-md">
26 <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
27 <div class="flex items-center justify-between h-16 gap-4">
28 {{-- Logo & Název --}}
29 <div class="flex items-center gap-3">
30 <a href="{{ route('course.index') }}" class="flex items-center gap-3 group focus:outline-none">
31 <div class="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-500 via-purple-600 to-amber-500 p-0.5 shadow-lg shadow-indigo-500/20 group-hover:shadow-indigo-500/40 transition">
32 <div class="w-full h-full bg-slate-950 rounded-[10px] flex items-center justify-center font-mono font-bold text-base text-amber-400 group-hover:text-amber-300">
33 &lt;/&gt;
34 </div>
35 </div>
36 <div>
37 <div class="flex items-center gap-2">
38 <span class="font-bold text-base tracking-tight text-white group-hover:text-amber-300 transition">
39 Anatomie Laravelu 12 & PHP 8.4
40 </span>
41 <span class="hidden sm:inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-semibold bg-indigo-500/10 text-indigo-400 ring-1 ring-inset ring-indigo-500/20">
42 v12.x
43 </span>
44 </div>
45 <p class="text-xs text-slate-400">
46 Živá výuková platforma · <span class="text-slate-300">laravel.heatsharing.cz</span>
47 </p>
48 </div>
49 </a>
50 </div>
51
52 {{-- Hlavní menu --}}
53 <nav class="hidden md:flex items-center gap-1">
54 <a href="{{ route('course.index') }}"
55 class="px-3 py-2 rounded-lg text-sm font-medium transition {{ request()->routeIs('course.*') && !request()->routeIs('anatomy.*') ? 'bg-indigo-950/60 text-indigo-300 border border-indigo-800/50 shadow-sm' : 'text-slate-300 hover:text-white hover:bg-slate-900' }}">
56 📚 Osnova & Moduly
57 </a>
58 <a href="{{ route('anatomy.index') }}"
59 class="px-3 py-2 rounded-lg text-sm font-medium transition {{ request()->routeIs('anatomy.*') ? 'bg-indigo-950/60 text-indigo-300 border border-indigo-800/50 shadow-sm' : 'text-slate-300 hover:text-white hover:bg-slate-900' }}">
60 🔬 Živá Anatomie & Request Lifecycle
61 </a>
62 </nav>
63
64 {{-- Postup studenta & Přenosový kód (Alpine.js dropdown) --}}
65 <div class="flex items-center gap-3" x-data="{ open: false }">
66 <div class="relative">
67 <button @click="open = !open"
68 type="button"
69 class="flex items-center gap-2.5 px-3 py-1.5 rounded-lg bg-slate-900 border border-slate-800 hover:border-slate-700 text-xs font-medium text-slate-200 transition focus:outline-none focus:ring-2 focus:ring-indigo-500/50"
70 aria-expanded="false"
71 :aria-expanded="open.toString()">
72 <span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span>
73 <span class="hidden sm:inline text-slate-400">Profil:</span>
74 <span class="font-mono text-amber-300 font-semibold">
75 {{ $student->transfer_code ?? 'LRV-POSTUP' }}
76 </span>
77 <svg class="w-3.5 h-3.5 text-slate-400 transition-transform duration-200" :class="{ 'rotate-180': open }" fill="none" viewBox="0 0 24 24" stroke="currentColor">
78 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
79 </svg>
80 </button>
81
82 {{-- Dropdown panel --}}
83 <div x-show="open"
84 @click.outside="open = false"
85 x-transition:enter="transition ease-out duration-150"
86 x-transition:enter-start="opacity-0 scale-95"
87 x-transition:enter-end="opacity-100 scale-100"
88 x-transition:leave="transition ease-in duration-100"
89 x-transition:leave-start="opacity-100 scale-100"
90 x-transition:leave-end="opacity-0 scale-95"
91 class="absolute right-0 mt-2 w-80 sm:w-96 rounded-xl bg-slate-900 border border-slate-800 shadow-2xl p-4 text-xs text-slate-300 z-50"
92 style="display: none;">
93 <div class="flex items-center justify-between pb-3 border-b border-slate-800">
94 <div>
95 <h4 class="font-semibold text-white text-sm">Anonymní profil studenta</h4>
96 <p class="text-[11px] text-slate-400">Postup je uložen v cookie a v PostgreSQL.</p>
97 </div>
98 <span class="px-2 py-0.5 rounded text-[10px] font-semibold bg-emerald-500/10 text-emerald-400 ring-1 ring-inset ring-emerald-500/20">
99 Aktivní
100 </span>
101 </div>
102
103 <div class="py-3 space-y-2">
104 <span class="text-slate-400 font-medium block">Tvůj unikátní přenosový kód:</span>
105 <div class="flex items-center gap-2">
106 <code class="flex-1 px-3 py-2 bg-slate-950 border border-slate-800 rounded-lg text-amber-300 font-mono text-sm tracking-wider select-all text-center font-bold">
107 {{ $student->transfer_code ?? 'LRV-GENEROVANI' }}
108 </code>
109 </div>
110 <p class="text-[11px] text-slate-500">
111 Tento kód si uložte, pokud budete chtít pokračovat ve výuce na jiném zařízení nebo v jiném prohlížeči.
112 </p>
113 </div>
114
115 <div class="pt-3 border-t border-slate-800">
116 <form action="{{ route('course.switch-profile') }}" method="POST" class="space-y-2">
117 @csrf
118 <label for="transfer_code_input" class="block text-slate-300 font-medium">
119 Načíst profil z jiného zařízení:
120 </label>
121 <div class="flex gap-2">
122 <input type="text"
123 id="transfer_code_input"
124 name="transfer_code"
125 placeholder="LRV-XXXX-XXXX"
126 required
127 class="flex-1 px-3 py-1.5 rounded-lg bg-slate-950 border border-slate-700 text-white font-mono text-xs uppercase placeholder:text-slate-600 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500">
128 <button type="submit"
129 class="px-3 py-1.5 bg-indigo-600 hover:bg-indigo-500 text-white font-semibold rounded-lg text-xs transition shadow-sm">
130 Aktivovat
131 </button>
132 </div>
133 </form>
134 </div>
135 </div>
136 </div>
137 </div>
138 </div>
139
140 {{-- Mobilní navigace --}}
141 <div class="flex md:hidden border-t border-slate-800/80 py-2 gap-2">
142 <a href="{{ route('course.index') }}"
143 class="flex-1 text-center px-3 py-1.5 rounded text-xs font-medium {{ request()->routeIs('course.*') && !request()->routeIs('anatomy.*') ? 'bg-indigo-950 text-indigo-300' : 'text-slate-400 hover:text-white' }}">
144 📚 Osnova & Moduly
145 </a>
146 <a href="{{ route('anatomy.index') }}"
147 class="flex-1 text-center px-3 py-1.5 rounded text-xs font-medium {{ request()->routeIs('anatomy.*') ? 'bg-indigo-950 text-indigo-300' : 'text-slate-400 hover:text-white' }}">
148 🔬 Živá Anatomie
149 </a>
150 </div>
151 </div>
152 </header>
153
154 {{-- Flash zprávy --}}
155 @if(session('success'))
156 <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-4">
157 <div class="rounded-xl bg-emerald-950/60 border border-emerald-800/80 p-4 text-emerald-200 flex items-start gap-3 shadow-lg">
158 <svg class="w-5 h-5 text-emerald-400 shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
159 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
160 </svg>
161 <div class="text-sm">
162 <span class="font-semibold text-white">Úspěch:</span> {{ session('success') }}
163 </div>
164 </div>
165 </div>
166 @endif
167
168 @if(session('error'))
169 <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-4">
170 <div class="rounded-xl bg-rose-950/60 border border-rose-800/80 p-4 text-rose-200 flex items-start gap-3 shadow-lg">
171 <svg class="w-5 h-5 text-rose-400 shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
172 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
173 </svg>
174 <div class="text-sm">
175 <span class="font-semibold text-white">Chyba:</span> {{ session('error') }}
176 </div>
177 </div>
178 </div>
179 @endif
180
181 {{-- Hlavní obsah --}}
182 <main class="flex-1">
183 {{ $slot ?? '' }}
184 @yield('content')
185 </main>
186
187 {{-- Patička --}}
188 <footer class="mt-20 border-t border-slate-800/80 bg-slate-950 text-slate-400 text-xs">
189 <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
190 <div class="grid grid-cols-1 md:grid-cols-3 gap-8 pb-8 border-b border-slate-900">
191 {{-- O platformě --}}
192 <div class="space-y-3">
193 <div class="flex items-center gap-2 text-white font-semibold">
194 <span class="text-amber-400 font-mono">&lt;/&gt;</span>
195 <span>Anatomie Laravelu 12 & PHP 8.4</span>
196 </div>
197 <p class="text-slate-400 leading-relaxed text-xs">
198 Sebereferenční výuková platforma. Aplikace sama slouží jako exponát – kód, architektura i konfigurace, které studujete, pohání tento běžící web.
199 </p>
200 </div>
201
202 {{-- Produkční infrastruktura --}}
203 <div class="space-y-3">
204 <h5 class="text-white font-semibold">Infrastruktura & Produkční Stack</h5>
205 <ul class="space-y-1.5 text-slate-400">
206 <li class="flex items-center gap-2">
207 <span class="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
208 <span>FrankenPHP v1 + PHP 8.4 (Worker Mode, multi-stage Docker)</span>
209 </li>
210 <li class="flex items-center gap-2">
211 <span class="w-1.5 h-1.5 rounded-full bg-indigo-400"></span>
212 <span>Laravel 12.x + Livewire 3 + Alpine.js + Tailwind CSS</span>
213 </li>
214 <li class="flex items-center gap-2">
215 <span class="w-1.5 h-1.5 rounded-full bg-sky-400"></span>
216 <span>PostgreSQL 18 na Hetzner serveru (<code class="text-slate-300 font-mono">debian-hell</code> / 65.109.132.79)</span>
217 </li>
218 <li class="flex items-center gap-2">
219 <span class="w-1.5 h-1.5 rounded-full bg-amber-400"></span>
220 <span>Reverzní proxy Caddy v2 s automatickým TLS Let's Encrypt</span>
221 </li>
222 </ul>
223 </div>
224
225 {{-- Rozcestník & Odkazy --}}
226 <div class="space-y-3">
227 <h5 class="text-white font-semibold">Rychlé odkazy</h5>
228 <ul class="space-y-1.5 text-slate-400">
229 <li>
230 <a href="{{ route('course.index') }}" class="hover:text-amber-300 transition">📚 Kompletní osnova (10 modulů, 30 lekcí)</a>
231 </li>
232 <li>
233 <a href="{{ route('anatomy.index') }}" class="hover:text-amber-300 transition">🔬 Živý Průzkumník anatomie</a>
234 </li>
235 <li>
236 <a href="https://heatsharing.cz" target="_blank" rel="noopener noreferrer" class="hover:text-amber-300 transition flex items-center gap-1">
237 <span>🌐 Rozcestník projektů heatsharing.cz</span>
238 <svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
239 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
240 </svg>
241 </a>
242 </li>
243 <li>
244 <a href="{{ route('api.status') }}" target="_blank" class="hover:text-amber-300 transition flex items-center gap-1 font-mono text-[11px] text-slate-500">
245 <span>⚡ Status API: /api/status</span>
246 </a>
247 </li>
248 </ul>
249 </div>
250 </div>
251
252 <div class="pt-6 flex flex-col sm:flex-row items-center justify-between gap-4 text-[11px] text-slate-500">
253 <p>© {{ date('Y') }} Heatsharing.cz · Anatomie Laravelu 12. Volně přístupné vzdělávací dílo.</p>
254 <p class="font-mono text-slate-500">PHP 8.4.x · Laravel 12.x · Zero Hallucinations Policy</p>
255 </div>
256 </div>
257 </footer>
258
259 @livewireScripts
260 </body>
261 </html>
262
cesta: resources/views/course/index.blade.php
234 řádků · 14.7 KB
Soubor aplikační architektury v modulu Blade View.
1 @extends('layouts.app')
2
3 @php
4 $student = $currentStudent ?? (app()->bound(\App\Services\StudentProgressService::class) ? app(\App\Services\StudentProgressService::class)->resolveProfile(request()->cookie(\App\Services\StudentProgressService::COOKIE_NAME ?? 'agy_student_token')) : null);
5 $phases = $lifecyclePhases ?? \App\Enums\LifecyclePhase::cases();
6 @endphp
7
8 @section('content')
9 <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10 space-y-14">
10
11 {{-- Hero sekce: Sebereferenční Živá Anatomie --}}
12 <section class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-slate-900 via-slate-900/90 to-indigo-950/60 border border-slate-800 p-8 sm:p-12 shadow-2xl">
13 <div class="absolute -right-20 -top-20 w-96 h-96 bg-indigo-600/10 rounded-full blur-3xl pointer-events-none"></div>
14 <div class="absolute -left-20 -bottom-20 w-96 h-96 bg-amber-500/10 rounded-full blur-3xl pointer-events-none"></div>
15
16 <div class="relative z-10 max-w-4xl space-y-6">
17 {{-- Tagy stacku --}}
18 <div class="flex flex-wrap items-center gap-2">
19 <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold bg-indigo-500/15 text-indigo-300 ring-1 ring-inset ring-indigo-500/30">
20 <span class="w-2 h-2 rounded-full bg-indigo-400"></span>
21 Moderní PHP 8.4 (strict_types=1)
22 </span>
23 <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold bg-rose-500/15 text-rose-300 ring-1 ring-inset ring-rose-500/30">
24 <span class="w-2 h-2 rounded-full bg-rose-400"></span>
25 Laravel 12.x
26 </span>
27 <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold bg-emerald-500/15 text-emerald-300 ring-1 ring-inset ring-emerald-500/30">
28 <span class="w-2 h-2 rounded-full bg-emerald-400"></span>
29 FrankenPHP v1 (Worker Mode)
30 </span>
31 <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold bg-sky-500/15 text-sky-300 ring-1 ring-inset ring-sky-500/30">
32 <span class="w-2 h-2 rounded-full bg-sky-400"></span>
33 PostgreSQL 18
34 </span>
35 <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold bg-amber-500/15 text-amber-300 ring-1 ring-inset ring-amber-500/30">
36 <span class="w-2 h-2 rounded-full bg-amber-400"></span>
37 Živá Anatomie
38 </span>
39 </div>
40
41 {{-- Hlavní nadpis & podtitul --}}
42 <div class="space-y-3">
43 <h1 class="text-3xl sm:text-4xl lg:text-5xl font-extrabold text-white tracking-tight leading-tight">
44 Anatomie Laravelu 12 &amp; Moderního PHP 8.4
45 </h1>
46 <p class="text-base sm:text-lg text-slate-300 leading-relaxed max-w-3xl">
47 První <span class="text-amber-400 font-semibold">sebereferenční výuková platforma</span> v ČR. Každé vysvětlení, princip i architektura odkazuje na <span class="text-white font-medium underline decoration-indigo-500 underline-offset-4">skutečné soubory této aplikace</span>, které můžete interaktivně zkoumat přímo za běhu na serveru.
48 </p>
49 </div>
50
51 {{-- Ukazatel postupu studenta --}}
52 <div class="pt-4 border-t border-slate-800/80">
53 <div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
54 {{-- Dokončené lekce --}}
55 <div class="bg-slate-950/60 rounded-xl p-4 border border-slate-800/80">
56 <div class="text-xs text-slate-400 font-medium">Dokončené lekce</div>
57 <div class="mt-1 flex items-baseline gap-2">
58 <span class="text-2xl font-bold text-white">{{ $stats['completedCount'] ?? 0 }}</span>
59 <span class="text-xs text-slate-400">z celkem {{ $stats['totalCount'] ?? 30 }}</span>
60 </div>
61 </div>
62
63 {{-- Celkový postup v % --}}
64 <div class="bg-slate-950/60 rounded-xl p-4 border border-slate-800/80">
65 <div class="text-xs text-slate-400 font-medium">Celkový postup kurzem</div>
66 <div class="mt-1 flex items-baseline gap-2">
67 <span class="text-2xl font-bold text-amber-400">{{ $stats['percentage'] ?? 0 }}%</span>
68 <span class="text-xs text-slate-400">hotovo</span>
69 </div>
70 <div class="mt-2 w-full bg-slate-800 rounded-full h-1.5 overflow-hidden">
71 <div class="bg-gradient-to-r from-indigo-500 to-amber-400 h-1.5 rounded-full transition-all duration-500" style="width: {{ $stats['percentage'] ?? 0 }}%"></div>
72 </div>
73 </div>
74
75 {{-- Průměrné skóre kvízů --}}
76 <div class="bg-slate-950/60 rounded-xl p-4 border border-slate-800/80">
77 <div class="text-xs text-slate-400 font-medium">Průměrné skóre kvízů</div>
78 <div class="mt-1 flex items-baseline gap-2">
79 <span class="text-2xl font-bold text-emerald-400">{{ $stats['averageQuizScore'] ?? 0 }}%</span>
80 <span class="text-xs text-slate-400">úspěšnost</span>
81 </div>
82 </div>
83 </div>
84 </div>
85 </div>
86 </section>
87
88 {{-- Životní cyklus HTTP požadavku (Lifecycle Bar) --}}
89 <section class="space-y-4">
90 <div class="flex items-center justify-between">
91 <div>
92 <h2 class="text-xl font-bold text-white tracking-tight flex items-center gap-2">
93 <span class="text-indigo-400 font-mono">⚡</span>
94 Životní cyklus HTTP požadavku v Laravelu 12
95 </h2>
96 <p class="text-xs text-slate-400 mt-0.5">
97 11 fází cesty požadavku od FrankenPHP socketu přes Service Container a Controller až k vyrenderovanému Blade HTML:
98 </p>
99 </div>
100 <a href="{{ route('anatomy.index') }}" class="text-xs text-indigo-400 hover:text-indigo-300 font-medium hidden sm:inline-flex items-center gap-1 transition">
101 <span>Podrobný rozbor v Živé Anatomii</span>
102 <span>&rarr;</span>
103 </a>
104 </div>
105
106 <div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-11 gap-2">
107 @foreach($phases as $phase)
108 <a href="{{ route('anatomy.index') }}"
109 class="group bg-slate-900/80 hover:bg-slate-850 border border-slate-800 hover:border-indigo-500/50 rounded-xl p-2.5 transition text-left focus:outline-none focus:ring-1 focus:ring-indigo-500">
110 <div class="flex items-center justify-between text-[10px] text-slate-400 group-hover:text-indigo-300 font-mono">
111 <span>#{{ $phase->stepOrder() }}</span>
112 <span class="w-1.5 h-1.5 rounded-full bg-indigo-500/50 group-hover:bg-indigo-400"></span>
113 </div>
114 <div class="mt-1 font-semibold text-xs text-slate-200 group-hover:text-white line-clamp-1">
115 {{ preg_replace('/^\d+\.\s*/', '', $phase->label()) }}
116 </div>
117 </a>
118 @endforeach
119 </div>
120 </section>
121
122 {{-- Přehled modulů kurzu --}}
123 <section class="space-y-6">
124 <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
125 <div>
126 <h2 class="text-2xl font-bold text-white tracking-tight flex items-center gap-2">
127 <span>📚</span>
128 <span>Osnova kurzu — 10 architektonických modulů</span>
129 </h2>
130 <p class="text-sm text-slate-400 mt-1">
131 Od moderního PHP 8.4 přes architekturu Laravelu 12, Livewire 3 a PostgreSQL 18 až po produkční kontejnerizaci s FrankenPHP na Hetzner serveru.
132 </p>
133 </div>
134 <div class="text-xs text-slate-400 font-mono">
135 30 lekcí s reálným kódem & kvízy
136 </div>
137 </div>
138
139 <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
140 @foreach($modules as $module)
141 @php
142 $completedInModule = $student ? $module->lessons->filter(fn($l) => $student->hasCompletedLesson($l->id))->count() : 0;
143 $totalInModule = $module->lessons->count();
144 $isModuleComplete = $totalInModule > 0 && $completedInModule === $totalInModule;
145 @endphp
146 <div class="flex flex-col justify-between rounded-2xl bg-slate-900/70 border border-slate-800 hover:border-slate-700/80 p-6 transition shadow-lg group">
147 <div class="space-y-4">
148 {{-- Hlavička modulu --}}
149 <div class="flex items-start justify-between gap-4">
150 <div>
151 <div class="flex items-center gap-2 mb-1.5">
152 <span class="text-xs font-mono font-bold text-indigo-400 uppercase tracking-wider">
153 Modul {{ str_pad((string)$module->number, 2, '0', STR_PAD_LEFT) }}
154 </span>
155 <span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-medium {{ $module->difficulty->badgeClass() }}">
156 {{ $module->difficulty->label() }}
157 </span>
158 <span class="text-[11px] text-slate-400 flex items-center gap-1 font-mono">
159 ⏱ {{ $module->estimated_minutes }} min
160 </span>
161 </div>
162 <h3 class="text-lg font-bold text-white group-hover:text-amber-300 transition">
163 <a href="{{ route('course.module', $module) }}">
164 {{ $module->title }}
165 </a>
166 </h3>
167 </div>
168
169 @if($isModuleComplete)
170 <span class="shrink-0 inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-semibold bg-emerald-500/10 text-emerald-400 ring-1 ring-inset ring-emerald-500/20">
171 ✓ Dokončeno
172 </span>
173 @else
174 <span class="shrink-0 text-xs font-mono text-slate-400 bg-slate-950 px-2.5 py-1 rounded-lg border border-slate-800">
175 {{ $completedInModule }}/{{ $totalInModule }}
176 </span>
177 @endif
178 </div>
179
180 {{-- Popis modulu --}}
181 <p class="text-xs text-slate-300 leading-relaxed">
182 {{ $module->summary }}
183 </p>
184
185 {{-- Seznam lekcí v modulu --}}
186 <div class="space-y-2 pt-2 border-t border-slate-800/80">
187 <div class="text-[11px] font-semibold text-slate-400 uppercase tracking-wider">Lekce v modulu:</div>
188 <div class="space-y-1.5">
189 @foreach($module->lessons as $lesson)
190 @php
191 $lessonDone = $student && $student->hasCompletedLesson($lesson->id);
192 @endphp
193 <a href="{{ route('course.lesson', [$module, $lesson]) }}"
194 class="flex items-center justify-between p-2 rounded-lg bg-slate-950/60 hover:bg-slate-950 border border-slate-800/60 hover:border-indigo-500/50 text-xs transition group/item">
195 <div class="flex items-center gap-2.5 min-w-0 pr-2">
196 @if($lessonDone)
197 <span class="w-4 h-4 rounded-full bg-emerald-500/20 text-emerald-400 flex items-center justify-center shrink-0 text-[10px] font-bold">✓</span>
198 @else
199 <span class="w-4 h-4 rounded-full bg-slate-800 text-slate-400 flex items-center justify-center shrink-0 text-[10px] font-mono">{{ $lesson->number }}</span>
200 @endif
201 <span class="text-slate-300 group-hover/item:text-white truncate font-medium">
202 {{ $lesson->title }}
203 </span>
204 </div>
205 <div class="shrink-0 flex items-center gap-1.5 text-[10px] text-slate-400 font-mono">
206 <span class="hidden sm:inline text-indigo-400/80">
207 {{ count($lesson->anatomy_files ?? []) }} souborů
208 </span>
209 <span class="text-slate-600 group-hover/item:text-indigo-400">&rarr;</span>
210 </div>
211 </a>
212 @endforeach
213 </div>
214 </div>
215 </div>
216
217 {{-- Spodní akce modulu --}}
218 <div class="mt-6 pt-4 border-t border-slate-800/60 flex items-center justify-between">
219 <span class="text-xs text-slate-400">
220 {{ $totalInModule }} lekce s interaktivním kvízem
221 </span>
222 <a href="{{ route('course.module', $module) }}"
223 class="inline-flex items-center gap-1.5 px-3.5 py-1.5 bg-indigo-600/80 hover:bg-indigo-600 text-white rounded-lg text-xs font-semibold transition shadow-sm">
224 <span>Zobrazit modul</span>
225 <span>&rarr;</span>
226 </a>
227 </div>
228 </div>
229 @endforeach
230 </div>
231 </section>
232
233 </div>
234 @endsection
235
Zdroj: skutečné soubory této aplikace Plný průzkumník anatomie →