@@ -32,18 +32,43 @@ const validatorCode = `public sealed class RegisterUserCommandValidator
3232 }
3333} ` ;
3434
35+ // Active files (clickable). Order matters for indices.
3536const files = [
36- { name: ' RegisterUserEndpoint.cs' , code: endpointCode , lines: 9 },
37- { name: ' RegisterUserCommandHandler.cs' , code: handlerCode , lines: 7 },
38- { name: ' RegisterUserCommandValidator.cs' , code: validatorCode , lines: 10 },
37+ {
38+ name: ' RegisterUserEndpoint.cs' ,
39+ short: ' Endpoint.cs' ,
40+ lines: 9 ,
41+ code: endpointCode ,
42+ },
43+ {
44+ name: ' RegisterUserCommandHandler.cs' ,
45+ short: ' Handler.cs' ,
46+ lines: 7 ,
47+ code: handlerCode ,
48+ },
49+ {
50+ name: ' RegisterUserCommandValidator.cs' ,
51+ short: ' Validator.cs' ,
52+ lines: 10 ,
53+ code: validatorCode ,
54+ },
55+ ];
56+
57+ // Collapsed feature folders (visual-only — show the breadth of VSA)
58+ const otherFeatures = [
59+ ' LoginUser' ,
60+ ' AssignUserRole' ,
61+ ' ChangePassword' ,
62+ ' DeleteUser' ,
63+ ' RefreshToken' ,
3964];
4065---
4166<section class =" relative border-t border-border bg-subtle/40" >
4267 <div class =" section-dots absolute inset-0 -z-10 opacity-60" aria-hidden =" true" ></div >
4368
44- <div class =" mx-auto max-w-6xl px-6 py-24 sm:py-32" >
69+ <div class =" mx-auto max-w-7xl px-6 py-24 sm:py-32" >
4570 <div class =" grid items-start gap-12 lg:grid-cols-[minmax(0,_5fr)_minmax(0,_7fr)] lg:gap-16" >
46- <!-- Left : editorial copy -->
71+ <!-- LEFT : editorial copy -->
4772 <div class =" lg:sticky lg:top-24" >
4873 <p class =" text-[11px] font-medium uppercase tracking-[0.18em] text-primary" >Vertical Slice Architecture</p >
4974 <h2 class =" mt-4 font-display text-4xl font-semibold leading-[1.05] tracking-[-0.03em] text-foreground sm:text-5xl" >
@@ -82,60 +107,128 @@ const files = [
82107 </a >
83108 </div >
84109
85- <!-- Right: custom-framed editor window -->
86- <div class =" code-window relative" >
87- <!-- Glow behind window (only intense on hover) -->
88- <div class =" code-window -glow" aria-hidden =" true" ></div >
110+ <!-- RIGHT: VS-Code-style editor with file tree + code -->
111+ <div class =" editor relative" >
112+ <!-- Glow behind the window (visible on hover) -->
113+ <div class =" editor -glow" aria-hidden =" true" ></div >
89114
90- <div class =" code-window -frame relative overflow-hidden rounded-xl border border-border bg-card shadow-[0_30px_60px_-30px_rgba(21,128,61,0.18),0_8px_24px_-12px_rgba(0,0,0,0.08)] dark:shadow-[0_30px_60px_-30px_rgba(74,222,128,0.12),0_8px_24px_-12px_rgba(0,0,0,0.5)]" >
91- <!-- Title bar with traffic lights + path + langauge badge -->
92- <div class =" flex items-center gap-3 border-b border-border bg-subtle/50 px-4 py-2.5" >
115+ <div class =" editor -frame relative overflow-hidden rounded-xl border border-border bg-card shadow-[0_30px_60px_-30px_rgba(21,128,61,0.18),0_8px_24px_-12px_rgba(0,0,0,0.08)] dark:shadow-[0_30px_60px_-30px_rgba(74,222,128,0.12),0_8px_24px_-12px_rgba(0,0,0,0.5)]" >
116+ <!-- Title bar -->
117+ <div class =" flex items-center gap-3 border-b border-border bg-subtle/60 px-4 py-2.5" >
93118 <!-- Traffic lights -->
94119 <div class =" flex items-center gap-1.5" aria-hidden =" true" >
95120 <span class =" size-3 rounded-full bg-[#ff5f57]" ></span >
96121 <span class =" size-3 rounded-full bg-[#febc2e]" ></span >
97122 <span class =" size-3 rounded-full bg-[#28c840]" ></span >
98123 </div >
99- <!-- Path -->
100- <p class =" hidden flex-1 truncate font-mono text-[11.5px] text-muted-foreground sm:block" >
101- Modules.Identity / Features / v1 / Users / <span class =" text-foreground" >RegisterUser</span > /
124+ <!-- Breadcrumb path (updates with active file) -->
125+ <p class =" hidden min-w-0 flex-1 truncate font-mono text-[11.5px] text-muted-foreground sm:block" >
126+ <span >Modules.Identity</span >
127+ <span class =" text-border" >/</span >
128+ <span >Features</span >
129+ <span class =" text-border" >/</span >
130+ <span >v1</span >
131+ <span class =" text-border" >/</span >
132+ <span >Users</span >
133+ <span class =" text-border" >/</span >
134+ <span class =" text-foreground/80" >RegisterUser</span >
135+ <span class =" text-border" >/</span >
136+ <span class =" text-foreground" data-vsa-path >RegisterUserEndpoint.cs</span >
102137 </p >
103- <!-- C# language badge -->
104- <span class =" inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-2 py-0.5 font-mono text-[10px] font-semibold tracking-wide text-muted-foreground" >
138+ <!-- Language badge -->
139+ <span class =" ml-auto inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-2 py-0.5 font-mono text-[10px] font-semibold tracking-wide text-muted-foreground" >
105140 <span class =" size-1.5 rounded-full bg-primary" ></span >
106141 C#
107142 </span >
108143 </div >
109144
110- <!-- File tabs -->
111- <div role =" tablist" class =" flex overflow-x-auto border-b border-border bg-subtle/30 px-1.5 pt-1.5 no-scrollbar" >
112- { files .map ((f , i ) => (
113- <button
114- type = " button"
115- role = " tab"
116- data-cf-tab = { i }
117- aria-selected = { i === 0 ? ' true' : ' false' }
118- class = " cf-tab relative shrink-0 rounded-t-md px-3.5 py-2 font-mono text-[11.5px] text-muted-foreground transition-colors aria-selected:text-foreground"
119- >
120- <span class = " aria-selected:text-foreground" >{ f .name } </span >
121- </button >
122- ))}
123- </div >
145+ <!-- Editor body: file tree + code -->
146+ <div class =" grid grid-cols-1 md:grid-cols-[200px_minmax(0,_1fr)]" >
147+ <!-- File tree -->
148+ <aside class =" file-tree border-r-0 border-b border-border bg-subtle/30 py-3 font-mono text-[12px] md:border-b-0 md:border-r" >
149+ <ul class =" space-y-0.5" >
150+ <!-- Users folder header -->
151+ <li >
152+ <div class =" flex items-center gap-1.5 px-3 py-1 text-muted-foreground" >
153+ <svg width =" 11" height =" 11" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2.5" stroke-linecap =" round" stroke-linejoin =" round" aria-hidden =" true" >
154+ <polyline points =" 6 9 12 15 18 9" ></polyline >
155+ </svg >
156+ <svg width =" 13" height =" 13" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2" stroke-linecap =" round" stroke-linejoin =" round" aria-hidden =" true" class =" text-primary/70" >
157+ <path d =" M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
158+ </svg >
159+ <span class =" text-foreground" >Users</span >
160+ </div >
161+ </li >
162+
163+ <!-- RegisterUser/ — expanded, active feature folder -->
164+ <li class =" relative" >
165+ <!-- Vertical indent guide -->
166+ <span aria-hidden =" true" class =" pointer-events-none absolute left-[26px] top-7 bottom-1 w-px bg-border" ></span >
167+
168+ <div class =" flex items-center gap-1.5 px-3 py-1 pl-5 text-foreground" >
169+ <svg width =" 11" height =" 11" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2.5" stroke-linecap =" round" stroke-linejoin =" round" aria-hidden =" true" >
170+ <polyline points =" 6 9 12 15 18 9" ></polyline >
171+ </svg >
172+ <svg width =" 13" height =" 13" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2" stroke-linecap =" round" stroke-linejoin =" round" aria-hidden =" true" class =" text-primary" >
173+ <path d =" M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
174+ </svg >
175+ <span class =" font-medium" >RegisterUser</span >
176+ </div >
124177
125- <!-- Code panels (no EC frame) -->
126- { files .map ((f , i ) => (
127- <div data-cf-panel = { i } class = { ` cf-panel ${i === 0 ? ' ' : ' hidden' } ` } >
128- <Code code = { f .code } lang = " csharp" frame = " none" />
178+ <ul class =" ml-2" >
179+ { files .map ((f , i ) => (
180+ <li >
181+ <button
182+ type = " button"
183+ data-vsa-tab = { i }
184+ aria-selected = { i === 0 ? ' true' : ' false' }
185+ class = " vsa-file group flex w-full items-center gap-2 rounded-md px-3 py-1 pl-9 text-left text-muted-foreground transition-colors aria-selected:bg-primary/10 aria-selected:text-primary hover:bg-foreground/5"
186+ >
187+ <svg width = " 12" height = " 12" viewBox = " 0 0 24 24" fill = " none" stroke = " currentColor" stroke-width = " 2" stroke-linecap = " round" stroke-linejoin = " round" aria-hidden = " true" class = " opacity-70" >
188+ <path d = " M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
189+ <polyline points = " 14 2 14 8 20 8" ></polyline >
190+ </svg >
191+ <span class = " truncate" >{ f .short } </span >
192+ </button >
193+ </li >
194+ ))}
195+ </ul >
196+ </li >
197+
198+ <!-- Other feature folders (collapsed, visual only) -->
199+ { otherFeatures .map ((feature ) => (
200+ <li >
201+ <div class = " flex items-center gap-1.5 rounded-md px-3 py-1 pl-5 text-muted-foreground/80 transition-colors hover:bg-foreground/[0.04]" >
202+ <svg width = " 11" height = " 11" viewBox = " 0 0 24 24" fill = " none" stroke = " currentColor" stroke-width = " 2.5" stroke-linecap = " round" stroke-linejoin = " round" aria-hidden = " true" class = " opacity-60" >
203+ <polyline points = " 9 18 15 12 9 6" ></polyline >
204+ </svg >
205+ <svg width = " 13" height = " 13" viewBox = " 0 0 24 24" fill = " none" stroke = " currentColor" stroke-width = " 2" stroke-linecap = " round" stroke-linejoin = " round" aria-hidden = " true" class = " opacity-70" >
206+ <path d = " M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
207+ </svg >
208+ <span class = " truncate" >{ feature } </span >
209+ </div >
210+ </li >
211+ ))}
212+ </ul >
213+ </aside >
214+
215+ <!-- Code panels -->
216+ <div class =" relative" >
217+ { files .map ((f , i ) => (
218+ <div data-vsa-panel = { i } class = { ` vsa-panel ${i === 0 ? ' ' : ' hidden' } ` } >
219+ <Code code = { f .code } lang = " csharp" frame = " none" />
220+ </div >
221+ ))}
129222 </div >
130- )) }
223+ </ div >
131224
132225 <!-- Status footer -->
133- <div class =" flex items-center justify-between border-t border-border bg-subtle/30 px-4 py-2 font-mono text-[10.5px] text-muted-foreground" >
226+ <div class =" flex items-center justify-between border-t border-border bg-subtle/40 px-4 py-2 font-mono text-[10.5px] text-muted-foreground" >
134227 <span class =" inline-flex items-center gap-3" >
135228 <span class =" inline-flex items-center gap-1.5" ><span class =" size-1.5 rounded-full bg-primary" ></span >vsa</span >
136229 <span class =" hidden sm:inline" >utf-8 · LF · spaces: 4</span >
137230 </span >
138- <span data-cf -lineinfo >{ files [0 ].lines } lines</span >
231+ <span data-vsa -lineinfo >{ files [0 ].lines } lines</span >
139232 </div >
140233 </div >
141234 </div >
@@ -144,74 +237,87 @@ const files = [
144237</section >
145238
146239<style >
147- /* Tab active-state — green underline that animates between tabs. We use
148- box-shadow as the underline so we can transition it smoothly, and add
149- a subtle background tint on the active tab. */
150- .cf-tab[aria-selected='true'] {
151- background-color: var(--card);
152- box-shadow: inset 0 -2px 0 0 var(--primary);
153- }
154- .cf-tab[aria-selected='false']:hover {
155- color: var(--foreground);
156- background-color: color-mix(in oklab, var(--foreground) 4%, transparent);
157- }
158-
159- /* Window glow — appears on hover. Sits behind the frame and uses the brand
160- gradient. Designed to feel subtle, not the cliché "neon glow" trope. */
161- .code-window-glow {
240+ /* Hover glow behind the editor — same idiom as the rest of the site */
241+ .editor-glow {
162242 position: absolute;
163243 inset: -1px;
164244 border-radius: 0.85rem;
165245 padding: 1px;
166- background: linear-gradient(135deg, transparent, color-mix(in oklab, var(--primary) 28 %, transparent), transparent);
246+ background: linear-gradient(135deg, transparent, color-mix(in oklab, var(--primary) 30 %, transparent), transparent);
167247 -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
168248 -webkit-mask-composite: xor;
169249 mask-composite: exclude;
170250 opacity: 0;
171251 transition: opacity 320ms ease;
172252 pointer-events: none;
173253 }
174- .code-window :hover .code-window -glow {
254+ .editor :hover .editor -glow {
175255 opacity: 1;
176256 }
177257
178- /* Tame Expressive Code's default outer frame so it integrates with our
179- custom chrome cleanly. Targeting only inside this section to avoid
180- bleeding into docs pages. */
181- .code-window-frame :global(.expressive-code) {
258+ /* Active file row: subtle left-border accent that animates in */
259+ .vsa-file[aria-selected='true'] {
260+ position: relative;
261+ }
262+ .vsa-file[aria-selected='true']::before {
263+ content: '';
264+ position: absolute;
265+ left: 0;
266+ top: 4px;
267+ bottom: 4px;
268+ width: 2px;
269+ border-radius: 0 2px 2px 0;
270+ background: var(--primary);
271+ }
272+
273+ /* Tame Expressive Code inside the panel (no chrome, just code body) */
274+ .vsa-panel :global(.expressive-code) {
182275 margin: 0;
183- border-radius: 0;
184276 }
185- .code-window-frame :global(.expressive-code pre) {
186- border: 0;
187- border-radius: 0;
277+ .vsa-panel :global(.expressive-code pre) {
278+ border: 0 !important ;
279+ border-radius: 0 !important ;
188280 background-color: var(--card) !important;
189281 }
190- .code-window-frame :global(.expressive-code .frame.has-title) {
191- --code-background: var(--card);
282+ .vsa-panel :global(.expressive-code pre code) {
283+ padding-block: 1rem !important;
284+ padding-inline: 1.25rem !important;
192285 }
193- /* Reduced motion */
286+
194287 @media (prefers-reduced-motion: reduce) {
195- .code-window -glow { transition: none; }
288+ .editor -glow { transition: none; }
196289 }
197290</style >
198291
199- <script is:inline define:vars ={ { lineCounts: files .map (f => f .lines ) }} >
200- (() => {
201- const tabs = document.querySelectorAll('[data-cf-tab]');
202- const panels = document.querySelectorAll('[data-cf-panel]');
203- const info = document.querySelector('[data-cf-lineinfo]');
292+ <script is:inline >
293+ (function () {
294+ var lineCounts = [9, 7, 10];
295+ var fileNames = [
296+ 'RegisterUserEndpoint.cs',
297+ 'RegisterUserCommandHandler.cs',
298+ 'RegisterUserCommandValidator.cs',
299+ ];
300+
204301 function select(idx) {
205- tabs.forEach((t) => t.setAttribute('aria-selected', t.getAttribute('data-cf-tab') === String(idx) ? 'true' : 'false'));
206- panels.forEach((p) => {
207- if (p.getAttribute('data-cf-panel') === String(idx)) p.classList.remove('hidden');
302+ document.querySelectorAll('[data-vsa-tab]').forEach(function (t) {
303+ t.setAttribute(
304+ 'aria-selected',
305+ t.getAttribute('data-vsa-tab') === String(idx) ? 'true' : 'false',
306+ );
307+ });
308+ document.querySelectorAll('[data-vsa-panel]').forEach(function (p) {
309+ if (p.getAttribute('data-vsa-panel') === String(idx)) p.classList.remove('hidden');
208310 else p.classList.add('hidden');
209311 });
210- if (info) info.textContent = `${lineCounts[idx]} lines`;
312+ var info = document.querySelector('[data-vsa-lineinfo]');
313+ if (info) info.textContent = lineCounts[idx] + ' lines';
314+ var path = document.querySelector('[data-vsa-path]');
315+ if (path) path.textContent = fileNames[idx];
211316 }
212- tabs.forEach((t) => {
213- const idx = t.getAttribute('data-cf-tab');
214- t.addEventListener('click', () => select(Number(idx)));
317+
318+ document.querySelectorAll('[data-vsa-tab]').forEach(function (t) {
319+ var idx = t.getAttribute('data-vsa-tab');
320+ t.addEventListener('click', function () { select(Number(idx)); });
215321 });
216322 })();
217323</script >
0 commit comments