feat: add environment-based CSS theming with configurable colors and branding
This commit is contained in:
+19
-1
@@ -1 +1,19 @@
|
|||||||
VITE_API_BASE_URL=http://localhost:5000
|
# ===========================================
|
||||||
|
# Secuird UI Configuration
|
||||||
|
# ===========================================
|
||||||
|
# Copy this file to .env.local for local development
|
||||||
|
# or use mode-specific env files (.env.development, .env.staging, .env.production)
|
||||||
|
|
||||||
|
# API Configuration
|
||||||
|
VITE_API_BASE_URL=https://api.gatehouse.local/api/v1
|
||||||
|
|
||||||
|
# Theme Configuration
|
||||||
|
# Options: default (teal), dev (red), preprod (purple)
|
||||||
|
VITE_THEME=default
|
||||||
|
|
||||||
|
# Optional: Override app name and favicon
|
||||||
|
# VITE_APP_NAME=Secuird
|
||||||
|
# VITE_FAVICON=/favicon.svg
|
||||||
|
|
||||||
|
# Host Configuration
|
||||||
|
VITE_ALLOWED_HOSTS=ui.gatehouse.local,localhost
|
||||||
+15
-15
@@ -5,37 +5,37 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
<!-- Primary Meta Tags -->
|
<!-- Primary Meta Tags -->
|
||||||
<title>Secuird — Enterprise Identity & Access Management</title>
|
<title>%VITE_APP_NAME% — Enterprise Identity & Access Management</title>
|
||||||
<meta name="title" content="Secuird — Enterprise Identity & Access Management" />
|
<meta name="title" content="%VITE_APP_NAME% — Enterprise Identity & Access Management" />
|
||||||
<meta name="description" content="Secuird unifies social logins, MFA, and SSH certificate management in one platform. Enable enterprise SSO with Microsoft 365, Google Workspace, and GitHub without complex federation. Eliminate SSH key chaos with short-lived certificates tied to verified identities." />
|
<meta name="description" content="%VITE_APP_NAME% unifies social logins, MFA, and SSH certificate management in one platform. Enable enterprise SSO with Microsoft 365, Google Workspace, and GitHub without complex federation. Eliminate SSH key chaos with short-lived certificates tied to verified identities." />
|
||||||
<meta name="keywords" content="identity management, access management, SSO, single sign-on, SSH certificates, MFA, multi-factor authentication, OIDC, enterprise security, Microsoft 365, Google Workspace, GitHub authentication" />
|
<meta name="keywords" content="identity management, access management, SSO, single sign-on, SSH certificates, MFA, multi-factor authentication, OIDC, enterprise security, Microsoft 365, Google Workspace, GitHub authentication" />
|
||||||
<meta name="author" content="Secuird" />
|
<meta name="author" content="%VITE_APP_NAME%" />
|
||||||
<meta name="robots" content="index, follow" />
|
<meta name="robots" content="index, follow" />
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="%VITE_FAVICON%" />
|
||||||
<link rel="apple-touch-icon" href="/gatehouse-logo.svg" />
|
<link rel="apple-touch-icon" href="%VITE_FAVICON%" />
|
||||||
|
|
||||||
<!-- Open Graph / Facebook -->
|
<!-- Open Graph / Facebook -->
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:url" content="https://securd.com/" />
|
<meta property="og:url" content="https://securd.com/" />
|
||||||
<meta property="og:title" content="Secuird — Enterprise Identity & Access Management" />
|
<meta property="og:title" content="%VITE_APP_NAME% — Enterprise Identity & Access Management" />
|
||||||
<meta property="og:description" content="Secuird unifies social logins, MFA, and SSH certificate management in one platform. Enable enterprise SSO without complex federation. Eliminate SSH key chaos with short-lived certificates." />
|
<meta property="og:description" content="%VITE_APP_NAME% unifies social logins, MFA, and SSH certificate management in one platform. Enable enterprise SSO without complex federation. Eliminate SSH key chaos with short-lived certificates." />
|
||||||
<meta property="og:image" content="/og-image.png" />
|
<meta property="og:image" content="/og-image.png" />
|
||||||
<meta property="og:site_name" content="Secuird" />
|
<meta property="og:site_name" content="%VITE_APP_NAME%" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
|
|
||||||
<!-- Twitter -->
|
<!-- Twitter -->
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta name="twitter:url" content="https://securd.com/" />
|
<meta name="twitter:url" content="https://securd.com/" />
|
||||||
<meta name="twitter:title" content="Secuird — Enterprise Identity & Access Management" />
|
<meta name="twitter:title" content="%VITE_APP_NAME% — Enterprise Identity & Access Management" />
|
||||||
<meta name="twitter:description" content="Secuird unifies social logins, MFA, and SSH certificate management in one platform. Enable enterprise SSO without complex federation. Eliminate SSH key chaos with short-lived certificates." />
|
<meta name="twitter:description" content="%VITE_APP_NAME% unifies social logins, MFA, and SSH certificate management in one platform. Enable enterprise SSO without complex federation. Eliminate SSH key chaos with short-lived certificates." />
|
||||||
<meta name="twitter:image" content="/og-image.png" />
|
<meta name="twitter:image" content="/og-image.png" />
|
||||||
<meta name="twitter:site" content="@securd" />
|
<meta name="twitter:site" content="@securd" />
|
||||||
|
|
||||||
<!-- Theme color -->
|
<!-- Theme color -->
|
||||||
<meta name="theme-color" content="#36b9a6" />
|
<meta name="theme-color" content="%VITE_THEME_COLOR%" />
|
||||||
<meta name="msapplication-TileColor" content="#36b9a6" />
|
<meta name="msapplication-TileColor" content="%VITE_THEME_COLOR%" />
|
||||||
|
|
||||||
<!-- Canonical URL -->
|
<!-- Canonical URL -->
|
||||||
<link rel="canonical" href="https://securd.com/" />
|
<link rel="canonical" href="https://securd.com/" />
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
{
|
{
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "SoftwareApplication",
|
"@type": "SoftwareApplication",
|
||||||
"name": "Secuird",
|
"name": "%VITE_APP_NAME%",
|
||||||
"applicationCategory": "SecurityApplication",
|
"applicationCategory": "SecurityApplication",
|
||||||
"operatingSystem": "Web",
|
"operatingSystem": "Web",
|
||||||
"description": "Enterprise identity and access management platform providing secure authentication, organization-level security policy, SSH certificate management, and OIDC-based Single Sign-On.",
|
"description": "Enterprise identity and access management platform providing secure authentication, organization-level security policy, SSH certificate management, and OIDC-based Single Sign-On.",
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
{
|
{
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "Organization",
|
"@type": "Organization",
|
||||||
"name": "Secuird",
|
"name": "%VITE_APP_NAME%",
|
||||||
"url": "https://securd.com",
|
"url": "https://securd.com",
|
||||||
"logo": "https://securd.com/gatehouse-logo.svg",
|
"logo": "https://securd.com/gatehouse-logo.svg",
|
||||||
"sameAs": [
|
"sameAs": [
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<!-- Background - Dev theme color (Red #ef4444) -->
|
||||||
|
<rect width="24" height="24" rx="3" fill="#ef4444"/>
|
||||||
|
|
||||||
|
<!-- Left pillar -->
|
||||||
|
<path d="M4 4h3v16H4V4z" fill="#ffffff"/>
|
||||||
|
|
||||||
|
<!-- Right pillar -->
|
||||||
|
<path d="M17 4h3v16h-3V4z" fill="#ffffff"/>
|
||||||
|
|
||||||
|
<!-- Archway -->
|
||||||
|
<path d="M7 4h10v3H7V4z" fill="#ffffff" opacity="0.7"/>
|
||||||
|
|
||||||
|
<!-- Keyhole -->
|
||||||
|
<circle cx="12" cy="14" r="2" fill="#ffffff" opacity="0.5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 496 B |
@@ -0,0 +1,16 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<!-- Background - Preprod theme color (Purple #a855f7) -->
|
||||||
|
<rect width="24" height="24" rx="3" fill="#a855f7"/>
|
||||||
|
|
||||||
|
<!-- Left pillar -->
|
||||||
|
<path d="M4 4h3v16H4V4z" fill="#ffffff"/>
|
||||||
|
|
||||||
|
<!-- Right pillar -->
|
||||||
|
<path d="M17 4h3v16h-3V4z" fill="#ffffff"/>
|
||||||
|
|
||||||
|
<!-- Archway -->
|
||||||
|
<path d="M7 4h10v3H7V4z" fill="#ffffff" opacity="0.7"/>
|
||||||
|
|
||||||
|
<!-- Keyhole -->
|
||||||
|
<circle cx="12" cy="14" r="2" fill="#ffffff" opacity="0.5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 503 B |
@@ -1,6 +1,7 @@
|
|||||||
import { Link, Outlet, useLocation } from "react-router-dom";
|
import { Link, Outlet, useLocation } from "react-router-dom";
|
||||||
import { SecuirdLogo as GatehouseLogo } from "@/components/branding/SecuirdLogo";
|
import { SecuirdLogo as GatehouseLogo } from "@/components/branding/SecuirdLogo";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { config } from "@/config";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
Shield,
|
Shield,
|
||||||
@@ -35,7 +36,7 @@ return (
|
|||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<Link to="/" className="flex items-center gap-2.5">
|
<Link to="/" className="flex items-center gap-2.5">
|
||||||
<GatehouseLogo size="md" />
|
<GatehouseLogo size="md" />
|
||||||
<span className="text-xl font-semibold text-foreground tracking-tight">Secuird</span>
|
<span className="text-xl font-semibold text-foreground tracking-tight">{config.app.name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Desktop Navigation */}
|
{/* Desktop Navigation */}
|
||||||
@@ -130,7 +131,7 @@ return (
|
|||||||
<div className="col-span-2 lg:col-span-1">
|
<div className="col-span-2 lg:col-span-1">
|
||||||
<Link to="/" className="flex items-center gap-2.5">
|
<Link to="/" className="flex items-center gap-2.5">
|
||||||
<GatehouseLogo size="sm" />
|
<GatehouseLogo size="sm" />
|
||||||
<span className="text-lg font-semibold text-foreground tracking-tight">Secuird</span>
|
<span className="text-lg font-semibold text-foreground tracking-tight">{config.app.name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
<p className="mt-4 text-sm text-muted-foreground max-w-xs">
|
<p className="mt-4 text-sm text-muted-foreground max-w-xs">
|
||||||
Enterprise identity and access management. Secure by design, simple by choice.
|
Enterprise identity and access management. Secure by design, simple by choice.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Shield, Smartphone, Fingerprint, AlertTriangle, CheckCircle, Loader2 }
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { config } from '@/config';
|
||||||
import { AddPasskeyWizard } from '@/components/security/AddPasskeyWizard';
|
import { AddPasskeyWizard } from '@/components/security/AddPasskeyWizard';
|
||||||
import { TotpEnrollmentWizard } from '@/components/security/TotpEnrollmentWizard';
|
import { TotpEnrollmentWizard } from '@/components/security/TotpEnrollmentWizard';
|
||||||
import { api } from '@/lib/api';
|
import { api } from '@/lib/api';
|
||||||
@@ -98,7 +99,7 @@ export default function MfaEnforcementLayout() {
|
|||||||
<header className="h-14 border-b border-border bg-card flex items-center justify-between px-4 flex-shrink-0">
|
<header className="h-14 border-b border-border bg-card flex items-center justify-between px-4 flex-shrink-0">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Shield className="w-5 h-5 text-primary" />
|
<Shield className="w-5 h-5 text-primary" />
|
||||||
<span className="font-semibold text-foreground">Secuird</span>
|
<span className="font-semibold text-foreground">{config.app.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Outlet, Link } from "react-router-dom";
|
import { Outlet, Link } from "react-router-dom";
|
||||||
import { SecuirdLogo } from "@/components/branding/SecuirdLogo";
|
import { SecuirdLogo } from "@/components/branding/SecuirdLogo";
|
||||||
|
import { config } from "@/config";
|
||||||
|
|
||||||
export default function PublicLayout() {
|
export default function PublicLayout() {
|
||||||
return (
|
return (
|
||||||
@@ -12,7 +13,7 @@ export default function PublicLayout() {
|
|||||||
<div className="max-w-md mx-auto">
|
<div className="max-w-md mx-auto">
|
||||||
<Link to="/" className="flex items-center gap-2.5 justify-center">
|
<Link to="/" className="flex items-center gap-2.5 justify-center">
|
||||||
<SecuirdLogo size="md" />
|
<SecuirdLogo size="md" />
|
||||||
<span className="text-xl font-semibold text-foreground tracking-tight">Secuird</span>
|
<span className="text-xl font-semibold text-foreground tracking-tight">{config.app.name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -28,7 +29,7 @@ export default function PublicLayout() {
|
|||||||
<footer className="relative z-10 py-6 px-4">
|
<footer className="relative z-10 py-6 px-4">
|
||||||
<div className="max-w-md mx-auto text-center">
|
<div className="max-w-md mx-auto text-center">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
© {new Date().getFullYear()} Secuird. Identity & Access.
|
© {new Date().getFullYear()} {config.app.name}. Identity & Access.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { SecuirdLogo } from "@/components/branding/SecuirdLogo";
|
|||||||
import { NavLink } from "@/components/NavLink";
|
import { NavLink } from "@/components/NavLink";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
import { useOrg } from "@/contexts/OrgContext";
|
import { useOrg } from "@/contexts/OrgContext";
|
||||||
|
import { config } from "@/config";
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
@@ -104,7 +105,7 @@ export function AppSidebar() {
|
|||||||
<SecuirdLogo size="sm" variant="light" />
|
<SecuirdLogo size="sm" variant="light" />
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<span className="text-lg font-semibold text-sidebar-foreground tracking-tight">
|
<span className="text-lg font-semibold text-sidebar-foreground tracking-tight">
|
||||||
Secuird
|
{config.app.name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -221,7 +222,7 @@ export function AppSidebar() {
|
|||||||
<SidebarFooter className="p-4 border-t border-sidebar-border">
|
<SidebarFooter className="p-4 border-t border-sidebar-border">
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<div className="text-xs text-sidebar-muted">
|
<div className="text-xs text-sidebar-muted">
|
||||||
{import.meta.env.VITE_APP_VERSION ?? 'Secuird'}
|
{import.meta.env.VITE_APP_VERSION ?? config.app.name}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
|
|||||||
+63
-2
@@ -4,6 +4,60 @@
|
|||||||
// Base URL without /api/v1 suffix - used for CLI sign URL
|
// Base URL without /api/v1 suffix - used for CLI sign URL
|
||||||
const BASE_URL = import.meta.env.VITE_API_BASE_URL || "http://192.168.64.7:8888";
|
const BASE_URL = import.meta.env.VITE_API_BASE_URL || "http://192.168.64.7:8888";
|
||||||
|
|
||||||
|
// Theme configuration from environment
|
||||||
|
export type ThemeName = "default" | "dev" | "preprod";
|
||||||
|
|
||||||
|
interface ThemeColors {
|
||||||
|
primary: string;
|
||||||
|
primaryForeground: string;
|
||||||
|
sidebarPrimary: string;
|
||||||
|
sidebarPrimaryForeground: string;
|
||||||
|
ring: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const THEME_COLORS: Record<ThemeName, ThemeColors> = {
|
||||||
|
default: {
|
||||||
|
primary: "173 65% 36%", // teal
|
||||||
|
primaryForeground: "0 0% 100%",
|
||||||
|
sidebarPrimary: "173 65% 36%",
|
||||||
|
sidebarPrimaryForeground: "0 0% 100%",
|
||||||
|
ring: "173 65% 36%",
|
||||||
|
},
|
||||||
|
dev: {
|
||||||
|
primary: "0 72% 51%", // red
|
||||||
|
primaryForeground: "0 0% 100%",
|
||||||
|
sidebarPrimary: "0 72% 51%",
|
||||||
|
sidebarPrimaryForeground: "0 0% 100%",
|
||||||
|
ring: "0 72% 51%",
|
||||||
|
},
|
||||||
|
preprod: {
|
||||||
|
primary: "270 70% 55%", // purple
|
||||||
|
primaryForeground: "0 0% 100%",
|
||||||
|
sidebarPrimary: "270 70% 55%",
|
||||||
|
sidebarPrimaryForeground: "0 0% 100%",
|
||||||
|
ring: "270 70% 55%",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const THEME_DEFAULTS: Record<ThemeName, { appName: string; favicon: string }> = {
|
||||||
|
default: {
|
||||||
|
appName: "Secuird",
|
||||||
|
favicon: "/favicon.svg",
|
||||||
|
},
|
||||||
|
dev: {
|
||||||
|
appName: "Secuird Dev",
|
||||||
|
favicon: "/favicon-dev.svg",
|
||||||
|
},
|
||||||
|
preprod: {
|
||||||
|
appName: "Secuird Staging",
|
||||||
|
favicon: "/favicon-staging.svg",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const themeName = (import.meta.env.VITE_THEME || "default") as ThemeName;
|
||||||
|
const themeColors = THEME_COLORS[themeName] || THEME_COLORS.default;
|
||||||
|
const themeDefaults = THEME_DEFAULTS[themeName] || THEME_DEFAULTS.default;
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
// API Configuration
|
// API Configuration
|
||||||
api: {
|
api: {
|
||||||
@@ -13,10 +67,17 @@ export const config = {
|
|||||||
// Sign URL for CLI (same as base URL, no /api/v1)
|
// Sign URL for CLI (same as base URL, no /api/v1)
|
||||||
signUrl: BASE_URL,
|
signUrl: BASE_URL,
|
||||||
|
|
||||||
// App metadata
|
// App metadata - can be overridden per-theme or via env
|
||||||
app: {
|
app: {
|
||||||
name: "Secuird",
|
name: import.meta.env.VITE_APP_NAME || themeDefaults.appName,
|
||||||
description: "Identity & Access Platform",
|
description: "Identity & Access Platform",
|
||||||
|
favicon: import.meta.env.VITE_FAVICON || themeDefaults.favicon,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Theme configuration for CSS variable injection
|
||||||
|
theme: {
|
||||||
|
name: themeName,
|
||||||
|
colors: themeColors,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Feature flags
|
// Feature flags
|
||||||
|
|||||||
+15
-13
@@ -1,5 +1,7 @@
|
|||||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||||
|
|
||||||
|
@import './styles/theme-vars.css';
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
@@ -7,7 +9,7 @@
|
|||||||
/* Secuird Design System - Enterprise Identity & Access Platform
|
/* Secuird Design System - Enterprise Identity & Access Platform
|
||||||
Authoritative, infrastructure-grade aesthetic with slate/charcoal/muted blue palette
|
Authoritative, infrastructure-grade aesthetic with slate/charcoal/muted blue palette
|
||||||
Colors are HSL for theming flexibility
|
Colors are HSL for theming flexibility
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
@@ -21,9 +23,9 @@
|
|||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 222 47% 9%;
|
--popover-foreground: 222 47% 9%;
|
||||||
|
|
||||||
/* Primary — teal, fully saturated, dark enough to read on white */
|
/* Primary — theme-configurable (teal default, red for dev, purple for staging) */
|
||||||
--primary: 173 65% 36%;
|
--primary: var(--theme-primary);
|
||||||
--primary-foreground: 0 0% 100%;
|
--primary-foreground: var(--theme-primary-foreground);
|
||||||
|
|
||||||
/* Secondary — cool blue-gray, clearly darker than bg */
|
/* Secondary — cool blue-gray, clearly darker than bg */
|
||||||
--secondary: 216 20% 91%;
|
--secondary: 216 20% 91%;
|
||||||
@@ -33,9 +35,9 @@
|
|||||||
--muted: 216 18% 88%;
|
--muted: 216 18% 88%;
|
||||||
--muted-foreground: 222 18% 42%;
|
--muted-foreground: 222 18% 42%;
|
||||||
|
|
||||||
/* Accent — same teal as primary */
|
/* Accent — same as primary */
|
||||||
--accent: 173 65% 36%;
|
--accent: var(--theme-primary);
|
||||||
--accent-foreground: 0 0% 100%;
|
--accent-foreground: var(--theme-primary-foreground);
|
||||||
|
|
||||||
/* Semantic */
|
/* Semantic */
|
||||||
--destructive: 0 72% 48%;
|
--destructive: 0 72% 48%;
|
||||||
@@ -53,24 +55,24 @@
|
|||||||
/* UI chrome */
|
/* UI chrome */
|
||||||
--border: 216 18% 84%; /* clearly visible on white card */
|
--border: 216 18% 84%; /* clearly visible on white card */
|
||||||
--input: 216 18% 92%;
|
--input: 216 18% 92%;
|
||||||
--ring: 173 65% 36%;
|
--ring: var(--theme-ring);
|
||||||
|
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
|
|
||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
--sidebar-background: 222 30% 95%;
|
--sidebar-background: 222 30% 95%;
|
||||||
--sidebar-foreground: 222 47% 18%;
|
--sidebar-foreground: 222 47% 18%;
|
||||||
--sidebar-primary: 173 65% 36%;
|
--sidebar-primary: var(--theme-sidebar-primary);
|
||||||
--sidebar-primary-foreground: 0 0% 100%;
|
--sidebar-primary-foreground: var(--theme-sidebar-primary-foreground);
|
||||||
--sidebar-accent: 216 20% 88%;
|
--sidebar-accent: 216 20% 88%;
|
||||||
--sidebar-accent-foreground: 222 47% 9%;
|
--sidebar-accent-foreground: 222 47% 9%;
|
||||||
--sidebar-border: 216 18% 84%;
|
--sidebar-border: 216 18% 84%;
|
||||||
--sidebar-ring: 173 65% 36%;
|
--sidebar-ring: var(--theme-ring);
|
||||||
--sidebar-muted: 222 20% 48%;
|
--sidebar-muted: 222 20% 48%;
|
||||||
|
|
||||||
/* Gradients */
|
/* Gradients */
|
||||||
--gradient-brand: linear-gradient(135deg, hsl(173 65% 36%), hsl(173 65% 28%));
|
--gradient-brand: linear-gradient(135deg, hsl(var(--theme-primary)), hsl(var(--theme-primary) / 0.8));
|
||||||
--gradient-accent: linear-gradient(135deg, hsl(173 65% 36%), hsl(173 65% 28%));
|
--gradient-accent: linear-gradient(135deg, hsl(var(--theme-primary)), hsl(var(--theme-primary) / 0.8));
|
||||||
--gradient-subtle: linear-gradient(135deg, hsl(216 28% 97%), hsl(216 18% 93%));
|
--gradient-subtle: linear-gradient(135deg, hsl(216 28% 97%), hsl(216 18% 93%));
|
||||||
|
|
||||||
/* Shadows — stronger alpha so cards lift off the bg */
|
/* Shadows — stronger alpha so cards lift off the bg */
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/* Default theme - Secuird (Teal) */
|
||||||
|
@theme {
|
||||||
|
--color-primary: hsl(173 65% 36%);
|
||||||
|
--color-primary-foreground: hsl(0 0% 100%);
|
||||||
|
--color-sidebar-primary: hsl(173 65% 36%);
|
||||||
|
--color-sidebar-primary-foreground: hsl(0 0% 100%);
|
||||||
|
--color-ring: hsl(173 65% 36%);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/* Dev theme - Red */
|
||||||
|
@theme {
|
||||||
|
--color-primary: hsl(0 72% 51%);
|
||||||
|
--color-primary-foreground: hsl(0 0% 100%);
|
||||||
|
--color-sidebar-primary: hsl(0 72% 51%);
|
||||||
|
--color-sidebar-primary-foreground: hsl(0 0% 100%);
|
||||||
|
--color-ring: hsl(0 72% 51%);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/* Preprod theme - Purple */
|
||||||
|
@theme {
|
||||||
|
--color-primary: hsl(270 70% 55%);
|
||||||
|
--color-primary-foreground: hsl(0 0% 100%);
|
||||||
|
--color-sidebar-primary: hsl(270 70% 55%);
|
||||||
|
--color-sidebar-primary-foreground: hsl(0 0% 100%);
|
||||||
|
--color-ring: hsl(270 70% 55%);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/* Theme variables - values injected by Vite at build time */
|
||||||
|
:root {
|
||||||
|
--theme-primary: %%VITE_THEME_PRIMARY%%;
|
||||||
|
--theme-primary-foreground: 0 0% 100%;
|
||||||
|
--theme-sidebar-primary: %%VITE_THEME_SIDEBAR_PRIMARY%%;
|
||||||
|
--theme-sidebar-primary-foreground: 0 0% 100%;
|
||||||
|
--theme-ring: %%VITE_THEME_RING%%;
|
||||||
|
--theme-color: %%VITE_THEME_COLOR%%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/* Theme configuration - injected at runtime from config */
|
||||||
|
|
||||||
|
const themeConfig = {
|
||||||
|
default: {
|
||||||
|
primary: "173 65% 36%",
|
||||||
|
sidebarPrimary: "173 65% 36%",
|
||||||
|
ring: "173 65% 36%",
|
||||||
|
},
|
||||||
|
dev: {
|
||||||
|
primary: "0 72% 51%",
|
||||||
|
sidebarPrimary: "0 72% 51%",
|
||||||
|
ring: "0 72% 51%",
|
||||||
|
},
|
||||||
|
preprod: {
|
||||||
|
primary: "270 70% 55%",
|
||||||
|
sidebarPrimary: "270 70% 55%",
|
||||||
|
ring: "270 70% 55%",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const themeName = import.meta.env.VITE_THEME || "default";
|
||||||
|
const theme = themeConfig[themeName as keyof typeof themeConfig] || themeConfig.default;
|
||||||
|
|
||||||
|
const root = document.documentElement;
|
||||||
|
root.style.setProperty("--primary", theme.primary);
|
||||||
|
root.style.setProperty("--primary-foreground", "0 0% 100%");
|
||||||
|
root.style.setProperty("--sidebar-primary", theme.sidebarPrimary);
|
||||||
|
root.style.setProperty("--sidebar-primary-foreground", "0 0% 100%");
|
||||||
|
root.style.setProperty("--ring", theme.ring);
|
||||||
+45
-1
@@ -6,6 +6,19 @@ import { componentTagger } from "lovable-tagger";
|
|||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, process.cwd(), "");
|
const env = loadEnv(mode, process.cwd(), "");
|
||||||
|
|
||||||
|
const themeName = env.VITE_THEME || "default";
|
||||||
|
const themeColors: Record<string, { primary: string; sidebarPrimary: string; ring: string }> = {
|
||||||
|
default: { primary: "173 65% 36%", sidebarPrimary: "173 65% 36%", ring: "173 65% 36%" },
|
||||||
|
dev: { primary: "0 72% 51%", sidebarPrimary: "0 72% 51%", ring: "0 72% 51%" },
|
||||||
|
preprod: { primary: "270 70% 55%", sidebarPrimary: "270 70% 55%", ring: "270 70% 55%" },
|
||||||
|
};
|
||||||
|
const colors = themeColors[themeName] || themeColors.default;
|
||||||
|
|
||||||
|
const appName = env.VITE_APP_NAME || (themeName === "dev" ? "Secuird Dev" : themeName === "preprod" ? "Secuird Staging" : "Secuird");
|
||||||
|
const favicon = env.VITE_FAVICON || (themeName === "dev" ? "/favicon-dev.svg" : themeName === "preprod" ? "/favicon-staging.svg" : "/favicon.svg");
|
||||||
|
|
||||||
|
const themeColor = `hsl(${colors.primary})`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
server: {
|
server: {
|
||||||
host: "::",
|
host: "::",
|
||||||
@@ -15,7 +28,38 @@ export default defineConfig(({ mode }) => {
|
|||||||
"gatehouse-ui.hawkvelt.tech",
|
"gatehouse-ui.hawkvelt.tech",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
plugins: [react(), mode === "development" && componentTagger()].filter(Boolean),
|
plugins: [
|
||||||
|
react(),
|
||||||
|
mode === "development" && componentTagger(),
|
||||||
|
{
|
||||||
|
name: "vite-plugin-theme-injection",
|
||||||
|
transformIndexHtml: {
|
||||||
|
order: "pre",
|
||||||
|
handler: (html) => {
|
||||||
|
return html
|
||||||
|
.replace(/%VITE_APP_NAME%/g, appName)
|
||||||
|
.replace(/%VITE_FAVICON%/g, favicon)
|
||||||
|
.replace(/%VITE_THEME_COLOR%/g, themeColor);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
css: {
|
||||||
|
additionalData: `
|
||||||
|
:root {
|
||||||
|
--theme-primary: ${colors.primary};
|
||||||
|
--theme-primary-foreground: 0 0% 100%;
|
||||||
|
--theme-sidebar-primary: ${colors.sidebarPrimary};
|
||||||
|
--theme-sidebar-primary-foreground: 0 0% 100%;
|
||||||
|
--theme-ring: ${colors.ring};
|
||||||
|
--theme-color: hsl(${colors.primary});
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
|||||||
Reference in New Issue
Block a user