> ## Documentation Index
> Fetch the complete documentation index at: https://help.propops.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Brand Management

> Customise your PropOps instance with your organisation's colours, logo, and identity so the platform feels like your own.

export const SidebarBrandPreview = () => {
  const [color, setColor] = useState("#ff911f");
  const hexToRgb = hex => {
    const r = parseInt(hex.slice(1, 3), 16);
    const g = parseInt(hex.slice(3, 5), 16);
    const b = parseInt(hex.slice(5, 7), 16);
    return {
      r,
      g,
      b
    };
  };
  const getLuminance = (r, g, b) => {
    const [rs, gs, bs] = [r, g, b].map(c => {
      c = c / 255;
      return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
    });
    return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
  };
  const rgb = hexToRgb(color);
  const luminance = getLuminance(rgb.r, rgb.g, rgb.b);
  const contrastText = luminance > 0.4 ? "#000000" : "#ffffff";
  const presets = [{
    name: "PropOps Orange",
    hex: "#ff911f"
  }, {
    name: "Green",
    hex: "#00D048"
  }, {
    name: "Blue",
    hex: "#3B82F6"
  }, {
    name: "Indigo",
    hex: "#6366F1"
  }, {
    name: "Red",
    hex: "#EF4444"
  }, {
    name: "Pink",
    hex: "#EC4899"
  }, {
    name: "Purple",
    hex: "#8B5CF6"
  }, {
    name: "Teal",
    hex: "#14B8A6"
  }];
  return <div className="not-prose">
      {}
      <div className="mb-6 p-4 rounded-xl border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50">
        <div className="flex flex-col sm:flex-row gap-4">
          <div className="flex-1">
            <label className="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-2">
              Brand colour
            </label>
            <div className="flex items-center gap-3">
              <input type="color" value={color} onChange={e => setColor(e.target.value)} className="w-10 h-10 rounded-lg cursor-pointer border-0 p-0" />
              <code className="text-sm px-2 py-1 rounded bg-zinc-200 dark:bg-zinc-700 text-zinc-700 dark:text-zinc-300 font-mono">
                {color.toUpperCase()}
              </code>
              <select value={presets.find(p => p.hex.toLowerCase() === color.toLowerCase())?.hex || ""} onChange={e => {
    if (e.target.value) setColor(e.target.value);
  }} className="px-2 py-1.5 rounded-lg border border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-700 text-zinc-900 dark:text-zinc-100 text-sm focus:outline-none">
                <option value="" disabled>Presets</option>
                {presets.map(p => <option key={p.hex} value={p.hex}>{p.name}</option>)}
              </select>
            </div>
          </div>
        </div>
      </div>

      {}
      <div className="flex flex-col md:flex-row gap-6 items-start">
        {}
        <div className="w-full md:w-64 flex-shrink-0">
          <div className="text-xs font-medium text-zinc-500 dark:text-zinc-400 mb-2 uppercase tracking-wider">
            Sidebar preview
          </div>
          <div className="w-full rounded-2xl overflow-hidden bg-white/[0.92] dark:bg-gray-800/85 backdrop-blur-xl" style={{
    boxShadow: `0 1px 2px 0 rgba(0,0,0,0.06), 0 2px 5px -1px rgba(0,0,0,0.05), 0 4px 10px -2px rgba(0,0,0,0.04), 0 3px 14px -3px rgba(${rgb.r},${rgb.g},${rgb.b},0.22), inset 0 5px 12px -5px rgba(${rgb.r},${rgb.g},${rgb.b},0.20)`
  }}>
            {}
            <div className="relative overflow-hidden rounded-t-2xl" style={{
    minHeight: "100px"
  }}>
              {}
              <div className="absolute -bottom-14 -left-14 w-52 h-52 rounded-3xl pointer-events-none" style={{
    backgroundColor: color,
    opacity: 0.5,
    transform: "rotate(-20deg)"
  }} />
              <div className="absolute -bottom-20 -left-6 w-36 h-36 rounded-3xl pointer-events-none" style={{
    backgroundColor: color,
    opacity: 0.3,
    transform: "rotate(-35deg)"
  }} />
              {}
              <div className="absolute -top-14 -right-14 w-52 h-52 rounded-3xl pointer-events-none" style={{
    backgroundColor: color,
    opacity: 0.8,
    transform: "rotate(20deg)",
    zIndex: 2
  }} />
              <div className="absolute -top-20 -right-6 w-36 h-36 rounded-3xl pointer-events-none" style={{
    backgroundColor: color,
    opacity: 0.4,
    transform: "rotate(35deg)",
    zIndex: 2
  }} />
              {}
              <div className="relative flex items-center justify-center px-4 pt-4 pb-2" style={{
    zIndex: 10
  }}>
                <img src="/images/propops-full.svg" alt="PropOps" className="dark:invert" style={{
    height: "32px",
    width: "auto",
    filter: luminance > 0.4 ? "none" : "invert(1)"
  }} />
              </div>

              {}
              <div className="relative flex gap-2 px-3 py-2.5 border-b border-white/20" style={{
    zIndex: 10
  }}>
                <button className="flex-1 inline-flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-semibold text-gray-700 dark:text-gray-200 bg-white/30 dark:bg-white/10 border border-white/40 dark:border-white/20 backdrop-blur-sm rounded-lg">
                  <svg className="w-4 h-4 text-gray-500 dark:text-gray-400" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" /></svg>
                  <span>Search</span>
                </button>
                <button className="flex-1 inline-flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-semibold text-gray-700 dark:text-gray-200 bg-white/30 dark:bg-white/10 border border-white/40 dark:border-white/20 backdrop-blur-sm rounded-lg">
                  <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" /></svg>
                  Job
                </button>
                <button className="flex-1 inline-flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-semibold text-gray-700 dark:text-gray-200 bg-white/30 dark:bg-white/10 border border-white/40 dark:border-white/20 backdrop-blur-sm rounded-lg">
                  <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" d="M19 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0ZM4 19.235v-.11a6.375 6.375 0 0 1 12.75 0v.109A12.318 12.318 0 0 1 10.374 21c-2.331 0-4.512-.645-6.374-1.766Z" /></svg>
                  User
                </button>
              </div>
            </div>

            {}
            <div className="px-3 py-3 space-y-1">
              {[{
    label: "Dashboard",
    active: true,
    path: "M3 6a3 3 0 0 1 3-3h2.25a3 3 0 0 1 3 3v2.25a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6Zm9.75 0a3 3 0 0 1 3-3H18a3 3 0 0 1 3 3v2.25a3 3 0 0 1-3 3h-2.25a3 3 0 0 1-3-3V6ZM3 15.75a3 3 0 0 1 3-3h2.25a3 3 0 0 1 3 3V18a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3v-2.25Zm9.75 0a3 3 0 0 1 3-3H18a3 3 0 0 1 3 3V18a3 3 0 0 1-3 3h-2.25a3 3 0 0 1-3-3v-2.25Z",
    viewBox: "0 0 24 24"
  }, {
    label: "Jobs",
    path: "M10.5 3A1.501 1.501 0 0 0 9 4.5h6A1.5 1.5 0 0 0 13.5 3h-3Zm-2.693.178A3 3 0 0 1 10.5 1.5h3a3 3 0 0 1 2.694 1.678c.497.042.992.092 1.486.15 1.497.173 2.57 1.46 2.57 2.929V19.5a3 3 0 0 1-3 3H6.75a3 3 0 0 1-3-3V6.257c0-1.47 1.073-2.756 2.57-2.93.493-.057.989-.107 1.487-.15Z",
    viewBox: "0 0 24 24"
  }, {
    label: "Calendar",
    path: "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z",
    viewBox: "0 0 20 20"
  }, {
    label: "Finance",
    path: "M5 2a2 2 0 00-2 2v14l3.5-2 3.5 2 3.5-2 3.5 2V4a2 2 0 00-2-2H5zm2.5 3a1.5 1.5 0 100 3 1.5 1.5 0 000-3zm6.207.293a1 1 0 00-1.414 0l-6 6a1 1 0 101.414 1.414l6-6a1 1 0 000-1.414zM12.5 10a1.5 1.5 0 100 3 1.5 1.5 0 000-3z",
    viewBox: "0 0 20 20"
  }, {
    label: "Reports",
    path: "M5.625 1.5C4.58947 1.5 3.75 2.33947 3.75 3.375V20.625C3.75 21.6605 4.58947 22.5 5.625 22.5H18.375C19.4105 22.5 20.25 21.6605 20.25 20.625V12.75C20.25 10.6789 18.5711 9 16.5 9H14.625C13.5895 9 12.75 8.16053 12.75 7.125V5.25C12.75 3.17893 11.0711 1.5 9 1.5H5.625ZM7.5 15C7.5 14.5858 7.83579 14.25 8.25 14.25H15.75C16.1642 14.25 16.5 14.5858 16.5 15C16.5 15.4142 16.1642 15.75 15.75 15.75H8.25C7.83579 15.75 7.5 15.4142 7.5 15ZM8.25 17.25C7.83579 17.25 7.5 17.5858 7.5 18C7.5 18.4142 7.83579 18.75 8.25 18.75H12C12.4142 18.75 12.75 18.4142 12.75 18C12.75 17.5858 12.4142 17.25 12 17.25H8.25Z",
    viewBox: "0 0 24 24"
  }, {
    label: "Settings",
    path: "M11.078 2.25c-.917 0-1.699.663-1.85 1.567L9.05 4.889c-.02.12-.115.26-.297.348a7.493 7.493 0 0 0-.986.57c-.166.115-.334.126-.45.083L6.3 5.508a1.875 1.875 0 0 0-2.282.819l-.922 1.597a1.875 1.875 0 0 0 .432 2.385l.84.692c.095.078.17.229.154.43a7.598 7.598 0 0 0 0 1.139c.015.2-.059.352-.153.43l-.841.692a1.875 1.875 0 0 0-.432 2.385l.922 1.597a1.875 1.875 0 0 0 2.282.818l1.019-.382c.115-.043.283-.031.45.082.312.214.641.405.985.57.182.088.277.228.297.35l.178 1.071c.151.904.933 1.567 1.85 1.567h1.844c.916 0 1.699-.663 1.85-1.567l.178-1.072c.02-.12.114-.26.297-.349.344-.165.673-.356.985-.57.167-.114.335-.125.45-.082l1.02.382a1.875 1.875 0 0 0 2.28-.819l.923-1.597a1.875 1.875 0 0 0-.432-2.385l-.84-.692c-.095-.078-.17-.229-.154-.43a7.614 7.614 0 0 0 0-1.139c-.016-.2.059-.352.153-.43l.84-.692c.708-.582.891-1.59.433-2.385l-.922-1.597a1.875 1.875 0 0 0-2.282-.818l-1.02.382c-.114.043-.282.031-.449-.083a7.49 7.49 0 0 0-.985-.57c-.183-.087-.277-.227-.297-.348l-.179-1.072a1.875 1.875 0 0 0-1.85-1.567h-1.843ZM12 15.75a3.75 3.75 0 1 0 0-7.5 3.75 3.75 0 0 0 0 7.5Z",
    viewBox: "0 0 24 24"
  }].map(item => <div key={item.label} className="flex items-center p-2 rounded-lg text-base font-medium" style={item.active ? {
    backgroundColor: `rgba(${rgb.r},${rgb.g},${rgb.b},0.15)`
  } : {}}>
                  <svg className="flex-shrink-0 w-6 h-6 text-gray-500 dark:text-gray-400" viewBox={item.viewBox} fill="currentColor">
                    <path fillRule="evenodd" d={item.path} clipRule="evenodd" />
                  </svg>
                  <span className="ml-3 text-gray-900 dark:text-white">{item.label}</span>
                </div>)}
            </div>

            {}
            <div className="px-3 pb-3">
              <div className="flex items-center gap-2.5 p-2.5 rounded-xl bg-zinc-100 dark:bg-gray-700/50">
                <div className="w-8 h-8 rounded-full bg-zinc-300 dark:bg-gray-600 flex items-center justify-center text-xs font-bold text-zinc-600 dark:text-zinc-300">
                  JD
                </div>
                <div className="flex-1 min-w-0">
                  <div className="text-xs font-semibold text-zinc-800 dark:text-zinc-200 truncate">John Doe</div>
                  <div className="text-[10px] text-zinc-500 dark:text-zinc-400">SysOps Admin</div>
                </div>
                <div className="w-2 h-2 rounded-full bg-emerald-500" title="Online" />
              </div>
            </div>
          </div>
        </div>

        {}
        <div className="flex-1 min-w-0">
          <div className="text-xs font-medium text-zinc-500 dark:text-zinc-400 mb-2 uppercase tracking-wider">
            What changes
          </div>
          <div className="space-y-3">
            {[{
    title: "Sidebar glow",
    desc: "The subtle coloured shadow around the sidebar card updates to your brand colour."
  }, {
    title: "Decorative blobs",
    desc: "The four gradient shapes behind the logo shift to match your primary colour at varying opacities."
  }, {
    title: "Active nav highlight",
    desc: "The currently selected page gets a translucent background tint of your brand colour."
  }, {
    title: "Action buttons",
    desc: "Quick-action buttons in the header adopt a light tint of your brand colour."
  }, {
    title: "Accent elements",
    desc: "Buttons, links, toggles, and focus rings throughout the platform update to your chosen colour."
  }].map(item => <div key={item.title} className="flex items-start gap-3 p-3 rounded-lg border border-zinc-200 dark:border-zinc-700">
                <div className="w-2 h-2 rounded-full mt-1.5 flex-shrink-0" style={{
    backgroundColor: color
  }} />
                <div>
                  <div className="text-sm font-medium text-zinc-900 dark:text-zinc-100">
                    {item.title}
                  </div>
                  <div className="text-xs text-zinc-500 dark:text-zinc-400 mt-0.5">
                    {item.desc}
                  </div>
                </div>
              </div>)}
          </div>
        </div>
      </div>
    </div>;
};

This guide walks through the brand customisation settings available under **Admin → Settings → Branding**. Changes apply immediately across the entire platform for all users.

***

## Brand colour

Your primary brand colour drives the look of the sidebar, buttons, links, active states, and accent elements throughout the interface. Use the interactive preview below to see the effect in real time.

### Try it — interactive sidebar preview

Pick a colour or enter a hex value to see how the sidebar header, navigation highlights, and action buttons adapt to your brand.

<SidebarBrandPreview />

### Setting your brand colour

<Steps>
  <Step title="Open branding settings">
    Navigate to **Admin → Settings → Branding**. The **Brand Colours** card is in the second section.
  </Step>

  <Step title="Choose a primary colour">
    Click the colour swatch or enter a hex value (e.g. `#3B82F6`). The picker updates in real time so you can preview before saving.
  </Step>

  <Step title="Optionally set a secondary colour">
    The secondary colour is used for subtle backgrounds and muted accents. If you leave it blank, the default grey is used.
  </Step>

  <Step title="Save changes">
    Select **Save Settings**. The new colour is applied across the platform immediately — no restart required.
  </Step>
</Steps>

<Tip>
  Choose a colour with enough contrast against white text. PropOps automatically calculates whether to use black or white text on top of your brand colour based on luminance.
</Tip>

***

## Application logo

Your logo appears in the sidebar header, login page, and generated documents.

### Supported formats

| Format   | Recommended | Notes                                            |
| -------- | ----------- | ------------------------------------------------ |
| **SVG**  | Yes         | Scales perfectly at any size, smallest file size |
| **PNG**  | Yes         | Use a transparent background for best results    |
| **WebP** | Supported   | Good quality-to-size ratio                       |
| **JPEG** | Supported   | Avoid — no transparency support                  |

### Uploading your logo

<Steps>
  <Step title="Open branding settings">
    Go to **Admin → Settings → Branding** and scroll to the **Brand Assets** section.
  </Step>

  <Step title="Upload or set a path">
    Either drag and drop your logo file into the upload area, or enter a relative path to a logo already on the server (e.g. `assets/images/my-logo.svg`).
  </Step>

  <Step title="Check the preview">
    The preview panel shows how the logo renders. The logo is displayed using a CSS mask technique — it appears as a solid black shape in light mode and inverts to white in dark mode.
  </Step>

  <Step title="Save">
    Select **Save Settings**. The logo updates across the platform immediately.
  </Step>
</Steps>

<Info>
  The logo height is capped at **40px** and max width at **160px** by default. These limits are configurable via environment variables `NAV_LOGO_HEIGHT` and `NAV_LOGO_MAX_WIDTH`.
</Info>

<Tip>
  For best results, use a **flat, single-colour vector logo** in `.svg` format. The sidebar renders your logo using a CSS mask technique, so gradients, shadows, and photographic elements will not display correctly. A simple black SVG on a transparent background works perfectly — PropOps automatically inverts it for dark backgrounds.
</Tip>

***

## Company identity

| Setting         | Where it appears                                     |
| --------------- | ---------------------------------------------------- |
| **App Name**    | Browser tab title, email headers, PWA install prompt |
| **Short Name**  | PWA home screen icon label, compact UI areas         |
| **Description** | Meta description, PWA manifest                       |

Set these under the **Company Identity** card in the branding settings page.

***

## Favicons and app icons

PropOps Web supports full PWA installation. Upload the following icons to ensure a polished experience across all devices:

| Icon                   | Size    | Purpose               |
| ---------------------- | ------- | --------------------- |
| `favicon.ico`          | 48×48   | Browser tab icon      |
| `favicon-96x96.png`    | 96×96   | High-DPI browser tab  |
| `apple-touch-icon.png` | 180×180 | iOS home screen       |
| `manifest-192.png`     | 192×192 | Android home screen   |
| `manifest-512.png`     | 512×512 | Android splash screen |

<Warning>
  If you skip the PWA icons, users who install the app to their home screen will see a generic placeholder icon.
</Warning>

***

## Social sharing images

When someone shares a link to your PropOps instance, these images appear in the link preview card:

* **Social Thumbnail** — used as the default Open Graph image.
* **OG Image** — overrides the thumbnail for Facebook, LinkedIn, and other platforms.
* **Twitter Image** — used specifically for Twitter/X card previews.

Upload these under the **Social** section of the branding page. Recommended dimensions are **1200×630px**.

***

## Login page

### Slideshow images

The login page can display a carousel of images alongside the login form:

1. Go to **Admin → Settings → Branding**.
2. Scroll to **Login Slideshow Images**.
3. Upload or add paths for each slide image.
4. Drag to reorder. The first image is shown by default.

### Background

A custom background image can be set for the login page. This appears behind the login form and slideshow panel.
