Press Hook

A press-kit and sourcing platform where consumer brands publish a media dossier and journalists find products, experts and samples for a story.

presshook.com
Press Hook: Hero over fanned coverage clippings

What they came with

A brand's press material normally lives in a shared drive. Photographs in one folder, the founder bio in another, the current price list in somebody's inbox. A journalist working to a deadline needs all of that, plus an answer to a question nobody writes down anywhere: will this brand reply in time, and will they post a sample. Both halves of that exchange had to become records the site could index, filter and keep current, and one record had to read as a pitch to a brand and as a sourcing card to the press.

What the build had to do

The press kit as a record

A brand profile at /brands/<slug> assembles six anchored collections under one sticky section nav: overview, products, team and experts, press coverage, press releases and an asset library whose logo, banner and gallery each have their own download route. The work is in making each block hold its shape when a brand has filled in three of the six, so the page never reads as a form with gaps.

Three catalogues off one graph

Brands, products and experts each get their own index with its own filters, sort order and result count, but they read the same underlying records, so a product knows its brand and a brand knows its named experts. The expert index drops the card grid for rows under column headings, because expertise tags, active-since dates and certifications do not sit inside a tile.

Sourcing fields on the product card

Each product card carries price, category path, affiliate networks, retailers with an overflow count, ownership and sustainability tags, an estimated reply time and a sample request. Those are the fields a journalist actually decides on, and getting all of them onto one card without it turning into a wall is most of the design problem.

Coverage and releases share an address

Clips and releases both live at /newsfeed/<id> with an outlet or category label, a brand, a date and a body, and both surface again inside the relevant tab on the brand profile. Releases keep a long-form layout that ends with a press contact and brand boilerplate, so the same route has to render a two-line clip and a full announcement.

Plans, terms and a comparison grid

Pricing runs monthly, six-month and annual terms that reprice the cards in place, then a per-feature comparison table, then a separate agency tier priced by roster size. The free tier states what it does not include as plainly as what it does, which is a harder panel to lay out than a list of ticks.

Technical detail

A search assistant that runs in the browser

Site search loads @mlc-ai/web-llm and runs a quantised Qwen2.5-1.5B-Instruct model on the visitor's own GPU through WebGPU to turn a plain-language query into filters, with a smaller Llama-3.2-1B build for constrained hardware and a server-side path when WebGPU is absent. The engine probes the adapter for vendor, architecture and largest buffer and computes a VRAM budget before it commits, then handles stalled downloads, out-of-memory and mid-download device resets as named states rather than a spinner, which is what makes shipping a model to a browser survivable.

Server-rendered records, per-brand metadata

The profile is one QWeb server render with anchored sections rather than a client-side app, so a crawler and a journalist receive identical markup and the section nav is a fragment link, not a route change. Open Graph and Twitter card metadata are generated per brand from the brand record, so a shared link to /brands/saatva carries that brand's own title and description rather than a site-wide default.

Public catalogue, gated actions

Everything reads without an account and only the actions gate: a sample request or a brand message routes through /login?redirect= back to the page in hand, and a press contact address renders masked until you are signed in. Each template therefore renders the full record for an anonymous reader and swaps a control, and the discovery grids page server-side on a thirty-record offset carried on the grid element, so a 2,000-record index never ships as one document.

The stack

Platform

Odoo 19QWeb templatesOdoo WebsiteOdoo eCommercePythonPostgreSQL

In-browser search

@mlc-ai/web-llmWebGPUQwen2.5-1.5B-InstructLlama-3.2-1B-Instruct

Front end

Bootstrap gridFont AwesomeInter via Google FontsHashed asset bundles

Delivery and measurement

CloudflareGoogle Cloud StorageOpenPanelMeta PixelCalendlyJSON-LD Organization
Sector
Consumer PR, media sourcing
Audience
CPG brands, journalists, agencies
Shape
Directory plus brand press kits
Platform
Odoo 19 website
Press Hook on a phone
presshook.com/brands/saatva
Press Hook: Press kit: section nav, products, brand tags
presshook.com/discover/products
Press Hook: Sourcing catalogue: retailers, affiliates, reply times
Next case studyChinesePodA Mandarin learning service built on a large lesson library, sold to self-paced learners, corporate teams and schools from one site.

Something like this to build?

Tell us what runs today and where it hurts. An engineer reads it and replies.