Skip to content

Private deck ​

/deck is the pitch for investors and invited readers. It is not in the public navigation. Before the first slide the page asks for an email (name and company are optional). Without that step there are no slides: the images are not under public/.

The page is noindex (meta and X-Robots-Tag) and robots.txt disallows /deck.

What a viewer sees ​

One slide at a time, with a counter (3 / 11), previous and next buttons, keyboard arrows, and swipe on a phone. Language does not follow the site or the browser. An EN | ES control sits at the top right. ?lang=es or ?lang=en wins; otherwise the last choice stored in the browser; otherwise the deck opens in English. The same control translates the gate. The session is an HttpOnly cookie (futuros_deck) for 14 days, HMAC-signed, so the email is not asked again on every slide.

Where the emails go ​

Each valid submit is appended to the Redis list deck:viewers:v1 (Upstash, the same UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN as the trust ledger). Each row stores email, name and company when given, time, user-agent and referrer.

To read them:

  1. Upstash console: LRANGE deck:viewers:v1 0 -1 (newest at the end).
  2. Or GET /api/deck-access with Authorization: Bearer <DECK_GATE_SECRET>. Without that secret the list stays closed. A viewer's cookie does not list anyone else.
  3. If Mailgun is already configured (MAILGUN_API_KEY, MAILGUN_DOMAIN, MAILGUN_FROM), a note also goes to DECK_NOTIFY_EMAIL, or to marcus@futuros.xyz when that variable is unset.

If Redis is not configured the response says persisted: false. The cookie is still issued, and the PostHog event still carries the email as a person property.

Analytics ​

On the production domain PostHog receives deck_gate_view, deck_email_submit, deck_slide_view (with slide) and deck_complete. Each carries lang (en or es). Switching the control fires deck_lang_switch with from, to and slide. The browser event is marked via: "client". The server sends another deck_email_submit with via: "api" so an ad blocker cannot drop the submit. The notification mail includes the language as a Language field. The distinct_id is the same sha-256 pseudonym as the rest of the platform (c_ plus 24 hex), not the raw email. The email is a person property, the same way the /contribuir gate works.

Variables ​

VariableRole
UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKENStore the list deck:viewers:v1. If DECK_GATE_SECRET is unset, the token also signs the cookie.
DECK_GATE_SECRETSigns the cookie and authorizes the list. 16 characters or more.
DECK_NOTIFY_EMAILNotify address. Defaults to marcus@futuros.xyz when Mailgun is already on.
MAILGUN_*The same variables as the rest of the product. Without them there is no mail. The Redis list is still the record.

The slides ​

There are two slide languages, the same eleven frames. English lives in deck-slides/01.webp … 11.webp and is the default. Spanish lives in deck-slides/es/. A direct Spanish link is futuros.xyz/deck?lang=es. /api/deck-slide?n=3&lang=es returns the Spanish file and requires the same cookie. Any other lang falls back to English. Cache-Control: private, no-store so the CDN does not keep a slide without a session; the cache key is the full URL, including lang.

The English PDF is not published. It lives at deck-src/futuros-deck.pdf (flattened images, no text layer). scripts/render-deck-slides.sh rasterizes it to the English WebPs (288 dpi, 4× the point grid, 4320×2430, WebP quality 100). Those files are not regenerated from HTML.

The editable source for the Spanish deck — and for an English HTML proof — is deck-src/html/ (strings.json with both languages, deck.css). To re-render Spanish:

bash
bun scripts/render-deck-html.ts es

That writes deck-slides/es/01.webp … 11.webp at 4320×2430. bun scripts/render-deck-html.ts en --out <directory> writes an English proof only into that directory, never over deck-slides/.

Those WebP files are included in the function bundle (includeFiles: deck-slides/** in vercel.json), not under public/. If the folder is empty, the gate still stores the email and the page says it could not open the deck. GET /api/deck-access includes languages: ["en","es"].

Every figure with its source — traceability is the contract.