[READ-ONLY] Mirror of https://github.com/mrgnw/scripts.
0

Configure Feed

Select the types of activity you want to include in your feed.

include tree in output md; arg 2: output path

+603 -31
+129 -31
gitllm.py
··· 22 22 from rich.panel import Panel 23 23 from rich.syntax import Syntax 24 24 from rich.markdown import Markdown 25 - from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn 25 + from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn 26 26 from rich.tree import Tree 27 27 from rich import print as rprint 28 28 from tqdm.asyncio import tqdm as async_tqdm ··· 30 30 # Configuration 31 31 DEFAULT_BRANCH = 'main' 32 32 GITHUB_TOKEN = os.getenv('GITHUB_TOKEN') # Set your GitHub token in env variables 33 - OUTPUT_FILE = 'repo_structure.md' 33 + OUTPUT_FILE = 'repo_files.md' # Updated default file name 34 34 HEADERS = ( 35 35 {'Authorization': f'bearer {GITHUB_TOKEN}', 'Content-Type': 'application/json'} if GITHUB_TOKEN else {} 36 36 ) ··· 183 183 build_tree(file_tree, path_dict, True) 184 184 console.print(file_tree) 185 185 186 - console.print(f"\nFound [bold green]{len(matching_files)}[/] matching files out of [cyan]{len(all_files)}[/] total files.") 186 + console.print(f"\nFound [bold green]{len(matching_files)}[/] matching files. Fetching content...") 187 187 188 - return matching_files 188 + # Generate a markdown representation of the tree for the output file 189 + # Use GitHub-flavored markdown tree format 190 + md_tree_content = [] 191 + 192 + def build_md_tree(path_dict, prefix=""): 193 + items = sorted([(k, v) for k, v in path_dict.items() if k != "files"]) 194 + file_items = path_dict.get("files", []) 195 + 196 + # Process all items except the last one with ├── prefix 197 + for i, (key, value) in enumerate(items[:-1] if items else []): 198 + md_tree_content.append(f"{prefix}├── 📁 {key}/") 199 + new_prefix = f"{prefix}│ " 200 + build_md_tree(value, new_prefix) 201 + 202 + # Process the last directory item with └── prefix 203 + if items: 204 + key, value = items[-1] 205 + md_tree_content.append(f"{prefix}└── 📁 {key}/") 206 + new_prefix = f"{prefix} " 207 + build_md_tree(value, new_prefix) 208 + 209 + # Process files with appropriate prefixes 210 + sorted_files = sorted(file_items) 211 + for i, file in enumerate(sorted_files): 212 + ext = file.split('.')[-1] if '.' in file else '' 213 + icon = "📄" 214 + if ext in ['js', 'ts']: 215 + icon = "🟨" 216 + elif ext == 'json': 217 + icon = "🔧" 218 + elif ext == 'svelte': 219 + icon = "🔥" 220 + elif ext in ['md', 'markdown']: 221 + icon = "📝" 222 + 223 + # Use different prefix for last item 224 + if i == len(sorted_files) - 1: 225 + md_tree_content.append(f"{prefix}└── {icon} {file}") 226 + else: 227 + md_tree_content.append(f"{prefix}├── {icon} {file}") 228 + 229 + build_md_tree(path_dict) 230 + md_tree = "\n".join(md_tree_content) 231 + 232 + return matching_files, md_tree 189 233 190 234 191 235 async def fetch_content_async(repo, files, branch): 192 - """Fetch file contents asynchronously with a progress bar.""" 236 + """Fetch file contents asynchronously with individual progress bars for each file.""" 193 237 owner, name = repo.split('/') 194 238 195 - # Set up a rich progress display 239 + # Set up a rich progress display with multiple tasks 196 240 results = [] 197 241 198 - # Create custom progress display 242 + # Create multi-progress display 199 243 with Progress( 200 - SpinnerColumn(), 201 244 TextColumn("[bold blue]{task.description}"), 202 245 BarColumn(), 203 - TaskProgressColumn(), 204 - TextColumn("[cyan]{task.fields[file_path]}"), 246 + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), 247 + TextColumn("•"), 248 + TimeRemainingColumn(), 249 + console=console, 205 250 ) as progress: 206 - task = progress.add_task("[green]Downloading files...", total=len(files), file_path="") 251 + # Create a task for each file 252 + tasks = {} 253 + for i, file in enumerate(files): 254 + # Truncate file name if it's too long for display 255 + display_name = file['path'] 256 + if len(display_name) > 40: 257 + display_name = "..." + display_name[-37:] 258 + 259 + tasks[file['path']] = progress.add_task( 260 + f"[cyan]{display_name}[/]", 261 + total=1.0, 262 + completed=0.0 263 + ) 207 264 265 + # Process files with proper progress tracking 208 266 async with aiohttp.ClientSession() as session: 209 - for file in files: 210 - progress.update(task, advance=0, file_path=file['path']) 211 - try: 212 - content = await fetch_file_content_async(session, repo, file['path'], branch) 213 - results.append({ 214 - 'path': file['path'], 215 - 'content': content 216 - }) 217 - progress.update(task, advance=1) 218 - except Exception as e: 219 - console.print(f"[red]Error fetching {file['path']}: {e}[/]") 220 - progress.update(task, advance=1) 267 + # Process in batches to avoid hitting rate limits 268 + batch_size = 5 # Adjust based on API rate limits 269 + for i in range(0, len(files), batch_size): 270 + batch = files[i:i+batch_size] 271 + 272 + # Create tasks for each file in the batch 273 + batch_tasks = [] 274 + for file in batch: 275 + task = asyncio.create_task( 276 + fetch_file_with_progress( 277 + session, repo, file['path'], branch, 278 + progress, tasks[file['path']] 279 + ) 280 + ) 281 + batch_tasks.append(task) 282 + 283 + # Wait for this batch to complete 284 + batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True) 285 + 286 + # Process results from this batch 287 + for j, result in enumerate(batch_results): 288 + file_path = batch[j]['path'] 289 + if isinstance(result, Exception): 290 + console.print(f"[red]Error fetching {file_path}: {result}[/]") 291 + # Ensure progress bar is completed even on error 292 + progress.update(tasks[file_path], completed=1.0) 293 + else: 294 + results.append({ 295 + 'path': file_path, 296 + 'content': result 297 + }) 221 298 222 299 return results 300 + 301 + 302 + async def fetch_file_with_progress(session, repo, path, branch, progress, task_id): 303 + """Fetch a single file with progress tracking.""" 304 + # First update to show we're starting 305 + progress.update(task_id, completed=0.1) 306 + 307 + try: 308 + # Fetch the file content 309 + content = await fetch_file_content_async(session, repo, path, branch) 310 + # Mark as complete 311 + progress.update(task_id, completed=1.0) 312 + return content 313 + except Exception as e: 314 + progress.update(task_id, completed=1.0) 315 + raise e 223 316 224 317 225 318 async def fetch_file_content_async(session, repo, path, branch='main'): ··· 251 344 def parse_args(): 252 345 parser = argparse.ArgumentParser(description='Generate a structure document from GitHub repository files') 253 346 parser.add_argument('repo', nargs='?', help='GitHub repository in format owner/repo') 347 + parser.add_argument('output', nargs='?', default=OUTPUT_FILE, help='Output markdown file path') 254 348 parser.add_argument('--repo', dest='repo_option', help='GitHub repository in format owner/repo') 255 349 parser.add_argument('--branch', default='main', help='Repository branch (default: main)') 256 - parser.add_argument('--output', default=OUTPUT_FILE, help='Output markdown file') 257 350 parser.add_argument('--patterns', nargs='+', help='File patterns to include') 258 351 parser.add_argument('--all', action='store_true', help='Include all js/ts/svelte/json files') 259 352 parser.add_argument('--preview', action='store_true', help='Preview the markdown in terminal') ··· 300 393 301 394 try: 302 395 console.print(f"[bold]Fetching repository structure for [green]{args.repo}[/] (branch: [yellow]{args.branch}[/])...") 303 - matching_files = get_all_files(args.repo, args.branch) 396 + matching_files, md_tree = get_all_files(args.repo, args.branch) 304 397 305 398 if not matching_files: 306 399 console.print("[yellow]No files matched the specified patterns.[/]") 307 400 return 308 401 309 - console.print(f"Found [bold green]{len(matching_files)}[/] matching files. Fetching content...") 402 + # No need to repeat the file count here - removed redundant message 310 403 311 404 # Process files in sorted order for consistent output 312 405 matching_files.sort(key=lambda f: f['path']) ··· 314 407 # Fetch file contents asynchronously 315 408 file_contents = await fetch_content_async(args.repo, matching_files, args.branch) 316 409 317 - # Check if we actually got content for all files 318 - console.print(f"Successfully fetched content for [bold green]{len(file_contents)}[/] of [cyan]{len(matching_files)}[/] files") 410 + # Check if we actually got content for all files - simplified message 411 + console.print(f"Successfully fetched [bold green]{len(file_contents)}/{len(matching_files)}[/] files") 319 412 320 413 # Extract repository name for the header 321 414 repo_name = args.repo.split('/')[1] 415 + 416 + # Generate markdown with tree structure at the top - more GitHub-friendly format 322 417 md_content = f'# {repo_name} Project Structure\n\n' 418 + md_content += "## File Tree\n\n" 419 + md_content += md_tree 420 + md_content += "\n\n## File Contents\n\n" 323 421 324 422 for file_data in file_contents: 325 423 file_path = file_data['path'] 326 424 content = file_data['content'] 327 425 # Add language-specific syntax highlighting 328 426 extension = file_path.split('.')[-1] if '.' in file_path else '' 329 - md_content += f"## {file_path}\n\n```{extension}\n{content}\n```\n\n" 427 + md_content += f"### {file_path}\n\n```{extension}\n{content}\n```\n\n" 330 428 331 - Path(output_file).write_text(md_content, encoding='utf-8') 332 - console.print(f"[bold green]Generated:[/] {output_file}") 429 + Path(args.output).write_text(md_content, encoding='utf-8') 430 + console.print(f"[bold green]Generated:[/] {args.output}") 333 431 334 432 if args.preview: 335 433 console.print("\n[bold]Preview of the generated document:[/]")
+237
repo_files.md
··· 1 + # sk-view-transitions Project Structure 2 + 3 + ## File Tree 4 + 5 + - 📁 **src/** 6 + - 📁 **lib/** 7 + - 🟨 `data.js` 8 + - 🟨 `index.js` 9 + - 📁 **routes/** 10 + - 📁 **item/** 11 + - 📁 **[id]/** 12 + - 🔥 `+page.svelte` 13 + - 🔥 `+layout.svelte` 14 + - 🔥 `+page.svelte` 15 + - 🔧 `package.json` 16 + 17 + ## File Contents 18 + 19 + ### package.json 20 + 21 + ```json 22 + { 23 + "name": "view-transitions", 24 + "version": "0.0.1", 25 + "private": true, 26 + "scripts": { 27 + "dev": "vite dev", 28 + "build": "vite build", 29 + "preview": "vite preview" 30 + }, 31 + "devDependencies": { 32 + "@sveltejs/adapter-auto": "^2.0.0", 33 + "@sveltejs/kit": "^1.20.4", 34 + "autoprefixer": "^10.4.14", 35 + "postcss": "^8.4.24", 36 + "postcss-load-config": "^4.0.1", 37 + "svelte": "^4.0.5", 38 + "tailwindcss": "^3.3.2", 39 + "vite": "^4.4.2" 40 + }, 41 + "type": "module" 42 + } 43 + 44 + ``` 45 + 46 + ### src/lib/data.js 47 + 48 + ```js 49 + export default [ 50 + { 51 + id: '1', 52 + name: 'Ethereal Serenity', 53 + excerpt: 'Capturing Tranquility', 54 + description: 'This abstract art piece captures the essence of serenity and tranquility with its soft, flowing lines and calming pastel hues. It invites viewers to a world of peaceful contemplation.', 55 + }, 56 + { 57 + id: '2', 58 + name: 'Cosmic Whirlwind', 59 + excerpt: 'A Journey Through the Cosmos', 60 + description: 'A dynamic explosion of colors and shapes that resemble a cosmic whirlwind. This abstract art piece is a mesmerizing journey through the vastness of the universe.', 61 + }, 62 + { 63 + id: '3', 64 + name: 'Harmony in Chaos', 65 + excerpt: 'Order Amidst the Chaos', 66 + description: 'In the midst of chaos, there is a hidden order. "Harmony in Chaos" portrays the delicate balance of structured geometry within a chaotic and vibrant backdrop.', 67 + }, 68 + { 69 + id: '4', 70 + name: 'Surreal Dreamscape', 71 + excerpt: 'Where Reality Meets Imagination', 72 + description: 'Step into a surreal dreamscape where reality and imagination merge. This artwork will take you on a journey through the abstract and the extraordinary.', 73 + }, 74 + { 75 + id: '5', 76 + name: 'Emerald Mirage', 77 + excerpt: 'Journey to a Lush Oasis', 78 + description: 'Emerald Mirage is a shimmering sea of emerald and turquoise, evoking the serenity of a tranquil oasis hidden in the heart of a desert. It\'s a visual escape to a lush paradise.', 79 + }, 80 + { 81 + id: '6', 82 + name: 'Aurora Borealis Reverie', 83 + excerpt: 'Mimicking the Northern Lights', 84 + description: 'This abstract art piece mimics the ethereal beauty of the Northern Lights. The swirling colors and patterns create a vivid portrayal of a mystical celestial phenomenon.', 85 + }, 86 + { 87 + id: '7', 88 + name: 'Metamorphosis Unleashed', 89 + excerpt: 'Embracing Change and Possibilities', 90 + description: 'Metamorphosis Unleashed is a representation of change and transformation. The vivid and bold colors symbolize the limitless possibilities that come with embracing change.', 91 + }, 92 + { 93 + id: '8', 94 + name: 'Fragmented Realities', 95 + excerpt: 'Exploring Perceptions', 96 + description: 'Fragmented Realities delves into the fractured nature of our perception of the world. It explores the idea that our understanding of reality is composed of countless interconnected fragments.', 97 + }, 98 + ]; 99 + 100 + ``` 101 + 102 + ### src/lib/index.js 103 + 104 + ```js 105 + // place files you want to import through the `$lib` alias in this folder. 106 + 107 + ``` 108 + 109 + ### src/routes/+layout.svelte 110 + 111 + ```svelte 112 + <script> 113 + import '../app.postcss' 114 + import { onNavigate } from '$app/navigation' 115 + 116 + onNavigate((navigation) => { 117 + if (!document.startViewTransition) return 118 + 119 + return new Promise((resolve) => { 120 + document.startViewTransition(async () => { 121 + resolve() 122 + await navigation.complete 123 + }) 124 + }) 125 + }) 126 + </script> 127 + 128 + <div class="font-sans"> 129 + <header> 130 + <nav class="border-b"> 131 + <div class="container mx-auto px-6 lg:px-0 py-6"> 132 + <a class="text-teal-700 font-medium" href="/">ZIKEA</a> 133 + </div> 134 + </nav> 135 + </header> 136 + <div class="container mx-auto px-6 lg:px-0"> 137 + <slot /> 138 + </div> 139 + </div> 140 + 141 + ``` 142 + 143 + ### src/routes/+page.svelte 144 + 145 + ```svelte 146 + <script> 147 + import items from '$lib/data.js' 148 + </script> 149 + 150 + <section class="py-12"> 151 + <div class="w-full grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 gap-y-10"> 152 + {#each items as item} 153 + <a class="item text-slate-900 hover:text-teal-700" href="/item/{item.id}"> 154 + <img 155 + src={`/images/${item.id}.jpg`} 156 + class="object-cover rounded aspect-[4/3]" 157 + style={`view-transition-name: item-image-${item.id};`} 158 + alt={item.name} 159 + /> 160 + <h2 class="pt-4 font-semibold">{item.name}</h2> 161 + <p class="pt-1 text-gray-700">{item.excerpt}</p> 162 + </a> 163 + {/each} 164 + </div> 165 + </section> 166 + 167 + ``` 168 + 169 + ### src/routes/item/[id]/+page.svelte 170 + 171 + ```svelte 172 + <script> 173 + import { page } from '$app/stores' 174 + import items from '$lib/data.js' 175 + 176 + $: id = $page.params.id 177 + 178 + $: item = items.find((item) => item.id === id) 179 + 180 + $: otherItems = items.filter((item) => item.id !== id) 181 + </script> 182 + 183 + <section class="py-12"> 184 + <div class="flex flex-col lg:flex-row gap-10 lg:gap-20"> 185 + <img 186 + src={`/images/${item.id}.jpg`} 187 + class="object-cover rounded w-full max-w-md xl:max-w-2xl aspect-[4/3]" 188 + style={`view-transition-name: item-image-${item.id};`} 189 + alt={item.name} 190 + /> 191 + <div> 192 + <h1 class="text-5xl font-bold tracking-tight text-slate-900">{item.name}</h1> 193 + <p class="mt-3 text-xl text-gray-700">{item.excerpt}</p> 194 + <p class="mt-6 text-gray-600 max-w-xl">{item.description}</p> 195 + <button 196 + type="button" 197 + disabled 198 + class="mt-6 text-white bg-teal-700 hover:bg-teal-800 focus:ring-4 focus:outline-none focus:ring-teal-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center mr-2 dark:bg-teal-600 dark:hover:bg-teal-700 dark:focus:ring-teal-800" 199 + > 200 + <svg 201 + class="w-3.5 h-3.5 mr-2" 202 + aria-hidden="true" 203 + xmlns="http://www.w3.org/2000/svg" 204 + fill="currentColor" 205 + viewBox="0 0 18 21" 206 + > 207 + <path 208 + d="M15 12a1 1 0 0 0 .962-.726l2-7A1 1 0 0 0 17 3H3.77L3.175.745A1 1 0 0 0 2.208 0H1a1 1 0 0 0 0 2h.438l.6 2.255v.019l2 7 .746 2.986A3 3 0 1 0 9 17a2.966 2.966 0 0 0-.184-1h2.368c-.118.32-.18.659-.184 1a3 3 0 1 0 3-3H6.78l-.5-2H15Z" 209 + /> 210 + </svg> 211 + Coming Soon 212 + </button> 213 + </div> 214 + </div> 215 + </section> 216 + <section class="pb-12"> 217 + <h2 class="font-bold text-2xl text-slate-900 tracking-tight mb-12"> 218 + Explore More From Our Collection 219 + </h2> 220 + <div class="w-full grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 gap-y-10"> 221 + {#each otherItems as item} 222 + <a class="item text-slate-900 hover:text-teal-700" href="/item/{item.id}"> 223 + <img 224 + src={`/images/${item.id}.jpg`} 225 + class="object-cover rounded aspect-[4/3]" 226 + style={`view-transition-name: item-image-${item.id};`} 227 + alt={item.name} 228 + /> 229 + <h2 class="pt-4 font-semibold">{item.name}</h2> 230 + <p class="pt-1 text-gray-700">{item.excerpt}</p> 231 + </a> 232 + {/each} 233 + </div> 234 + </section> 235 + 236 + ``` 237 +
+237
sk-view-transitions.md
··· 1 + # sk-view-transitions Project Structure 2 + 3 + ## File Tree 4 + 5 + └── 📁 src/ 6 + ├── 📁 lib/ 7 + │ ├── 🟨 data.js 8 + │ └── 🟨 index.js 9 + └── 📁 routes/ 10 + └── 📁 item/ 11 + └── 📁 [id]/ 12 + └── 🔥 +page.svelte 13 + ├── 🔥 +layout.svelte 14 + └── 🔥 +page.svelte 15 + └── 🔧 package.json 16 + 17 + ## File Contents 18 + 19 + ### package.json 20 + 21 + ```json 22 + { 23 + "name": "view-transitions", 24 + "version": "0.0.1", 25 + "private": true, 26 + "scripts": { 27 + "dev": "vite dev", 28 + "build": "vite build", 29 + "preview": "vite preview" 30 + }, 31 + "devDependencies": { 32 + "@sveltejs/adapter-auto": "^2.0.0", 33 + "@sveltejs/kit": "^1.20.4", 34 + "autoprefixer": "^10.4.14", 35 + "postcss": "^8.4.24", 36 + "postcss-load-config": "^4.0.1", 37 + "svelte": "^4.0.5", 38 + "tailwindcss": "^3.3.2", 39 + "vite": "^4.4.2" 40 + }, 41 + "type": "module" 42 + } 43 + 44 + ``` 45 + 46 + ### src/lib/data.js 47 + 48 + ```js 49 + export default [ 50 + { 51 + id: '1', 52 + name: 'Ethereal Serenity', 53 + excerpt: 'Capturing Tranquility', 54 + description: 'This abstract art piece captures the essence of serenity and tranquility with its soft, flowing lines and calming pastel hues. It invites viewers to a world of peaceful contemplation.', 55 + }, 56 + { 57 + id: '2', 58 + name: 'Cosmic Whirlwind', 59 + excerpt: 'A Journey Through the Cosmos', 60 + description: 'A dynamic explosion of colors and shapes that resemble a cosmic whirlwind. This abstract art piece is a mesmerizing journey through the vastness of the universe.', 61 + }, 62 + { 63 + id: '3', 64 + name: 'Harmony in Chaos', 65 + excerpt: 'Order Amidst the Chaos', 66 + description: 'In the midst of chaos, there is a hidden order. "Harmony in Chaos" portrays the delicate balance of structured geometry within a chaotic and vibrant backdrop.', 67 + }, 68 + { 69 + id: '4', 70 + name: 'Surreal Dreamscape', 71 + excerpt: 'Where Reality Meets Imagination', 72 + description: 'Step into a surreal dreamscape where reality and imagination merge. This artwork will take you on a journey through the abstract and the extraordinary.', 73 + }, 74 + { 75 + id: '5', 76 + name: 'Emerald Mirage', 77 + excerpt: 'Journey to a Lush Oasis', 78 + description: 'Emerald Mirage is a shimmering sea of emerald and turquoise, evoking the serenity of a tranquil oasis hidden in the heart of a desert. It\'s a visual escape to a lush paradise.', 79 + }, 80 + { 81 + id: '6', 82 + name: 'Aurora Borealis Reverie', 83 + excerpt: 'Mimicking the Northern Lights', 84 + description: 'This abstract art piece mimics the ethereal beauty of the Northern Lights. The swirling colors and patterns create a vivid portrayal of a mystical celestial phenomenon.', 85 + }, 86 + { 87 + id: '7', 88 + name: 'Metamorphosis Unleashed', 89 + excerpt: 'Embracing Change and Possibilities', 90 + description: 'Metamorphosis Unleashed is a representation of change and transformation. The vivid and bold colors symbolize the limitless possibilities that come with embracing change.', 91 + }, 92 + { 93 + id: '8', 94 + name: 'Fragmented Realities', 95 + excerpt: 'Exploring Perceptions', 96 + description: 'Fragmented Realities delves into the fractured nature of our perception of the world. It explores the idea that our understanding of reality is composed of countless interconnected fragments.', 97 + }, 98 + ]; 99 + 100 + ``` 101 + 102 + ### src/lib/index.js 103 + 104 + ```js 105 + // place files you want to import through the `$lib` alias in this folder. 106 + 107 + ``` 108 + 109 + ### src/routes/+layout.svelte 110 + 111 + ```svelte 112 + <script> 113 + import '../app.postcss' 114 + import { onNavigate } from '$app/navigation' 115 + 116 + onNavigate((navigation) => { 117 + if (!document.startViewTransition) return 118 + 119 + return new Promise((resolve) => { 120 + document.startViewTransition(async () => { 121 + resolve() 122 + await navigation.complete 123 + }) 124 + }) 125 + }) 126 + </script> 127 + 128 + <div class="font-sans"> 129 + <header> 130 + <nav class="border-b"> 131 + <div class="container mx-auto px-6 lg:px-0 py-6"> 132 + <a class="text-teal-700 font-medium" href="/">ZIKEA</a> 133 + </div> 134 + </nav> 135 + </header> 136 + <div class="container mx-auto px-6 lg:px-0"> 137 + <slot /> 138 + </div> 139 + </div> 140 + 141 + ``` 142 + 143 + ### src/routes/+page.svelte 144 + 145 + ```svelte 146 + <script> 147 + import items from '$lib/data.js' 148 + </script> 149 + 150 + <section class="py-12"> 151 + <div class="w-full grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 gap-y-10"> 152 + {#each items as item} 153 + <a class="item text-slate-900 hover:text-teal-700" href="/item/{item.id}"> 154 + <img 155 + src={`/images/${item.id}.jpg`} 156 + class="object-cover rounded aspect-[4/3]" 157 + style={`view-transition-name: item-image-${item.id};`} 158 + alt={item.name} 159 + /> 160 + <h2 class="pt-4 font-semibold">{item.name}</h2> 161 + <p class="pt-1 text-gray-700">{item.excerpt}</p> 162 + </a> 163 + {/each} 164 + </div> 165 + </section> 166 + 167 + ``` 168 + 169 + ### src/routes/item/[id]/+page.svelte 170 + 171 + ```svelte 172 + <script> 173 + import { page } from '$app/stores' 174 + import items from '$lib/data.js' 175 + 176 + $: id = $page.params.id 177 + 178 + $: item = items.find((item) => item.id === id) 179 + 180 + $: otherItems = items.filter((item) => item.id !== id) 181 + </script> 182 + 183 + <section class="py-12"> 184 + <div class="flex flex-col lg:flex-row gap-10 lg:gap-20"> 185 + <img 186 + src={`/images/${item.id}.jpg`} 187 + class="object-cover rounded w-full max-w-md xl:max-w-2xl aspect-[4/3]" 188 + style={`view-transition-name: item-image-${item.id};`} 189 + alt={item.name} 190 + /> 191 + <div> 192 + <h1 class="text-5xl font-bold tracking-tight text-slate-900">{item.name}</h1> 193 + <p class="mt-3 text-xl text-gray-700">{item.excerpt}</p> 194 + <p class="mt-6 text-gray-600 max-w-xl">{item.description}</p> 195 + <button 196 + type="button" 197 + disabled 198 + class="mt-6 text-white bg-teal-700 hover:bg-teal-800 focus:ring-4 focus:outline-none focus:ring-teal-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center mr-2 dark:bg-teal-600 dark:hover:bg-teal-700 dark:focus:ring-teal-800" 199 + > 200 + <svg 201 + class="w-3.5 h-3.5 mr-2" 202 + aria-hidden="true" 203 + xmlns="http://www.w3.org/2000/svg" 204 + fill="currentColor" 205 + viewBox="0 0 18 21" 206 + > 207 + <path 208 + d="M15 12a1 1 0 0 0 .962-.726l2-7A1 1 0 0 0 17 3H3.77L3.175.745A1 1 0 0 0 2.208 0H1a1 1 0 0 0 0 2h.438l.6 2.255v.019l2 7 .746 2.986A3 3 0 1 0 9 17a2.966 2.966 0 0 0-.184-1h2.368c-.118.32-.18.659-.184 1a3 3 0 1 0 3-3H6.78l-.5-2H15Z" 209 + /> 210 + </svg> 211 + Coming Soon 212 + </button> 213 + </div> 214 + </div> 215 + </section> 216 + <section class="pb-12"> 217 + <h2 class="font-bold text-2xl text-slate-900 tracking-tight mb-12"> 218 + Explore More From Our Collection 219 + </h2> 220 + <div class="w-full grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 gap-y-10"> 221 + {#each otherItems as item} 222 + <a class="item text-slate-900 hover:text-teal-700" href="/item/{item.id}"> 223 + <img 224 + src={`/images/${item.id}.jpg`} 225 + class="object-cover rounded aspect-[4/3]" 226 + style={`view-transition-name: item-image-${item.id};`} 227 + alt={item.name} 228 + /> 229 + <h2 class="pt-4 font-semibold">{item.name}</h2> 230 + <p class="pt-1 text-gray-700">{item.excerpt}</p> 231 + </a> 232 + {/each} 233 + </div> 234 + </section> 235 + 236 + ``` 237 +