Skip to content

Repository files navigation

DevSafe — Build. Secure. Protect.

The official landing page for DevSafe, a software development and cybersecurity agency based in Cameroon that also builds and owns its own products (e.g. BookBridge). Built with SvelteKit and styled with Tailwind CSS v3 to deliver a high-contrast, premium "SOC dashboard" dark tech aesthetic.


🚀 Technology Stack

  • Framework: SvelteKit (latest stable with Runes)
  • Styling: Tailwind CSS v3 (using fluid grids, glassmorphism, and radial glows)
  • Icons: @lucide/svelte (packaged with custom brand SVGs for GitHub and WhatsApp)
  • Fonts:
    • Bricolage Grotesque (Headings)
    • Inter (Body text)
    • JetBrains Mono (Code blocks, technical tags, inputs)

✨ Features

  • Interactive Consultation Gateway: A custom glassmorphic request form with animated submit states ("Establishing Secure Link...") and feedback panels.
  • Scroll-Triggered Entrance Animations: A use:reveal action animates elements as they enter the viewport. Content is always server-rendered, and the hidden pre-animation state only applies when JS is running (html.js), so crawlers and no-JS visitors see everything.
  • SEO: The site is prerendered to static HTML. Seo.svelte handles the title, description, canonical, Open Graph and Twitter tags, schema.ts adds the JSON-LD graph (ProfessionalService in Yaoundé, plus BookBridge and Eventra), and sitemap.xml and robots.txt are generated at build time.
  • Sticky Glassmorphism Header: Responsive navigation bar with dynamic borders, a language toggle, and a hamburger menu below the lg breakpoint.
  • Client Work vs. Owned Products: Separate showcase sections for agency client work (e.g. Eventra) and DevSafe-owned products (e.g. BookBridge), rendered by a shared ProjectShowcase component.
  • Bilingual (English / French): English lives at / and French at /fr, with an FR/EN toggle in the header. Each version has its own <html lang>, canonical, hreflang alternates, og:locale and JSON-LD inLanguage, and both are listed in the sitemap.
  • Centralized Data Layer: All copy lives in src/lib/data/content/ (one file per language), avoiding hardcoded strings inside markup.

📁 Project Structure

src/
├── lib/
│   ├── actions/
│   │   └── reveal.ts         # SSR-safe scroll reveal action
│   ├── assets/
│   │   ├── devsafe-emblem.webp       # DS mask emblem (dark theme)
│   │   ├── devsafe-emblem-light.webp # Same emblem with dark strokes (light theme)
│   │   ├── Founder.jpg       # Profile picture for founder profile
│   │   └── screens/          # Product screenshots (WebP)
│   ├── components/
│   │   ├── BrandIcon.svelte  # GitHub / LinkedIn / WhatsApp SVG marks (Lucide has no brand icons)
│   │   ├── CTABanner.svelte  # Consultation form banner + WhatsApp button
│   │   ├── Footer.svelte     # Footer links and WhatsApp integration
│   │   ├── Hero.svelte       # Hero with the Cameroon hero photo (full colour)
│   │   ├── Navbar.svelte     # Responsive glass header & mobile drawer
│   │   ├── PhoneFrame.svelte      # Device frame for real product screenshots
│   │   ├── ProjectCard.svelte     # Single project card with product screenshot
│   │   ├── ProjectShowcase.svelte # Client work & products sections (+ NDA client card)
│   │   ├── SectionPhoto.svelte    # Decorative palette-tinted background photo
│   │   ├── SectionVideo.svelte    # Silent looping background video (WhyDevSafe)
│   │   ├── Seo.svelte        # <svelte:head> meta, canonical, OG, JSON-LD
│   │   ├── Services.svelte   # Bento grid of services
│   │   ├── TrustStrip.svelte # Awards and client proof points under the hero
│   │   ├── Team.svelte       # Founders photo, bios, highlights, LinkedIn/GitHub
│   │   ├── WhatsAppButton.svelte  # Floating WhatsApp chat button
│   │   └── WhyDevSafe.svelte # Corporate differentiator grids
│   ├── config/
│   │   └── site.ts           # Site URL, languages, locale helpers, indexable routes
│   ├── data/content/
│   │   ├── en.ts             # English copy (source of truth for the content shape)
│   │   ├── fr.ts             # French copy, typed as SiteContent
│   │   ├── types.ts          # Shared content types
│   │   └── index.ts          # t() / getContent(lang) helpers
│   └── seo/
│       ├── schema.ts         # JSON-LD structured data
│       ├── sitemap.ts        # Sitemap XML renderer (hreflang alternates, escaping)
│       └── sitemap-routes.ts # Static routes + dynamic collections published in the sitemap
├── routes/
│   ├── +layout.svelte        # Imports global CSS & favicon link
│   ├── +layout.ts            # prerender = true for the whole site
│   ├── [[lang=lang]]/        # Home page for / (en) and /fr (fr)
│   │   ├── +page.svelte      # Main assembly page with SEO headers
│   │   └── +page.ts          # Prerender entries for /fr
│   ├── robots.txt/+server.ts # Prerendered robots.txt
│   └── sitemap.xml/+server.ts# Prerendered sitemap (from sitemap-routes.ts)
├── params/lang.ts            # Route matcher: only "fr" is a valid prefix
├── hooks.server.ts           # Sets <html lang> per page
├── app.css                   # Global styles, variables & utility classes
├── app.html                  # HTML shell template
static/
├── apple-touch-icon.png      # iOS home-screen icon
├── favicon.png               # 64px emblem favicon
├── images/                   # Hero photo + grayscale section photos (AVIF/WebP/JPEG)
├── videos/                   # Silent grayscale loop (WebM/MP4) + poster used by SectionVideo.svelte
│                             #   hero-cameroon-* (full-colour hero photo), cta-code-* (contact section)
├── logo-512.png              # Logo referenced by JSON-LD
└── og-image.png              # 1200x630 social share image

🎨 Theme Tokens

Colours live as RGB-channel CSS variables in src/app.css (--ds-*, with overrides under :root.light) and are exposed to Tailwind as ds-* colours, so opacity modifiers like bg-ds-accent/10 work. Use the semantic classes instead of raw Tailwind colours:

Class Use for
text-ds-fg / text-ds-fg-muted / text-ds-fg-subtle headings / body copy / captions
bg-ds-bg, bg-ds-surface, bg-ds-elevated, border-ds-border page, cards, raised elements, borders
text-ds-accent, text-ds-accent-2 accent labels and links
bg-ds-primary text-ds-on-primary hover:bg-ds-primary-hover primary buttons
ds-success, ds-warning, ds-danger status colours

Every text/background pairing meets WCAG AA (4.5:1) in both dark and light mode.

The palette is Cameroonian: a warm-black "Toghu night" with gold accents in dark mode, and a Sahel sand background in light mode. app.css also has a few cultural motif utilities, all original SVGs:

Class What it draws
pattern-bg Faint Ndop-cloth diamond lattice behind a section (content must be relative z-10)
toghu-band Toghu embroidery strip (black, red edges, gold braid) under the navbar and above the footer
cm-rule Green, red and yellow flag rule under section headings
cm-flag Tiny Cameroon flag used in the hero badge and footer

The flag colours (--cm-green, --cm-red, --cm-yellow) are decorative only; never use them for text.

The site defaults to dark mode; light mode only applies after a visitor picks it with the theme toggle (saved in localStorage and applied in app.html before first paint).

The hero photo (hero-cameroon-*) is shown in full colour: on mobile it sits under the navbar at its natural 3:2 ratio, on desktop it fills the right 62% of the hero with the Reunification monument and statue in view. Other photos stay on-palette by shipping them in grayscale and tinting them in CSS with mix-blend-mode: luminosity over ds-bg (src/lib/components/SectionPhoto.svelte, used by the contact section; on mobile it is shown at its natural ratio instead of cropped). To add or replace a photo, export {name}-{width}.avif|webp for two widths plus {name}-{smallWidth}.jpg:

vips thumbnail source.jpg tmp.v 2400 && vips colourspace tmp.v gray.png b-w
avifenc -q 50 --yuv 400 gray.png static/images/cta-code-1920.avif

Background videos (SectionVideo.svelte) use the same tint. They have no audio track and are skipped for visitors with reduced motion or Save-Data enabled, who see the poster instead. The video only loads near the viewport and pauses off-screen. Encode as {name}-loop.webm|mp4 plus {name}-poster.avif|webp|jpg:

ffmpeg -i trimmed.mp4 -an -vf "fps=24,hue=s=0" -c:v libvpx-vp9 -crf 50 -b:v 0 static/videos/workshop-loop.webm
ffmpeg -i trimmed.mp4 -an -vf "fps=24,hue=s=0" -c:v libx264 -crf 28 -preset veryslow -pix_fmt yuv420p -movflags +faststart static/videos/workshop-loop.mp4

🌍 Translations

English (src/lib/data/content/en.ts) defines the content shape. The French file is typed as SiteContent, so npm run check fails if a key is missing from fr.ts.

  • Edit copy: change the string in both en.ts and fr.ts.
  • Add a string: add it to en.ts, add the translation to fr.ts, then read it in a component with const c = $derived(t()).
  • French typography: use ’ for apostrophes and a non-breaking space (U+00A0) before : ? ! and inside « ».

Visitors are never redirected based on browser language; they choose with the toggle, and search engines pick the right version from the hreflang tags.

🗺️ Sitemap

Routes are registered in src/lib/seo/sitemap-routes.ts:

  • Static pages: add { path: '/services', changefreq: 'weekly', priority: 0.8 } to staticRoutes.
  • Collections (case studies, docs, …): add a dynamicSources entry with the route pattern (e.g. /work/[slug]) and a load() that returns one entry per item. load may be async (CMS/API).

A registered route is only published once its +page.svelte exists, so the sitemap never lists a 404. The build log prints [sitemap] not yet published (no page): … for routes still waiting on a page. /services, /about, /contact and /work/[slug] are pre-registered.

Pages under src/routes/[[lang=lang]]/ are published in English and French with hreflang alternates. Pages outside it are English-only. Give each page a <Seo path="/your-path" lang={c.meta.lang} />; because prerender.origin is set in svelte.config.js, its hreflang links make the prerenderer build the French version too.

💻 Local Development

1. Install Dependencies

npm install

2. Start Development Server

npm run dev

Open http://localhost:5173 in your browser.

3. Run Static Checks & Lints

npm run check

4. Create Production Build

npm run build

Preview the production build locally:

npm run preview

☁️ Vercel Deployment

This project uses @sveltejs/adapter-auto, meaning it is configured out-of-the-box for serverless hosting on Vercel:

  1. Push this repository to GitHub/GitLab/Bitbucket.
  2. Go to Vercel and select Add New Project.
  3. Import the repository.
  4. Click Deploy. Vercel will automatically configure the build output settings and host the site.

About

DevSafe, a professional software development and cybersecurity startup based in Cameroon.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages