spb/svgarden Public
SVGarden — searchable bank of 74 self-contained SVG+CSS animation snippets (svgarden.dev)
HTML 79.2%
Astro 10.3%
JavaScript 6%
CSS 3.7%
Shell 0.8%
1---2/**3 * ============================================================4 * SVGarden — https://www.svgarden.dev5 * Author : Simon-Pierre Boucher6 * Contact: contact@spboucher.ai7 * File : src/layouts/Base.astro8 * Desc : Base layout — head, theme init, header nav, footer9 * ============================================================10 */11import '../styles/global.css';1213interface Props {14 title?: string;15 description?: string;16}1718const {19 title = 'SVGarden — SVG + CSS animation snippets',20 description = 'A searchable garden of self-contained SVG + CSS animations. Preview, customize, copy — every snippet works in a blank HTML file.',21} = Astro.props;2223const canonical = new URL(Astro.url.pathname, Astro.site);24const current = Astro.url.pathname;25---2627<!DOCTYPE html>28<html lang="en">29 <head>30 <meta charset="utf-8" />31 <meta name="viewport" content="width=device-width, initial-scale=1" />32 <title>{title}</title>33 <meta name="description" content={description} />34 <meta name="author" content="Simon-Pierre Boucher" />35 <link rel="canonical" href={canonical} />36 <link rel="icon" type="image/svg+xml" href="/favicon.svg" />37 <meta property="og:title" content={title} />38 <meta property="og:description" content={description} />39 <meta property="og:type" content="website" />40 <meta property="og:url" content={canonical} />41 <meta property="og:site_name" content="SVGarden" />42 <script is:inline>43 // Theme init — runs before paint to avoid a flash of the wrong theme.44 (() => {45 const stored = localStorage.getItem('sg-theme');46 const theme = stored ?? (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');47 document.documentElement.dataset.theme = theme;48 })();49 </script>50 </head>51 <body>52 <a class="sg-skip-link" href="#sg-main">Skip to content</a>53 <header class="sg-header">54 <div class="sg-container sg-header-inner">55 <a class="sg-logo" href="/" aria-label="SVGarden home">56 <svg width="26" height="26" viewBox="0 0 32 32" aria-hidden="true">57 <path d="M16 28 V14" stroke="var(--sg-accent)" stroke-width="2.5" stroke-linecap="round" fill="none" />58 <path d="M16 18 C 16 12, 10 12, 7 7 C 14 7, 17 11, 16 18 Z" fill="var(--sg-accent)" />59 <path d="M16 22 C 16 17, 21 17, 25 12 C 18 12, 15 16, 16 22 Z" fill="var(--sg-accent)" opacity="0.55" />60 </svg>61 SVGarden62 </a>63 <nav class="sg-nav" aria-label="Main">64 <a href="/" aria-current={current === '/' ? 'page' : undefined}>Gallery</a>65 <a href="/about" aria-current={current === '/about' ? 'page' : undefined}>About</a>66 <button class="sg-theme-toggle" type="button" aria-label="Toggle dark mode" data-sg-theme-toggle>67 <svg class="sg-icon-moon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">68 <path d="M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8Z" />69 </svg>70 <svg class="sg-icon-sun" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">71 <circle cx="12" cy="12" r="4" />72 <path d="M12 2v2m0 16v2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M2 12h2m16 0h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />73 </svg>74 </button>75 </nav>76 </div>77 </header>78 <main id="sg-main" class="sg-container">79 <slot />80 </main>81 <footer class="sg-footer">82 <div class="sg-container">83 © Simon-Pierre Boucher — <a href="mailto:contact@spboucher.ai">contact@spboucher.ai</a> — <a href="https://www.svgarden.dev">svgarden.dev</a>84 </div>85 </footer>86 <script>87 const toggle = document.querySelector('[data-sg-theme-toggle]');88 toggle?.addEventListener('click', () => {89 const next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';90 document.documentElement.dataset.theme = next;91 localStorage.setItem('sg-theme', next);92 });93 </script>94 </body>95</html>96