// Communiqué — shared components. Rebuilt 2026-09-03 to Jo's CMQ-website.psd (see design-assets/cmq-psd-extract/). const IMG_BASE = 'assets/images/psd'; const ICON_BASE = 'assets/icons'; const imgUrl = (name) => (window.__resources && window.__resources[`img_${name}`]) || `${IMG_BASE}/${name}`; const iconUrl = (name) => (window.__resources && window.__resources[`icon_${name}`]) || `${ICON_BASE}/${name}.svg`; const Icon = ({ name, size = 16, style }) => ( ); // Eye mark + wordmark, as placed in the PSD nav bar (eye 74 × 46, wordmark 185 × 48). const Wordmark = ({ variant = 'cream' }) => ( Communiqué ); const Button = ({ variant = 'primary', children, onClick, type, href }) => ( href ? {children} : ); // Section title with a rule either side. ruleLeft / ruleRight are flex values for the two rules // (the PSD fixes one rule's length and lets the other run to the gutter). const SectionTitle = ({ children, ruleLeft, ruleRight, className = '', id }) => (

{children}

); // ───────── Themes (labels as drawn in the PSD, hover fills per button) ───────── const THEMES = [ { key: 'labor', label: 'Labor Struggles', hover: 'var(--t-labor)', ink: 'var(--ink)' }, { key: 'militarization', label: 'Militarization', hover: 'var(--t-milit)', ink: 'var(--cream)' }, { key: 'solidarity', label: 'International Solidarity', hover: 'var(--t-solid)', ink: 'var(--cream)' }, { key: 'data-centers', label: 'Data Centers', hover: 'var(--t-data)', ink: 'var(--ink)' }, { key: 'ports', label: 'The Ports', hover: 'var(--t-ports)', ink: 'var(--cream)' }, { key: 'palestine', label: 'Palestine / West Asia', hover: 'var(--t-pal)', ink: 'var(--ink)' }, { key: 'belgium', label: 'Belgium / Europe', hover: 'var(--t-bel)', ink: 'var(--ink)' }, { key: 'us', label: 'United States', hover: 'var(--t-us)', ink: 'var(--ink)' }, ]; // ───────── Navigation (PSD: WHAT WE DO · THEMES · IMPACT · ABOUT US · JOIN + search + language) ───────── // The PSD draws the bar only; the dropdown contents come from the site copy's "Navigation Elements". const NAV_DATA = [ { key: 'what', label: 'What we do', href: 'index.html#latest', items: [ ['News', 'index.html#latest'], ['Articles / Analysis', 'index.html#latest'], ['Investigations', 'index.html#investigations'], ['Reports', 'index.html#latest'], ['Campaigns', 'index.html#latest'], ['Training', 'about.html'], ['Media Support', 'about.html'], ['Newsletter', '#newsletter'], ]}, { key: 'themes', label: 'Themes', href: 'index.html#themes', items: THEMES.map(t => [t.label, `index.html#themes`]) }, { key: 'impact', label: 'Impact', href: 'impact.html' }, { key: 'about', label: 'About us', href: 'about.html', items: [ ['Mission & values', 'about.html'], ['Team', 'team.html'], ['Board of directors', 'team.html'], ['How we are funded', 'funding.html'], ['Methodologies', 'methodologies.html'], ['Partnerships', 'about.html'], ['Annual reports', 'about.html'], ['Pitch us / Careers', 'careers.html'], ['Contact & FAQ', 'faq.html'], ['Anonymous tips', 'whistleblowing.html'], ]}, { key: 'join', label: 'Join', href: 'index.html#membership' }, ]; const Nav = ({ onMember }) => { const [open, setOpen] = React.useState(null); const [menu, setMenu] = React.useState(false); return (
); }; // ───────── Scrolling banner (PSD: lime ticker under the nav) ───────── const TICKER_ITEMS = [ ['Appeal:', "Dockers' legal fund — 62% raised", 'index.html#membership'], ['Event:', 'Open newsroom, 28 August, Rue Locquenghien 41', 'faq.html#contact'], ['New:', 'Court orders release of nineteen withheld inspection reports', 'index.html#investigations'], ]; const Ticker = () => { const items = [...TICKER_ITEMS, ...TICKER_ITEMS]; return (
{items.map(([label, text, href], i) => ( = TICKER_ITEMS.length ? 'true' : undefined} tabIndex={i >= TICKER_ITEMS.length ? -1 : undefined}> {label}{text} ))}
); }; const TopRibbon = Ticker; // inner pages still call // ───────── Hero (PSD: featured investigation over a full-bleed photo) ───────── const Hero = () => (

Investigations - featured story

Logistics for Genocide: FedEx makes billions on war, shipping weapons to Israel

); // ───────── Open investigations gallery (PSD: cream band, editorial collage + two buttons) ───────── // Jo, note 8 (2026-09-08, revised the same evening): the band is a carousel now. The arrows the // PSD drew as decoration page the track (behaviour: assets/js/home-extras.js) and the CENTRE // IMAGE is what grows: the collage is 1.85x its PSD size (685 × 393 design px to 1267 × 727). // The slide stacks the collage above the headline and the CTAs so the two buttons keep sitting // side by side; the geometry and the arithmetic are in styles/site.css under .gallery. const OPEN_INV = { image: 'editorial-fedex.png', alt: 'Collage: a FedEx cargo aircraft, fighter jets, bombs and US defence agency seals over an orange map', title: ["Exposing FedEx's role in", 'the genocide supply chain'], href: '#investigations', ctas: [['Read the latest report', '#investigations', 'dark'], ['Subscribe for updates', '#newsletter', 'light']], }; // PLACEHOLDER (Jo: "same thing repeated for now"). Communiqué has one open investigation, so the // one item is repeated to give the carousel something to page through. When there is a real list, // replace OPEN_INV/INV_PLACEHOLDER_COPIES with that list and map over it. const INV_PLACEHOLDER_COPIES = 4; const InvestigationSlide = ({ item, index }) => (
{item.alt}/

{item.title[0]}{' '}
{item.title[1]}

{item.ctas.map(([label, href, kind]) => {label})}
); const OpenInvestigations = () => (
Open Investigations
{Array.from({ length: INV_PLACEHOLDER_COPIES }, (_, i) => )}

); // ───────── Themes (PSD: orange textured band with the torn cream header, hard-shadow buttons) ───────── const Themes = () => (
{/* On desktop the word THEMES and its rule are part of the textured header artwork from the PSD; this heading is shown on small screens and read by assistive tech. */} Themes
); // ───────── Al-bayan (PSD: purple textured band, plant, giant headline, hand-drawn buttons, stacked video card) ───────── // Jo, note 11 (2026-09-08): clicking anywhere in this band goes to al-bayan.communique.press // except the video box, which plays in place. The full-bleed .albayan-cover anchor does the // linking and the CSS makes the content layer click-through over it. const ALBAYAN_URL = 'https://al-bayan.communique.press'; const AlBayan = () => (
Independent media lab

The al-bayan{' '}
project

Over 270 local reporters have been killed during Israel's genocidal assault, and foreign reporters are still barred from entry. Gaza's people need to have their voices heard. Journalists need funding in order to live and to tell their stories.

{/* Notes 12/13: the two hrefs come from DONATE_URL and ALBAYAN_INSTAGRAM_URL at the top of assets/js/home-extras.js. Until a constant is filled in the button falls back to the al-bayan landing page, which is where both pointed before. */} Donate Follow al-bayan
Video still: boys reading in a mosque in Gaza

A day in the life of a Sheik in Gaza

Charter contracts, budget lines and the three brokers that move people out of Zaventem on public money.

9 August 2026 — L. Mertens ENAR
); // ───────── Become a member (PSD: dark band, lime headline, two 774 × 98 buttons) ───────── const Membership = () => (

Become a member

We take no advertising and no state money. Members fund the records requests, the translators and the legal defence — about €14 a month keeps one investigation moving.

Set up a monthly donation Volunteer with Communiqué
); // ───────── Footer (PSD: green, black type, sign-up, three columns, eye mark, legal row) ───────── const FOOTER_COLS = [ ['What we do', [['News', 'index.html#latest'], ['Analysis', 'index.html#latest'], ['Investigations', 'index.html#investigations'], ['Reports', 'index.html#latest'], ['Campaigns', 'index.html#latest']]], ['About', [['Mission & values', 'about.html'], ['Team', 'team.html'], ['Board of directors', 'team.html'], ['How we are funded', 'funding.html'], ['Annual reports', 'about.html']]], ['Get involved', [['Become a member', 'index.html#membership'], ['Donate', 'faq.html#donations'], ['Volunteer', 'careers.html#volunteer'], ['Pitch us', 'careers.html#pitches'], ['Anonymous tips', 'whistleblowing.html'], ['Contact & FAQ', 'faq.html']]], ]; const LEGAL = ['Privacy policy', 'Accessibility', 'Cookie statement', 'Permissions', 'Disclaimers', 'Copyright notice']; const Footer = () => { const [email, setEmail] = React.useState(''); const [done, setDone] = React.useState(false); return ( ); }; // The PSD has a single fixed palette; the old light/dark toggle is retired but the export stays for the page shells. const ThemeToggle = () => null; Object.assign(window, { Icon, Wordmark, Button, SectionTitle, THEMES, Nav, Ticker, TopRibbon, Hero, OpenInvestigations, Themes, AlBayan, Membership, Footer, ThemeToggle, imgUrl });