// ── FULL EDITION · freemium teaser  ↔  WiseTip+ full article ──
// Edition #161 · "Earth Day 2026: the mint is still alive" · Markus, Barcelona
//
// Two readings of the SAME edition, switchable in-page:
//   • FREEMIUM  — the touch-point. Short version of 4 of 7 blocks. A taste
//                 that earns the subscribe click, then a paywall.
//   • WISETIP+  — the full article. Long, expanded version of all 7 of 7
//                 blocks, including the 3 members-only blocks.
// Every free block therefore has {short, long}: freemium shows short,
// premium shows long. Editorial hard-constraints respected throughout:
// children never named; wife's quote verbatim; Victor = Vienna; disclaimer
// verbatim; scene-first, short paragraphs.

const { useState: useFE } = React;

const FE_EDITION = {
  num: 161,
  phase: "E",
  editor: { name: "Markus", location: { en: "Barcelona, Spain", es: "Barcelona, España" }, color: "#4CB868" },
  coEditors: [
    { name: "Markus", city: { en: "Barcelona", es: "Barcelona" }, color: "#4CB868" },
    { name: "Victor", city: { en: "Vienna",    es: "Viena" },     color: "#3A5FA5" },
    { name: "Marian", city: { en: "London",    es: "Londres" },   color: "#7A4A8A" },
    { name: "Alex",   city: { en: "New York",  es: "Nueva York" },color: "#C45A3A" },
  ],
  date: { en: "April 8, 2026", es: "8 de abril de 2026" },
  readFree: { en: "1 min taste", es: "1 min de muestra" },
  readFull: { en: "9 min full read", es: "9 min lectura completa" },
  title: {
    en: "Earth Day 2026: the mint is still alive",
    es: "Día de la Tierra 2026: la hierbabuena sigue viva",
  },
  subtitle: {
    en: "The same question I've asked for four years — and one small plant that answered it.",
    es: "La misma pregunta que llevo cuatro años haciéndome — y una planta pequeña que la respondió.",
  },
  // ── the four shared blocks: short = freemium teaser, long = premium ──
  blocks: {
    opening: {
      short: {
        en: "This week I asked myself the same question I've asked for four years: what do I want my children to remember from these months?\n\nNot the achievements. The moments with the texture of having been real.",
        es: "Esta semana me hice la misma pregunta que llevo cuatro años haciéndome: ¿qué quiero que mis hijos recuerden de estos meses?\n\nNo los logros. Los momentos que tienen la textura de haber sido reales.",
      },
      long: {
        en: "This week I asked myself the same question I've asked for four years: what do I want my children to remember from these months?\n\nNot the achievements. Not the report cards, not the swimming badges, not the birthday that took three weeks to plan.\n\nThe moments with the texture of having been real. The ones nobody photographed because nobody thought to.\n\nI keep a short list. It's never the things I would have guessed in advance. This year the list has a plant on it — and I want to tell you why, because I think the reason is the whole point of this newsletter.",
        es: "Esta semana me hice la misma pregunta que llevo cuatro años haciéndome: ¿qué quiero que mis hijos recuerden de estos meses?\n\nNo los logros. No las notas, ni las medallas de natación, ni el cumpleaños que tardó tres semanas en organizarse.\n\nLos momentos que tienen la textura de haber sido reales. Los que nadie fotografió porque a nadie se le ocurrió.\n\nLlevo una lista corta. Nunca son las cosas que habría adivinado de antemano. Este año la lista tiene una planta — y quiero contarte por qué, porque creo que la razón es el sentido entero de esta newsletter.",
      },
    },
    story: {
      short: {
        en: "On the balcony there's a pot of mint we'd almost given up on. In January it was three brown stalks. I told the five-year-old it was probably finished.\n\nHe kept watering it anyway. Last Saturday there were green shoots.",
        es: "En el balcón hay una maceta de hierbabuena que casi dimos por perdida. En enero eran tres tallos marrones. Le dije al de cinco años que seguramente estaba muerta.\n\nÉl siguió regándola igual. El sábado pasado había brotes verdes.",
      },
      long: {
        en: "On the balcony there's a pot of mint we'd almost given up on. In January it was three brown stalks and a crust of dry soil. I told the five-year-old it was probably finished.\n\nHe kept watering it anyway. Not every day. When he remembered. A splash from his own cup, most of the water landing on the tiles.\n\nSome weeks he forgot entirely. I didn't remind him — I wanted to see what he'd do on his own, without me managing it.\n\nThen a cold snap in February, and I was sure that was that. He watered it the next morning regardless, standing in his socks on the freezing balcony.\n\nLast Saturday there were green shoots. Small ones, close to the soil, easy to miss.\n\nHe didn't say \u201cI told you so\u201d — he doesn't know that phrase yet. He just crouched down and looked at it for a long time, the way you look at something you helped without meaning to.\n\nThen he asked if we could put it in the salad. I said not yet. He accepted that with a patience he does not usually have for anything else.",
        es: "En el balcón hay una maceta de hierbabuena que casi dimos por perdida. En enero eran tres tallos marrones y una costra de tierra seca. Le dije al de cinco años que seguramente estaba muerta.\n\nÉl siguió regándola igual. No cada día. Cuando se acordaba. Un chorro de su propio vaso, casi toda el agua en las baldosas.\n\nAlgunas semanas se olvidó del todo. No se lo recordé — quería ver qué hacía por su cuenta, sin que yo lo gestionara.\n\nLuego una ola de frío en febrero, y di por hecho que ahí acababa. A la mañana siguiente la regó igual, de pie en calcetines en el balcón helado.\n\nEl sábado pasado había brotes verdes. Pequeños, pegados a la tierra, fáciles de no ver.\n\nNo dijo «te lo dije» — todavía no conoce esa frase. Solo se agachó y la miró un buen rato, como se mira algo que ayudaste sin querer.\n\nLuego preguntó si podíamos ponerla en la ensalada. Le dije que aún no. Lo aceptó con una paciencia que no suele tener para ninguna otra cosa.",
      },
    },
    reflection: {
      short: {
        en: "What's already there this month: conversations at the table, the stone they found in the park, the afternoon everything went wrong and then we laughed.\n\nNone of it planned. All of it counted.",
        es: "Lo que ya está ahí este mes: conversaciones en la mesa, la piedra que encontraron en el parque, la tarde en que todo salió mal y luego nos reímos.\n\nNada planeado. Todo contó.",
      },
      long: {
        en: "What's already there this month, without me arranging any of it: conversations at the table that ran long, the stone they found in the park and argued over, the afternoon everything went wrong and then we laughed until it didn't matter.\n\nNone of it planned. All of it counted.\n\nHere's what four years of watching has taught me: the moments that stick are almost never the ones we stage. They're the by-products. The plant we nearly threw out. The wrong turn on a walk that became the walk.\n\nWe spend so much effort building experiences for our children. And the thing they carry is usually something that happened in the margins of that effort, while we weren't looking.\n\nThat's not an argument for doing nothing. It's an argument for leaving room — for not filling every hour so full that the margins disappear.",
        es: "Lo que ya está ahí este mes, sin que yo organizara nada: conversaciones en la mesa que se alargaron, la piedra que encontraron en el parque y por la que discutieron, la tarde en que todo salió mal y luego nos reímos hasta que dejó de importar.\n\nNada planeado. Todo contó.\n\nEsto es lo que cuatro años de observar me han enseñado: los momentos que se quedan casi nunca son los que montamos. Son los subproductos. La planta que casi tiramos. El giro equivocado en un paseo que se convirtió en el paseo.\n\nDedicamos muchísimo esfuerzo a construir experiencias para nuestros hijos. Y lo que se llevan suele ser algo que pasó en los márgenes de ese esfuerzo, mientras no mirábamos.\n\nNo es un argumento para no hacer nada. Es un argumento para dejar espacio — para no llenar cada hora tanto que los márgenes desaparezcan.",
      },
    },
    practical_insight: {
      short: {
        en: "Earth Day is easy to perform and hard to live. The performable version is a recycled-cardboard craft, photographed, posted, forgotten by Friday.",
        es: "El Día de la Tierra es fácil de aparentar y difícil de vivir. La versión aparente es una manualidad de cartón reciclado, fotografiada, publicada y olvidada para el viernes.",
      },
      long: {
        en: "Earth Day is easy to perform and hard to live.\n\nThe performable version is a recycled-cardboard craft, photographed, posted, forgotten by Friday. It asks nothing of the child except forty minutes and a smile for the camera.\n\nThe lived version is slower: one plant, tended badly, by a child who forgets and remembers. It asks for months, and it accepts failure as part of the deal.\n\nHere's the practical part, the thing you can actually do this week:\n\nDon't add an Earth Day activity to the calendar. Instead, hand your child one living thing and step back. Let the caring be imperfect. Let them own whether it survives.\n\nThe forgetting is not a failure of the exercise — it is the exercise. A plant that lives because a child chose to return to it, again and again, on their own, teaches something no craft can: that care is a decision you make repeatedly, especially when no one is checking.\n\nThat's the version that survives the winter. In the pot, and in them.",
        es: "El Día de la Tierra es fácil de aparentar y difícil de vivir.\n\nLa versión aparente es una manualidad de cartón reciclado, fotografiada, publicada y olvidada para el viernes. No le pide nada al niño salvo cuarenta minutos y una sonrisa para la cámara.\n\nLa versión vivida es más lenta: una planta, cuidada mal, por un niño que olvida y recuerda. Pide meses, y acepta el fracaso como parte del trato.\n\nAquí está la parte práctica, lo que de verdad puedes hacer esta semana:\n\nNo añadas una actividad del Día de la Tierra al calendario. En su lugar, dale a tu hijo un ser vivo y apártate. Deja que el cuidado sea imperfecto. Deja que él decida si sobrevive.\n\nEl olvido no es un fallo del ejercicio — es el ejercicio. Una planta que vive porque un niño eligió volver a ella, una y otra vez, por su cuenta, enseña algo que ninguna manualidad puede: que cuidar es una decisión que se toma repetidamente, sobre todo cuando nadie mira.\n\nEsa es la versión que sobrevive al invierno. En la maceta, y en ellos.",
      },
    },
  },
  // ── premium-only blocks ──
  activity: {
    title: { en: "The Garden of One Thing", es: "El huerto de una sola cosa" },
    ages: "5–17",
    duration: { en: "45 min + a month of small moments", es: "45 min + un mes de momentos pequeños" },
    tags: ["outdoor", "calm", "fine-motor"],
    intro: {
      en: "Plant one thing. Not a garden — one. Then tend it badly, on purpose: let the child own whether it lives.",
      es: "Planta una sola cosa. No un huerto — una. Y cuídala mal, a propósito: deja que el niño decida si vive.",
    },
    materials: {
      en: ["One pot", "A handful of soil", "One herb seedling — mint, basil, whatever the shop has", "A sunny ledge", "A cup for water"],
      es: ["Una maceta", "Un puñado de tierra", "Un plantón de hierba — hierbabuena, albahaca, lo que haya", "Un alféizar con sol", "Un vaso para el agua"],
    },
    steps: {
      en: [
        "Let the child choose the one plant. Resist suggesting a \u201cbetter\u201d one.",
        "Plant it together, once. After that, it is theirs to remember.",
        "Do not remind them to water it. This is the hard part, and the whole point.",
        "On the last day of the month, cook with it — or just smell it together and name what changed.",
      ],
      es: [
        "Deja que el niño elija la planta. Resiste la tentación de sugerir una «mejor».",
        "Plantadla juntos, una vez. A partir de ahí, es suya para recordarla.",
        "No le recuerdes que la riegue. Esta es la parte difícil, y todo el sentido.",
        "El último día del mes, cocinad con ella — o solo oledla juntos y nombrad qué cambió.",
      ],
    },
    notice: {
      en: "Who remembers, who forgets, and what they say when it nearly dies. The forgetting is data, not failure.",
      es: "Quién se acuerda, quién olvida, y qué dicen cuando casi se muere. El olvido es información, no un fracaso.",
    },
    why: {
      en: "Ownership grows from consequence, not instruction. A plant that survives because a child chose to care is worth more than ten that thrive on your reminders.",
      es: "La responsabilidad crece desde la consecuencia, no desde la instrucción. Una planta que sobrevive porque un niño decidió cuidarla vale más que diez que prosperan con tus recordatorios.",
    },
  },
  resources: {
    audiobook: {
      title: { en: "Earth Day 2026 · the mint is still alive", es: "Día de la Tierra 2026 · la hierbabuena sigue viva" },
      narrator: { en: "Narrated by Markus", es: "Narrado por Markus" },
      meta: { en: "16:42 · ages 5–11 · listen before bed", es: "16:42 · edades 5–11 · escuchar antes de dormir" },
    },
    recipe: {
      title: { en: "Fresh Mint Lemonade", es: "Limonada de hierbabuena fresca" },
      role: { en: "Child's role: tears the leaves, tastes, decides if it needs more.", es: "Rol del niño: rompe las hojas, prueba, decide si falta." },
      meta: { en: "Ages all · 15 min · no heat needed", es: "Todas las edades · 15 min · sin fuego" },
    },
    ritual: {
      title: { en: "Three things on the table", es: "Tres cosas en la mesa" },
      body: {
        en: "Before dinner this week, each person names three things — one that grew this month, one that ended, one they want to start.",
        es: "Antes de cenar esta semana, cada uno nombra tres cosas — una que creció este mes, una que terminó, una que quiere empezar.",
      },
    },
    companions: {
      en: [
        { code: "A.001", name: "The Stone Journal" },
        { code: "A.013", name: "The Quiet Advent" },
      ],
      es: [
        { code: "A.001", name: "Diario de piedras" },
        { code: "A.013", name: "El Adviento tranquilo" },
      ],
    },
  },
  closing: {
    en: "The four of us write this together: Markus from Barcelona, Victor from Vienna, Marian from London, Alex from New York. Four households, one shared observation: children with present adults learn to be present.\n\nWe are not anti-screen. We are pro-intention. The difference matters.\n\nAnd from the very beginning, the line we keep returning to — my wife's, not mine: \u00abWe didn't take away their screens. We forgot the cable. They did the rest.\u00bb",
    es: "Los cuatro escribimos esto juntos: Markus desde Barcelona, Victor desde Viena, Marian desde Londres, Alex desde Nueva York. Cuatro casas, una observación compartida: los niños con adultos presentes aprenden a estar presentes.\n\nNo somos anti-pantallas. Somos pro-intención. La diferencia importa.\n\nY desde el principio, la frase a la que siempre volvemos — de mi mujer, no mía: \u00abNo les quitamos las pantallas. Olvidamos el cable. Ellos hicieron el resto.\u00bb",
  },
  disclaimer: {
    en: "Names of people and brands in these editions are changed. Whoever was there and reads this knows what we're talking about.",
    es: "Los nombres de personas y marcas en estas ediciones están cambiados. Quien estuvo allí y lo lee sabe de qué hablamos.",
  },
};

const FE_BLOCKS = [
  { key: "opening",           name: { en: "Opening",            es: "Apertura" },      tier: "free" },
  { key: "story",             name: { en: "Story",              es: "Historia" },      tier: "free" },
  { key: "reflection",        name: { en: "Reflection",         es: "Reflexión" },     tier: "free" },
  { key: "practical_insight", name: { en: "Practical insight",  es: "Idea práctica" }, tier: "free" },
  { key: "activity",          name: { en: "Activity",           es: "Actividad" },     tier: "premium" },
  { key: "resources",         name: { en: "Resources",          es: "Recursos" },      tier: "premium" },
  { key: "closing",           name: { en: "Closing",            es: "Cierre" },        tier: "premium" },
];

const FE_EDITION_200 = {
  num: 200,
  gift: true,
  milestone: true,
  phase: "A",
  layout: "manuscript",
  accent: "#C45A3A",
  badge: { en: "200 · Gift edition", es: "200 · Edición regalo" },
  editor: { name: "Alex", location: { en: "New York, USA", es: "Nueva York, EE.UU." }, color: "#C45A3A" },
  coEditors: [
    { name: "Markus", city: { en: "Barcelona", es: "Barcelona" }, color: "#4CB868" },
    { name: "Victor", city: { en: "Vienna",    es: "Viena" },     color: "#3A5FA5" },
    { name: "Marian", city: { en: "London",    es: "Londres" },   color: "#7A4A8A" },
    { name: "Alex",   city: { en: "New York",  es: "Nueva York" },color: "#C45A3A" },
  ],
  date: { en: "September 22, 2026", es: "22 de septiembre de 2026" },
  readFree: { en: "1 min taste", es: "1 min de muestra" },
  readFull: { en: "9 min full read", es: "9 min lectura completa" },
  title: {
    en: "The settled child. What happens under the surface once the excitement of new wears off.",
    es: "El niño que ya es de casa. Lo que ocurre bajo la superficie cuando la emoción de lo nuevo se desvanece.",
  },
  subtitle: {
    en: "On the second autumn, the neurology of belonging, and why the child who seems fine is the one worth watching.",
    es: "Sobre el segundo otoño, la neurología de la pertenencia, y por qué el niño que parece estar bien es el que merece más atención.",
  },
  blocks: {
    opening: {
      short: {
        en: "The second autumn after my daughter started school is the one I remember. Not the first — everyone prepares for the first. The second is when you find out who your child actually is once they've stopped performing their best behaviour for the teacher.",
        es: "El segundo otoño desde que mi hija empezó el colegio es el que recuerdo. No el primero — todo el mundo se prepara para el primero. El segundo es cuando descubres cómo es tu hijo cuando ha dejado de actuar para la maestra.",
      },
      long: {
        en: "The second autumn after my daughter started school is the one I remember. Not the first — everyone prepares for the first. There are the new shoes, the practiced route, the conversations about what happens if she needs the bathroom.\n\nThe second is different. By September of year two, she knew where her coat peg was without looking. She knew which table was hers. She stopped asking me at the gate whether I was sure I'd come back.\n\nWhat I noticed, watching her, was that she had gotten quieter in the mornings. Not sad-quiet. A different quiet — the quiet of someone who has stopped bracing.\n\nI am a fashion designer. My whole career is about reading how people carry themselves, what the body telegraphs before a word is spoken. And what I saw in my daughter, that second October, was the posture of a person who belongs somewhere.",
        es: "El segundo otoño desde que mi hija empezó el colegio es el que recuerdo. No el primero — todo el mundo se prepara para el primero. Los zapatos nuevos, el camino ensayado, las conversaciones sobre qué hacer si necesita el baño.\n\nEl segundo es diferente. En septiembre del segundo año, ya sabía dónde estaba su perchero sin mirar. Sabía cuál era su mesa. Había dejado de preguntarme en la puerta si estaba segura de que volvería.\n\nLo que noté, observándola, fue que se había quedado más callada por las mañanas. No un silencio triste. Un silencio distinto — el silencio de alguien que ha dejado de prepararse para el impacto.\n\nSoy diseñadora de moda. Toda mi carrera consiste en leer cómo se mueve la gente, lo que el cuerpo dice antes de que se pronuncie una palabra. Y lo que vi en mi hija ese segundo octubre fue la postura de alguien que pertenece a un lugar.",
      },
    },
    story: {
      short: {
        en: "In October of that year, she started choosing her own breakfast without asking. She knew where everything was — the cereal, the bowl, the spoon. She had made the kitchen hers.",
        es: "En octubre de ese año, empezó a elegir su propio desayuno sin preguntar. Sabía dónde estaba todo — los cereales, el bol, la cuchara. Había hecho suya la cocina.",
      },
      long: {
        en: "In October of that year, she started choosing her own breakfast without asking. She knew where everything was.\n\nThe cereal, the bowl, the spoon — she moved through the kitchen with the confidence of someone who had lived there a long time. Which, by then, she had. But a year earlier she hadn't moved like that.\n\nI have a clear memory of watching her the previous autumn — same kitchen, same cereal, same bowl — reaching for the wrong drawer, adjusting, trying again. Not because she didn't know. She knew. But she was still checking herself, still verifying that she was allowed.\n\nThe settled child is not performing competence. She has it. The difference is in the shoulders.\n\nI did not say anything. You do not celebrate this out loud. You notice it the way you notice a room has been cleaned — quietly, from the doorway, with something close to relief.",
        es: "En octubre de ese año, empezó a elegir su propio desayuno sin preguntar. Sabía dónde estaba todo.\n\nLos cereales, el bol, la cuchara — se movía por la cocina con la confianza de alguien que llevara mucho tiempo viviendo allí. Que ya era el caso. Pero un año antes no se movía así.\n\nTengo un recuerdo nítido de observarla el otoño anterior — misma cocina, mismos cereales, mismo bol — buscando el cajón equivocado, corrigiéndose, intentándolo de nuevo. No porque no supiera. Lo sabía. Pero aún se estaba comprobando a sí misma, verificando si tenía permiso.\n\nEl niño que ya es de casa no está demostrando competencia. La tiene. La diferencia está en los hombros.\n\nNo dije nada. Esto no se celebra en voz alta. Lo observas como observas que una habitación ha sido ordenada — en silencio, desde el umbral, con algo parecido al alivio.",
      },
    },
    reflection: {
      short: {
        en: "What is your child doing on autopilot this month that needed scaffolding a year ago?\n\nName one thing — just one — and hold it.",
        es: "¿Qué hace tu hijo en piloto automático este mes que necesitaba andamiaje hace un año?\n\nNombra una cosa — solo una — y sostenla.",
      },
      long: {
        en: "What is your child doing on autopilot this month that needed scaffolding a year ago?\n\nName one thing — just one — and hold it.\n\nNot a skill. Not a milestone. A posture. A way of moving through a space.\n\nThe settled child is easy to miss, because settled looks a lot like fine, and fine is the thing we stop watching. We watch the drama. We watch the struggle. Settled is quiet.\n\nBut settled is the whole point. It is what every first day of school, every new bedroom, every changed routine is trying to build toward. A child who knows where she is.\n\nTwo hundred editions. The question has never changed: what does your child need from you right now?\n\nThis autumn, for many children, the answer is: to be seen settling. To have someone notice that they have arrived.",
        es: "¿Qué hace tu hijo en piloto automático este mes que necesitaba andamiaje hace un año?\n\nNombra una cosa — solo una — y sostenla.\n\nNo una habilidad. No un hito. Una postura. Una manera de moverse por un espacio.\n\nEl niño que ya es de casa es fácil de pasar por alto, porque asentado se parece mucho a bien, y bien es lo que dejamos de observar. Observamos el drama. Observamos la lucha. Asentado es silencioso.\n\nPero asentado es el objetivo. Es lo que cada primer día de colegio, cada habitación nueva, cada rutina cambiada está intentando construir. Un niño que sabe dónde está.\n\nDoscientas ediciones. La pregunta nunca ha cambiado: ¿qué necesita tu hijo de ti ahora mismo?\n\nEste otoño, para muchos niños, la respuesta es: que alguien vea que se han asentado. Que alguien note que han llegado.",
      },
    },
    practical_insight: {
      short: {
        en: "The settled child is not the easy child. Settling looks like resistance, like refusal, like opinions you didn't ask for. That is not regression. That is security.",
        es: "El niño asentado no es el niño fácil. Asentarse parece resistencia, parece negativa, parece opiniones que no pediste. Eso no es regresión. Es seguridad.",
      },
      long: {
        en: "The settled child is not the easy child. This is the thing most parents misread.\n\nSettling looks like resistance. Like the child who, last year, would have accepted anything now says no to things. Like the one who was compliant at pick-up now has opinions about the route home.\n\nThis is not regression. This is security.\n\nA nervous child conserves energy. She complies. She doesn't spend capital she's not sure she has. The settled child has capital. She spends it.\n\nIn fashion — which is, at its core, about how people use external signals to communicate internal states — we call this dressing with authority. You only wear what you actually want to wear once you've stopped worrying about being wrong.\n\nThe child who's started arguing with you about small things has, in all likelihood, stopped worrying about being wrong.\n\nWatch the shoulders. Watch the drawers. Watch where they put their bag when they come in. The body tells you first.",
        es: "El niño asentado no es el niño fácil. Esto es lo que más padres malinterpretan.\n\nAsentarse parece resistencia. El niño que el año pasado hubiera aceptado cualquier cosa ahora dice que no a las cosas. El que era dócil en el recogimiento ahora tiene opiniones sobre el camino de vuelta a casa.\n\nEsto no es regresión. Es seguridad.\n\nUn niño nervioso conserva energía. Obedece. No gasta capital del que no está seguro de tener. El niño asentado tiene capital. Lo gasta.\n\nEn la moda — que es, en esencia, sobre cómo la gente usa señales externas para comunicar estados internos — a esto lo llamamos vestir con autoridad. Solo usas lo que de verdad quieres ponerte cuando has dejado de preocuparte por equivocarte.\n\nEl niño que ha empezado a discutir contigo sobre cosas pequeñas probablemente ha dejado de preocuparse por equivocarse.\n\nObserva los hombros. Observa los cajones. Observa dónde deja la mochila cuando entra. El cuerpo te lo dice primero.",
      },
    },
  },
  activity: {
    title: { en: "The Arrival Inventory", es: "El Inventario de Llegada" },
    ages: "5–15",
    duration: { en: "15 min · weekly, one month", es: "15 min · semanal, un mes" },
    tags: ["calm", "observation", "connection"],
    intro: {
      en: "Notice what your child does without being told in the first thirty minutes after school. Write it down. After a month, read it back.",
      es: "Observa lo que tu hijo hace sin que se lo digan en los primeros treinta minutos después del colegio. Anótalo. Después de un mes, vuelve a leerlo.",
    },
    materials: {
      en: ["A small notebook", "A pen", "Four weeks"],
      es: ["Un cuaderno pequeño", "Un bolígrafo", "Cuatro semanas"],
    },
    steps: {
      en: [
        "For the next four Mondays, observe the first thirty minutes after school without intervening.",
        "Notice: what does your child do first? What do they reach for without asking?",
        "Write three lines per week. Observations only — not judgements. 'She took her shoes off before I asked.' That kind of thing.",
        "After four weeks, read the four entries side by side. What became automatic?",
      ],
      es: [
        "Durante los próximos cuatro lunes, observa los primeros treinta minutos después del colegio sin intervenir.",
        "Observa: ¿qué hace primero? ¿Qué busca sin preguntar?",
        "Escribe tres líneas por semana. Solo observaciones, no juicios. 'Se quitó los zapatos antes de que yo se lo pidiera.' Ese tipo de cosas.",
        "Después de cuatro semanas, lee las cuatro entradas juntas. ¿Qué se ha vuelto automático?",
      ],
    },
    notice: {
      en: "Whether the actions become more automatic week by week — and what that tells you about where your child feels safe.",
      es: "Si las acciones se vuelven más automáticas semana a semana — y lo que eso te dice sobre dónde se siente seguro tu hijo.",
    },
    why: {
      en: "Autonomy grows in safety. A child who checks before every action has not settled. A child who doesn't need to check has. The inventory makes that shift visible before it becomes invisible — which is what usually happens.",
      es: "La autonomía crece en la seguridad. Un niño que comprueba antes de cada acción no se ha asentado. Un niño que no necesita comprobar, sí. El inventario hace visible ese cambio antes de que se vuelva invisible — que es lo que suele pasar.",
    },
  },
  resources: {
    audiobook: {
      title: { en: "The settled child · edition 200", es: "El niño que ya es de casa · edición 200" },
      narrator: { en: "Narrated by Alex", es: "Narrado por Alex" },
      meta: { en: "18:14 · ages 6–12 · listen on the way home", es: "18:14 · edades 6–12 · escuchar de camino a casa" },
    },
    recipe: {
      title: { en: "New-season minestrone", es: "Minestrone de temporada nueva" },
      role: { en: "Child's role: names each vegetable, tells you which one they want more of.", es: "Rol del niño: nombra cada verdura, dice cuál quiere más." },
      meta: { en: "Ages all · 30 min · stovetop", es: "Todas las edades · 30 min · fogón" },
    },
    ritual: {
      title: { en: "The arrival question", es: "La pregunta de llegada" },
      body: {
        en: "When your child comes home, ask: 'What did you do today that nobody asked you to do?' Not a test — just curiosity. See what they say.",
        es: "Cuando tu hijo llegue a casa, pregunta: '¿Qué hiciste hoy que nadie te pidió que hicieras?' No es un examen — es curiosidad. Fíjate en lo que dice.",
      },
    },
    companions: {
      en: [
        { code: "A.007", name: "The Kitchen Inventory" },
        { code: "A.019", name: "The Autumn Walk" },
      ],
      es: [
        { code: "A.007", name: "El Inventario de Cocina" },
        { code: "A.019", name: "El Paseo de Otoño" },
      ],
    },
  },
  closing: {
    en: "Two hundred letters. We have been writing together — Markus from Barcelona, Victor from Vienna, Marian from London, myself from New York — for three and a half years.\n\nWe started with one question. We still have one question. It has not changed because children have not changed, and what they need from the adults around them has not changed.\n\nThank you for reading. Thank you for being the kind of parent who reads this.\n\nAs Markus's wife said, once, in a sentence none of us have stopped returning to: «We didn't take away their screens. We forgot the cable. They did the rest.»",
    es: "Doscientas cartas. Llevamos escribiendo juntos — Markus desde Barcelona, Victor desde Viena, Marian desde Londres, yo desde Nueva York — tres años y medio.\n\nEmpezamos con una pregunta. Seguimos con una pregunta. No ha cambiado porque los niños no han cambiado, y lo que necesitan de los adultos que los rodean no ha cambiado.\n\nGracias por leer. Gracias por ser el tipo de padre o madre que lee esto.\n\nComo dijo una vez la mujer de Markus, en una frase a la que ninguno hemos dejado de volver: «No les quitamos las pantallas. Olvidamos el cable. Ellos hicieron el resto.»",
  },
  disclaimer: {
    en: "Names of people and brands in these editions are changed. Whoever was there and reads this knows what we're talking about.",
    es: "Los nombres de personas y marcas en estas ediciones están cambiados. Quien estuvo allí y lo lee sabe de qué hablamos.",
  },
  bonus_story: {
    title: { en: "Two hundred mornings", es: "Doscientas mañanas" },
    author: { en: "by Alex · New York", es: "de Alex · Nueva York" },
    body: {
      en: "On the morning of the two hundredth edition, I asked my younger one — she is seven now — what she remembered from a letter I read to her once.\n\nShe said: the mint.\n\nShe meant the mint that Markus's son grew in Barcelona. I had read it to her when she was five, at the kitchen table in our apartment on West 83rd, and she had asked three questions about it. Could they eat it? What did it taste like? Did the boy name it?\n\nI told her: yes, probably, and I don't think so.\n\nShe has asked me this every autumn since. I find it extraordinary — that a child in New York carries in her body the texture of a Barcelona balcony, a brown pot, a boy's patience, a spring.\n\nTwo hundred mornings. That is what these letters are. Not information — mornings. Small moments of someone else's attention, transferred.\n\nIf you have been reading since the beginning, then some of your mornings have Markus in them, Victor, Marian, me. Strangers. Four parents from four cities, writing about the same thing: the child who is right there, asking to be seen, at exactly the moment you think there is nothing to see.\n\nMy daughter still asks about the mint.\n\nI think she always will.",
      es: "La mañana de la edición doscientos, le pregunté a la pequeña — ahora tiene siete años — qué recordaba de una carta que le leí una vez.\n\nDijo: la hierbabuena.\n\nSe refería a la hierbabuena que el hijo de Markus cultivó en Barcelona. Se la había leído cuando tenía cinco años, en la mesa de la cocina de nuestro apartamento en West 83rd, y me había hecho tres preguntas. ¿Se la podían comer? ¿A qué sabía? ¿Le puso nombre el niño?\n\nLe dije: sí, probablemente, y no creo.\n\nMe lo pregunta cada otoño desde entonces. Me parece extraordinario — que una niña en Nueva York lleve en su cuerpo la textura de un balcón en Barcelona, una maceta marrón, la paciencia de un niño, una primavera.\n\nDoscientas mañanas. Eso son estas cartas. No información — mañanas. Pequeños momentos de la atención de otra persona, transferidos.\n\nSi llevas leyendo desde el principio, entonces algunas de tus mañanas tienen a Markus, a Victor, a Marian, a mí. Desconocidos. Cuatro padres de cuatro ciudades, escribiendo sobre lo mismo: el niño que está ahí mismo, pidiendo ser visto, exactamente en el momento en que crees que no hay nada que ver.\n\nMi hija sigue preguntando por la hierbabuena.\n\nCreo que siempre lo hará.",
    },
  },
};

const FE_T = {
  freeLabel:  { en: "Freemium", es: "Freemium" },
  plusLabel:  { en: "WiseTip+", es: "WiseTip+" },
  freeTag:    { en: "Free preview", es: "Vista gratis" },
  plusTag:    { en: "Full edition", es: "Edición completa" },
};

function FullEdition(props) {
  // Merge known editions into the shared map so the reader can switch by num.
  if (window.WS_EDITIONS_FULL) {
    if (!window.WS_EDITIONS_FULL[161]) {
      FE_EDITION.layout = FE_EDITION.layout || "gutter";
      FE_EDITION.accent = FE_EDITION.accent || "#2E6B3A";
      FE_EDITION.badge = FE_EDITION.badge || { en: "Earth Day", es: "Día de la Tierra" };
      window.WS_EDITIONS_FULL[161] = FE_EDITION;
    }
    if (!window.WS_EDITIONS_FULL[200]) {
      window.WS_EDITIONS_FULL[200] = FE_EDITION_200;
    }
  }
  // Dual-mode: driven by router props when embedded in the app, or by its own
  // local state when opened as the standalone "Full Edition.html".
  const inApp = !!(props && props.nav);
  const [langS, setLangS] = useFE("en");
  const [darkS, setDarkS] = useFE(true);
  const [modeS, setModeS] = useFE("plus"); // "free" | "plus"

  const lang = inApp ? props.lang : langS;
  const setLang = inApp ? (props.setLang || (() => {})) : setLangS;
  const dark = inApp ? props.dark : darkS;
  const setDark = inApp ? (() => {}) : setDarkS;
  const theme = inApp ? props.theme : (dark ? window.WS_DARK : window.WS_LIGHT);
  // In-app, free/plus maps to the global reader tier (isMember).
  const mode = inApp ? (props.isMember ? "plus" : "free") : modeS;
  const setMode = inApp ? (m => props.setIsMember(m === "plus")) : setModeS;

  const ALL = window.WS_EDITIONS_FULL || {};
  const num = inApp && props.pageData && props.pageData.num ? props.pageData.num : 200;
  const ed = ALL[num] || FE_EDITION_200;
  const layout = ed.layout || "gutter";
  const accent = ed.accent || "#7DBE5A";
  const isPlus = mode === "plus";
  const isGift = ed.gift === true;

  const bonusBlock = { key: "bonus_story", name: { en: "Bonus story", es: "Historia especial" }, tier: "premium" };
  const visibleBlocks = isGift
    ? [...FE_BLOCKS, bonusBlock]
    : (isPlus ? FE_BLOCKS : FE_BLOCKS.filter(b => b.tier === "free"));
  const freeCount = FE_BLOCKS.filter(b => b.tier === "free").length;

  return (
    <div style={{ background: theme.bg, color: theme.text, minHeight: "100vh" }}>
      <style>{window.WS_FONT_CSS}</style>

      {/* CONTROL BAR — standalone only (the app supplies its own global Nav) */}
      {!inApp && <div style={{
        position: "sticky", top: 0, zIndex: 20,
        background: theme.navBg, backdropFilter: "blur(14px)", WebkitBackdropFilter: "blur(14px)",
        borderBottom: `1px solid ${theme.border}`,
        padding: ".8rem clamp(1.25rem, 5vw, 3rem)",
        display: "flex", alignItems: "center", justifyContent: "space-between", gap: "1rem", flexWrap: "wrap",
      }}>
        <div style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
          <span className="serif italic" style={{ fontSize: "1.3rem", color: theme.text, fontWeight: 500 }}>WiseTip</span>
          <span style={{ width: 4, height: 4, borderRadius: "50%", background: isPlus ? theme.gold : theme.sage, display: "inline-block" }}/>
          <span className="label-tiny" style={{ color: isPlus ? theme.gold : theme.sage }}>
            {isPlus ? (lang === "en" ? "Full article · 7 of 7" : "Artículo completo · 7 de 7") : (lang === "en" ? "Teaser · 4 of 7" : "Muestra · 4 de 7")}
          </span>
        </div>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }}>
          <ModeToggle theme={theme} dark={dark} mode={mode} setMode={setMode} lang={lang}/>
          <FESeg theme={theme} dark={dark} value={lang} onChange={setLang} options={[["en", "EN"], ["es", "ES"]]}/>
          <button onClick={() => setDark(!dark)} title="Toggle theme" style={{
            background: theme.sage3, border: `1px solid ${theme.border2}`, borderRadius: 16,
            color: theme.text2, padding: "6px 12px", fontSize: ".74rem",
          }}>{dark ? "☾" : "☀"}</button>
        </div>
      </div>}

      {/* HERO — layout-aware so no two editions read the same */}
      <EditionHero ed={ed} layout={layout} accent={accent} lang={lang} isPlus={isPlus} inApp={inApp} dark={dark} theme={theme}/>

      {/* MODE RIBBON */}
      <div style={{ background: theme.bg, padding: "0 clamp(1.5rem, 5vw, 5vw)" }}>
        <div className="wrap-sm" style={{
          marginTop: "-1.6rem", position: "relative", zIndex: 5,
          background: dark ? (isGift ? "#261A08" : isPlus ? "#1A2C14" : "#13301E") : "#FFFFFF",
          border: `1px solid ${isGift ? theme.gold + "88" : isPlus ? theme.gold + "55" : theme.border2}`, borderRadius: 8,
          boxShadow: dark ? "0 18px 50px -20px rgba(0,0,0,.6)" : "0 18px 50px -24px rgba(46,107,58,.35)",
          padding: "1.1rem 1.4rem", display: "flex", justifyContent: "space-between", alignItems: "center", gap: "1rem", flexWrap: "wrap",
        }}>
          {isGift ? (
            <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <span style={{
                width: 34, height: 34, borderRadius: "50%", background: theme.gold,
                color: dark ? "#1A1206" : "#2A1E08", display: "flex", alignItems: "center", justifyContent: "center",
                fontFamily: "'Cormorant Garamond',serif", fontSize: "1.1rem",
              }}>🎁</span>
              <div>
                <div className="label-tiny" style={{ color: theme.gold, marginBottom: 2 }}>
                  {lang === "en" ? "Edition 200 · Gift edition · Fully open" : "Edición 200 · Edición regalo · Completamente abierta"}
                </div>
                <div style={{ fontSize: ".84rem", color: theme.text2, fontWeight: 300 }}>
                  {isPlus
                    ? (lang === "en" ? "All 7 blocks + Bonus story — reading the complete edition." : "Los 7 bloques + Historia especial — leyendo la edición completa.")
                    : (lang === "en" ? "All 7 blocks are open as a gift. The Bonus story is for WiseTip+ members." : "Los 7 bloques están abiertos como regalo. La Historia especial es para miembros WiseTip+.")}
                </div>
              </div>
            </div>
          ) : (
            <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <span style={{
                width: 34, height: 34, borderRadius: "50%", background: isPlus ? theme.gold : theme.sage,
                color: dark ? "#050F07" : "#fff", display: "flex", alignItems: "center", justifyContent: "center",
                fontFamily: "'Cormorant Garamond',serif", fontSize: "1.1rem",
              }}>{isPlus ? "✦" : "◔"}</span>
              <div>
                <div className="label-tiny" style={{ color: isPlus ? theme.gold : theme.sage, marginBottom: 2 }}>
                  {isPlus
                    ? (lang === "en" ? "You're reading the full WiseTip+ article" : "Estás leyendo el artículo completo de WiseTip+")
                    : (lang === "en" ? "You're reading the free teaser" : "Estás leyendo la muestra gratis")}
                </div>
                <div style={{ fontSize: ".84rem", color: theme.text2, fontWeight: 300 }}>
                  {isPlus
                    ? (lang === "en" ? "All 7 blocks, each in its long form — plus Activity, Resources & Closing." : "Los 7 bloques, cada uno en su versión larga — más Actividad, Recursos y Cierre.")
                    : (lang === "en" ? "Short versions of 4 blocks. The full article is 2–3× longer." : "Versiones cortas de 4 bloques. El artículo completo es 2–3× más largo.")}
                </div>
              </div>
            </div>
          )}
          {!isGift && !isPlus && (
            <button onClick={() => setMode("plus")} style={{
              background: theme.gold, border: "none", color: dark ? "#1A1206" : "#2A1E08",
              padding: "9px 18px", borderRadius: 20, fontSize: ".8rem", fontWeight: 600, whiteSpace: "nowrap",
            }}>{lang === "en" ? "Read the full article →" : "Leer el artículo completo →"}</button>
          )}
          {!isGift && isPlus && (
            <button onClick={() => setMode("free")} style={{
              background: "transparent", border: `1px solid ${theme.border2}`, color: theme.text3,
              padding: "8px 16px", borderRadius: 20, fontSize: ".76rem", whiteSpace: "nowrap",
            }}>{lang === "en" ? "See the free teaser" : "Ver la muestra gratis"}</button>
          )}
          {isGift && !isPlus && (
            <button onClick={() => setMode("plus")} style={{
              background: theme.gold, border: "none", color: dark ? "#1A1206" : "#2A1E08",
              padding: "9px 18px", borderRadius: 20, fontSize: ".8rem", fontWeight: 600, whiteSpace: "nowrap",
            }}>{lang === "en" ? "Unlock Bonus story →" : "Desbloquear Historia especial →"}</button>
          )}
        </div>
      </div>

      {/* BODY */}
      <article style={{ padding: "3rem clamp(1.5rem, 5vw, 5vw) 1rem" }}>
        <div className="wrap-sm">
          {visibleBlocks.map((b, i) => {
            const firstPremium = isPlus ? FE_BLOCKS.findIndex(x => x.tier === "premium") : -1;
            return (
              <React.Fragment key={b.key}>
                {isPlus && i === firstPremium && (
                  <div style={{ margin: "1.5rem 0 2.75rem", display: "flex", alignItems: "center", gap: 14 }}>
                    <span style={{ flex: 1, height: 1, background: theme.divider }}/>
                    <span className="label-tiny" style={{ color: theme.gold, whiteSpace: "nowrap" }}>
                      {lang === "en" ? "WiseTip+ · the members-only half" : "WiseTip+ · la mitad solo miembros"}
                    </span>
                    <span style={{ flex: 1, height: 1, background: theme.divider }}/>
                  </div>
                )}
                {(layout === "gutter" || layout === "duotone") ? (
                  <BlockShell theme={theme} idx={i} name={b.name[lang]} tier={b.tier} lang={lang} isPlus={isPlus} accent={accent} layout={layout}>
                    <BlockBody b={b} ed={ed} theme={theme} dark={dark} lang={lang} isPlus={isPlus} layout={layout} accent={accent}/>
                  </BlockShell>
                ) : (
                  <BlockFlow theme={theme} idx={i} name={b.name[lang]} tier={b.tier} lang={lang} isPlus={isPlus} accent={accent} layout={layout}>
                    <BlockBody b={b} ed={ed} theme={theme} dark={dark} lang={lang} isPlus={isPlus} layout={layout} accent={accent}/>
                  </BlockFlow>
                )}
              </React.Fragment>
            );
          })}

          {/* PAYWALL — freemium only, not shown for gift editions */}
          {!isPlus && !isGift && <Paywall theme={theme} dark={dark} lang={lang} onUnlock={() => setMode("plus")} ed={ed}/>}

          {/* DISCLAIMER */}
          <div style={{
            marginTop: "1.5rem", padding: "1.25rem 1.5rem",
            background: theme.sage3, border: `1px dashed ${theme.border2}`, borderRadius: 6,
            fontSize: ".82rem", color: theme.text3, lineHeight: 1.65,
          }}>{ed.disclaimer[lang]}</div>
        </div>
      </article>


    </div>
  );
}

// ── mode toggle (Freemium / WiseTip+) ────────────────────────
function ModeToggle({ theme, dark, mode, setMode, lang }) {
  const opts = [["free", FE_T.freeLabel[lang]], ["plus", FE_T.plusLabel[lang]]];
  return (
    <div style={{ display: "flex", gap: 3, padding: 3, background: theme.sage3, borderRadius: 16, border: `1px solid ${theme.border}` }}>
      {opts.map(([v, l]) => {
        const on = mode === v;
        const plus = v === "plus";
        return (
          <button key={v} onClick={() => setMode(v)} style={{
            padding: "5px 13px", borderRadius: 13, border: "none",
            background: on ? (plus ? theme.gold : theme.sage) : "transparent",
            color: on ? (dark ? "#0A0A0A" : "#fff") : theme.text2,
            fontSize: ".72rem", fontWeight: on ? 600 : 400, whiteSpace: "nowrap",
          }}>{plus ? "✦ " : ""}{l}</button>
        );
      })}
    </div>
  );
}

// ── block shell (number + label gutter) ──────────────────────
function BlockShell({ theme, idx, name, tier, lang, isPlus, accent, layout, children }) {
  const isPrem = tier === "premium";
  const acc = accent || theme.sage;
  const duo = layout === "duotone";
  return (
    <div style={{ display: "grid", gridTemplateColumns: "112px 1fr", gap: "1.75rem", marginBottom: "2.75rem" }} className="fe-block">
      <div style={ duo ? { borderLeft: `2px solid ${isPrem ? theme.gold : acc}`, paddingLeft: 12 } : undefined }>
        <div className="mono" style={{ fontSize: ".68rem", color: theme.text3 }}>{String(idx + 1).padStart(2, "0")}</div>
        <div className="label-tiny" style={{ marginTop: 5, color: isPrem ? theme.gold : acc }}>{name}</div>
        <div style={{ fontSize: ".62rem", marginTop: 4, color: theme.text4, letterSpacing: ".05em" }}>
          {isPrem ? "WiseTip+" : (isPlus ? (lang === "en" ? "Full" : "Completo") : (lang === "en" ? "Teaser" : "Muestra"))}
        </div>
      </div>
      <div>{children}</div>
      <style>{`@media (max-width: 700px){ .fe-block{ grid-template-columns: 1fr !important; gap:.5rem !important; } }`}</style>
    </div>
  );
}

// ── flowing block shell (manuscript / letter / feature) ──────
function BlockFlow({ theme, idx, name, tier, lang, isPlus, accent, layout, children }) {
  const isPrem = tier === "premium";
  const labelColor = isPrem ? theme.gold : (accent || theme.sage);
  const center = layout === "feature" || layout === "manuscript";
  return (
    <div style={{ marginBottom: "2.75rem" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: "1.1rem", justifyContent: center ? "center" : "flex-start" }}>
        <span className="mono" style={{ fontSize: ".66rem", color: theme.text3 }}>{String(idx + 1).padStart(2, "0")}</span>
        <span style={{ width: 24, height: 1, background: labelColor, opacity: .55 }}/>
        <span className="label-tiny" style={{ color: labelColor }}>{name}</span>
        <span style={{ fontSize: ".6rem", color: theme.text4, letterSpacing: ".05em" }}>{isPrem ? "WiseTip+" : (isPlus ? (lang === "en" ? "Full" : "Completo") : (lang === "en" ? "Teaser" : "Muestra"))}</span>
      </div>
      <div>{children}</div>
    </div>
  );
}

// ── EDITION HERO — five visual treatments keyed off ed.layout ─
function EditionHero({ ed, layout, accent, lang, isPlus, inApp, dark, theme }) {
  const pad = inApp
    ? "clamp(5.5rem, 9vh, 7.5rem) clamp(1.5rem, 5vw, 5vw) clamp(3rem, 7vh, 5rem)"
    : "clamp(3rem, 8vh, 6rem) clamp(1.5rem, 5vw, 5vw) clamp(3rem, 7vh, 5rem)";
  const badge = ed.badge ? ed.badge[lang] : null;
  const isGiftEd = ed.gift === true;
  const tierPill = isGiftEd ? (
    <Pill color="#C8A85A" bd="#C8A85A66" style={{ whiteSpace: "nowrap" }}>
      {lang === "en" ? "🎁 Gift edition · all blocks open" : "🎁 Edición regalo · todos los bloques abiertos"}
    </Pill>
  ) : (
    <Pill color={isPlus ? "#C8A85A" : accent} bd={isPlus ? "#C8A85A66" : accent + "66"} style={{ whiteSpace: "nowrap" }}>
      {isPlus ? (lang === "en" ? "WiseTip+ · full 7 blocks" : "WiseTip+ · 7 bloques") : (lang === "en" ? "Free · 4-block teaser" : "Gratis · muestra 4 bloques")}
    </Pill>
  );
  const Byline = ({ onDark }) => (
    <div style={{ display: "flex", alignItems: "center", gap: 14, paddingTop: "1.5rem", borderTop: `1px solid ${onDark ? "rgba(255,255,255,.1)" : theme.divider}` }}>
      <div style={{ width: 46, height: 46, borderRadius: "50%", background: `${ed.editor.color}28`, border: `1.5px solid ${ed.editor.color}77`, display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "'Cormorant Garamond',serif", fontSize: "1.4rem", color: ed.editor.color }}>{ed.editor.name[0]}</div>
      <div style={{ textAlign: "left" }}>
        <div style={{ fontSize: ".96rem", color: onDark ? "#F0EBE0" : theme.text, fontWeight: 500 }}>{ed.editor.name}</div>
        <div style={{ fontSize: ".74rem", color: onDark ? "rgba(240,235,224,.5)" : theme.text3 }}>{ed.editor.location[lang]} · {ed.date[lang]} · {isPlus ? ed.readFull[lang] : ed.readFree[lang]}</div>
      </div>
    </div>
  );

  // ── MANUSCRIPT — centered, ruled, literary ──
  if (layout === "manuscript") {
    const warmAccent = !!(accent && accent.startsWith("#C4"));
    const bg = dark ? (warmAccent ? "#241208" : "#12251A") : (warmAccent ? "#F5EDE4" : "#ECE5D6");
    const tx = dark ? (warmAccent ? "#EDE0D4" : "#EDE7D8") : (warmAccent ? "#2B1510" : "#1B2B1E");
    return (
      <header style={{ background: bg, color: tx, padding: pad, textAlign: "center" }}>
        <div className="wrap-sm" style={{ margin: "0 auto" }}>
          <div style={{ display: "flex", justifyContent: "center", gap: 8, marginBottom: "1.5rem", flexWrap: "wrap" }}>
            <Pill color={accent} bd={accent + "66"}>#{ed.num}</Pill>
            {badge && <Pill color={accent} bd={accent + "66"}>{badge}</Pill>}
            {tierPill}
          </div>
          <div style={{ width: 54, height: 1, background: accent, margin: "0 auto 1.5rem", opacity: .6 }}/>
          <h1 className="serif" style={{ fontWeight: 400, fontSize: "clamp(2rem,5vw,3.6rem)", lineHeight: 1.12, letterSpacing: "-.01em", marginBottom: "1rem", color: tx }}>{ed.title[lang]}</h1>
          <p className="serif italic" style={{ fontSize: "clamp(1.05rem,2.2vw,1.3rem)", fontWeight: 300, lineHeight: 1.6, color: dark ? "rgba(237,231,216,.72)" : "rgba(27,43,30,.7)", maxWidth: 560, margin: "0 auto 1.5rem" }}>{ed.subtitle[lang]}</p>
          <div className="mono" style={{ fontSize: ".74rem", letterSpacing: ".08em", color: dark ? "rgba(237,231,216,.55)" : "rgba(27,43,30,.6)" }}>{ed.editor.name} · {ed.editor.location[lang]} · {ed.date[lang]}</div>
        </div>
      </header>
    );
  }

  // ── DUOTONE — split accent panel + dark title panel ──
  if (layout === "duotone") {
    return (
      <header style={{ display: "grid", gridTemplateColumns: "minmax(0,38%) 1fr" }} className="fe-duo-hero">
        <div style={{ background: accent, color: "#0B160E", padding: pad, display: "flex", flexDirection: "column", justifyContent: "flex-end", position: "relative", overflow: "hidden" }}>
          <Grain amount={.06}/>
          <div style={{ position: "relative" }}>
            <div className="serif" style={{ fontSize: "clamp(3rem,7vw,5rem)", fontWeight: 300, lineHeight: .9 }}>#{ed.num}</div>
            {badge && <div className="label-tiny" style={{ marginTop: 12, color: "rgba(11,22,14,.7)" }}>{badge}</div>}
          </div>
        </div>
        <div style={{ background: dark ? "#0B1C12" : "#0E2318", color: "#F0EBE0", padding: pad, position: "relative", overflow: "hidden" }}>
          <Particles count={12} theme={{ sage: "#7DBE5A", sage3: "rgba(125,190,90,.08)" }}/>
          <div style={{ position: "relative", maxWidth: 640 }}>
            <div style={{ marginBottom: "1.5rem" }}>{tierPill}</div>
            <h1 className="serif" style={{ fontStyle: "italic", fontWeight: 300, fontSize: "clamp(2rem,4.5vw,3.6rem)", lineHeight: 1.06, letterSpacing: "-.015em", marginBottom: "1.1rem" }}>{ed.title[lang]}</h1>
            <p style={{ color: "rgba(240,235,224,.6)", fontSize: "clamp(1rem,2vw,1.15rem)", lineHeight: 1.7, fontWeight: 300, marginBottom: "2rem", maxWidth: 560 }}>{ed.subtitle[lang]}</p>
            <Byline onDark={true}/>
          </div>
        </div>
        <style>{`@media (max-width:760px){ .fe-duo-hero{ grid-template-columns:1fr !important; } }`}</style>
      </header>
    );
  }

  // ── DARK-FOREST family: gutter / feature / letter ──
  const centered = layout === "feature";
  const compact = layout === "letter";
  return (
    <header style={{ position: "relative", overflow: "hidden", background: dark ? "#0B1C12" : "#0E2318", color: "#F0EBE0", padding: pad, textAlign: centered ? "center" : "left" }}>
      <Particles count={compact ? 10 : 18} theme={{ sage: "#7DBE5A", sage3: "rgba(125,190,90,.08)" }}/>
      <Trees theme={{ sage: "#7DBE5A" }} dark={true}/>
      <Grain amount={.05}/>
      <div className="wrap-sm" style={{ position: "relative", margin: centered ? "0 auto" : undefined }}>
        <div style={{ display: "flex", gap: 8, marginBottom: compact ? "1.25rem" : "1.75rem", flexWrap: "wrap", justifyContent: centered ? "center" : "flex-start" }}>
          <Pill color={accent} bd={accent + "66"} style={{ whiteSpace: "nowrap" }}>#{ed.num}</Pill>
          {badge && <Pill color="#C8A85A" bd="#C8A85A66" style={{ whiteSpace: "nowrap" }}>★ {badge}</Pill>}
          {tierPill}
        </div>
        {compact && <div className="mono" style={{ fontSize: ".72rem", color: accent, marginBottom: "1rem", letterSpacing: ".08em" }}>{lang === "en" ? "A letter from" : "Una carta desde"} {ed.editor.location[lang]}</div>}
        <h1 className="serif" style={{ fontStyle: "italic", fontWeight: 300, fontSize: centered ? "clamp(2.6rem,7vw,5rem)" : (compact ? "clamp(1.9rem,4.5vw,3.2rem)" : "clamp(2.2rem,6vw,4.2rem)"), color: "#F0EBE0", lineHeight: 1.04, letterSpacing: "-.015em", marginBottom: "1.25rem", maxWidth: centered ? 900 : undefined, marginLeft: centered ? "auto" : undefined, marginRight: centered ? "auto" : undefined }}>{ed.title[lang]}</h1>
        <p style={{ color: "rgba(240,235,224,.6)", fontSize: "clamp(1rem,2.2vw,1.18rem)", lineHeight: 1.7, fontWeight: 300, marginBottom: "2.25rem", maxWidth: 600, marginLeft: centered ? "auto" : undefined, marginRight: centered ? "auto" : undefined }}>{ed.subtitle[lang]}</p>
        <div style={{ display: "flex", justifyContent: centered ? "center" : "flex-start" }}><Byline onDark={true}/></div>
      </div>
    </header>
  );
}

// ── per-block body ───────────────────────────────────────────
function BlockBody({ b, ed, theme, dark, lang, isPlus, layout, accent }) {
  if (b.key === "activity")  return <ActivityBlock a={ed.activity} theme={theme} dark={dark} lang={lang}/>;
  if (b.key === "resources") return <ResourcesBlock r={ed.resources} theme={theme} dark={dark} lang={lang}/>;

  if (b.key === "bonus_story") {
    const bs = ed.bonus_story;
    if (!bs) return null;
    if (!isPlus) {
      return (
        <div style={{
          background: dark ? "linear-gradient(135deg, #1E1408, #26180A)" : "linear-gradient(135deg, #FBF6EC, #F5EDDA)",
          border: `1px solid ${theme.gold}55`, borderRadius: 10,
          padding: "clamp(1.5rem,4vw,2.25rem)", textAlign: "center",
        }}>
          <div style={{ fontSize: "1.4rem", marginBottom: ".6rem" }}>✦</div>
          <div className="label-tiny" style={{ color: theme.gold, marginBottom: ".5rem" }}>
            {lang === "en" ? "WiseTip+ · members only" : "WiseTip+ · solo miembros"}
          </div>
          <h4 className="serif italic" style={{ fontWeight: 400, fontSize: "clamp(1.2rem,3vw,1.6rem)", color: theme.text, lineHeight: 1.2, marginBottom: ".5rem" }}>
            {bs.title[lang]}
          </h4>
          <div style={{ fontSize: ".8rem", color: theme.text3, marginBottom: "1.25rem" }}>{bs.author[lang]}</div>
          <p style={{ fontSize: ".9rem", color: theme.text2, fontWeight: 300, lineHeight: 1.6, maxWidth: 440, margin: "0 auto 1.5rem" }}>
            {lang === "en"
              ? "A personal reflection written for the 200th edition. Available to WiseTip+ members."
              : "Una reflexión personal escrita para la edición 200. Disponible para miembros WiseTip+."}
          </p>
          <div style={{ fontSize: ".74rem", color: theme.text4 }}>
            {lang === "en" ? "Already a member? Sign in above." : "¿Ya eres miembro? Inicia sesión arriba."}
          </div>
        </div>
      );
    }
    return (
      <div style={{
        background: dark ? theme.panel : "#FFFDF8",
        border: `1px solid ${theme.gold}44`, borderRadius: 10,
        padding: "clamp(1.5rem,4vw,2.25rem)",
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: "1.25rem", paddingBottom: ".9rem", borderBottom: `1px solid ${theme.divider}` }}>
          <span style={{ color: theme.gold, fontSize: "1.1rem" }}>✦</span>
          <div>
            <h4 className="serif italic" style={{ fontWeight: 400, fontSize: "1.25rem", color: theme.text, lineHeight: 1.2 }}>{bs.title[lang]}</h4>
            <div style={{ fontSize: ".76rem", color: theme.text3 }}>{bs.author[lang]}</div>
          </div>
        </div>
        <p style={{ fontSize: "1.06rem", lineHeight: 1.88, fontWeight: 300, color: dark ? "#E8DED0" : theme.text, whiteSpace: "pre-line" }}>{bs.body[lang]}</p>
      </div>
    );
  }

  if (b.key === "closing") {
    const text = ed.closing[lang];
    return (
      <div>
        <p className="serif italic" style={{
          fontSize: "1.18rem", lineHeight: 1.7, fontWeight: 300, color: dark ? "#E8DED0" : theme.text,
          borderLeft: `3px solid ${theme.sage}`, paddingLeft: "1.25rem", whiteSpace: "pre-line", marginBottom: "1.75rem",
        }}>{text}</p>
        <div style={{ display: "flex", gap: "1.25rem", flexWrap: "wrap" }}>
          {ed.coEditors.map(e => (
            <div key={e.name} style={{ display: "flex", alignItems: "center", gap: 9 }}>
              <span style={{
                width: 30, height: 30, borderRadius: "50%", background: `${e.color}22`, border: `1px solid ${e.color}77`,
                display: "flex", alignItems: "center", justifyContent: "center",
                fontFamily: "'Cormorant Garamond',serif", fontSize: ".95rem", color: e.color,
              }}>{e.name[0]}</span>
              <span style={{ fontSize: ".8rem", color: theme.text2 }}>{e.name} · <span style={{ color: theme.text3 }}>{e.city[lang]}</span></span>
            </div>
          ))}
        </div>
      </div>
    );
  }

  // shared blocks: short (freemium) or long (premium)
  const variant = isPlus ? "long" : "short";
  const text = ed.blocks[b.key][variant][lang];
  if (b.key === "opening") {
    if (layout === "manuscript" || layout === "feature") {
      return (
        <p className="serif" style={{ fontStyle: layout === "manuscript" ? "normal" : "italic", fontSize: layout === "feature" ? "1.42rem" : "1.3rem", lineHeight: 1.75, fontWeight: 300, color: dark ? "#E8DED0" : theme.text, whiteSpace: "pre-line" }}>
          <span style={{ float: "left", fontFamily: "'Cormorant Garamond',serif", fontSize: "3.6rem", lineHeight: .8, paddingRight: ".09em", marginTop: ".06em", color: accent || theme.sage, fontWeight: 500 }}>{text.charAt(0)}</span>
          {text.slice(1)}
        </p>
      );
    }
    return <p className="serif italic" style={{ fontSize: "1.35rem", lineHeight: 1.7, fontWeight: 300, color: dark ? "#E8DED0" : theme.text, whiteSpace: "pre-line" }}>{text}</p>;
  }
  return <p style={{ fontSize: "1.08rem", lineHeight: 1.85, fontWeight: 300, color: theme.text2, whiteSpace: "pre-line" }}>{text}</p>;
}

// ── PAYWALL (freemium) ───────────────────────────────────────
function Paywall({ theme, dark, lang, onUnlock, ed }) {
  const lockedBlocks = FE_BLOCKS.filter(b => b.tier === "premium");
  return (
    <div style={{ position: "relative", marginTop: ".5rem", marginBottom: "1rem" }}>
      {/* faded preview of what's behind the wall */}
      <div style={{
        position: "relative", maxHeight: 150, overflow: "hidden",
        maskImage: "linear-gradient(180deg, rgba(0,0,0,.5), transparent)",
        WebkitMaskImage: "linear-gradient(180deg, rgba(0,0,0,.5), transparent)",
      }}>
        <div style={{ display: "grid", gridTemplateColumns: "112px 1fr", gap: "1.75rem" }} className="fe-block">
          <div>
            <div className="mono" style={{ fontSize: ".68rem", color: theme.text3 }}>05</div>
            <div className="label-tiny" style={{ marginTop: 5, color: theme.gold }}>{lang === "en" ? "Activity" : "Actividad"}</div>
          </div>
          <div>
            <div className="serif italic" style={{ fontSize: "1.7rem", color: theme.text }}>{ed.activity.title[lang]}</div>
            <p style={{ fontSize: "1.05rem", color: theme.text2, marginTop: 8, fontWeight: 300 }}>{ed.activity.intro[lang]}</p>
          </div>
        </div>
      </div>

      {/* the wall */}
      <div style={{
        marginTop: "-1rem", position: "relative", zIndex: 2,
        background: dark ? "linear-gradient(180deg, #14301D, #0F2417)" : "linear-gradient(180deg, #fff, #F4F1E8)",
        border: `1px solid ${theme.gold}55`, borderRadius: 12,
        boxShadow: dark ? "0 24px 60px -24px rgba(0,0,0,.7)" : "0 24px 60px -28px rgba(46,107,58,.4)",
        padding: "clamp(1.75rem, 4vw, 2.75rem)", textAlign: "center",
      }}>
        <span style={{
          display: "inline-flex", alignItems: "center", justifyContent: "center",
          width: 46, height: 46, borderRadius: "50%", background: `${theme.gold}22`, border: `1px solid ${theme.gold}77`,
          color: theme.gold, fontSize: "1.2rem", marginBottom: "1rem",
        }}>✦</span>
        <div className="label-tiny" style={{ color: theme.gold, marginBottom: ".6rem" }}>
          {lang === "en" ? "The other 3 blocks are for members" : "Los otros 3 bloques son para miembros"}
        </div>
        <h3 className="serif italic" style={{ fontWeight: 400, fontSize: "clamp(1.5rem, 3.5vw, 2.1rem)", color: theme.text, lineHeight: 1.15, marginBottom: ".85rem", maxWidth: 520, marginLeft: "auto", marginRight: "auto" }}>
          {lang === "en" ? "Keep reading the full article" : "Sigue leyendo el artículo completo"}
        </h3>
        <p style={{ fontSize: ".95rem", color: theme.text2, fontWeight: 300, lineHeight: 1.6, maxWidth: 460, margin: "0 auto 1.5rem" }}>
          {lang === "en"
            ? "WiseTip+ expands every block to its full length and unlocks the three that make the edition usable at home:"
            : "WiseTip+ amplía cada bloque a su versión completa y desbloquea los tres que hacen la edición útil en casa:"}
        </p>

        {/* what you unlock */}
        <div style={{ display: "flex", flexDirection: "column", gap: 10, maxWidth: 420, margin: "0 auto 1.75rem", textAlign: "left" }}>
          {[
            { g: "🌱", t: lang === "en" ? "The Activity" : "La Actividad", d: lang === "en" ? "Materials, steps, what to notice, why it works." : "Materiales, pasos, qué observar, por qué funciona." },
            { g: "🎧", t: lang === "en" ? "Resources" : "Recursos", d: lang === "en" ? "Audiobook, recipe & table ritual." : "Audiolibro, receta y ritual de mesa." },
            { g: "✍", t: lang === "en" ? "The Closing" : "El Cierre", d: lang === "en" ? "The four editors' shared sign-off." : "El cierre compartido de los cuatro editores." },
          ].map((x, i) => (
            <div key={i} style={{ display: "flex", gap: 12, alignItems: "flex-start" }}>
              <span style={{ fontSize: "1.05rem", width: 24, textAlign: "center", flexShrink: 0 }}>{x.g}</span>
              <div>
                <div style={{ fontSize: ".92rem", color: theme.text, fontWeight: 500 }}>{x.t}</div>
                <div style={{ fontSize: ".82rem", color: theme.text3, fontWeight: 300, lineHeight: 1.45 }}>{x.d}</div>
              </div>
            </div>
          ))}
          <div style={{ display: "flex", gap: 12, alignItems: "flex-start" }}>
            <span style={{ fontSize: "1.05rem", width: 24, textAlign: "center", flexShrink: 0 }}>＋</span>
            <div>
              <div style={{ fontSize: ".92rem", color: theme.text, fontWeight: 500 }}>{lang === "en" ? "Longer versions of the 4 free blocks" : "Versiones largas de los 4 bloques gratis"}</div>
              <div style={{ fontSize: ".82rem", color: theme.text3, fontWeight: 300, lineHeight: 1.45 }}>{lang === "en" ? "Each block, 2–3× the depth you just read." : "Cada bloque, 2–3× la profundidad que acabas de leer."}</div>
            </div>
          </div>
        </div>

        <button onClick={onUnlock} style={{
          background: theme.gold, border: "none", color: dark ? "#1A1206" : "#2A1E08",
          padding: "12px 26px", borderRadius: 24, fontSize: ".92rem", fontWeight: 600, cursor: "pointer",
        }}>{lang === "en" ? "Unlock the full edition →" : "Desbloquear la edición completa →"}</button>
        <div style={{ fontSize: ".72rem", color: theme.text4, marginTop: ".9rem" }}>
          {lang === "en" ? "Preview — flips to the WiseTip+ reading." : "Vista previa — cambia a la lectura WiseTip+."}
        </div>
      </div>
    </div>
  );
}

// ── ACTIVITY — fully built premium block ─────────────────────
function ActivityBlock({ a, theme, dark, lang }) {
  return (
    <div style={{
      background: dark ? theme.panel : "#fff",
      border: `1px solid ${theme.border2}`, borderRadius: 8, overflow: "hidden",
    }}>
      <div style={{ padding: "1.5rem 1.6rem 1.25rem", borderBottom: `1px solid ${theme.divider}` }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 12, flexWrap: "wrap" }}>
          <h3 className="serif italic" style={{ fontWeight: 400, fontSize: "1.7rem", color: theme.text, lineHeight: 1.1 }}>{a.title[lang]}</h3>
          <span className="mono" style={{ fontSize: ".7rem", color: theme.text3, whiteSpace: "nowrap" }}>{a.ages} · {a.duration[lang]}</span>
        </div>
        <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: ".85rem" }}>
          {a.tags.map(t => {
            const tag = (window.WS_NEEDS_TAGS || []).find(x => x.id === t);
            if (!tag) return null;
            return <span key={t} style={{ fontSize: ".64rem", padding: "2px 9px", borderRadius: 10, background: `${tag.hue}1c`, color: tag.hue, border: `1px solid ${tag.hue}55` }}>{tag.glyph} {tag.label[lang]}</span>;
          })}
        </div>
        <p className="serif italic" style={{ fontSize: "1.12rem", lineHeight: 1.65, color: dark ? "#E8DED0" : theme.text, marginTop: "1rem", fontWeight: 300 }}>{a.intro[lang]}</p>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 0 }} className="grid-2">
        <div style={{ padding: "1.4rem 1.6rem", borderRight: `1px solid ${theme.divider}` }}>
          <div className="label-tiny" style={{ color: theme.sage, marginBottom: ".9rem" }}>{lang === "en" ? "You need" : "Necesitas"}</div>
          <ul style={{ listStyle: "none", display: "flex", flexDirection: "column", gap: 9 }}>
            {a.materials[lang].map((m, i) => (
              <li key={i} style={{ display: "flex", gap: 9, alignItems: "flex-start", fontSize: ".88rem", color: theme.text2, lineHeight: 1.45 }}>
                <span style={{ color: theme.sage, marginTop: 2 }}>—</span>{m}
              </li>
            ))}
          </ul>
        </div>
        <div style={{ padding: "1.4rem 1.6rem" }}>
          <div className="label-tiny" style={{ color: theme.sage, marginBottom: ".9rem" }}>{lang === "en" ? "How it goes" : "Cómo va"}</div>
          <ol style={{ listStyle: "none", display: "flex", flexDirection: "column", gap: 12 }}>
            {a.steps[lang].map((s, i) => (
              <li key={i} style={{ display: "flex", gap: 11, alignItems: "flex-start" }}>
                <span style={{ flexShrink: 0, width: 22, height: 22, borderRadius: "50%", background: theme.sage3, border: `1px solid ${theme.border2}`, color: theme.sage, fontSize: ".7rem", display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "'JetBrains Mono',monospace" }}>{i + 1}</span>
                <span style={{ fontSize: ".88rem", color: theme.text2, lineHeight: 1.5 }}>{s}</span>
              </li>
            ))}
          </ol>
        </div>
      </div>

      <div style={{ padding: "1.3rem 1.6rem", borderTop: `1px solid ${theme.divider}`, display: "grid", gridTemplateColumns: "1fr 1fr", gap: "1.4rem" }} className="grid-2">
        <div>
          <div className="label-tiny" style={{ color: theme.gold, marginBottom: ".5rem" }}>{lang === "en" ? "What to notice" : "Qué observar"}</div>
          <p style={{ fontSize: ".86rem", color: theme.text2, lineHeight: 1.6, fontWeight: 300 }}>{a.notice[lang]}</p>
        </div>
        <div>
          <div className="label-tiny" style={{ color: theme.gold, marginBottom: ".5rem" }}>{lang === "en" ? "Why it works" : "Por qué funciona"}</div>
          <p style={{ fontSize: ".86rem", color: theme.text2, lineHeight: 1.6, fontWeight: 300 }}>{a.why[lang]}</p>
        </div>
      </div>
    </div>
  );
}

// ── RESOURCES — fully built premium block ────────────────────
function ResourcesBlock({ r, theme, dark, lang }) {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: ".85rem" }}>
      <div style={{ background: dark ? theme.panel : "#fff", border: `1px solid ${theme.border}`, borderRadius: 8, padding: "1.25rem 1.4rem", display: "flex", alignItems: "center", gap: "1.1rem", flexWrap: "wrap" }}>
        <button style={{ flexShrink: 0, width: 46, height: 46, borderRadius: "50%", background: theme.sage, border: "none", color: dark ? "#050F07" : "#fff", fontSize: "1rem", display: "flex", alignItems: "center", justifyContent: "center" }}>▶</button>
        <div style={{ flex: 1, minWidth: 180 }}>
          <div className="label-tiny" style={{ color: theme.sage, marginBottom: 3 }}>🎧 {lang === "en" ? "Audiobook" : "Audiolibro"}</div>
          <div className="serif italic" style={{ fontSize: "1.1rem", color: theme.text, lineHeight: 1.15 }}>{r.audiobook.title[lang]}</div>
          <div style={{ fontSize: ".74rem", color: theme.text3, marginTop: 3 }}>{r.audiobook.narrator[lang]} · {r.audiobook.meta[lang]}</div>
        </div>
        <div style={{ flexShrink: 0 }}><Waveform color={theme.sage} bars={22} height={32}/></div>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: ".85rem" }} className="grid-2">
        <div style={{ background: theme.sage3, border: `1px solid ${theme.border}`, borderRadius: 8, padding: "1.25rem 1.4rem" }}>
          <div className="label-tiny" style={{ color: theme.gold, marginBottom: 6 }}>🍳 {lang === "en" ? "Recipe" : "Receta"}</div>
          <div className="serif italic" style={{ fontSize: "1.12rem", color: theme.text, lineHeight: 1.2, marginBottom: 6 }}>{r.recipe.title[lang]}</div>
          <p style={{ fontSize: ".84rem", color: theme.text2, lineHeight: 1.55, fontWeight: 300, marginBottom: 8 }}>{r.recipe.role[lang]}</p>
          <div style={{ fontSize: ".72rem", color: theme.text3 }} className="mono">{r.recipe.meta[lang]}</div>
        </div>
        <div style={{ background: theme.sage3, border: `1px solid ${theme.border}`, borderRadius: 8, padding: "1.25rem 1.4rem" }}>
          <div className="label-tiny" style={{ color: theme.gold, marginBottom: 6 }}>◇ {lang === "en" ? "Table ritual" : "Ritual de mesa"}</div>
          <div className="serif italic" style={{ fontSize: "1.12rem", color: theme.text, lineHeight: 1.2, marginBottom: 6 }}>{r.ritual.title[lang]}</div>
          <p style={{ fontSize: ".84rem", color: theme.text2, lineHeight: 1.55, fontWeight: 300 }}>{r.ritual.body[lang]}</p>
        </div>
      </div>

      <div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap", padding: ".25rem 0" }}>
        <span className="label-tiny" style={{ color: theme.text3 }}>{lang === "en" ? "Pairs with" : "Combina con"}</span>
        {r.companions[lang].map(c => (
          <span key={c.code} style={{ display: "inline-flex", alignItems: "center", gap: 7, fontSize: ".8rem", color: theme.text2, background: theme.sage3, border: `1px solid ${theme.border}`, borderRadius: 16, padding: "5px 12px" }}>
            <span className="mono" style={{ fontSize: ".66rem", color: theme.sage }}>{c.code}</span>{c.name}
          </span>
        ))}
      </div>
    </div>
  );
}

// ── small segmented control ──────────────────────────────────
function FESeg({ theme, dark, value, onChange, options }) {
  return (
    <div style={{ display: "flex", gap: 3, padding: 3, background: theme.sage3, borderRadius: 16, border: `1px solid ${theme.border}` }}>
      {options.map(([v, l]) => (
        <button key={v} onClick={() => onChange(v)} style={{
          padding: "5px 12px", borderRadius: 13, border: "none",
          background: value === v ? theme.sage : "transparent",
          color: value === v ? (dark ? "#050F07" : "#fff") : theme.text2,
          fontSize: ".72rem", fontWeight: value === v ? 600 : 400,
        }}>{l}</button>
      ))}
    </div>
  );
}

// Export for the in-app router (Homepage.html loads this before app.jsx).
Object.assign(window, { FullEdition });

// Self-render only when opened as the standalone "Full Edition.html",
// which sets window.WS_SELF_RENDER = "edition" before this script runs.
if (window.WS_SELF_RENDER === "edition") {
  ReactDOM.createRoot(document.getElementById("root")).render(<FullEdition/>);
}
