diff --git a/.astro/types.d.ts b/.astro/types.d.ts index 03d7cc4..f964fe0 100644 --- a/.astro/types.d.ts +++ b/.astro/types.d.ts @@ -1,2 +1 @@ /// <reference types="astro/client" /> -/// <reference path="content.d.ts" /> \ No newline at end of file diff --git a/config/README.md b/config/README.md new file mode 100644 index 0000000..5b865e0 --- /dev/null +++ b/config/README.md @@ -0,0 +1,56 @@ +# Site Configuration + +This directory contains configuration files for the website. Instead of hardcoding values in the TypeScript files, these JSON files are used to make the site more configurable. + +## Available Configuration Files + +- **site.json**: Contains global site metadata, menu structure, and text strings +- **services.json**: Contains homelab services organized by category +- **socials.json**: Contains social media profile configurations (the single source of truth for all social profiles) + +## How to Use + +To modify any configuration values, simply edit the appropriate JSON file. The changes will be reflected in the application without having to modify any TypeScript code. + +### Example: Updating Menu Items + +To add or remove a menu item, edit the `menu` section in `site.json`: + +```json +"menu": { + "home": "/", + "about": "/about", + "blog": "/blog", + "projects": "/projects", + "homelab": "/homelab", + "code": "https://code.justin.deal", + "new-page": "/new-page" +} +``` + +### Example: Adding a New Service + +To add a new service, find the appropriate category in `services.json` and add a new item: + +```json +"Media": [ + { + "name": "Jellyfin", + "link": "https://watch.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/jellyfin.svg", + "alt": "Jellyfin", + "tags": [] + }, + { + "name": "Plex", + "link": "https://plex.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/plex.svg", + "alt": "Plex Media Server", + "tags": ["media", "streaming"] + } +] +``` + +## Technical Implementation + +The configuration files are loaded from this directory using the `loadConfig` function in `src/lib/config.ts`. The function reads the JSON files, parses them, and caches the results to avoid reading the files multiple times. diff --git a/config/services.json b/config/services.json new file mode 100644 index 0000000..fe778a4 --- /dev/null +++ b/config/services.json @@ -0,0 +1,153 @@ +{ + "Websites": [ + { + "name": "justin.deal", + "link": "https://justin.deal", + "icon": "/pixel_avatar.png", + "alt": "Personal Website" + } + ], + "Utilities": [ + { + "name": "Silverbullet", + "link": "https://notes.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/png/silverbullet.png", + "alt": "Silverbullet", + "tags": ["notes", "markdown", "knowledge base"] + }, + { + "name": "Vikunja", + "link": "https://todo.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vikunja.svg", + "alt": "Vikunja", + "tags": ["todo", "tasks", "productivity"] + }, + { + "name": "Actual", + "link": "https://budget.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/actual-budget.svg", + "alt": "Actual", + "tags": ["finance", "budget", "money"] + }, + { + "name": "Searxng", + "link": "https://search.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/searxng.svg", + "alt": "Searxng", + "tags": ["search", "privacy", "metasearch"] + }, + { + "name": "BaiKal", + "link": "https://dav.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/png/baikal.png", + "alt": "BaiKal", + "tags": [] + }, + { + "name": "Cryptpad", + "link": "https://docs.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/cryptpad.svg", + "alt": "Cryptpad", + "tags": [] + } + ], + "Development": [ + { + "name": "Gitea", + "link": "https://code.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea.svg", + "alt": "Gitea", + "tags": [] + }, + { + "name": "OpenGist", + "link": "https://snippets.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/opengist.svg", + "alt": "OpenGist", + "tags": [] + }, + { + "name": "IT-Tools", + "link": "https://tools.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/it-tools.svg", + "alt": "IT-Tools", + "tags": [] + } + ], + "Media": [ + { + "name": "Jellyfin", + "link": "https://watch.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/jellyfin.svg", + "alt": "Jellyfin", + "tags": [] + }, + { + "name": "Calibre-Web", + "link": "https://books.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/calibre-web.svg", + "alt": "Calibre-Web", + "tags": [] + } + ], + "Analytics": [ + { + "name": "Uptime Kuma", + "link": "https://status.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/uptime-kuma.svg", + "alt": "Uptime Kuma", + "tags": [] + }, + { + "name": "Umami", + "link": "https://analytics.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/umami.svg", + "alt": "Umami", + "tags": [] + }, + { + "name": "TeslaMate", + "link": "https://tesla.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/teslamate.svg", + "alt": "TeslaMate", + "tags": [] + } + ], + "Infrastructure": [ + { + "name": "Pi-hole", + "link": "https://pihole.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/pi-hole.svg", + "alt": "Pi-hole", + "tags": [] + }, + { + "name": "Ntfy", + "link": "https://ntfy.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ntfy.svg", + "alt": "Ntfy", + "tags": [] + }, + { + "name": "Vaultwarden", + "link": "https://passwords.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vaultwarden.svg", + "alt": "Vaultwarden", + "tags": [] + }, + { + "name": "Authentik", + "link": "https://auth.justin.deal", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/authentik.svg", + "alt": "Authentik", + "tags": [] + }, + { + "name": "Traefik", + "link": "https://proxy.justin.deal:8080", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/traefik.svg", + "alt": "Traefik", + "tags": [] + } + ] +} diff --git a/config/site.json b/config/site.json new file mode 100644 index 0000000..2a694d2 --- /dev/null +++ b/config/site.json @@ -0,0 +1,32 @@ +{ + "username": "Justin Deal", + "rootUrl": "https://justin.deal", + "shortDescription": "My personal slice of the internet", + "longDescription": "My personal blog, portfolio, and homelab dashboard built with Astro, TypeScript, TailwindCSS, and Alpine.js.", + + "articlesName": "Articles", + "projectsName": "Projects", + "viewAll": "View All", + + "noArticles": "No featured articles yet.", + "noProjects": "No featured projects yet.", + + "blogTitle": "My Thoughts & Takes", + "blogShortDescription": "Practical wisdom, unfiltered thoughts, and hot takes.", + "blogLongDescription": "Web development, tech trends, and the occasional programming mishap.", + + "projectTitle": "Projects and Code", + "projectShortDescription": "A list of my web development projects and developer tools.", + "projectLongDescription": "All of my projects, including both frontend and full-stack applications.", + + "profileImage": "pixel_avatar.png", + + "menu": { + "home": "/", + "about": "/about", + "blog": "/blog", + "projects": "/projects", + "homelab": "/homelab", + "code": "https://code.justin.deal" + } +} diff --git a/config/socials.json b/config/socials.json new file mode 100644 index 0000000..23c1382 --- /dev/null +++ b/config/socials.json @@ -0,0 +1,29 @@ +[ + { + "name": "Gitea", + "url": "https://code.justin.deal/dealjus", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea-dark.svg", + "iconLight": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea-light.svg", + "alt": "Gitea Profile", + "showInFooter": true, + "showInAbout": true + }, + { + "name": "LinkedIn", + "url": "https://www.linkedin.com/in/justin-deal/", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/linkedin-dark.svg", + "iconLight": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/linkedin-light.svg", + "alt": "LinkedIn Profile", + "showInFooter": true, + "showInAbout": true + }, + { + "name": "GitHub", + "url": "https://github.com/justintdeal", + "iconDark": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/github-dark.svg", + "iconLight": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/github-light.svg", + "alt": "GitHub Profile", + "showInFooter": true, + "showInAbout": true + } +] diff --git a/src/components/Footer.astro b/src/components/Footer.astro index 4354dad..7290f8b 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -20,7 +20,8 @@ const footerSocials = socials.filter(social => social.showInFooter); <SocialIcon name={social.name} url={social.url} - icon={social.icon} + iconDark={social.iconDark} + iconLight={social.iconLight} alt={social.alt} /> ))} diff --git a/src/components/common/SocialIcon.astro b/src/components/common/SocialIcon.astro index 54ddf43..db1ce08 100644 --- a/src/components/common/SocialIcon.astro +++ b/src/components/common/SocialIcon.astro @@ -4,12 +4,18 @@ import Anchor from "./Anchor.astro"; export interface Props { name: string; url: string; - icon: string; + iconDark?: string; + iconLight?: string; + icon?: string; // For backward compatibility alt: string; size?: "sm" | "md" | "lg"; } -const { name, url, icon, alt, size = "md" } = Astro.props; +const { name, url, iconDark, iconLight, icon, alt, size = "md" } = Astro.props; + +// Use provided icons or fallback to the legacy icon prop +const darkIcon = iconDark || icon; +const lightIcon = iconLight || icon; const sizeClasses = { sm: "w-6 h-6", @@ -19,9 +25,28 @@ const sizeClasses = { --- <Anchor url={url} aria-label={alt}> + <!-- Dark icon shown in light theme --> <img - src={icon} + src={darkIcon} alt={alt} - class={`${sizeClasses[size]} zag-transition`} + class={`${sizeClasses[size]} light-theme-only zag-transition`} + /> + <!-- Light icon shown in dark theme --> + <img + src={lightIcon} + alt={alt} + class={`${sizeClasses[size]} dark-theme-only zag-transition`} /> </Anchor> + +<style> + /* Hide dark icon in dark theme */ + :global(.dark-theme) .light-theme-only { + display: none; + } + + /* Hide light icon in light theme */ + :global(.light-theme) .dark-theme-only { + display: none; + } +</style> diff --git a/src/lib/config.ts b/src/lib/config.ts new file mode 100644 index 0000000..f1de7fe --- /dev/null +++ b/src/lib/config.ts @@ -0,0 +1,97 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// Get the directory of the current module +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +// Path to the config directory (two levels up from lib directory) +const configDir = path.resolve(__dirname, '../../config'); + +/** + * Cache to avoid loading config files multiple times + */ +const configCache = new Map<string, any>(); + +/** + * Load and parse a JSON configuration file + * @param name The name of the config file without extension + * @returns The parsed configuration object + */ +export function loadConfig<T>(name: string): T { + // If the config is already in cache, return it + if (configCache.has(name)) { + return configCache.get(name) as T; + } + + try { + // Load the config file + const configPath = path.join(configDir, `${name}.json`); + const configData = fs.readFileSync(configPath, 'utf-8'); + const config = JSON.parse(configData); + + // Cache the result + configCache.set(name, config); + + return config as T; + } catch (error) { + console.error(`Error loading config '${name}':`, error); + throw new Error(`Failed to load config: ${name}`); + } +} + +/** + * Site configuration type + */ +export interface SiteConfig { + username: string; + rootUrl: string; + shortDescription: string; + longDescription: string; + articlesName: string; + projectsName: string; + viewAll: string; + noArticles: string; + noProjects: string; + blogTitle: string; + blogShortDescription: string; + blogLongDescription: string; + projectTitle: string; + projectShortDescription: string; + projectLongDescription: string; + profileImage: string; + menu: Record<string, string>; +} + +/** + * Service category type (from types.ts) + */ +export type ServiceCategory = Record<string, Service[]>; + +/** + * Service type (based on types.ts) + */ +export interface Service { + name: string; + link: string; + icon: string; + alt: string; + tags?: string[]; +} + +/** + * Social media type (based on types.ts) + */ +export interface SocialMedia { + name: string; + url: string; + iconDark: string; + iconLight: string; + alt: string; + showInFooter: boolean; + showInAbout: boolean; +} + +// Convenience exports for commonly used configs +export const site = loadConfig<SiteConfig>('site'); +export const services = loadConfig<ServiceCategory>('services'); +export const socials = loadConfig<SocialMedia[]>('socials'); diff --git a/src/lib/socials.ts b/src/lib/socials.ts index 754c732..2b4cfe8 100644 --- a/src/lib/socials.ts +++ b/src/lib/socials.ts @@ -1,31 +1,12 @@ import { type SocialMedia } from "./types"; +import { socials as socialsConfig } from "./config"; /** * Social media profiles + * This is now loaded from a JSON configuration file + * located at /config/socials.json + * + * To modify social profiles, edit that JSON file rather than + * modifying the values here. */ -export const socials: SocialMedia[] = [ - { - name: "Gitea", - url: "https://code.justin.deal/dealjus", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea-dark.svg", - alt: "Gitea Profile", - showInFooter: true, - showInAbout: true - }, - { - name: "LinkedIn", - url: "https://www.linkedin.com/in/justin-deal/", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/linkedin-dark.svg", - alt: "LinkedIn Profile", - showInFooter: true, - showInAbout: true - }, - { - name: "GitHub", - url: "https://github.com/justintdeal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/github-dark.svg", - alt: "GitHub Profile", - showInFooter: true, - showInAbout: true - } -]; +export const socials: SocialMedia[] = socialsConfig; diff --git a/src/lib/types.ts b/src/lib/types.ts index 25ebffe..11b34cb 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -140,9 +140,16 @@ export type SocialMedia = { url: string; /** - * The URL to the icon from selfh.st/icons + * The URL to the dark version of the icon from selfh.st/icons + * Used in light theme */ - icon: string; + iconDark: string; + + /** + * The URL to the light version of the icon from selfh.st/icons + * Used in dark theme + */ + iconLight: string; /** * Alternative text for the icon diff --git a/src/lib/variables.ts b/src/lib/variables.ts index 7bebe10..8f5835f 100644 --- a/src/lib/variables.ts +++ b/src/lib/variables.ts @@ -1,72 +1,12 @@ -// Set any item to undefined to remove it from the site or to use the default value - /** * Global variables used throughout the site - * @property {string} username - The username displayed on the site - * @property {string} rootUrl - The root URL of the site - * @property {string} shortDescription - A short description of the site - * @property {string} longDescription - A longer description of the site - * @property {string} articlesName - The name used for articles - * @property {string} projectsName - The name used for projects - * @property {string} viewAll - The text used for "View All" links - * @property {string} noArticles - The text used when there are no articles - * @property {string} noProjects - The text used when there are no projects - * @property {string} blogTitle - The title of the blog section - * @property {string} blogShortDescription - A short description of the blog - * @property {string} blogLongDescription - A longer description of the blog - * @property {string} projectTitle - The title of the projects section - * @property {string} projectShortDescription - A short description of the projects - * @property {string} projectLongDescription - A longer description of the projects - * @property {string} profileImage - The profile image filename - * @property {string} githubProfile - The URL to the GitHub profile - * @property {string} linkedinProfile - The URL to the LinkedIn profile - * @property {string} giteaProfile - The URL to the Gitea profile - * @property {Object} menu - The menu items + * This is now loaded from a JSON configuration file + * located at /config/site.json + * + * To modify site configuration, edit that JSON file + * rather than modifying the values here. */ -export const GLOBAL = { - // Site metadata - username: "Justin Deal", - rootUrl: "https://justin.deal", - shortDescription: "My personal slice of the internet", - longDescription: "My personal blog, portfolio, and homelab dashboard built with Astro, TypeScript, TailwindCSS, and Alpine.js.", - - // Common text names used throughout the site - articlesName: "Articles", - projectsName: "Projects", - viewAll: "View All", - - // Common descriptions used throughout the site - noArticles: "No featured articles yet.", - noProjects: "No featured projects yet.", +import { site } from './config'; - // Blog metadata - blogTitle: "My Thoughts & Takes", - blogShortDescription: "Practical wisdom, unfiltered thoughts, and hot takes.", - blogLongDescription: "Web development, tech trends, and the occasional programming mishap.", - - // Project metadata - projectTitle: "Projects and Code", - projectShortDescription: "A list of my web development projects and developer tools.", - projectLongDescription: "All of my projects, including both frontend and full-stack applications.", - - // Profile image - profileImage: "pixel_avatar.png", - - // Social media profiles - githubProfile: "https://github.com/justindeal", - linkedinProfile: "https://www.linkedin.com/in/justin-deal/", - giteaProfile: "https://code.justin.deal/dealjus", - - // Menu items - menu: { - home: "/", - about: "/about", - blog: "/blog", - projects: "/projects", - homelab: "/homelab", - code: "https://code.justin.deal", - // videos: "https://www.youtube.com/@justin_deal", - // homelab: "https://homelab.justin.deal", - // contact: "/contact", - } -}; +// Re-export site configuration as GLOBAL for backward compatibility +export const GLOBAL = site; diff --git a/src/pages/about.astro b/src/pages/about.astro index 461bda3..527b8ba 100644 --- a/src/pages/about.astro +++ b/src/pages/about.astro @@ -299,7 +299,10 @@ const aboutSocials = socials.filter(social => social.showInAbout); <div class="flex flex-wrap gap-4"> {aboutSocials.map((social) => ( <a href={social.url} target="_blank" rel="noopener noreferrer" class="flex items-center gap-2 zag-bg-alt p-4 rounded-lg shadow-sm hover:zag-bg-accent-light transition-colors"> - <img src={social.icon} alt={social.alt} class="w-6 h-6 zag-text" /> + <!-- Dark icon for light theme --> + <img src={social.iconDark} alt={social.alt} class="w-6 h-6 zag-text light-theme-only" /> + <!-- Light icon for dark theme --> + <img src={social.iconLight} alt={social.alt} class="w-6 h-6 zag-text dark-theme-only" /> <span>{social.name}</span> </a> ))} diff --git a/src/pages/homelab/services.ts b/src/pages/homelab/services.ts index 80555ac..6c45e2c 100644 --- a/src/pages/homelab/services.ts +++ b/src/pages/homelab/services.ts @@ -1,158 +1,12 @@ import { type Service, type ServiceCategory } from "../../lib/types"; +import { services as servicesConfig } from "../../lib/config"; /** * Services available in the homelab, organized by category + * This is now loaded from a JSON configuration file + * located at /config/services.json + * + * To modify services, edit that JSON file rather than + * modifying the values here. */ -export const services: ServiceCategory = { - Websites: [ - { - name: "justin.deal", - link: "https://justin.deal", - icon: "/pixel_avatar.png", - alt: "Personal Website" - } - ], - Utilities: [ - { - name: "Silverbullet", - link: "https://notes.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/png/silverbullet.png", - alt: "Silverbullet", - tags: ["notes", "markdown", "knowledge base"] - }, - { - name: "Vikunja", - link: "https://todo.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vikunja.svg", - alt: "Vikunja", - tags: ["todo", "tasks", "productivity"] - }, - { - name: "Actual", - link: "https://budget.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/actual-budget.svg", - alt: "Actual", - tags: ["finance", "budget", "money"] - }, - { - name: "Searxng", - link: "https://search.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/searxng.svg", - alt: "Searxng", - tags: ["search", "privacy", "metasearch"] - }, - { - name: "BaiKal", - link: "https://dav.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/png/baikal.png", - alt: "BaiKal", - tags: [] - }, - { - name: "Cryptpad", - link: "https://docs.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/cryptpad.svg", - alt: "Cryptpad", - tags: [] - } - ], - Development: [ - { - name: "Gitea", - link: "https://code.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/gitea.svg", - alt: "Gitea", - tags: [] - }, - { - name: "OpenGist", - link: "https://snippets.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/opengist.svg", - alt: "OpenGist", - tags: [] - }, - { - name: "IT-Tools", - link: "https://tools.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/it-tools.svg", - alt: "IT-Tools", - tags: [] - } - ], - Media: [ - { - name: "Jellyfin", - link: "https://watch.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/jellyfin.svg", - alt: "Jellyfin", - tags: [] - }, - { - name: "Calibre-Web", - link: "https://books.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/calibre-web.svg", - alt: "Calibre-Web", - tags: [] - } - ], - Analytics: [ - { - name: "Uptime Kuma", - link: "https://status.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/uptime-kuma.svg", - alt: "Uptime Kuma", - tags: [] - }, - { - name: "Umami", - link: "https://analytics.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/umami.svg", - alt: "Umami", - tags: [] - }, - { - name: "TeslaMate", - link: "https://tesla.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/teslamate.svg", - alt: "TeslaMate", - tags: [] - }, - ], - Infrastructure: [ - { - name: "Pi-hole", - link: "https://pihole.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/pi-hole.svg", - alt: "Pi-hole", - tags: [] - }, - { - name: "Ntfy", - link: "https://ntfy.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ntfy.svg", - alt: "Ntfy", - tags: [] - }, - { - name: "Vaultwarden", - link: "https://passwords.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vaultwarden.svg", - alt: "Vaultwarden", - tags: [] - }, - { - name: "Authentik", - link: "https://auth.justin.deal", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/authentik.svg", - alt: "Authentik", - tags: [] - }, - { - name: "Traefik", - link: "https://proxy.justin.deal:8080", - icon: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/traefik.svg", - alt: "Traefik", - tags: [] - } - ] - }; +export const services: ServiceCategory = servicesConfig; diff --git a/src/styles/global.css b/src/styles/global.css index 6ff7db6..247cd24 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -394,6 +394,21 @@ } @layer utilities { + /* Theme-specific visibility utilities */ + .light-theme-only { + display: block; + :where(.dark, .dark *) & { + display: none; + } + } + + .dark-theme-only { + display: none; + :where(.dark, .dark *) & { + display: block; + } + } + /* Base backgrounds and text */ .zag-bg { background-color: var(--color-zag-light);