diff --git a/contents/founders/registering-trademarks.md b/contents/founders/registering-trademarks.md index 52d810c808df..8bf3f0074f27 100644 --- a/contents/founders/registering-trademarks.md +++ b/contents/founders/registering-trademarks.md @@ -23,7 +23,7 @@ Those first crucial months present bigger problems to worry about - like finding A word mark is basically just the name of the brand (as written in a specific font, if applicable). In our case, it's the word 'PostHog.' A logo mark is typically pictorial - in our case, the hedgehog logo either alone or combined with a stylized word. The word mark is the most versatile, so that should be your first trademark priority.
- +PostHog
When you decide to register your trademark, you don't need to spend a fortune doing it. With a tactical combination of using the right type of lawyer and doing it yourself, you should be able to get trademarks filed in the US, UK, EU and China for ~$3,000 _in total_. @@ -45,4 +45,3 @@ We’ve currently secured our brand name ('PostHog') and word mark in the four t The phrase 'open source' might denote a public codebase, but a brand name can definitely be trademarked. For example, we want people to [deploy our open source edition](https://github.com/PostHog/posthog) wherever they please, but we don't want them to slap the 'PostHog' name on random products. Besides, if you’re [raising money](https://posthog.com/blog/open-source-business-models) for your startup in the future, IP is a box that investors will want you to check as part of their diligence. - diff --git a/contents/handbook/brand/assets.md b/contents/handbook/brand/assets.md index 6314863e207b..e456bfab077e 100644 --- a/contents/handbook/brand/assets.md +++ b/contents/handbook/brand/assets.md @@ -47,6 +47,7 @@ Each logo below is rendered live from our [`@posthog/brand`](https://github.com/ - On **light backgrounds, prefer the standard logo**, and only reach for the **dark logo** (solid black) when a single-color mark is required. - Use the **print (4-color)** version only for print or other limited-palette contexts where the gradients can't be reproduced. - Use the **logomark** on its own only at small sizes — favicons, app icons — where the full lockup won't fit and the overlapping gradients would get too busy. +- Use a **square logomark** for avatars and integrations that require a square image. Its transparent square canvas keeps the logomark's proportions intact — never stretch the standard logomark to fill a square. Use the standard gradient version on light backgrounds, or the light solid-white version on dark backgrounds. - Use the **stacked** logo for portrait or square placements where the standard (landscape) lockup is too wide. _Never_ modify the colors in the logomark (for example, don't recolor the hedgehog's face white on a dark background — use the light logo instead). diff --git a/contents/handbook/brand/visual-identity.md b/contents/handbook/brand/visual-identity.md index dea9b76cd0c2..a5ccfbacf570 100644 --- a/contents/handbook/brand/visual-identity.md +++ b/contents/handbook/brand/visual-identity.md @@ -29,7 +29,7 @@ Find logos on our [brand assets](/handbook/brand/assets) page. | Stacked logo | When horizontal space is limited but the full name is important. | | 4-color logo | Print version, when gradients can't be printed. Only use the 4-color logo for this case. | -Download all variants at [/handbook/company/brand-assets.md](/handbook/company/brand-assets). SVG is always preferred. +Download all variants from [brand assets](/handbook/brand/assets). SVG is always preferred. ### Rules @@ -264,4 +264,4 @@ The same light/dark rule applies: if a visualization appears on posthog.com, it **Annotations and context** -A chart without context can mislead. When publishing a chart – especially in a blog post – annotate significant moments: a feature launch, a spike, a known data anomaly. Readers should understand *why* something happened, not just *that* it happened. \ No newline at end of file +A chart without context can mislead. When publishing a chart – especially in a blog post – annotate significant moments: a feature launch, a spike, a known data anomaly. Readers should understand *why* something happened, not just *that* it happened. diff --git a/contents/handbook/company/brand-assets.md b/contents/handbook/company/brand-assets.md deleted file mode 100644 index ecebe0a91490..000000000000 --- a/contents/handbook/company/brand-assets.md +++ /dev/null @@ -1,212 +0,0 @@ ---- -title: 'Logos, brand, hedgehogs' -sidebar: Handbook -showTitle: true -hideAnchor: false ---- - -> **Looking for brand voice, design philosophy, and visual identity guidelines?** Check out our [Style guide](/handbook/brand/style-guide). - -> **Want to use our hedgehogs for your community event or article?** We have [a huge library of them you can use](https://www.figma.com/design/I0VKEEjbkKUDSVzFus2Lpu/Hoggies?node-id=2226-55&t=1sj1GezTKuCfaybF-1). Can't see what you need? [Let us know](mailto:joe@posthog.com)! Please don't use AI art though. We're quite particular about our illustrations and AI just doesn't get it right. - -## Logo and brand usage for third-parties - -We’re really happy people want to build on top of PostHog, but we want to keep it clear when something is made by us or made by someone else. If you've built a third-party app on top of PostHog or want to partner with us in some way, here is some high-level guidance for you to bear in mind. - -- We're _generally OK_ with people using the PostHog name to describe compatibility. For example, you can say your product "works with PostHog," is "built for PostHog" or "built on PostHog". - -- We're _not OK_ with people using the PostHog name to make it look like your project is made by, endorsed by, or is officially partnered with PostHog if it isn't. So for example, while "Desktop Studio for PostHog" would be fine, "Official PostHog Desktop Studio" or "PostHog Desktop Studio" would not be. - -- You can use our logo or brand assets only in unmodified form, and not as the main branding for your own project. However, you may not use our hedgehog mascot or other illustrative brand assets in any commercial or marketing materials without explicit permission, as this can imply endoresement and confuse people. You cannot make it seem like your product is an official PostHog product, or that we've endorsed your product or partnered with you if we haven't. Please make sure that logo, brand asset and name usage are consistent with the rules we've laid out in this page. - -We don't like doing it, but if we spot some name, brand asset or logo usage that are inconsistent with our guidelines or brand, we will reach out to try to get that sorted out, so please try to be thoughtful about branding and try to be consistent with the guidelines we've set out here. If you have questions, please reach out to us at marketing@posthog.com for clarification. - -## Logo - -If you're looking for the PostHog logo, you came to the right place. Please keep the logo intact. SVG is always preferred as it will infinitely scale with no quality loss. - -(Images shown below have transparent backgrounds but appear here with a solid background color.) - -| Preview | Name | Vector | PNG | PNG w/ padding\* | -| ----------------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -|
| Standard logo | SVG | PNG \| PNG @2x | PNG \| PNG @2x | -|
![](/brand/posthog-logo-black.svg)
| Dark logo | SVG | PNG \| PNG @2x | PNG \| PNG @2x | -|
![](/brand/posthog-logo-white.svg)
| Light logo | SVG | PNG \| PNG @2x | PNG \| PNG @2x | -|
![](/brand/posthog-logomark.svg)
| Logomark | SVG | PNG \| PNG @2x | PNG \| PNG @2x | -|
![](/brand/posthog-logo-stacked.svg)
| Logo (stacked) | SVG | PNG \| PNG @2x | PNG \| PNG @2x | - -\*PNGs with padding are useful when uploading the logo to a third-party service where there is limited control over padding/margin around the logo. - -When using the logo on a dark background, use the white-only version of the logo. _Never_ modify the colors in the logomark (like changing the hedgehog's face color to white when using on a dark background). - -The @2x version of PNGs are designed for [hi-dpi (or "Retina") screens](https://en.wikipedia.org/wiki/Retina_display). When using the logo in third party services that support uploading multiple versions (standard and hi-dpi), please be sure to include the @2x logo as it will appear crisper on newer devices, tablets and high resolution mobile devices. - -> **Important:** We updated our logo in 2021. (Note the square font and sharp edges on the logomark in the old version.) Please be sure to use the _correct_ version. 👇🏼 - -![Logo usage examples](/brand/logo-usage.png) - -If you have any questions or need clarification about which version to use, ask Cory, or reach out in [our community page](/posts) and we'll be happy to help. - -## Typography - -We use Displaay's typeface called _Matter SQ_. (SQ = square dots.) - -On the website, we use this for all text. In-product, we only use for titles and buttons. - -### Building for web - -On posthog.com, we use the [variable font](https://web.dev/variable-fonts/) version. This allows us to specify our own font weights, which we do for paragraph text. - -Context: _Matter Regular_'s weight is `430` and the next step up is _Matter Medium_ at `570`, so we use our own weight of `475` for paragraph text. - -### Developing locally - -Fonts are hosted outside of our posthog.com GitHub repo (due to licensing reasons). To protect the font files, they are restricted to loading on posthog.com and are not currently used for local development. Contributors will see the system default font load in place of Matter. - -**Workaround for local development** - -Restricted to PostHog employees, it's possible to reference the font locally to see an exact replication of what will be published on posthog.com. - -[global.css](https://github.com/PostHog/posthog.com/blob/master/src/styles.css) contains some commented out code which can be used, in conjunction with the [variable webfont files](https://github.com/PostHog/company-internal/blob/master/MatterSQVF.zip) (restricted to PostHog organization members). Here's how to use them: - -1. Download the webfont files from the zip above -1. Extract the files and place them in `/public/fonts` -1. In `global.css`, comment out the `src` for both fonts with production (Cloudfront) URLs and uncomment the relative URLs. -1. Optionally use `.gitignore` to keep the files locally without inadvertently checking them in - -Note: When submitting a PR, be sure to revert changes made to `global.css` - -### Designing on desktop - -We use 4 cuts of Displaay's _Matter SQ_ typeface (SQ stands for square dots): - -1. _Bold_ (titles and section headers) -2. _Semibold_ (paragraphs accompanying headers and paragraph links) -3. _Regular_ & _Regular Italic_ (paragraph text) - -Note that _Regular_ and _Regular Italic_ are lighter than the font-weight we use on the web, so paragraph text in Figma mockups will look noticeably thinner than how it appears on posthog.com. - -When designing ads or other content with non-paragraph text, use _Semibold_ instead of _Regular_. - -We have a handful of licenses for desktop use of Matter. Contact Cory if you need the desktop fonts (OTFs). - -| Name | Weight | Size | Letter spacing | Line height | -| ------------------------------------- | -------- | ---- | -------------- | ----------- | -| h1 | Bold | 64px | -1% | 100% | -| h2 | Bold | 48px | -1% | 120% | -| h3 | Bold | 30px | -2% | 140% | -| h4 | Bold | 24px | -2% | | -| h5 | Semibold | 20px | -2% | | -| h6 | Semibold | 16px | 0 | | -| Paragraphs accompanying large headers | Semibold | 20px | -1% | 125% | -| p | Regular | 17px | | 175% | -| p (small) | Regular | 15px | | 150% | - -## Other fonts - -We use two other fonts for special purposes. Please adhere to their usage guidelines listed below. - -### Squeak - -Squeak is used in informal settings, generally accompanied by hedgehog artwork. - -#### Usage guidelines - -- When used for headlines or at larger sizes, use the `Bold` variant -- Only for small (description) text, use the `Normal` variant in regular casing. Never use for more than a couple lines of text in a row. -- Always use uppercase letters -- Letter spacing: -2% -- Line height: 100% (generally) - -#### Examples - - - -![Squeak font example](https://res.cloudinary.com/dmukukwp6/image/upload/squeak_example_37b83e4cde.jpg) - -![Squeak font example](https://res.cloudinary.com/dmukukwp6/image/upload/squeak_example_2_4323797eb0.png) - -![Squeak font example](https://res.cloudinary.com/dmukukwp6/image/upload/squeak_example_3_d367809597.png) - - - -### Loud Noises - -Loud Noises is used for quotes in hedgehog artwork. - -#### Usage guidelines - -- Only use for quotes in hedgehog artwork or where hedgehogs are otherwise communicating something -- Only use uppercase - -#### Example - -Loud Noises is used in the sign the hedgehog is holding: - -![](https://res.cloudinary.com/dmukukwp6/image/upload/loud_noises_5919818659.png) - ---- - -If you have questions about which font to use, please ask in #team-website - don't just do what feels right to you! - - -## Colors - -We have two color schemes (light and dark mode), but primarily use light mode. - -We use the same set of colors, and only swap out a couple hues depending on the color scheme. - -Colors denoted with an asterisk (\*) are the same between palettes. - -| Name | Light mode | Dark mode | -| --------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------- | -| Text color (at 90% opacity) | `#151515` | `#EEEFE9` | -| Background color | `#EEEFE9` | `#151515` | -| Accent | `#E5E7E0` | `#2C2C2C` | -| Dashed divider line | `#D0D1C9` | `#4B4B4B` | -| Red\* | `#F54E00` | | -| Yellow | `#DC9300` | `#F1A82C` | -| Blue\* | `#1D4AFF` | | -| Gray\* | `#BFBFBC` | | -| Links | Use Red | | - -### Use `opacity` over more colors - -When possible, use opacity to modify colors. This allows us to use fewer colors in our palette, which is light years easier when working with two color schemes. - -| Paragraph text | `rgba($value, 90%)` | -| -------------- | ----------------------------------- | -| Links | `rgba($value, 95%)` (and semibold) | -| Links:hover | `rgba($value, 100%)` (and semibold) | - -## Presentations - -We use [Pitch](https://pitch.com) for polished presentations (like when giving a talk). Read more about this in our [communication guidelines](/handbook/company/communication#google-docs-and-slides). - -## Illustration guide - -Our hedgehog mascot is called Max and we're quite particular about how he (or any of his hoggy pals) are illustrated. We're exploring AI tools for internal use, but currently ask that you don't use AI tools to create your own hedgehog art. Instead, you can follow the guidelines below, or [create a new art request](/handbook/brand/art-requests). - -![How to draw a hedgehog](https://res.cloudinary.com/dmukukwp6/image/upload/q_auto,f_auto/hog_guide_1_2fab7b9cb6.png) - -If Max is drawn in color he should always have a beige body with brown spines, arms, and legs. His arms should only bend once in the middle and he doesn't have fingers unless swearing or pointing. His feet are stubby by design and his snout lines should be visible unless obscured by a mask or beard. His expression comes mainly from his eyebrows. - -![Draw the rest of the hedgehog](https://res.cloudinary.com/dmukukwp6/image/upload/q_auto,f_auto/hog_guide2_d61482248f.png) - -He should be outlined with a strong, black monoline with consistent thickness. He should always face left, right, or straight-on but shouldn't be drawn with a side profile or from behind as he's self-conscious. - -> A more detailed version of this guide is available on Figma for team members. - -## Hedgehog library - -For team members we keep all our currently approved hedgehogs in this Figma file. This enables us to look through the library of approved hogs, and to export them at required sizes without relying on the design team. - -Here's how: - -1. Open the Figma file. You can manually browse, or use `Cmd + F` to search based on keywords such as 'happy', 'sad', or 'will smith'. -2. Select the hog you want. If needed, adjust the size using the 'Frame' menu in the top of the right-hand sidebar. -3. At the bottom of the right-hand sidebar, select the file type you need in the 'Export' menu, choose `@2x`, then select 'Export [filename]' to download the image. - -If you can't find a suitable hog, you can [request one from the design team](/handbook/brand/art-requests). - -> Non-team members can find some of the most-used hogs to download on [our press page](/media). diff --git a/contents/handbook/marketing/events.md b/contents/handbook/marketing/events.md index 657e9ebf123c..efdd79b971ce 100644 --- a/contents/handbook/marketing/events.md +++ b/contents/handbook/marketing/events.md @@ -92,7 +92,7 @@ Our brand is a reflection of us and [how we’re experienced by others](/blog/br **Words:** Naming products is hard. Same goes for naming events and writing their descriptions. As a prerequisite, read our primer on [writing for developers](/founders/writing-for-developers). Try your best to come up with event names that communicate the 'what?' and will attract the 'who?' And then again ask yourself, "would I attend this?" -**Pictures:** Every event is improved with a flyer or poster that showcases the essence of the experience. We keep a comprehensive list of brand assets and guidelines on the [brand assets page](/handbook/company/brand-assets). Share your assets and we’ll give feedback. Depending on the scale and timing of the event, our team may be able to help with branding as well. +**Pictures:** Every event is improved with a flyer or poster that showcases the essence of the experience. We keep a comprehensive list of brand assets and guidelines on the [brand assets page](/handbook/brand/assets). Share your assets and we’ll give feedback. Depending on the scale and timing of the event, our team may be able to help with branding as well. ### Event recaps diff --git a/contents/handbook/marketing/ownership.md b/contents/handbook/marketing/ownership.md index e851016cd552..1991b35d4e35 100644 --- a/contents/handbook/marketing/ownership.md +++ b/contents/handbook/marketing/ownership.md @@ -128,7 +128,7 @@ Read [art and branding requests](/handbook/brand/art-requests) first!
I need a font, logo, etc. -See [Logos, brand, hedgehogs](/handbook/company/brand-assets) +See [Logos, brand, hedgehogs](/handbook/brand/assets)
diff --git a/contents/handbook/marketing/speaker-guide.md b/contents/handbook/marketing/speaker-guide.md index 00d2770afe3a..2943f5d9a17b 100644 --- a/contents/handbook/marketing/speaker-guide.md +++ b/contents/handbook/marketing/speaker-guide.md @@ -86,7 +86,7 @@ A few principles for building out slides: * If a slide doesn't support the one true thing you identified in step 3, cut it. * Speaking of speaker notes, you will save yourself time and head space if you always have notes. -For feedback on design or help with navigating the [PostHog brand assets](https://posthog.com/handbook/company/brand-assets) ([Hoggies](https://www.figma.com/design/I0VKEEjbkKUDSVzFus2Lpu/Hoggies?node-id=2226-55&t=1sj1GezTKuCfaybF-1) included), stop by [\#team-graphics](https://posthog.slack.com/archives/C0AU440KS6P) +For feedback on design or help with navigating the [PostHog brand assets](/handbook/brand/assets) ([Hoggies](https://www.figma.com/design/I0VKEEjbkKUDSVzFus2Lpu/Hoggies?node-id=2226-55&t=1sj1GezTKuCfaybF-1) included), stop by [\#team-graphics](https://posthog.slack.com/archives/C0AU440KS6P) ## **6. Practice out loud. Twice minimum.** @@ -130,4 +130,4 @@ If you receive a question that you believe is off-topic or unfitting for the set * Feb 2026 - - [10 learning from launching an agentic AI product at scale](https://www.figma.com/slides/zMNBsNihNxKyS6z7sSwiRK/10-learnings-from-launching-an-agentic-AI-product-at-scale?t=6hlWStUDlHsITOKo-6) * Nov 2025 - - [How to build a cult](https://drive.google.com/file/d/1BMgl2y817m5t43D6NCr9hstnGO1kKxJy/view) * Oct 2025 - - [Code that fixes itself](https://docs.google.com/presentation/d/1sYsmTPugdttQqshPa6yIKQcUu5Rje3-fPMbRGtyrfZg/edit?slide=id.p#slide=id.p) -* Oct 2024 - - [Parsing at the speed of light](http://figma.com/slides/y0rMgEaWbYe0nSpEhWkyY6/Parsing-at-the-speed-of-metal?node-id=1-536&t=uiCktez3IWxAKI8D-0) \ No newline at end of file +* Oct 2024 - - [Parsing at the speed of light](http://figma.com/slides/y0rMgEaWbYe0nSpEhWkyY6/Parsing-at-the-speed-of-metal?node-id=1-536&t=uiCktez3IWxAKI8D-0) diff --git a/contents/library/ops/registering-trademarks.md b/contents/library/ops/registering-trademarks.md index 2a50bb424ba3..0b320785c4c9 100644 --- a/contents/library/ops/registering-trademarks.md +++ b/contents/library/ops/registering-trademarks.md @@ -25,7 +25,7 @@ Those first crucial months present bigger problems to worry about - like finding A word mark is basically just the name of the brand (as written in a specific font, if applicable). In our case, it's the word 'PostHog.' A logo mark is typically pictorial - in our case, the hedgehog logo either alone or combined with a stylized word. The word mark is the most versatile, so that should be your first trademark priority.
- +PostHog
When you decide to register your trademark, you don't need to spend a fortune doing it. With a tactical combination of using the right type of lawyer and doing it yourself, you should be able to get trademarks filed in the US, UK, EU and China for ~$3,000 _in total_. @@ -44,4 +44,3 @@ We’ve currently secured our brand name ('PostHog') and word mark in the four t The phrase 'open source' might denote a public codebase, but a brand name can definitely be trademarked. For example, we want people to [deploy our open source edition](https://github.com/PostHog/posthog) wherever they please, but we don't want them to slap the 'PostHog' name on random products. Besides, if you’re [raising money](https://posthog.com/blog/open-source-business-models) for your startup in the future, IP is a box that investors will want you to check as part of their diligence. _Enjoyed this? Subscribe to our [newsletter](https://newsletter.posthog.com/subscribe) to hear more from us twice a month!_ - diff --git a/contents/media-contents.mdx b/contents/media-contents.mdx index 9bf082b24026..ee0190d67499 100644 --- a/contents/media-contents.mdx +++ b/contents/media-contents.mdx @@ -40,7 +40,7 @@ You can find biographies and social links for our founders, [James](/community/p ## PostHog logos -We've created brand assets for you. You don't have to negotiate legal agreements for them. You can find them on our [brand assets](/handbook/company/brand-assets) page. +We've created brand assets for you. You don't have to negotiate legal agreements for them. You can find them on our [brand assets](/handbook/brand/assets) page. ## Founder headshots diff --git a/contents/tutorials/how-to-connect-posthog-and-notion-with-zapier.md b/contents/tutorials/how-to-connect-posthog-and-notion-with-zapier.md index 1830de5c7d6a..75ec00b44baa 100644 --- a/contents/tutorials/how-to-connect-posthog-and-notion-with-zapier.md +++ b/contents/tutorials/how-to-connect-posthog-and-notion-with-zapier.md @@ -32,7 +32,7 @@ First, head to the [‘My Apps’ section of your Zapier account](https://zapier Next, we need to create a Zapier bot in Notion, which will perform the action of updating a database with information whenever an action is defined in PostHog. You’ll need to be a Notion admin to do this. -Start by heading to Notion’s Settings page, then click the Integrations tab and select ‘Develop your own integrations’ to start creating your bot. Here you can follow the instructions to give your bot a name, as well as customize it in other ways. You can even grab the PostHog logo from [our brand assets](/handbook/company/brand-assets) and use it as the icon for your bot! +Start by heading to Notion’s Settings page, then click the Integrations tab and select ‘Develop your own integrations’ to start creating your bot. Here you can follow the instructions to give your bot a name, as well as customize it in other ways. You can even grab the PostHog logo from [our brand assets](/handbook/brand/assets) and use it as the icon for your bot! ![Notion Integrations Page](https://res.cloudinary.com/dmukukwp6/image/upload/v1710055416/posthog.com/contents/images/tutorials/notion/create-integration.png) @@ -61,4 +61,4 @@ If the action is specific to a page or on-page selector, this information is als As an output, we want the workflow to populate the information to a Notion page — so we’ll use Notion’s ‘Write to database’ output. With these options created you can tailor how the information contained in the event is written to your Notion database. Instituting best practices, such as requiring action creators to include descriptions for their actions, will enable you to track all tracked actions within a simple Notion table. - \ No newline at end of file + diff --git a/package.json b/package.json index b4fe79722dc8..a57c6531ce03 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,13 @@ ], "private": true, "scripts": { + "prebuild-move": "pnpm generate-brand-assets", "build-move": "gatsby build && bash ./src/scripts/move-index.sh", + "prebuild": "pnpm generate-brand-assets", "build": "gatsby build", + "prebuild:minimal": "pnpm generate-brand-assets", "build:minimal": "GATSBY_MINIMAL=true gatsby build", + "prestart": "pnpm generate-brand-assets", "start": "./bin/start", "serve": "gatsby serve", "format": "prettier --write \"**/*.{html,js,ts,tsx,json,yml,css,scss}\"", @@ -29,6 +33,7 @@ "test-redirects": "jest scripts", "test:bundle": "node --test scripts/bundle/check-eager-graph.test.mjs", "check-links-post-build": "node scripts/check-links-post-build.js", + "generate-brand-assets": "node scripts/generate-brand-assets.mjs", "storybook": "start-storybook -s ./static -p 6006", "build-storybook": "build-storybook", "postinstall": "patch-package", @@ -59,7 +64,7 @@ "@mdx-js/react": "^1.6.22", "@mdxeditor/editor": "^3.32.3", "@popperjs/core": "^2.11.2", - "@posthog/brand": "^0.8.0", + "@posthog/brand": "0.8.0", "@posthog/hedgehog-mode": "^0.0.52", "@posthog/icons": "0.36.6", "@radix-ui/react-accordion": "^1.2.3", @@ -271,6 +276,7 @@ "puppeteer": "^13.0.1", "react-dev-utils": "^11.0.4", "sass-loader": "^12.2.0", + "sharp": "0.34.5", "storybook-addon-gatsby": "0.0.2", "storybook-dark-mode": "^1.0.8", "style-loader": "^3.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 335a4dfa5e62..dcffdeb519fc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,7 +72,7 @@ importers: specifier: ^2.11.2 version: 2.11.8 '@posthog/brand': - specifier: ^0.8.0 + specifier: 0.8.0 version: 0.8.0(react@18.3.1) '@posthog/hedgehog-mode': specifier: ^0.0.52 @@ -702,6 +702,9 @@ importers: sass-loader: specifier: ^12.2.0 version: 12.6.0(sass@1.92.1)(webpack@5.101.3) + sharp: + specifier: 0.34.5 + version: 0.34.5 storybook-addon-gatsby: specifier: 0.0.2 version: 0.0.2(gatsby@4.25.9(@types/webpack@4.41.40)(babel-eslint@10.1.0(eslint@7.32.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.41.0)(typescript@4.9.5)(webpack-hot-middleware@2.26.1)) diff --git a/scripts/generate-brand-assets.mjs b/scripts/generate-brand-assets.mjs new file mode 100644 index 000000000000..45e468ab3592 --- /dev/null +++ b/scripts/generate-brand-assets.mjs @@ -0,0 +1,134 @@ +import fs from 'node:fs/promises' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import React from 'react' +import { renderToStaticMarkup } from 'react-dom/server' +import sharp from 'sharp' +import { Logo } from '@posthog/brand/logo' + +// Materialize stable /brand URLs from the canonical package before Gatsby copies static/ to public/. +// package.json lifecycle hooks run this automatically for the standard start and build commands. +const outputDirectory = fileURLToPath(new URL('../static/brand/', import.meta.url)) + +const scaleDimensions = ([width, height], scale) => [width * scale, height * scale] +const padDimensions = ([width, height], padding) => [width + padding * 2, height + padding * 2] + +const getNativeDimensions = (props) => { + const markup = renderToStaticMarkup(React.createElement(Logo, props)) + const viewBox = markup.match(/\bviewBox="([^"]+)"/)?.[1] + const dimensions = viewBox?.trim().split(/\s+/).map(Number) + + if (dimensions?.length !== 4 || dimensions.some((value) => !Number.isFinite(value))) { + throw new Error(`Unable to read logo viewBox from: ${markup.slice(0, 200)}`) + } + + return dimensions.slice(2) +} + +const createAsset = ({ name, props, pngScale = 1, padding = 5, paddedSvg = true }) => { + const svg = getNativeDimensions(props) + const png = scaleDimensions(svg, pngScale) + const padded = padDimensions(png, padding) + + return { + name, + props, + svg, + png, + png2x: scaleDimensions(png, 2), + padded, + padded2x: scaleDimensions(padded, 2), + paddedSvg, + } +} + +const assets = [ + createAsset({ + name: 'posthog-logo', + props: {}, + }), + createAsset({ + name: 'posthog-logo-black', + props: { variant: 'mono', color: '#111' }, + }), + createAsset({ + name: 'posthog-logo-white', + props: { variant: 'mono', color: '#FAFAFA' }, + }), + createAsset({ + name: 'posthog-logomark', + props: { layout: 'logomark' }, + }), + createAsset({ + name: 'posthog-logo-stacked', + props: { layout: 'stacked' }, + pngScale: 5, + padding: 20, + paddedSvg: false, + }), +] + +const replaceRootSvgAttributes = (markup, attributes) => + markup.replace(/^]*)>/, (_match, currentAttributes) => { + const cleanedAttributes = currentAttributes.replace( + /\s(?:aria-hidden|height|role|width|x|y)=(?:"[^"]*"|'[^']*')/g, + '' + ) + const serializedAttributes = Object.entries(attributes) + .map(([name, value]) => ` ${name}="${value}"`) + .join('') + return `` + }) + +const renderLogo = (props, [width, height]) => + replaceRootSvgAttributes(renderToStaticMarkup(React.createElement(Logo, props)), { width, height }) + +const renderPaddedLogo = (props, [contentWidth, contentHeight], [width, height]) => { + const x = (width - contentWidth) / 2 + const y = (height - contentHeight) / 2 + const innerLogo = replaceRootSvgAttributes(renderToStaticMarkup(React.createElement(Logo, props)), { + x, + y, + width: contentWidth, + height: contentHeight, + }) + + return `${innerLogo}` +} + +const writeSvg = (filename, svg) => fs.writeFile(path.join(outputDirectory, filename), `${svg}\n`) + +const writePng = (filename, svg, [width, height]) => + sharp(Buffer.from(svg), { density: 288 }) + .resize(width, height, { fit: 'fill' }) + .png({ compressionLevel: 9, palette: true }) + .toFile(path.join(outputDirectory, filename)) + +await fs.mkdir(outputDirectory, { recursive: true }) + +for (const asset of assets) { + const svg = renderLogo(asset.props, asset.svg) + const pngSvg = renderLogo(asset.props, asset.png) + const png2xSvg = renderLogo(asset.props, asset.png2x) + const paddedSvg = renderPaddedLogo(asset.props, asset.png, asset.padded) + const padded2xSvg = renderPaddedLogo(asset.props, asset.png2x, asset.padded2x) + + await Promise.all([ + writeSvg(`${asset.name}.svg`, svg), + writePng(`${asset.name}.png`, pngSvg, asset.png), + writePng(`${asset.name}@2x.png`, png2xSvg, asset.png2x), + writePng(`${asset.name}-padded.png`, paddedSvg, asset.padded), + writePng(`${asset.name}-padded@2x.png`, padded2xSvg, asset.padded2x), + ...(asset.paddedSvg ? [writeSvg(`${asset.name}-padded.svg`, paddedSvg)] : []), + ]) +} + +// Image-only integrations often force avatars into square boxes. Keep the official +// logomark aspect ratio intact and center it on a transparent square canvas instead of stretching it. +const squareLogomarkProps = { layout: 'logomark' } +await writeSvg( + 'posthog-logomark-square.svg', + renderPaddedLogo(squareLogomarkProps, getNativeDimensions(squareLogomarkProps), [60, 60]) +) + +console.log(`Generated ${assets.length} public logo variants in ${path.relative(process.cwd(), outputDirectory)}`) diff --git a/src/components/About/v2/Letterhead.tsx b/src/components/About/v2/Letterhead.tsx index 6f7131a6d8db..b1720a3a02da 100644 --- a/src/components/About/v2/Letterhead.tsx +++ b/src/components/About/v2/Letterhead.tsx @@ -1,7 +1,6 @@ import React from 'react' import Link from 'components/Link' import { IconXNotTwitter } from 'components/OSIcons' -import Logo from 'components/Logo' import CloudinaryImage from 'components/CloudinaryImage' import { useApp } from '../../../context/App' import { DebugContainerQuery } from 'components/DebugContainerQuery' @@ -12,10 +11,10 @@ export const Letterhead = () => { return (
-
diff --git a/src/components/BrandLogos/README.md b/src/components/BrandLogos/README.md index 2bb8e75552e5..4d79bed194db 100644 --- a/src/components/BrandLogos/README.md +++ b/src/components/BrandLogos/README.md @@ -2,8 +2,12 @@ Renders the PostHog logo lockups on the [brand assets handbook page](/handbook/brand/assets), live from the [`@posthog/brand`](https://github.com/PostHog/brand) library — the single source -of truth for the mark. Nothing is pre-baked into `static/brand`, so the page can't drift from -the library. +of truth for the mark. The page previews and downloads do not depend on `static/brand`, so they +can't drift from the library. + +Stable public `/brand/*` URLs are maintained separately for external consumers. The +`scripts/generate-brand-assets.mjs` script materializes those compatibility files from the same +package and runs automatically before the standard Gatsby start and build commands. ## Usage @@ -17,6 +21,8 @@ Registered as an MDX shortcode, so handbook/docs pages can drop it in directly: - Each lockup is a live `` from `@posthog/brand/logo`, configured via `layout` / `variant` / `color` (see `LOCKUPS`). +- The standard and light square logomarks keep the package's 52:28 mark intact and center it on + a transparent 60:60 canvas for avatars and integrations that require square images. - Download buttons serialize the rendered `` on demand: - **SVG** — clones the node, bakes `currentColor` (used by the `mono` lockups) into an explicit fill so the file stands alone, and serializes it. @@ -24,6 +30,9 @@ Registered as an MDX shortcode, so handbook/docs pages can drop it in directly: PNG blob. - **Padded** variants expand the `viewBox` by `PAD_FRACTION` of the shorter side, for services that offer no control over margin around the logo. + - **Square** variants first expand the `viewBox` equally around the unmodified mark. Their + padded downloads then add the same extra clear space outside that square canvas as the + other lockups. All download work happens in the browser on click; the component itself renders fine under SSR (just the `` previews). diff --git a/src/components/BrandLogos/index.tsx b/src/components/BrandLogos/index.tsx index 0d89b94dc4c7..dcdac43c4a8c 100644 --- a/src/components/BrandLogos/index.tsx +++ b/src/components/BrandLogos/index.tsx @@ -14,6 +14,8 @@ interface Lockup { background: string /** Preview width in px (height follows the lockup's aspect ratio). */ previewWidth: number + /** Center the unmodified mark on a transparent square canvas. */ + square?: boolean } const LOCKUPS: Lockup[] = [ @@ -24,13 +26,6 @@ const LOCKUPS: Lockup[] = [ background: '#EEEFE9', previewWidth: 180, }, - { - slug: 'posthog-logo-black', - name: 'Dark logo', - logo: { layout: 'landscape', variant: 'mono', color: '#111' }, - background: '#EEEFE9', - previewWidth: 180, - }, { slug: 'posthog-logo-white', name: 'Light logo', @@ -45,6 +40,13 @@ const LOCKUPS: Lockup[] = [ background: '#EEEFE9', previewWidth: 180, }, + { + slug: 'posthog-logo-black', + name: 'Dark logo', + logo: { layout: 'landscape', variant: 'mono', color: '#111' }, + background: '#EEEFE9', + previewWidth: 180, + }, { slug: 'posthog-logomark', name: 'Logomark', @@ -59,6 +61,22 @@ const LOCKUPS: Lockup[] = [ background: '#111', previewWidth: 64, }, + { + slug: 'posthog-logomark-square', + name: 'Square logomark', + logo: { layout: 'logomark', variant: 'gradient' }, + background: '#EEEFE9', + previewWidth: 64, + square: true, + }, + { + slug: 'posthog-logomark-square-white', + name: 'Light square logomark', + logo: { layout: 'logomark', variant: 'mono', color: '#fff' }, + background: '#111', + previewWidth: 64, + square: true, + }, { slug: 'posthog-logo-stacked', name: 'Logo (stacked)', @@ -77,9 +95,10 @@ const LOCKUPS: Lockup[] = [ const PX_PER_UNIT = 6 // rasterized pixels per viewBox unit at 1x (@2x doubles it) const PAD_FRACTION = 0.18 // transparent margin, as a fraction of the shorter viewBox side +const SQUARE_PADDING = 4 // 52-unit-wide logomark centered on the same 60 × 60 canvas as /brand /** Serialize a rendered to a standalone SVG string, optionally with padding. */ -function serializeSvg(source: SVGSVGElement, padded: boolean): string { +function serializeSvg(source: SVGSVGElement, padded: boolean, square = false): string { const svg = source.cloneNode(true) as SVGSVGElement // mono lockups draw with `currentColor` + an inline `color`; bake it into an explicit // fill so the downloaded file renders correctly on its own. @@ -92,6 +111,13 @@ function serializeSvg(source: SVGSVGElement, padded: boolean): string { const vb = source.viewBox.baseVal let { x, y, width, height } = vb + if (square) { + const size = Math.max(width, height) + SQUARE_PADDING * 2 + x -= (size - width) / 2 + y -= (size - height) / 2 + width = size + height = size + } if (padded) { const pad = Math.min(width, height) * PAD_FRACTION x -= pad @@ -116,13 +142,19 @@ function triggerDownload(blob: Blob, filename: string): void { URL.revokeObjectURL(url) } -function downloadSvg(source: SVGSVGElement, slug: string, padded: boolean): void { - const svg = serializeSvg(source, padded) +function downloadSvg(source: SVGSVGElement, slug: string, padded: boolean, square = false): void { + const svg = serializeSvg(source, padded, square) triggerDownload(new Blob([svg], { type: 'image/svg+xml' }), `${slug}${padded ? '-padded' : ''}.svg`) } -async function downloadPng(source: SVGSVGElement, slug: string, scale: number, padded: boolean): Promise { - const svg = serializeSvg(source, padded) +async function downloadPng( + source: SVGSVGElement, + slug: string, + scale: number, + padded: boolean, + square = false +): Promise { + const svg = serializeSvg(source, padded, square) const match = svg.match(/viewBox="([\d.-]+) ([\d.-]+) ([\d.-]+) ([\d.-]+)"/) if (!match) return const width = Number(match[3]) @@ -176,21 +208,52 @@ function LockupCard({ lockup }: { lockup: Lockup }): JSX.Element { className="flex items-center justify-center rounded p-4 min-h-[96px]" style={{ background: lockup.background }} > - +
+ +

{lockup.name}

- downloadSvg(s, lockup.slug, false))} /> - downloadPng(s, lockup.slug, 1, false))} /> - downloadPng(s, lockup.slug, 2, false))} /> + downloadSvg(s, lockup.slug, false, lockup.square))} + /> + downloadPng(s, lockup.slug, 1, false, lockup.square))} + /> + downloadPng(s, lockup.slug, 2, false, lockup.square))} + /> downloadPng(s, lockup.slug, 1, true))} + onClick={withRef((s) => downloadPng(s, lockup.slug, 1, true, lockup.square))} /> downloadPng(s, lockup.slug, 2, true))} + onClick={withRef((s) => downloadPng(s, lockup.slug, 2, true, lockup.square))} />
diff --git a/src/components/Breadcrumbs/index.tsx b/src/components/Breadcrumbs/index.tsx index 3cc14b5b6d02..d8a931c72fc6 100644 --- a/src/components/Breadcrumbs/index.tsx +++ b/src/components/Breadcrumbs/index.tsx @@ -1,5 +1,5 @@ import cntl from 'cntl' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { Link } from 'gatsby' import React from 'react' @@ -80,7 +80,8 @@ export default function Breadcrumbs({ className="w-5 h-4" variant={linkColor ? 'mono' : 'gradient'} color={linkColor} - wordmark={false} + layout="logomark" + width="auto" /> } /> diff --git a/src/components/Careers/FounderNote/index.tsx b/src/components/Careers/FounderNote/index.tsx index 8e61277d0483..e69fc19c161b 100644 --- a/src/components/Careers/FounderNote/index.tsx +++ b/src/components/Careers/FounderNote/index.tsx @@ -13,7 +13,6 @@ import jamesQuote7 from './Audio/quote-7.mp3' import jamesQuote8 from './Audio/quote-8.mp3' import jamesQuote9 from './Audio/quote-9.mp3' import jamesQuote10 from './Audio/quote-10.mp3' -import Logo from 'components/Logo' import { Tweet } from 'components/Tweet' import CloudinaryImage from 'components/CloudinaryImage' diff --git a/src/components/Careers/Transparency/index.tsx b/src/components/Careers/Transparency/index.tsx index 7eac55e262bd..df1b483147b9 100644 --- a/src/components/Careers/Transparency/index.tsx +++ b/src/components/Careers/Transparency/index.tsx @@ -6,7 +6,7 @@ import { BoardMeetings, Compensation, Feedback, Finances } from 'components/Care import boardMeetingsImg from './images/board-meetings.svg' import financesImg from './images/finances.svg' import feedbackImg from './images/feedback.svg' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { StaticImage } from 'gatsby-plugin-image' import { CallToAction } from 'components/CallToAction' import CloudinaryImage from 'components/CloudinaryImage' @@ -112,7 +112,7 @@ export const Transparency = () => {
- +
Management report
diff --git a/src/components/ColoringBook/index.js b/src/components/ColoringBook/index.js index d11eb10a556e..ab0943b8d8dc 100644 --- a/src/components/ColoringBook/index.js +++ b/src/components/ColoringBook/index.js @@ -6,7 +6,6 @@ import * as Icons from '@posthog/icons' import Link from 'components/Link' import { IconBold, IconLink } from 'components/OSIcons' import ScrollArea from 'components/RadixUI/ScrollArea' -import Logo from 'components/Logo' import { RadioGroup } from 'components/RadixUI/RadioGroup' import { navigate } from 'gatsby' import usePostHog from '../../hooks/usePostHog' diff --git a/src/components/CustomerLogos/PostHogLogo.tsx b/src/components/CustomerLogos/PostHogLogo.tsx deleted file mode 100644 index 8abd6ea018e9..000000000000 --- a/src/components/CustomerLogos/PostHogLogo.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react' - -interface PostHogLogoProps { - className?: string -} - -export default function PostHogLogo({ className = '' }: PostHogLogoProps) { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) -} diff --git a/src/components/EventGraphic/index.tsx b/src/components/EventGraphic/index.tsx index e51c782afcb4..8f3a6062f479 100644 --- a/src/components/EventGraphic/index.tsx +++ b/src/components/EventGraphic/index.tsx @@ -1,6 +1,6 @@ import React, { forwardRef } from 'react' import dayjs from 'dayjs' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { PROFILE_COLORS } from 'constants/profileColors' export type EventGraphicSpeaker = { @@ -90,7 +90,7 @@ const EventGraphic = forwardRef(function Even speaker ? '' : 'mt-auto' }`} > - + {partnerNames.length > 0 && ( <> diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx index a6fe1b5cb356..f7ef2c6ea2c5 100644 --- a/src/components/Footer/Footer.tsx +++ b/src/components/Footer/Footer.tsx @@ -1,6 +1,6 @@ import cntl from 'cntl' import Link from 'components/Link' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import React from 'react' import { IProps, LinkListItem } from './LinkList' import { GitHub, LinkedIn, YouTube, Twitter } from 'components/Icons/Icons' @@ -402,7 +402,7 @@ export function Footer(): JSX.Element { className="left-[calc(50%-40px)] w-20 h-12 inline-flex justify-center items-center absolute z-10 rounded bg-light dark:bg-dark px-2 pt-1.5 pb-1 mb-1 border border-b-3 border-primary hover:bg-accent hover:translate-y-[-1px] active:translate-y-[1px] active:transition-all" > - +
diff --git a/src/components/Home/Control/index.tsx b/src/components/Home/Control/index.tsx index a637ee420e24..1f0d8b53ae90 100644 --- a/src/components/Home/Control/index.tsx +++ b/src/components/Home/Control/index.tsx @@ -16,7 +16,7 @@ import { } from 'components/OSIcons' import useProduct from 'hooks/useProduct' import { JsxComponentDescriptor } from '@mdxeditor/editor' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { useApp } from '../../../context/App' import { useWindow } from '../../../context/Window' import MDXEditor from 'components/MDXEditor' @@ -284,7 +284,12 @@ const jsxComponentDescriptors: JsxComponentDescriptor[] = [ const { siteSettings } = useApp() return ( <> - {' '} + {' '} ) }, diff --git a/src/components/Home/HeroCarousel/slides.tsx b/src/components/Home/HeroCarousel/slides.tsx index 80dcc78bfb83..ed93dc2f269c 100644 --- a/src/components/Home/HeroCarousel/slides.tsx +++ b/src/components/Home/HeroCarousel/slides.tsx @@ -3,7 +3,7 @@ import { useStaticQuery, graphql } from 'gatsby' import { IconFlag, IconLightBulb, IconRocket, IconSearch, IconSparkles } from '@posthog/icons' import Link from 'components/Link' import Tooltip from 'components/RadixUI/Tooltip' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { getLogo } from 'constants/logos' import useSourcePlatforms from 'hooks/useSourcePlatforms' import { SELF_HOSTED_SOURCES } from 'constants/sources' @@ -402,8 +402,8 @@ const emptyStateActions = [ const PostHogAIEmptyState = () => (
- - + +

Hello, builder!

diff --git a/src/components/Home/Index.tsx b/src/components/Home/Index.tsx index 5f00059b60b6..9b026e830360 100644 --- a/src/components/Home/Index.tsx +++ b/src/components/Home/Index.tsx @@ -4,7 +4,6 @@ import Link from 'components/Link' import Editor from 'components/Editor' import OSTable from 'components/OSTable' import ScrollArea from 'components/RadixUI/ScrollArea' -import Logo from 'components/Logo' export default function Home(): JSX.Element { return ( diff --git a/src/components/Home/Sections/Hero.tsx b/src/components/Home/Sections/Hero.tsx index 43c0dc7f983d..aa944dcc49f5 100644 --- a/src/components/Home/Sections/Hero.tsx +++ b/src/components/Home/Sections/Hero.tsx @@ -7,7 +7,7 @@ import { DebugFixSlide, TestRolloutSlide, } from 'components/Home/HeroCarousel/slides' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { useApp } from '../../../context/App' import { DebugContainerQuery } from 'components/DebugContainerQuery' import { CTAs } from 'components/CTAs' @@ -91,6 +91,7 @@ export const Hero = () => { className="inline-block h-10 w-auto align-baseline relative ml-1.5 mr-1 relative top-2" variant={siteSettings.theme === 'dark' ? 'mono' : 'gradient'} color={siteSettings.theme === 'dark' ? 'white' : undefined} + width="auto" />{' '} diff --git a/src/components/Home/Test/Demos.tsx b/src/components/Home/Test/Demos.tsx index 130e8f6709e8..ee2daec1b832 100644 --- a/src/components/Home/Test/Demos.tsx +++ b/src/components/Home/Test/Demos.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useRef, useState } from 'react' import { CallToAction } from 'components/CallToAction' import ScrollArea from 'components/RadixUI/ScrollArea' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import OSButton from 'components/OSButton' import { useApp } from '../../../context/App' import WistiaVideo, { WistiaVideoRef } from 'components/WistiaVideo' @@ -738,6 +738,7 @@ const jsxComponentDescriptors: JsxComponentDescriptor[] = [ className="inline-block" variant={siteSettings.theme === 'dark' ? 'mono' : 'gradient'} color={siteSettings.theme === 'dark' ? 'white' : undefined} + width="auto" /> ) }, diff --git a/src/components/Home/Test/index.tsx b/src/components/Home/Test/index.tsx index 1071850f64c8..390b60c80279 100644 --- a/src/components/Home/Test/index.tsx +++ b/src/components/Home/Test/index.tsx @@ -11,7 +11,7 @@ import HitCounter from 'components/Home/HitCounter' import Link from 'components/Link' import { IconHeadset, IconPlayFilled } from '@posthog/icons' import { IconMCP } from 'components/OSIcons' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { CallToAction } from 'components/CallToAction' import IntegrationPrompt from 'components/IntegrationPrompt' import { motion } from 'framer-motion' @@ -142,6 +142,7 @@ function Hero(): JSX.Element { className="max-w-[157px]" variant={isDark ? 'mono' : 'gradient'} color={isDark ? 'white' : undefined} + width="auto" /> diff --git a/src/components/Home/images/Logomark.js b/src/components/Home/images/Logomark.js deleted file mode 100644 index 8a8b1e23fa42..000000000000 --- a/src/components/Home/images/Logomark.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' - -export default function Logo({ className = '' }) { - return ( - - - - - - - - ) -} diff --git a/src/components/Korean/KoreanTaskBarMenu/menuData.tsx b/src/components/Korean/KoreanTaskBarMenu/menuData.tsx index c931c54e4c88..d64d68393665 100644 --- a/src/components/Korean/KoreanTaskBarMenu/menuData.tsx +++ b/src/components/Korean/KoreanTaskBarMenu/menuData.tsx @@ -3,7 +3,7 @@ import React from 'react' import { docsMenu, handbookSidebar } from '../../../navs' import * as Icons from '@posthog/icons' import { useSmallTeamsMenuItems } from './SmallTeamsMenuItems' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { APP_COUNT } from 'constants/index' import SearchableProductMenu from './SearchableProductMenu' import { @@ -883,8 +883,19 @@ export function useMenuData(): MenuType[] { trigger: ( <>
- - + +
diff --git a/src/components/Layout/Fonts.css b/src/components/Layout/Fonts.css index 73767eeaf29b..b1e4d7fa0c25 100644 --- a/src/components/Layout/Fonts.css +++ b/src/components/Layout/Fonts.css @@ -20,9 +20,10 @@ font-style: normal; } +/* RoundHog's font files and weight scale come from @posthog/brand. */ @font-face { font-family: 'RoundHog'; - src: url('https://res.cloudinary.com/dmukukwp6/raw/upload/fonts/RoundHog.woff2') format('woff2'); + src: url('~@posthog/brand/fonts/RoundHog.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; @@ -30,7 +31,7 @@ @font-face { font-family: 'RoundHog'; - src: url('https://res.cloudinary.com/dmukukwp6/raw/upload/fonts/RoundHog-Italic.woff2') format('woff2'); + src: url('~@posthog/brand/fonts/RoundHog-Italic.woff2') format('woff2'); font-weight: 400; font-style: italic; font-display: swap; @@ -38,7 +39,7 @@ @font-face { font-family: 'RoundHog'; - src: url('https://res.cloudinary.com/dmukukwp6/raw/upload/fonts/RoundHog-Medium.woff2') format('woff2'); + src: url('~@posthog/brand/fonts/RoundHog-Medium.woff2') format('woff2'); font-weight: 500; font-style: normal; font-display: swap; @@ -46,7 +47,7 @@ @font-face { font-family: 'RoundHog'; - src: url('https://res.cloudinary.com/dmukukwp6/raw/upload/fonts/RoundHog-Medium-Italic.woff2') format('woff2'); + src: url('~@posthog/brand/fonts/RoundHog-Medium-Italic.woff2') format('woff2'); font-weight: 500; font-style: italic; font-display: swap; @@ -54,32 +55,32 @@ @font-face { font-family: 'RoundHog'; - src: url('https://res.cloudinary.com/dmukukwp6/raw/upload/fonts/RoundHog-SemiBold.woff2') format('woff2'); - font-weight: 600; + src: url('~@posthog/brand/fonts/RoundHog-SemiBold.woff2') format('woff2'); + font-weight: 700; font-style: normal; font-display: swap; } @font-face { font-family: 'RoundHog'; - src: url('https://res.cloudinary.com/dmukukwp6/raw/upload/fonts/RoundHog-SemiBold-Italic.woff2') format('woff2'); - font-weight: 600; + src: url('~@posthog/brand/fonts/RoundHog-SemiBold-Italic.woff2') format('woff2'); + font-weight: 700; font-style: italic; font-display: swap; } @font-face { font-family: 'RoundHog'; - src: url('https://res.cloudinary.com/dmukukwp6/raw/upload/fonts/RoundHog-Bold.woff2') format('woff2'); - font-weight: 700; + src: url('~@posthog/brand/fonts/RoundHog-Bold.woff2') format('woff2'); + font-weight: 800; font-style: normal; font-display: swap; } @font-face { font-family: 'RoundHog'; - src: url('https://res.cloudinary.com/dmukukwp6/raw/upload/fonts/RoundHog-Bold-Italic.woff2') format('woff2'); - font-weight: 700; + src: url('~@posthog/brand/fonts/RoundHog-Bold-Italic.woff2') format('woff2'); + font-weight: 800; font-style: italic; font-display: swap; } diff --git a/src/components/Logo/index.tsx b/src/components/Logo/index.tsx deleted file mode 100644 index 66a4fdea20fb..000000000000 --- a/src/components/Logo/index.tsx +++ /dev/null @@ -1,358 +0,0 @@ -import React, { useId } from 'react' - -type Variant = 'gradient' | 'print' | 'mono' - -type LogoProps = { - variant?: Variant - color?: string - wordmark?: boolean - code?: boolean - stacked?: boolean - className?: string -} - -type FillProps = { fill?: string; className?: string } - -function resolveSolidFill(variant: Variant, color: string): FillProps { - if (variant !== 'mono') return { fill: '#111' } - if (color === 'primary') return { className: 'fill-primary' } - if (color === 'white') return { fill: '#FAFAFA' } - if (color === 'black') return { fill: '#111' } - return { fill: color } -} - -function getViewBox(stacked: boolean, wordmark: boolean, code: boolean): string { - if (!wordmark) return '0 0 52 28' - if (stacked) return code ? '0 0 52 59' : '0 0 52 47' - return code ? '0 0 229 28' : '0 0 160 28' -} - -export default function Logo({ - variant = 'gradient', - color = 'black', - wordmark = true, - code = false, - stacked = false, - className = '', -}: LogoProps) { - const uid = useId().replace(/:/g, '') - const blueMaskId = `posthog-logo-${uid}-mask-blue` - const redMaskId = `posthog-logo-${uid}-mask-red` - const yellowMaskId = `posthog-logo-${uid}-mask-yellow` - const clipId = `posthog-logo-${uid}-clip` - - const solid = resolveSolidFill(variant, color) - const viewBox = getViewBox(stacked, wordmark, code) - - return ( - - {variant === 'gradient' && } - {variant === 'print' && ( - - )} - {variant === 'mono' && } - - {wordmark && !stacked && } - {wordmark && !stacked && code && } - {wordmark && stacked && } - {wordmark && stacked && code && } - - {variant === 'gradient' && } - {variant === 'print' && } - - ) -} - -function GradientLogomark({ uid, clipId, solid }: { uid: string; clipId: string; solid: FillProps }) { - return ( - - - - - - - - - - - - - ) -} - -function MonoLogomark({ solid }: { solid: FillProps }) { - return ( - - ) -} - -function PrintLogomark({ - blueMaskId, - redMaskId, - yellowMaskId, - clipId, -}: { - blueMaskId: string - redMaskId: string - yellowMaskId: string - clipId: string -}) { - return ( - - - - - - - - - - - - - - - - - - - - - - ) -} - -function LandscapeWordmark({ solid }: { solid: FillProps }) { - return ( - - - - - - - - - - ) -} - -function LandscapeCodeWordmark({ solid }: { solid: FillProps }) { - return ( - - ) -} - -function StackedWordmark({ solid }: { solid: FillProps }) { - return ( - - - - - - - - - - ) -} - -function StackedCodeWordmark({ solid }: { solid: FillProps }) { - return ( - - ) -} - -function GradientDefs({ uid, clipId }: { uid: string; clipId: string }) { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) -} - -function PrintDefs({ clipId }: { clipId: string }) { - return ( - - - - - - ) -} diff --git a/src/components/MainNav/index.tsx b/src/components/MainNav/index.tsx index 8eb553b63e6f..dce7b121e8c1 100644 --- a/src/components/MainNav/index.tsx +++ b/src/components/MainNav/index.tsx @@ -1,6 +1,6 @@ import CloudinaryImage from 'components/CloudinaryImage' import Link from 'components/Link' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { useValues } from 'kea' import { layoutLogic } from '../../logic/layoutLogic' import { @@ -507,6 +507,7 @@ export const Main = () => { variant={websiteTheme === 'dark' ? 'mono' : 'gradient'} color={websiteTheme === 'dark' ? 'white' : undefined} className="h-[24px] relative px-2 box-content" + width="auto" /> )} diff --git a/src/components/Merch/OrderHistory.tsx b/src/components/Merch/OrderHistory.tsx index 760e3c053068..4c6e4cd08d59 100644 --- a/src/components/Merch/OrderHistory.tsx +++ b/src/components/Merch/OrderHistory.tsx @@ -1,4 +1,4 @@ -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import React from 'react' import dayjs from 'dayjs' import { IconSpinner } from '@posthog/icons' @@ -54,7 +54,12 @@ export default function OrderHistory({ orders }: { orders: any[] }) {
- +
diff --git a/src/components/Presentation/Templates/PricingTemplate.tsx b/src/components/Presentation/Templates/PricingTemplate.tsx index b0f0064471e3..8746c7cb2113 100644 --- a/src/components/Presentation/Templates/PricingTemplate.tsx +++ b/src/components/Presentation/Templates/PricingTemplate.tsx @@ -1,7 +1,6 @@ import React from 'react' import ParseHtml from '../Utilities/parseHtml' import CloudinaryImage from 'components/CloudinaryImage' -import Logo from 'components/Logo' import FreeTier from 'components/Pricing/Test/FreeTier' import { Accordion } from 'components/Pricing/Test/PricingAccordion' import { useState } from 'react' diff --git a/src/components/Presentation/Templates/StackedTemplate.tsx b/src/components/Presentation/Templates/StackedTemplate.tsx index 44ec1f9cb28b..a21aa5c7961a 100644 --- a/src/components/Presentation/Templates/StackedTemplate.tsx +++ b/src/components/Presentation/Templates/StackedTemplate.tsx @@ -1,7 +1,6 @@ import React from 'react' import ParseHtml from '../Utilities/parseHtml' import CloudinaryImage from 'components/CloudinaryImage' -import Logo from 'components/Logo' import { IconHeartFilled } from '@posthog/icons' import SalesRep from '../Utilities/SalesRep' import Logos from '../Utilities/Logos' diff --git a/src/components/Presentation/Utilities/Logos.tsx b/src/components/Presentation/Utilities/Logos.tsx index 0a8ff2667bb9..0a85cd99f77e 100644 --- a/src/components/Presentation/Utilities/Logos.tsx +++ b/src/components/Presentation/Utilities/Logos.tsx @@ -1,4 +1,4 @@ -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { IconHeartFilled } from '@posthog/icons' import React from 'react' @@ -16,10 +16,11 @@ export default function Logos({ return (
{companyLogo && ( <> diff --git a/src/components/Pricing/CloudVsSelfHost/index.tsx b/src/components/Pricing/CloudVsSelfHost/index.tsx index 9471612025d0..91567e95b64e 100644 --- a/src/components/Pricing/CloudVsSelfHost/index.tsx +++ b/src/components/Pricing/CloudVsSelfHost/index.tsx @@ -1,4 +1,4 @@ -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import React from 'react' export const CloudVsSelfHost = ({ className = '' }) => { @@ -10,7 +10,7 @@ export const CloudVsSelfHost = ({ className = '' }) => {

- + PostHog Web

diff --git a/src/components/Pricing/Test/SimilarProducts.tsx b/src/components/Pricing/Test/SimilarProducts.tsx index f5e4a9d5c198..d9578d1cf548 100644 --- a/src/components/Pricing/Test/SimilarProducts.tsx +++ b/src/components/Pricing/Test/SimilarProducts.tsx @@ -4,7 +4,7 @@ import { section, SectionHeader } from './Sections' import { usePlatform } from '../Platform/usePlatform' import useProducts from '../Products' import { IconCheck, IconX } from '@posthog/icons' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { StaticImage } from 'gatsby-plugin-image' import { CTA } from '../Plans' import ScrollArea from 'components/RadixUI/ScrollArea' @@ -102,7 +102,7 @@ const comparison = [ name: 'PostHog', logo: (
- +
), products: { diff --git a/src/components/Product/Comparison.tsx b/src/components/Product/Comparison.tsx index 645b48bd4e41..ea6fd34088ad 100644 --- a/src/components/Product/Comparison.tsx +++ b/src/components/Product/Comparison.tsx @@ -1,7 +1,7 @@ import { IconCheck, IconX } from '@posthog/icons' import { CallToAction } from 'components/CallToAction' import Link from 'components/Link' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import React, { useState } from 'react' const companies = { Amplitude: { @@ -100,7 +100,7 @@ export default function Comparison({ comparison, columnCount, truncate, classNam const { comparisonURL } = companies[company] return company.toLowerCase() === 'posthog' ? (
- +
) : (
diff --git a/src/components/ProductComparisonTable/index.tsx b/src/components/ProductComparisonTable/index.tsx index 56f774ede433..cce4bd6c18c6 100644 --- a/src/components/ProductComparisonTable/index.tsx +++ b/src/components/ProductComparisonTable/index.tsx @@ -1,6 +1,6 @@ import React from 'react' import OSTable from 'components/OSTable' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import Link from 'components/Link' import { IconArrowUpRight } from '@posthog/icons' import { useApp } from '../../context/App' @@ -1017,6 +1017,7 @@ export default function ProductComparisonTable({ className="h-5 mx-auto w-auto max-w-full" variant={isDark ? 'mono' : 'gradient'} color={isDark ? 'white' : undefined} + width="auto" /> ) : competitorData[key]?.name ? ( competitorData[key].name diff --git a/src/components/Products/Competitor/VsPostHog.tsx b/src/components/Products/Competitor/VsPostHog.tsx index 9cabb0911108..62959860c822 100644 --- a/src/components/Products/Competitor/VsPostHog.tsx +++ b/src/components/Products/Competitor/VsPostHog.tsx @@ -1,7 +1,7 @@ import CloudinaryImage from 'components/CloudinaryImage' import React from 'react' import { StaticImage } from 'gatsby-plugin-image' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' export const VsPostHog = ({ children, className }) => { return ( @@ -17,7 +17,7 @@ export const VsPostHog = ({ children, className }) => {

- Reasons to choose + Reasons to choose

{children}
diff --git a/src/components/Products/ReaderViewProduct/templates/ComparisonSummary.tsx b/src/components/Products/ReaderViewProduct/templates/ComparisonSummary.tsx index d3ef35e4579e..7b6555df6a60 100644 --- a/src/components/Products/ReaderViewProduct/templates/ComparisonSummary.tsx +++ b/src/components/Products/ReaderViewProduct/templates/ComparisonSummary.tsx @@ -1,5 +1,5 @@ import React from 'react' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { SectionComponentProps } from '../types' import CloudinaryImage from 'components/CloudinaryImage' @@ -57,10 +57,11 @@ const ComparisonSummary = ({ id, productData }: SectionComponentProps) => {

Go with{' '} {' '} if...

diff --git a/src/components/Products/Slides/ComparisonSummarySlide.tsx b/src/components/Products/Slides/ComparisonSummarySlide.tsx index d9b6f1cf4b9c..edce997107eb 100644 --- a/src/components/Products/Slides/ComparisonSummarySlide.tsx +++ b/src/components/Products/Slides/ComparisonSummarySlide.tsx @@ -1,6 +1,6 @@ import React from 'react' import CloudinaryImage from 'components/CloudinaryImage' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' interface ComparisonItem { title: string @@ -65,10 +65,10 @@ export default function ComparisonSummarySlide({ them, us }: ComparisonSummarySl

Reasons to choose{' '} {' '} PostHog

diff --git a/src/components/Products/Slides/CustomersSlide.tsx b/src/components/Products/Slides/CustomersSlide.tsx index ceab83388840..15a73315e2c5 100644 --- a/src/components/Products/Slides/CustomersSlide.tsx +++ b/src/components/Products/Slides/CustomersSlide.tsx @@ -1,7 +1,7 @@ import React from 'react' import { Link } from 'gatsby' import OSTable from 'components/OSTable' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { SlideContainer } from './SlidesTemplate' import ScrollArea from 'components/RadixUI/ScrollArea' import OSButton from 'components/OSButton' @@ -126,13 +126,13 @@ export default function CustomersSlide({ return ( -

+

Customers who love{' '} {' '} {productName}

diff --git a/src/components/Products/Slides/TracesComparisonTable.tsx b/src/components/Products/Slides/TracesComparisonTable.tsx index 57182473e817..9d72ec731fc4 100644 --- a/src/components/Products/Slides/TracesComparisonTable.tsx +++ b/src/components/Products/Slides/TracesComparisonTable.tsx @@ -1,6 +1,6 @@ import React from 'react' import OSTable from 'components/OSTable' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import { useApp } from '../../../context/App' // Small building blocks for the comparison cells @@ -38,7 +38,13 @@ export default function TracesComparisonTable(): JSX.Element { width: '1fr', }, { - name: , + name: ( + + ), align: 'center' as const, width: '1fr', }, @@ -55,49 +61,56 @@ export default function TracesComparisonTable(): JSX.Element { }) // Feature row: label + one cell per competitor (Better Stack, Datadog, Sentry, PostHog) - const featureRow = (label: React.ReactNode, values: React.ReactNode[]) => ({ - cells: [{ content: label, className: '!items-start text-left font-semibold' }, ...values.map((content) => ({ content }))], + const featureRow = (label: React.ReactNode, ...values: React.ReactNode[]) => ({ + cells: [ + { content: label, className: '!items-start text-left font-semibold' }, + ...values.map((content) => ({ content })), + ], }) const rows = [ sectionRow('Tracing'), - featureRow('Distributed trace waterfall', [, , , ]), - featureRow('Service / dependency map', [, , , ]), - featureRow('Code-level profiling (flame graphs)', [, , , ]), - featureRow('Sampling & retention controls', [ + featureRow('Distributed trace waterfall', , , , ), + featureRow('Service / dependency map', , , , ), + featureRow('Code-level profiling (flame graphs)', , , , ), + featureRow( + 'Sampling & retention controls', , , Head sampling only, - Via OTel SDK, - ]), + Via OTel SDK + ), sectionRow('Standards & setup'), - featureRow('Native OpenTelemetry ingestion', [ + featureRow( + 'Native OpenTelemetry ingestion', , , , - , - ]), - featureRow('No proprietary SDK required', [, Partial, Partial, ]), - featureRow('Instrumentation', [ + + ), + featureRow('No proprietary SDK required', , Partial, Partial, ), + featureRow( + 'Instrumentation', eBPF, Agent, SDK, - OpenTelemetry, - ]), + OpenTelemetry + ), sectionRow('One platform'), - featureRow('Errors, logs & session replay alongside traces', [, , , ]), + featureRow('Errors, logs & session replay alongside traces', , , , ), sectionRow('AI & self-driving'), - featureRow('AI opens a code fix PR', [, , , ]), - featureRow('Fix a bug and open a PR from Slack', [, , , ]), + featureRow('AI opens a code fix PR', , , , ), + featureRow('Fix a bug and open a PR from Slack', , , , ), sectionRow('Pricing & licensing'), - featureRow('Pricing model', [ + featureRow( + 'Pricing model', Per GB ingested, Per host + per span, Per span, - Usage-based, no per-host fee, - ]), - featureRow('Free tier', [, Trial only, , ]), - featureRow('Open source', [, , Source-available (FSL), ]), + Usage-based, no per-host fee + ), + featureRow('Free tier', , Trial only, , ), + featureRow('Open source', , , Source-available (FSL), ), ] return ( diff --git a/src/components/SignUp/Intro.js b/src/components/SignUp/Intro.js index de4ec5922f1a..91a513e311d3 100644 --- a/src/components/SignUp/Intro.js +++ b/src/components/SignUp/Intro.js @@ -1,5 +1,5 @@ import Link from 'components/Link' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import React from 'react' import { heading, section } from './classes' @@ -7,7 +7,7 @@ export default function Intro({ title, children = null }) { return (
- +

{title}

{children} diff --git a/src/components/SignUp/Layout.js b/src/components/SignUp/Layout.js index 9343d382f237..6486f1745a81 100644 --- a/src/components/SignUp/Layout.js +++ b/src/components/SignUp/Layout.js @@ -1,6 +1,6 @@ import Breadcrumbs from 'components/Breadcrumbs' import Link from 'components/Link' -import Logo from 'components/Logo' +import { Logo } from '@posthog/brand/logo' import React from 'react' export default function Layout({ children, crumbs = [] }) { @@ -9,7 +9,13 @@ export default function Layout({ children, crumbs = [] }) {
{children}