Public Site Pages — Copy Reference

website/pages.md

A page-by-page, key-by-key map of every string the public studio.chat site renders, in render order, so a copywriter (or copywriting agent) can add new copy variants with confidence. For each variant-enabled section you get the exact namespace, base key, the existing _vN sibling keys, the current EN/ES text, and the human-readable variant label registered in the office.

Read docs/website/copy-guide.md first for voice. Short version: lead with podcasts; sharp, dry, craft-led, allergic to agency fluff; rentals are relationship-gated; Spanish is a parallel rewrite, never a literal translation. Brand is always studio.chat (lowercase, with the dot).

Where copy lives (four sources)

  1. locales/en.json / locales/es.json — the bulk of UI/marketing copy, grouped by namespace (home, services, podcasts, …). Pages read it via useTranslations("ns") (client) or getTranslations("ns") / getTranslations({ locale, namespace }) (server). This is where you add variant keys.
  2. src/lib/copy/variants.ts (COPY_PAGES) — the registry of which page/section/fields support variants, plus the human label per variant ("v2 · sharpened generalist"). A _vN key in the locale JSON is invisible to the office until you also register it here. See the final section.
  3. content/*/{en,es}.json and content/projects/*.mdx — content-file copy, loaded by src/lib/content.ts. Covers services list, FAQs, privacy, team, stills captions, and project detail bodies. No variant system — edit the file directly. Listed per page below.
  4. Supabase items table (via src/lib/rentals.ts) — rental catalog item names, summaries, manufacturer, MPN, included accessories, category. Not editable from locale JSON or content files; managed in /office inventory. The catalog/item page chrome (labels, pagination, category display names) does live in locale JSON (rentalsCatalog, rentalsItem) and is covered below.

The eyebrow convention (toEyebrow)

Small mono labels above headings render with toEyebrow() (src/lib/eyebrow.ts): spaces become _ and a trailing _ is appended. The JSON value is natural text — write "say hello", the page renders say_hello_. Never bake underscores into a translation value. Example: footer.sayHello = "say hello" → renders say_hello_. This applies to every <p className="label"> / eyebrow slot noted below.

Page title template

The locale layout (src/app/[locale]/layout.tsx) wraps each page's metadata title as "<title> — studio.chat". Most pages set title to their namespace's title key (e.g. services.title = "services"). The home page overrides with site.metaTitle. The launch page sets an absolute title (no template).


home — /

src/app/[locale]/page.tsx. Namespace: home (plus site, projects). Purpose: the front door. Bold, sparse, visual; a 4-line hero claim, one featured project, the five-service grid, and a single closing CTA. Reads variants at request time (dynamic = "force-dynamic").

1. Hero (VARIANT — home.hero, ns home, fields heroLine1..heroLine4)

A site.tagline eyebrow over a 4-line H1; line 4 is dimmed (text-white/70). The hero is built from four separate keys joined by <br/>, so a variant must supply all four lines as a set. Background image is hardcoded (not copy).

elementkeyENES
eyebrowsite.taglineaudiovisualaudiovisual
line 1home.heroLine1We craft experiencesCreamos experiencias
line 2home.heroLine2in the form of social content,en forma de contenido social,
line 3home.heroLine3short films, and live podcasts —cortometrajes y podcasts en vivo —
line 4 (dimmed)home.heroLine4from concept to color and final mix.del concepto al color y mezcla final.

Variants (registered labels):

  • default = "default" → the four base keys above.
  • v1 = "v1 · podcasts-led" → heroLine1_v1heroLine4_v1
    • EN: Record the show. / Leave with the clips. / Live podcast production in Medellín — / multi-cam, clean audio, same-day cuts.
    • ES: Graba el programa. / Sal con los clips. / Producción de podcasts en vivo en Medellín — / multicámara, audio limpio, cortes el mismo día.
  • v2 = "v2 · sharpened generalist" → heroLine1_v2heroLine4_v2
    • EN: Make it look as good as it sounded. / Social content, short films, and live podcasts, / shot in Medellín — / concept to color, finished in-house.
    • ES: Que se vea tan bien como sonó. / Contenido social, cortometrajes y podcasts en vivo, / grabados en Medellín — / del concepto al color, terminado en casa.

To add v3: add heroLine1_v3heroLine4_v3 to both locale files, then add { id: "v3", label: "v3 · …" } to the home.hero section's variants array.

elementkey / sourceENES
eyebrowhome.featuredfeatureddestacados
link (top-right → /projects)home.viewAllview all projectsver todos los proyectos
cardcontent file — featured project (see projects section)
fallback if no featuredprojects.emptyMore work soon.Pronto más trabajo.

The featured card itself is rendered by ProjectCard from the content/projects/*.mdx frontmatter (title / type / deliverables). See Project shared content below.

3. Services (VARIANT — home.services, ns home, field servicesIntro)

A home.servicesTitle eyebrow over a large display servicesIntro line, then a 5-up grid built from content/services/{en,es}.json (NOT locale JSON).

elementkey / sourceENES
eyebrowhome.servicesTitleservicesservicios
display linehome.servicesIntro (variant base)End-to-end production for brands, artists, and platforms.Producción integral para marcas, artistas y plataformas.
service items (×5)content/services/{locale}.jsontitle + body eachtitle + body each
catalog link (on items with href)home.viewCatalogview catalogver catálogo

servicesIntro variants:

  • default = "default" → base above.
  • v1 = "v1 · studio day" → home.servicesIntro_v1
    • EN: The studio day that ends with a finished episode and a week of clips.
    • ES: El día de estudio que termina con un episodio listo y una semana de clips.
  • v2 = "v2 · record once" → home.servicesIntro_v2
    • EN: Record once. Feed the internet for a week.
    • ES: Graba una vez. Alimenta el internet por una semana.

Service grid items (content file, edit directly — no variants). The 3rd and 5th items carry an href so they become links and render the viewCatalog label:

#title (EN / ES)body (EN)href
01Social content / Contenido socialVertical and horizontal shorts for brands, artists, and platforms. Brief to delivery in weeks, not months.
02Short films / CortometrajesNarrative and documentary work, from treatment to final deliverable. Crews scaled to scope.
03Live podcasts / Podcasts en vivoEnd-to-end live capture: set design, multi-cam, live audio, same-day social cuts./services/podcasts
04Post-production / PostproducciónEditorial, color, sound design, and final mix. Standalone or part of a full production.
05Rentals / AlquilerA small, curated shelf of our cinema gear … rented to producers we trust between our own shoots./services/rentals

4. CTA (VARIANT — home.cta, ns home, fields ctaTitle, ctaBody, ctaButton)

ctaTitle renders as the eyebrow; ctaBody as the big display line; ctaButton is the pill button (→ /contact).

elementkey (variant base)ENES
eyebrowhome.ctaTitlestart a projectcomencemos
display linehome.ctaBodyTell us what you're making. We'll come back with a plan within two business days.Cuéntanos qué estás creando. Te respondemos con un plan en dos días hábiles.
button (→ /contact)home.ctaButtonGet in touchEscríbenos

Variants:

  • default = "default".
  • v1 = "v1 · book a session" → ctaTitle_v1 / ctaBody_v1 / ctaButton_v1
    • EN: book a session / Tell us about the show — format, hosts, episode count. We reply with a plan and a quote within two business days. / Book a session
    • ES: reserva una sesión / Cuéntanos del programa — formato, presentadores, número de episodios. Te respondemos con un plan y una cotización en dos días hábiles. / Reserva una sesión
  • v2 = "v2 · start with a pilot" → ctaTitle_v2 / ctaBody_v2 / ctaButton_v2
    • EN: start with a pilot / New show? Book a single pilot session and see the workflow before committing to a season. / Start with a pilot
    • ES: empieza con un piloto / ¿Programa nuevo? Reserva una sesión piloto y conoce el flujo antes de comprometer una temporada. / Empieza con un piloto

services hub — /services

src/app/[locale]/services/page.tsx. Namespace: services (also reads cinematography, podcasts, rentals, stills). Purpose: the bookable- offering index. One intro, then four offering cards that teaser each service. Dynamic — also resolves the podcasts/rentals hero variants so the teaser taglines stay in sync.

1. Header (VARIANT — services.intro, ns services, field intro)

SectionHeader with services.title as the big H1 and the variant intro below.

elementkeyENES
H1 / titleservices.titleservicesservicios
introservices.intro (variant base)What we offer end-to-end. Bookable as a single show, a season, or a kit-only rental.Lo que ofrecemos de punta a punta. Reservable como show único, temporada completa o alquiler solo del equipo.

intro variants:

  • default = "default".
  • v1 = "v1 · kit ladder" → services.intro_v1
    • EN: Everything we run, end-to-end. Book a single show, a full season, or just the gear.
    • ES: Todo lo que hacemos, de punta a punta. Reserva un show único, una temporada completa o solo el equipo.
  • v2 = "v2 · one room" → services.intro_v2
    • EN: Crew, studio, and kit under one roof. Take it as a single show, a season, or a rental.
    • ES: Gente, estudio y equipo bajo un mismo techo. Tómalo como un show único, una temporada o un alquiler.

2. Offering cards (×4) (taglines pull from other namespaces — see notes)

Each card: a label eyebrow, a big tagline headline, and a cta framed link. The whole card links to href.

#hreflabel keytagline keycta key
1/contactcinematography.title (cinematography/cinematografía)cinematography.taglinecinematography.cta (request a quote / solicitar cotización)
2/services/podcastspodcasts.title (podcasts)podcasts.tagline (variant — see podcasts.hero)podcasts.viewAll (see the offering / ver la oferta)
3/services/rentalsrentals.title (rentals/alquiler)rentals.tagline (variant — see rentals.hero)rentals.viewAll (see the kit / ver el equipo)
4/services/stillsstills.title (stills/fotografía)stills.taglinestills.viewAll (view the gallery / ver la galería)

Notes: Cinematography has no dedicated page by design (held back until staffing catches up) — its card points at /contact with a "request a quote" CTA. cinematography.tagline EN = Cinematic capture — DP, lighting, color, final grade. / ES = Captura cinematográfica — DP, iluminación, color, gradación final. Cards 2 and 3 reuse the podcasts/rentals hero tagline variants (next two sections), so editing those there changes this hub too.


podcasts — /services/podcasts

src/app/[locale]/services/podcasts/page.tsx. Namespace: podcasts. Purpose: the priority offer. Sells the cinematic-lounge live-podcast format, the inclusions, the kit, season packages, and a closing CTA. Dynamic.

1. Hero (VARIANT — podcasts.hero, ns podcasts, fields tagline, intro)

PageHero: podcasts.title eyebrow, tagline headline, intro paragraph.

elementkey (variant base)ENES
eyebrowpodcasts.titlepodcastspodcasts
headlinepodcasts.taglineConversational podcasts, shot like cinema.Podcasts conversacionales, con look de cine.
intropodcasts.intro(long; see locale JSON line 202)(long; see locale JSON line 202)

podcasts.hero variants (this set also drives the /services hub card #2 tagline):

  • default = "default".
  • v1 = "v1 · final_final_v3" → tagline_v1 / intro_v1
    • EN tagline: Your show, not a folder of final_final_v3.mp4.
    • ES tagline: Tu show, no una carpeta de final_final_v3.mp4.
    • intro: long paragraph, EN/ES at locale JSON line 203.
  • v2 = "v2 · long conversations" → tagline_v2 / intro_v2
    • EN tagline: Long conversations, shot the way film is.
    • ES tagline: Conversaciones largas, rodadas como se rueda el cine.
    • intro: long paragraph, EN/ES at locale JSON line 204.

2. What you get (plain — array)

elementkeyENES
eyebrowpodcasts.offerTitlewhat you getqué recibes
list (×7)podcasts.offerLines (JSON array)7 bullet strings7 bullet strings

offerLines is a 7-item array read via dynamic import (t.raw-style). To reword inclusions, edit the array in both locale files (same length). Current EN items: lounge layout; invisible audio capture; up to 6-cam Sony Cinema Line + 3 ProRes RAW; timecode-locked picture/sound; continuous LED lighting; live program output; same-day vertical/square social cut. (Full text at locale JSON lines 206–214.) Not registered for the variant system — no _vN slot.

3. The kit (plain)

elementkeyENES
eyebrowpodcasts.kitTitlethe kitel equipo
bodypodcasts.kitBodyStudio-owned, regularly maintained, shot with on our own productions. …Equipo propio del estudio, mantenido regularmente, …
framed link (→ /services/rentals)podcasts.viewCatalogview rental catalogver catálogo de alquiler

4. Season packages (plain)

elementkeyENES
eyebrowpodcasts.seasonTitleseason packagespaquetes de temporada
bodypodcasts.seasonBodyMulti-episode bookings get crew familiarity, lighting and audio presets …Reservas de varios episodios reciben familiaridad de crew, …

5. CTA (VARIANT — podcasts.cta, ns podcasts, fields ctaTitle, ctaBody, ctaButton)

CtaSection: ctaTitle eyebrow, ctaBody heading, ctaButton pill (→ /contact).

elementkey (variant base)ENES
eyebrowpodcasts.ctaTitlestart a showempezar un show
headingpodcasts.ctaBodyTell us about your show — format, host count, episodes per season — …Cuéntanos sobre tu show — formato, cantidad de hosts, …
buttonpodcasts.ctaButtonGet in touchEscríbenos

Variants:

  • default = "default".
  • v1 = "v1 · book a session" → ctaTitle_v1 (book a session / reserva una sesión), ctaBody_v1, ctaButton_v1 (Book a session / Reservar una sesión)
  • v2 = "v2 · bring the show" → ctaTitle_v2 (bring us the show / tráenos el show), ctaBody_v2, ctaButton_v2 (Book a session / Reservar una sesión)

rentals — /services/rentals

src/app/[locale]/services/rentals/page.tsx. Namespace: rentals. Purpose: the secondary, relationship-gated offer. Curated-shelf positioning, a catalog jump, 4-step "how it works," rates + eligibility, and a CTA. Dynamic.

1. Hero (VARIANT — rentals.hero, ns rentals, fields tagline, intro)

PageHero: rentals.title eyebrow, tagline headline, intro paragraph.

elementkey (variant base)ENES
eyebrowrentals.titlerentalsalquiler
headlinerentals.taglineA small shelf of the gear we shoot with.Un estante pequeño del equipo con el que rodamos.
introrentals.intro(long; locale JSON line 170)(long; locale JSON line 170)

rentals.hero variants (also drives /services hub card #3 tagline):

  • default = "default".
  • v1 = "v1 · on purpose" → tagline_v1 (A small shelf. On purpose. / Un estante pequeño. A propósito.) + intro_v1 (locale JSON line 171)
  • v2 = "v2 · out on loan" → tagline_v2 (The kit we shoot with, out on loan. / El equipo con el que rodamos, en alquiler.) + intro_v2 (locale JSON line 172)

2. Catalog jump (plain)

elementkeyENES
framed link (→ /services/rentals/catalog)rentals.viewAllsee the kitver el equipo

3. How it works (plain — 4 steps)

rentals.howItWorks eyebrow (how it works / cómo funciona), then a 4-step list, each a title + body:

steptitle keybody key
01rentals.step1Title (Quote within a day.)rentals.step1Body
02rentals.step2Title (Contract and deposit.)rentals.step2Body
03rentals.step3Title (Pickup from the studio.)rentals.step3Body
04rentals.step4Title (Return and walkthrough.)rentals.step4Body

(Full EN/ES bodies at locale JSON lines 174–181.) No variant slots.

4. Rates + eligibility (plain — two columns)

columneyebrow keybody key
ratesrentals.ratesTitle (rates / tarifas)rentals.ratesBody
eligibilityrentals.eligibilityTitle (eligibility / elegibilidad)rentals.eligibilityBody

(Full EN/ES at locale JSON lines 182–185.) No variant slots.

5. CTA (VARIANT — rentals.cta, ns rentals, fields ctaTitle, ctaBody, ctaButton)

CtaSection: ctaTitle eyebrow, ctaBody heading, ctaButton pill (→ /contact).

elementkey (variant base)ENES
eyebrowrentals.ctaTitlerequest a quotesolicitar cotización
headingrentals.ctaBodyTell us what you're shooting, the dates, and the kit you want. …Cuéntanos qué vas a rodar, las fechas y el equipo que necesitas. …
buttonrentals.ctaButtonRequest a quoteSolicitar cotización

Variants:

  • default = "default".
  • v1 = "v1 · ask about the shelf" → ctaTitle_v1 (ask about the shelf / pregunta por el estante), ctaBody_v1, ctaButton_v1 (Ask about the shelf / Pregunta por el estante)
  • v2 = "v2 · check availability" → ctaTitle_v2 (check availability / consulta disponibilidad), ctaBody_v2, ctaButton_v2 (Check availability / Consultar disponibilidad)

Important: the rentals catalog and catalog item pages reuse the rentals.ctaTitle / ctaBody / ctaButton base keys only (NOT the variant picker). So changing a base key there changes the CTA on all three rentals pages, but a chosen _vN only shows on /services/rentals itself.


rentals catalog — /services/rentals/catalog

src/app/[locale]/services/rentals/catalog/page.tsx. Namespaces: rentalsCatalog (+ rentals for the closing CTA). Purpose: browse the shelf. A hero, category filter chips, a paginated grid of item cards, a per-page control, and the rentals CTA band. No variant slots anywhere on this page (the hero uses base keys).

1. Hero (plain)

PageHero — uses base rentalsCatalog keys (not variant):

elementkeyENES
eyebrowrentalsCatalog.titlecatalogcatálogo
headlinerentalsCatalog.taglineWhat we shoot with.Con qué rodamos.
introrentalsCatalog.introEverything below comes off the same shelf … within a day.Todo lo de abajo sale del mismo estante … en un día hábil.

2. Category filter chips (plain)

<nav aria-label> = rentalsCatalog.jumpNav (Catalog filters / Filtros del catálogo). First chip is "all"; the rest come from categoryCounts (only categories with items appear). Each chip label is rentalsCatalog.categories.<key> followed by a count.

elementkeyENES
all chiprentalsCatalog.categories.allalltodos
per-category chipsrentalsCatalog.categories.<category_key>display namedisplay name

Category keys + display names (full map at locale JSON lines 237–268; examples): camera_bodycamera body/cuerpo de cámara, camera_lenscamera lens/lente, lightlight/luz, microphonemicrophone/micrófono, gripgrip/grip, batterybattery/batería, computercomputer/computadora, etc. (29 categories).

3. Grid + pagination (plain)

elementkeyENES
empty staterentalsCatalog.emptyNothing in this category yet — try another.Nada en esta categoría por ahora — prueba otra.
pagination aria-labelrentalsCatalog.pagination.labelCatalog paginationPaginación del catálogo
prev linkrentalsCatalog.pagination.prevprevanterior (rendered as ← prev)
next linkrentalsCatalog.pagination.nextnextsiguiente (rendered as next →)
"of" separatorrentalsCatalog.pagination.ofofde (shown as NN of NN)
per-page labelrentalsCatalog.pagination.perPageper pagepor página
card "sold" (sr-only)rentalsCatalog.status.soldsoldvendido
card "coming soon" (sr-only)rentalsCatalog.status.comingSooncoming soonpróximamente

Each grid cell is a RentalItemCard linking to /services/rentals/catalog/[sku]. The card's manufacturer / name / summary come from the Supabase items table, not from copy files — not editable here. soldLabel/comingSoonLabel are the two status.* strings above (screen-reader only; the X overlay is visual).

4. CTA band (reuses rentals base keys — plain)

CtaSection with rentals.ctaTitle / rentals.ctaBody / rentals.ctaButton (base keys only — see the rentals CTA note above).


catalog item — /services/rentals/catalog/[sku]

src/app/[locale]/services/rentals/catalog/[sku]/page.tsx. Namespaces: rentalsItem, rentalsCatalog (category label), rentals (CTA). Purpose: one piece of gear — name, summary, spec rows, image gallery, optional in-use shots, and the rentals CTA. No variant slots. Item facts (manufacturer / name / summary / MPN / accessories / category / quantity) come from Supabase items — not copy files.

1. Detail header + spec list (chrome from rentalsItem; data from DB)

elementkey / sourceENES
manufacturer (eyebrow)DB item.manufacturer
H1DB item.name
summaryDB item.summary
spec: category labelrentalsItem.categorycategorycategoría
spec: availability labelrentalsItem.availabilityavailabilitydisponibilidad
availability valuerentalsItem.availableCount (ICU {count}){count} available{count, plural, one {# disponible} other {# disponibles}}
spec: model labelrentalsItem.mpnmodelmodelo
spec: accessories labelrentalsItem.includedaccessoriesaccesorios
category valuerentalsCatalog.categories.<key>display namedisplay name
back link (in rentalsItem, currently unused on this page)rentalsItem.backcatalogcatálogo

2. In use (plain — only if review images exist)

elementkeyENES
eyebrowrentalsItem.reviewin useen uso

3. CTA (reuses rentals base keys — plain)

CtaSection with rentals.ctaTitle / ctaBody / ctaButton (base keys).


stills — /services/stills

src/app/[locale]/services/stills/page.tsx. Namespace: stills. Purpose: the photography practice — a tagline hero, a masonry gallery, and a CTA. No variant slots. Gallery images + per-image captions come from content/stills/stills.json (single file, not localized).

1. Hero (plain)

elementkeyENES
eyebrowstills.titlestillsfotografía
headlinestills.taglineFrame first, gear last.Encuadre primero, equipo después.
introstills.introOur stills practice runs from street and fashion to travel, portraiture, and still life — …Nuestra práctica fotográfica va de lo urbano y la moda al viaje, el retrato y el bodegón — …

Each still has alt (screen-reader description / placeholder label), gear (caption left), settings (caption right). Numbered overlay (01, 02 …) is generated. Single shared file — edit content/stills/stills.json. Example entry: alt: "Side profile portrait, natural light", gear: "Sony A7R V, 70mm", settings: "ƒ/5, 1/60s, ISO 100".

3. CTA (plain — CtaSection)

elementkeyENES
eyebrowstills.ctaTitlestart a projectcomencemos
headingstills.ctaBodyNeed stills for a campaign, a set, or a trip? …¿Necesitas fotografía para una campaña, un set o un viaje? …
button (→ /contact)stills.ctaButtonGet in touchEscríbenos

projects — /projects

src/app/[locale]/projects/page.tsx. Namespace: projects. Purpose: the work index — a title/intro header and a card grid. No variant slots. Cards come from content/projects/*.mdx frontmatter.

1. Header (plain)

elementkeyENES
H1projects.titleprojectsproyectos
introprojects.introSelected work, from social shorts to feature-length productions.Trabajo seleccionado, desde piezas para redes hasta producciones de largo formato.
empty stateprojects.emptyMore work soon.Pronto más trabajo.

2. Card grid (content file — see Project shared content)

Each ProjectCard shows the MDX title, an in-image deliverables stamp (falls back to title), and the type label. Card links to /projects/[slug].


project detail — /projects/[slug]

src/app/[locale]/projects/[slug]/page.tsx. Namespace: projects. Purpose: a single project — parallax hero, title + description, metadata, body prose, optional gallery, and a "next project" link. No variant slots. Almost all copy is per-project MDX frontmatter + body (content/projects/<slug>.<locale>.mdx).

Sections in order

elementkey / sourceEN labelES label
hero imageMDX cover / coverPosition
year (eyebrow)MDX year
H1MDX title
descriptionMDX description
metadata: timeframe labelprojects.timeframetimeframeduración
metadata: client labelprojects.clientclientcliente
metadata: role labelprojects.rolerolerol
metadata valuesMDX timeframe / client / role[]
body proseMDX body (below frontmatter)
gallery section label + aria-labelprojects.gallerygallerygalería
next-project linkMDX title of next project
watch (in JSON, for video link)projects.watchwatchver

Project shared content (content/projects/*.mdx)

The copywriter edits these .mdx files for project copy. Frontmatter fields (see src/lib/content.ts Project type): title, year, type (card label, e.g. "Podcast"), timeframe, deliverables (card stamp, e.g. "12 episodes"), client, role[], cover, coverPosition, gallery[], description, videoUrl, featured, order. Body (prose after ---) becomes the detail paragraphs. Files are per-locale: <slug>.en.mdx and <slug>.es.mdx. Current slugs: dosnomadas-podcast, lost-in-vegas-bts, monaco-panaderia, short-film-tropical.


team — /team

src/app/[locale]/team/page.tsx. Namespace: team. Purpose: the small core team + a recruitment band. No variant slots. Member cards come from content/team/team.json; the recruitment band is locale JSON.

1. Header (plain)

elementkeyENES
H1team.titleteamequipo
introteam.introA small core team between Los Angeles and Medellín. We scale up per project …Un equipo central pequeño entre Los Ángeles y Medellín. …

2. Member grid (content file — content/team/team.json)

Each member: name, role.{en,es} (rendered as an underscored label, e.g. Executive_Producer_), bio.{en,es}, photo. Edit content/team/team.json. Current members: Brandon Dee Zacharie (Executive Producer / Productor Ejecutivo), Keren Monsalve Cordero (Head of Operations / Directora de Operaciones).

3. Recruitment band (plain — locale JSON)

elementkeyENES
eyebrowteam.recruitStampjoin ussúmate
headingteam.recruitTitleBuild a studio with us.Construyamos un estudio juntos.
bodyteam.recruitBodyWe're growing the team. Editors most of all — …Estamos creciendo el equipo. Sobre todo, edición — …
button (mailto)team.recruitButtonGet in touchEscríbenos
mailto addressteam.recruitEmailhi@studio.chathola@studio.chat
mailto subjectteam.recruitEmailSubjectJoining the team — your nameSumándome al equipo — tu nombre

contact — /contact

src/app/[locale]/contact/page.tsx + src/components/ContactForm.tsx. Namespace: contact (+ site for email). Purpose: low-friction, warm, direct — make sending the message easy. Header, the form, and an aside with email + studio address + map. Dynamic (resolves the intro + form variants server-side and passes them into the client form as props).

1. Header (VARIANT — contact.intro, ns contact, field intro)

SectionHeader: contact.title H1 + variant intro.

elementkeyENES
H1contact.titlecontactcontacto
introcontact.intro (variant base)Tell us about your project. We respond within two business days.Cuéntanos sobre tu proyecto. Respondemos en dos días hábiles.

contact.intro variants:

  • default = "default".
  • v1 = "v1 · two-day turnaround" → contact.intro_v1
    • EN: Tell us what you're making. We read every message and reply within two business days.
    • ES: Cuéntanos qué estás creando. Leemos cada mensaje y respondemos en dos días hábiles.
  • v2 = "v2 · a few details" → contact.intro_v2
    • EN: A few details about the project go a long way. We reply within two business days.
    • ES: Unos cuantos detalles del proyecto nos ayudan mucho. Respondemos en dos días hábiles.

2. Contact form (VARIANT — contact.form, ns contact, fields message, submit)

The form fields and submit button. Only the message field label and submit button label are variant-controlled (passed in as messageLabel/submitLabel); the other field labels are plain. The form has an email/WhatsApp toggle (only one contact-method field shows at a time).

elementkeyENES
name labelcontact.nameNameNombre
email label / togglecontact.emailE-mailCorreo
whatsapp label / togglecontact.whatsappWhatsAppWhatsApp
company labelcontact.companyCompany / ProjectEmpresa / Proyecto
budget labelcontact.budgetEstimated budgetPresupuesto estimado
message label (variant)contact.messageTell us about itCuéntanos
submit button (variant)contact.submitSend messageEnviar
pending button copycontact.sendingSending...Enviando...
success block headingcontact.successGot it. We'll be in touch soon.Recibido. Te contactamos pronto.
inline submit errorcontact.errorSomething went wrong. Email us directly at hi@studio.chat.Algo salió mal. Escríbenos directamente a hola@studio.chat.
budget optionscontact.budgetOptions.*Under $5k / $5k–$15k / $15k–$50k / $50k+ / Not sure yetMenos de $5k / $5k–$15k / $15k–$50k / $50k+ / Aún no sé
field errorscontact.errors.*Required / Check this field / Enter a valid email / Enter a valid number / Tell us a little moreRequerido / Revisa este campo / Ingresa un correo válido / Ingresa un número válido / Cuéntanos un poco más

contact.form variants (each sets BOTH message and submit):

  • default = "default" → message / submit above.
  • v1 = "v1 · send the idea" → message_v1 (What you have in mind / Lo que tienes en mente), submit_v1 (Send the idea / Enviar la idea)
  • v2 = "v2 · the project" → message_v2 (About the project / Sobre el proyecto), submit_v2 (Send it over / Enviar)

3. Aside — email + studio (plain)

elementkeyENES
email eyebrowcontact.oror write us ato escríbenos a
email value (mailto)site.emailhi@studio.chathola@studio.chat
studio eyebrowcontact.studiostudioestudio
address line 1contact.addressLine1Cra. 34 #7-129, 103Cra. 34 #7-129, 103
address line 2contact.addressLine2Medellín, Antioquia, COMedellín, Antioquia, CO
directions link (external → Google Maps)contact.directionsget directionscómo llegar

The success state (after submit) re-uses contact.success, contact.or, and site.email.


faqs — /faqs

src/app/[locale]/faqs/page.tsx. Namespace: faqs. Purpose: quick answers to common pre-booking questions. A header + an accordion list. Dynamic (intro variant). FAQ Q&A pairs come from content/faqs/{en,es}.json.

1. Header (VARIANT — faqs.intro, ns faqs, field intro)

SectionHeader: faqs.title H1 + variant intro.

elementkeyENES
H1faqs.titlefaqsfaqs
introfaqs.intro (variant base)Quick answers to the questions we hear most.Respuestas rápidas a las preguntas que más nos hacen.

faqs.intro variants:

  • default = "default".
  • v1 = "v1 · before the first email" → faqs.intro_v1
    • EN: The questions we get before the first email.
    • ES: Las preguntas que llegan antes del primer correo.
  • v2 = "v2 · what people ask" → faqs.intro_v2
    • EN: What people ask before they book a session.
    • ES: Lo que la gente pregunta antes de reservar una sesión.

2. FAQ accordion (content file — content/faqs/{locale}.json)

Each entry: q (question, the summary line) + a (answer body). Numbered (01, 02 …) automatically. Edit content/faqs/en.json and content/faqs/es.json — no variant system. Current questions (EN): "Where are you based?", "What's the typical timeline?", "Do you work in English?", "Can you handle post only?", "How do we start?".


privacy — /privacy

src/app/[locale]/privacy/page.tsx. Namespace: privacy. Purpose: the privacy policy. A header + a numbered section list. No variant slots. Section bodies come from content/privacy/{en,es}.json.

1. Header (plain)

elementkeyENES
H1privacy.titleprivacyprivacidad
introprivacy.introHow we handle the information you share with us.Cómo tratamos la información que compartes con nosotros.
"last updated" labelprivacy.updatedLast updatedÚltima actualización

2. Sections (content file — content/privacy/{locale}.json)

A date plus a sections[] array of { title, body }. Edit content/privacy/en.json / es.json. Current section titles (EN): Who we are; What we collect; How we use it; Who we share it with; Error and session monitoring; Cookies; Your rights; Changes to this policy.


launch gate — /launch

src/app/[locale]/launch/page.tsx + src/components/launch/{LaunchHero, Countdown,LaunchReveal,BypassForm}.tsx. Namespace: launch (+ launch.meta for metadata). Purpose: the pre-launch holding page (every gated URL is rewritten here). Full-bleed image, a live countdown, an editorial blurb that flips to a disguised subscribe form (which secretly accepts @studio.chat insider credentials to bypass). No variant slots.

elementkeyENES
title (absolute)launch.meta.titlestudio.chatstudio.chat
descriptionlaunch.meta.descriptionTrue audiovisual production is coming to the heart of Medellín. Launching June 26, 2026 — 12:00 Bogotá.Auténtica producción audiovisual está llegando al corazón de Medellín. Lanzamiento el 26 de junio de 2026 — 12:00 Bogotá.
OG image altlaunch.meta.ogAltstudio.chat — true audiovisual production coming to Medellínstudio.chat — auténtica producción audiovisual llegando a Medellín

Countdown units (plain — launch.units)

elementkeyENES
days labellaunch.units.ddaysdías
hours labellaunch.units.hhrshrs
minutes labellaunch.units.mminmin
seconds labellaunch.units.ssecseg

Blurb reveal (LaunchReveal) (plain)

elementkeyENES
blurb (rich, <em> around inner phrase)launch.blurbTrue <em>audiovisual productions</em> are coming to the heart of Medellín.Auténticas <em>producciones audiovisuales</em> están llegando al corazón de Medellín.
reveal button (FramedLink)launch.learnMorelearn moreconoce más

Note: launch.eyebrow (until launch / hasta el lanzamiento) exists in JSON but is not currently rendered by the page; keep it consistent if reworded.

Subscribe / bypass form (BypassForm, labels from launch.subscribe) (plain)

elementkeyENES
form eyebrowlaunch.subscribe.eyebrowsubscribe for updatessuscríbete para novedades
name labellaunch.subscribe.namenamenombre
email labellaunch.subscribe.emailemailemail
password label (insider only)launch.subscribe.passwordpasswordcontraseña
submit (subscribe mode)launch.subscribe.ctaNotify meAvísame
submit (insider/unlock mode)launch.subscribe.submitUnlockDesbloquear
in-flight button copylaunch.subscribe.loadingLoading...Cargando...
invalid-credentials errorlaunch.subscribe.invalidInvalidNo válido
endpoint-failure errorlaunch.subscribe.unavailableUnavailableNo disponible
subscribed confirmationlaunch.subscribe.subscribedThanks — we'll send launch updates.Gracias — te avisaremos del lanzamiento.

The form POSTs to /api/launch-subscribe (normal) or /api/launch-bypass (when the email ends in @studio.chat). Submitting an insider email reveals the password field and switches the button label from cta to submit.


Shared chrome (header, footer, nav)

These render on every public page except /launch (which uses a minimal header and no footer). No variant slots.

Namespace: nav. Desktop shows: Projects · Services (dropdown) · Contact. The Services dropdown lists Services / Podcasts / Rentals / Stills. On mobile the parent "services" link is dropped and its children show inline as peers.

nav itemkeyENEShref
projectsnav.projectsprojectsproyectos/projects
services (parent + landing)nav.servicesservicesservicios/services
podcastsnav.podcastspodcastspodcasts/services/podcasts
rentalsnav.rentalsrentalsalquiler/services/rentals
stillsnav.stillsstillsfotografía/services/stills
contactnav.contactcontactcontacto/contact

Other nav.* keys used elsewhere: nav.team (team/equipo, footer), nav.faqs (faqs/faqs, footer), nav.privacy (privacy/privacidad, footer), nav.location (location/ubicación, not currently rendered). LocaleSwitch shows en/es as literal toggles (not translated).

Namespaces: footer, nav, site. Three eyebrow columns (explore, manage, say hello) + a legal bar.

elementkeyENES
explore eyebrowfooter.exploreexploreexplorar
explore linksnav.projects, nav.team, nav.services, nav.podcasts, nav.rentals, nav.stills, nav.faqs, nav.privacysee nav tablesee nav table
manage eyebrow (staff/preview only)footer.managemanageadministrar
office link (→ /office)footer.officeofficeoficina
say-hello eyebrowfooter.sayHellosay helloescríbenos
email link (→ /contact)site.emailhi@studio.chathola@studio.chat
social links (Instagram/Vimeo/YouTube/TikTok)handle studiodotchat (in code)
legal — companyfooter.companySTUDIO.CHAT S.A.S.STUDIO.CHAT S.A.S.
legal — made-insite.madeInMADE WITH ♥︎ IN MEDELLÍNHECHO CON ♥︎ EN MEDELLÍN

Error / not-found pages (plain)

src/app/[locale]/not-found.tsx (404) and error.tsx (500). Namespace: errors.

pageelementkeyENES
404eyebrowerrors.notFound.eyebrownot foundno encontrado
404titleerrors.notFound.titleWe can't find that page.No encontramos esa página.
404bodyerrors.notFound.bodyThe link may be broken, or the page may have moved. …Puede que el enlace esté roto … sí existe.
404home linkerrors.notFound.homeback homevolver al inicio (rendered with leading )
500eyebrowerrors.serverError.eyebrowerrorerror
500titleerrors.serverError.titleSomething went wrong on our end.Algo salió mal de nuestro lado.
500bodyerrors.serverError.bodyAn unexpected error stopped this page from loading. …Un error inesperado impidió cargar esta página. …
500retry buttonerrors.serverError.retrytry againreintentar
500home linkerrors.serverError.homeback homevolver al inicio

site / brand strings (used across pages) (plain — site ns)

keyENES
site.namestudio.chatstudio.chat
site.taglineaudiovisualaudiovisual
site.metaTitleSTUDIO.CHAT Audiovisual ProductionsSTUDIO.CHAT Producciones Audiovisuales
site.descriptionAudiovisual production in Medellín — social content, short films, and live podcasts, end to end.Producción audiovisual en Medellín — contenido social, cortometrajes y podcasts en vivo, de principio a fin.
site.madeInMADE WITH ♥︎ IN MEDELLÍNHECHO CON ♥︎ EN MEDELLÍN
site.emailhi@studio.chathola@studio.chat

Not covered here: the portal namespace (the client reservation portal at /portal, separate from the marketing site) and all of /office (admin). They are not part of the public marketing surface this doc targets.


How variants work + how to add one

Variants let the office (/office/copy) A/B different copy per page section without code changes. Mechanism (src/lib/copy/variants.ts):

  1. Key convention. The chosen variant id doubles as the locale-key suffix. default → the base key (heroLine1); v1heroLine1_v1; v2heroLine1_v2. The helper variantKey(base, id) does this lookup. So every variant is just more sibling keys in the same namespace.
  2. The registry. COPY_PAGES lists each page → its sections. A section has: key (e.g. home.hero, the page.section id stored in the copy_variants table), label, namespace (which locale namespace the fields live under), fields (the base keys this section controls), and variants ({ id, label }label is the human string shown in the office, e.g. "v2 · record once").
  3. Selection. The office writes the chosen id to the copy_variants table keyed by page.section. At request time the page calls getCopyVariant("page.section") and renders t(variantKey(field, id)). If the stored id isn't in the registry, it falls back to the section's first variant (default).

To add a v3 (or any new variant) to an existing section

  1. In both locales/en.json and locales/es.json, add a _v3 sibling for every field the section lists in COPY_PAGES.fields. Example for home.hero (fields heroLine1..heroLine4): add heroLine1_v3, heroLine2_v3, heroLine3_v3, heroLine4_v3 in each file. (For a multi-field CTA section, add ctaTitle_v3, ctaBody_v3, ctaButton_v3.) Write the Spanish as a parallel rewrite, not a literal translation.
  2. In src/lib/copy/variants.ts, add { id: "v3", label: "v3 · <short label>" } to that section's variants array. Without this step the office won't show the new variant even though the keys exist.
  3. That's it — no page code changes. The page already resolves the section via getCopyVariant + variantKey.

Sections that currently support variants (registry summary)

section keynamespacefieldsvariant ids (labels)
home.herohomeheroLine1, heroLine2, heroLine3, heroLine4default, v1 (podcasts-led), v2 (sharpened generalist)
home.serviceshomeservicesIntrodefault, v1 (studio day), v2 (record once)
home.ctahomectaTitle, ctaBody, ctaButtondefault, v1 (book a session), v2 (start with a pilot)
services.introservicesintrodefault, v1 (kit ladder), v2 (one room)
podcasts.heropodcaststagline, introdefault, v1 (final_final_v3), v2 (long conversations)
podcasts.ctapodcastsctaTitle, ctaBody, ctaButtondefault, v1 (book a session), v2 (bring the show)
rentals.herorentalstagline, introdefault, v1 (on purpose), v2 (out on loan)
rentals.ctarentalsctaTitle, ctaBody, ctaButtondefault, v1 (ask about the shelf), v2 (check availability)
contact.introcontactintrodefault, v1 (two-day turnaround), v2 (a few details)
contact.formcontactmessage, submitdefault, v1 (send the idea), v2 (the project)
faqs.introfaqsintrodefault, v1 (before the first email), v2 (what people ask)

Everything else on the public site (stills, projects, project detail, team, privacy, rentals catalog/item, launch, nav, footer, errors, and the non-variant fields within the pages above) is plain copy — edit the base key in locales/*.json (or the relevant content/* file) directly; there is no _vN slot until you create one and register it.

Revalidation note

COPY_PAGE_PATHS in variants.ts maps each page to the public URLs to revalidate when its copy changes. If you introduce a variant section on a new page, add that page's EN+ES paths there too (podcasts/rentals also list /services because their taglines surface on the hub).