Customisation Guide — UI03 ShieldOps Cybersecurity
Theme structure — the one-file promise
99% of visual customisation happens in one file: src/app/globals.css. The
three-tier OKLCH token system below is the single source of truth for every
colour, spacing, motion, and font binding in the kit.
Tier 1 — Primitives Tier 2 — Semantic aliases Tier 3 — Component tokens
( --p-*, raw OKLCH ) → ( --critical, --brand- → ( --background, --card,
accent, --safe, … ) --foreground, --border, … )
Primitives are the only place raw OKLCH values live. Change a primitive and it cascades through the semantic aliases to every component token and every Tailwind utility.
Where to edit
| You want to change… | Edit | Notes |
|---|---|---|
| The severity palette (critical/high/warning/info/safe) | globals.css SECTION A | Change the --p-red-500 / --p-orange-500 / --p-amber-400 / --p-cyan-500 / --p-green-500 primitives. Every badge, chart, alert, and border updates in one pass. |
| Light-mode background / text / borders | globals.css SECTION B | Adjust the --p-slate-* primitives. Component tokens (--background, --card, --border, …) derive from these. |
| Dark-mode background / text / borders | globals.css SECTION C | Adjust the --p-ink-* primitives. Same cascade, dark-side. |
| The brand accent (acid green) | globals.css --p-accent-400/500/600/700 | Update the four lightness levels and the glow primitives beneath them. --brand-accent points at --p-accent-400 to match the allocation HTML's lead swatch. |
| Border radius | globals.css SECTION D | --radius: 0.375rem — change once, every corner updates. |
| Motion timing / easing | globals.css --duration-* / --ease-* | --duration-normal, --ease-default (cubic-bezier(0.22, 1, 0.36, 1) / outExpo), etc. |
| Fonts | src/app/layout.tsx (import) + globals.css @theme inline (binding) | See "Typography" below. |
| Brand name / emails / social / legal entity | src/config/site.ts | SITE_CONFIG.name, SITE_CONFIG.email.*, SITE_CONFIG.legal.*. Legal pages consume these. |
Finding the sections in globals.css
grep -n "SECTION A:" src/app/globals.css # severity tokens
grep -n "SECTION B:" src/app/globals.css # light mode
grep -n "SECTION C:" src/app/globals.css # dark mode
grep -n "SECTION D:" src/app/globals.css # border radius
Documented exceptions (raw hex allowed)
A handful of files intentionally hold literal hex values because CSS custom properties are unavailable at that execution point. Each carries a top-of-file comment explaining why. If you retheme the accent or critical colour, update these too:
| File | Why it's a raw literal |
|---|---|
src/app/global-error.tsx | Runs when the root layout crashes — globals.css has not loaded, so var(--p-accent-500) cannot resolve. |
src/app/icon.svg | Standalone SVG served as the favicon — no CSS context. |
src/components/sections/hero.tsx (HERO_STAR_OKLCH) | StarBorder component paints the glow in a shader/canvas context that cannot read CSS vars at paint time. |
src/components/sections/features-grid.tsx (SEV_OKLCH) | Same reason — card glow literals pinned to severity primitives. |
public/og-image.png | Static image baked at design time — regenerate with your brand colour if rebranding. |
src/components/ui/radar-bg.tsx was previously in this list; it now uses SVG
currentColor and cascades from --brand-accent automatically. Pass a
color prop if you want to override it on a per-instance basis.
Threat severity palette
The severity system is driven by 5 OKLCH tokens in src/app/globals.css.
Change these to retheme every badge, chart, alert, and border in one place:
/* src/app/globals.css — :root — SECTION A */
--critical: var(--p-red-500); /* #c5211f ≈ oklch(0.55 0.24 27) — critical threats (darkened for 4.5:1 white-text contrast) */
--high: var(--p-orange-500); /* #f97316 ≈ — high severity */
--warning: var(--p-amber-400); /* #f59e0b ≈ — medium/warning */
--info: var(--p-cyan-500); /* #06b6d4 ≈ — informational */
--safe: var(--p-green-500); /* #22c55e ≈ — healthy/resolved */
To change the critical colour to hot magenta:
--p-red-500: oklch(0.60 0.28 340); /* overrides everywhere --critical is used */
Use oklch.com to pick values.
Light / dark mode
Light (:root, SECTION B)
--p-slate-* primitives drive the entire light palette. Modify a primitive to
shift every component token that derives from it:
/* Lighten the page background slightly */
--p-slate-100: oklch(0.98 0.002 248); /* was 0.96 */
/* Darken the border to sharpen card edges */
--p-slate-300: oklch(0.68 0.010 248); /* was 0.72 — keep ≥3:1 on --p-slate-100 for WCAG 1.4.11 */
Dark (.dark, SECTION C)
Dark primitives live under .dark { --p-ink-*: … }:
.dark {
--p-ink-1000: oklch(0.06 0.018 260); /* slightly deeper navy */
}
Contrast guardrails
Light-mode --p-slate-300 (the token that feeds --border) must stay at
oklch(≤0.72 …) to maintain the 3:1 non-text contrast ratio WCAG 2.2
SC 1.4.11 requires. Light-mode --p-slate-500 (the token that feeds
--muted-foreground) must stay at oklch(≤0.52 …) for 4.5:1 small-text
contrast.
Typography
Three fonts are loaded via next/font/google in src/app/layout.tsx and
bound to CSS variables in globals.css:
const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], display: "swap", adjustFontFallback: true });
const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"], display: "swap", adjustFontFallback: true });
const chakraPetch = Chakra_Petch({ variable: "--font-chakra-petch", subsets: ["latin"], weight: ["400","600","700"], display: "swap", preload: false });
| Role | Font | CSS variable (layout) | @theme inline binding (globals.css) |
|---|---|---|---|
| Body text | Geist Sans | --font-geist-sans | --font-sans |
| Code / mono | Geist Mono | --font-geist-mono | --font-mono |
| Display headings | Chakra Petch | --font-chakra-petch | --font-display |
To swap a font:
- Replace the import + loader call in
src/app/layout.tsx. - Keep the
variable:name the same (or update the@theme inlinebinding inglobals.cssto match). - That's it — every
.font-display,font-mono, and the defaultfont-sansalready reads from the CSS variables.
The display font keeps preload: false because it's only used on headlines —
no need to block render on pages that don't use it.
Animation tuning
Lenis scroll speed
Edit src/components/providers/lenis-provider.tsx:
const lenis = new Lenis({
duration: 1.2, // lower = faster (0.8 for snappier feel)
});
Motion timing tokens
--duration-normal: 300ms;
--ease-default: cubic-bezier(0.22, 1, 0.36, 1); /* outExpo — matches globals.css */
Use these in Tailwind utilities or inline styles for consistent timing.
Branding strings
The central config file is src/config/site.ts.
Change SITE_CONFIG.name, SITE_CONFIG.url, SITE_CONFIG.email.*,
SITE_CONFIG.social.*, and SITE_CONFIG.legal.* first. This covers
metadata, legal entity references, OG tags, and the privacy / terms / GDPR
intro strings (they read SITE_CONFIG.legal.companyFull etc.).
Some pages carry the brand name inline in marketing copy, blog content, or
page-level metadata titles. After editing site.ts, do a project-wide
find-and-replace for "ShieldOps" across:
| Area | Files to edit |
|---|---|
| Header & Footer | src/components/layout/site-header.tsx, src/components/layout/site-footer.tsx |
| Dashboard sidebar | src/components/dashboard/dashboard-shell.tsx |
| Hero & marketing | src/components/sections/hero.tsx, src/components/sections/features-grid.tsx, src/components/sections/cta-section.tsx, src/components/sections/threat-preview.tsx |
| Product pages | src/components/product/product-page.tsx, src/app/product/*/page.tsx (5 files) |
| Blog | src/app/blog/page.tsx, src/app/blog/[slug]/page.tsx, src/components/sections/blog-article-data.ts |
| Auth pages | src/app/login/page.tsx, src/app/register/page.tsx, src/app/forgot-password/page.tsx |
| Legal pages | src/app/privacy/page.tsx, src/app/terms/page.tsx, src/app/gdpr/page.tsx, src/app/security/page.tsx |
| Contact & Careers | src/app/contact/layout.tsx, src/app/contact/page.tsx, src/app/careers/layout.tsx, src/app/careers/page.tsx |
| Docs & About | src/app/docs/page.tsx, src/app/about/page.tsx |
| Dashboard | src/app/dashboard/empty/page.tsx |
| Brand assets | src/app/icon.svg (favicon — see exceptions table above) |
Also replace shieldops.io with your domain in legal pages and config.
Adding / removing pages
Pages live under src/app/. Create a folder with a page.tsx to add a
route. Delete the folder to remove it from the build. The static export
only includes routes that exist.
Replacing the OG image
Replace public/og-image.png (1200×630) with your own branded social share
image. Used for Open Graph / Twitter card previews.