/* Ink Atelier — website (part 3): Studio, Termin, App.

   INFORMATION ARCHITECTURE (agreed 2026-08-20, option B):
   four pages, each answering a different question —
     arbeiten  what the work looks like
     artists   who does it, and where an enquiry starts
     studio    where we are, who we are, what people say
     termin    how an enquiry works
   The separate "kontakt" page was removed: address and opening hours are
   facts about the place, so they live on Studio and in the footer.
   Termin deliberately does NOT re-list the artists as cards — that list
   lives once, on Artists, on the card with the photo. It ends with the
   direct channels as plain lines, because that is the action, not a
   second profile. See DESIGN-DIRECTION.md.

   All prose is LOREM IPSUM placeholder, and lives in content.js. */

/* ---- Studio ---------------------------------------------------------- */

function Studio({ route }) {
  return (
    <div>
      <Section first>
        <SubNav parent="studio" route={route} />
        <div className="ia-split" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "clamp(2.5rem, 6vw, 6rem)", alignItems: "center" }}>
          <div>
            <Head
              label="studio"
              title={<Txt k="studio.title" />}
              lead={<Txt k="studio.lead" />}
            />
            <div style={{ marginTop: "clamp(2.5rem, 5vw, 3.5rem)" }}>
              {[1, 2, 3].map((n, i) => (
                <Reveal key={n} delay={140 + i * 70}>
                  <div style={{ padding: "1.3rem 0", borderTop: "1px solid var(--line)" }}>
                    <Label tone="ink" style={{ marginBottom: "0.6rem" }}><Txt k={`studio.value${n}.title`} /></Label>
                    <p style={{ fontSize: "var(--text-sm)", lineHeight: "var(--leading-normal)", color: "var(--ink-mute)", margin: 0, maxWidth: "44ch" }}><Txt k={`studio.value${n}.text`} /></p>
                  </div>
                </Reveal>
              ))}
            </div>
          </div>
          <Reveal delay={120} y={28}>
            <div className="ia-photo-zoom">
              <PlacePhoto place="studio.photo" style={{ aspectRatio: "4 / 5" }} />
            </div>
            {/* The photographer, right-aligned under the photo (Celia,
                2026-10-11); empty hides it. */}
            {(T("studio.photo.caption").trim() || window.IA_EDIT_MODE) && (
              <Txt k="studio.photo.caption" as="p" placeholder="eine zeile unter dem foto — anklicken und schreiben" style={{
                margin: "0.8rem 0 0", textAlign: "right", fontSize: "var(--text-xs)", color: "var(--ink-mute)",
              }} />
            )}
          </Reveal>
        </div>
      </Section>

      {/* Per Celia, 2026-08-23: "beim Studio Abschnitt auch Fotos zum
          scrollen". The same Filmstrip as Arbeiten — full-bleed, so it
          sits outside the Section container. */}
      <div style={{ borderBlock: "1px solid var(--line)" }}>
        <Filmstrip items={reel("studio")} reel="studio" />
      </div>

      <Besuch />
      <Rezensionen />
    </div>
  );
}

/* ---- Studio ▸ Small Businesses ---------------------------------------
   Per Celia: "kann man Unterkategorien machen zum Studio zB Small
   Businesses?". The makers and their one-liners are hers (round 4), as is
   the aftercare product. Brand names keep their own spelling.

   Logos: "Da können wir gerne noch die Logos ganz klein hinzufügen" — not
   delivered yet. A maker gets a small logo the moment `logo` is set (a
   file in assets/logos/), and a link once `url` is; until then the row is
   just the name. The page intro and the photo are still placeholders.
   Names and one-liners are text in content.js (smallbusinesses.<id>.*);
   the list itself — who is on it, in what order — is here. */

const MAKERS = [
  { id: "muteddesign" },
  { id: "idaping" },
  { id: "studioella" },
  { id: "feineskleines" },
  { id: "headfullceramics" },
  { id: "tantemau" },
  { id: "studio33" },
];

const CARE = [
  { id: "believatattooaftercarecream" },
  // Celia, 2026-10-13: the new cleansing gel next to the cream.
  { id: "tattoomedcleansinggel" },
];

/* Names, one-liners and links are text in content.js. An empty name hides
   the row on the site (in ?edit it stays, so it can be filled again); the
   link is set in ?edit on a line only edit mode shows. */
const shownMakers = (list) => list.filter((m) => T(`smallbusinesses.${m.id}.name`).trim() || window.IA_EDIT_MODE);

function MakerRow({ maker }) {
  const k = "smallbusinesses." + maker.id;
  const url = T(k + ".url").trim();
  const text = <Txt k={k + ".name"} />;
  const name = url
    ? <a className="ia-link ia-link--quiet" href={url} target="_blank" rel="noopener noreferrer">{text}</a>
    : text;
  return (
    <div style={{ display: "flex", alignItems: "center", gap: "1rem", padding: "1.15rem 0", borderTop: "1px solid var(--line)" }}>
      {/* A small square to the left: a product or the logo (Celia,
          2026-10-08). Optional — a maker without one shows none. */}
      {(placeSrc(k + ".photo") || window.IA_EDIT_MODE) && (
        <span className={"ia-clip" + (placeSrc(k + ".photo") ? "" : " ia-edit-only")}
              style={{ width: "64px", height: "64px", flex: "none" }}>
          <PlacePhoto place={k + ".photo"} optional sizes="64px"
                      style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
        </span>
      )}
      <div style={{ minWidth: 0 }}>
        <div style={{ fontFamily: "var(--font-display)", fontSize: "1.05rem", color: "var(--ink)" }}>{name}</div>
        {(T(k + ".what") || window.IA_EDIT_MODE) && (
          <div style={{ fontSize: "var(--text-sm)", color: "var(--ink-mute)", marginTop: "0.3rem" }}><Txt k={k + ".what"} /></div>
        )}
        {window.IA_EDIT_MODE && (
          <div className="ia-edit-only" style={{
            fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", color: "var(--ink-mute)", marginTop: "0.4rem",
          }}>link: <Txt k={k + ".url"} kind="url" placeholder="keiner — anklicken zum eintragen" /></div>
        )}
      </div>
    </div>
  );
}

function SmallBusinesses({ route }) {
  return (
    <div>
      <Section first>
        <SubNav parent="studio" route={route} />
        <div className="ia-split" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "clamp(2.5rem, 6vw, 6rem)", alignItems: "start" }}>
          <div>
            <Head
              label="small businesses"
              title={<Txt k="smallbusinesses.title" />}
              lead={<Txt k="smallbusinesses.lead" />}
            />
            <Reveal delay={120} style={{ marginTop: "clamp(2.5rem, 5vw, 3.5rem)" }}>
              {shownMakers(MAKERS).map((m) => <MakerRow key={m.id} maker={m} />)}
            </Reveal>
            <Reveal delay={160} style={{ marginTop: "clamp(2.5rem, 5vw, 3.5rem)" }}>
              <Label style={{ marginBottom: "1.2rem" }}>pflegeprodukte im ink atelier</Label>
              {shownMakers(CARE).map((m) => <MakerRow key={m.id} maker={m} />)}
            </Reveal>
          </div>
          <Reveal delay={120} y={28} className="ia-photo-zoom">
            <PlacePhoto place="smallbusinesses.photo" style={{ aspectRatio: "4 / 5" }} />
          </Reveal>
        </div>
      </Section>
    </div>
  );
}

/* ---- Besuch ----------------------------------------------------------
   What used to be the Kontakt page. Address, hours and the studio's own
   channels — reference material about the place, not a second call to
   action. The call to action is on the artist cards. */

function Besuch() {
  return (
    <Section>
      <div className="ia-split" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "clamp(2.5rem, 6vw, 6rem)", alignItems: "center" }}>
        <div>
          <Head label="besuch" title={<Txt k="studio.besuch.title" />} max="18ch" />
          <ContactRows style={{ marginTop: "clamp(2.5rem, 5vw, 3.5rem)" }} />
          <Reveal delay={190} style={{ marginTop: "clamp(2.5rem, 5vw, 3.5rem)", display: "flex", gap: "1rem", flexWrap: "wrap" }}>
            <Btn variant="solid" href={instagramUrl(STUDIO.instagram)}>instagram</Btn>
            <Btn onClick={() => go("termin")}>termin anfragen</Btn>
          </Reveal>
        </div>
        <Reveal delay={120} y={28} className="ia-photo-zoom">
          <PlacePhoto place="studio.besuch" style={{ aspectRatio: "4 / 5" }} />
        </Reveal>
      </div>
    </Section>
  );
}

/* ---- Rezensionen ------------------------------------------------------
   Celia wants her Google reviews here — her words, and it matters to her.
   Round 4, Celia: "Rezensionen dürfen gerne übernommen werden".

   COPIED BY HAND, not synced. The Places API only ever returns five
   reviews and Google picks which; it needs a billed key; and a live embed
   would need a consent banner. So these are hand-picked Google reviews,
   copied WORD FOR WORD — never trimmed or corrected — with the reviewer
   shortened to first name and initial. The aggregate (rating and count)
   is text in content.js, so Celia can keep it current in ?edit; the
   reviews themselves are not editable there. `checked` is when Johannes
   last compared it with Google.

   § 5b Abs. 3 UWG: a site that shows customer reviews has to say whether
   and how it checks they come from real customers — hence the one line
   under the block. It says they are not checked, which is true (Johannes,
   2026-10-13): they are copied by hand and not kept in sync.

   On the styling: no gold stars. The site has no accent colour and no
   icons, so stars would be the loudest thing on the page. The rating is a
   number in the display face instead, which matches the numeric language
   already running through the site (01 / 09, 01 02 03). */

const REVIEWS = {
  checked: "2026-09-12",
  profileUrl: CONTACT.maps,
  // Word for word, emoji and line breaks included. Mixed so neighbours
  // differ in length and subject; two are about Hanna.
  items: [
    { author: "Antonia H.", text: "So ein ästhetischer Ort - perfekte Atmosphäre für ein Tattoo. Und die Liebe Celia ist eine sehr achtsame, emphatische Person, die ein schönes Tattooerlebnis schafft. Danke!!!" },
    { author: "Sally P.", text: "Ich bin super zufrieden! Celia hat mein Tattoo komplett von Hand angezeichnet und geduldig alle meine Wünsche berücksichtigt. Man merkt einfach, wie viel Liebe und Geduld in ihrer Arbeit steckt. Die Atmosphäre im Studio war super angenehm, habe mich sehr wohl gefühlt. Kann das Studio von Herzen empfehlen! 🥰" },
    { author: "Lea Marie L.", text: "Einfach fantastisch! Ich war bei der lieben Hannah für mein allererstes Tattoo und hab mich super wohl gefühlt ☺️👏🏼 Das Studio ist wunderschön und gemütlich eingerichtet, man wird super aufgeklärt, immer gefragt ob alles in Ordnung ist und die Artistinnen sind mega freundlich. Ihre Arbeit war wirklich fantastisch- wunderschöne, präzise Fine Line Art ich bin sooo zufrieden 👏🏼👏🏼👏🏼❤️ Vielen Lieben Dank, würde immer wieder gerne dort hin kommen!" },
    { author: "Lisa D.", text: "Ich war heute zum ersten Mal da und es war ganz wunderbar. Schon vorab war die Kommunikation angenehm und unkompliziert. Meine sehr unkonkreten Wünsche wurden perfekt zu einem tollen Tattoo umgesetzt und sich viel Zeit für Platzierung und co. genommen. Ich habe mich wohl gefühlt und werde auf jeden Fall wieder kommen 😍" },
    { author: "Alina M.", text: "Ich kann das Studio und vor allem Celia von Herzen weiterempfehlen! Man fühlt sich vom ersten Moment an wohl und gut aufgehoben. Ich bin mit meinem Tattoo super zufrieden und komme ganz bestimmt wieder🤍" },
    { author: "Jette A.", text: "Ich war jetzt schon zum zweiten Mal bei Hanna und kann sie und das Ink Atelier wärmstens weiterempfehlen 💕 die Atmosphäre ist sehr angenehm und ich habe mich rundum wohlgefühlt ☺️ Hanna hat meine Vorstellungen genauso umgesetzt, wie ich es wollte und hat mich bei Größe und Platzierung immer sehr gut beraten!!! Eins meiner Tattoos von ihr ist mittlerweile schon ein paar Monate alt und ist sehr gut verheilt 👍👍 alles in allem merkt man, dass Hanna mit großem Engagement und Leidenschaft tättowiert und ein Profi für Fine line Tattoos ist 🫶🫶" },
    { author: "Anna G.", text: "Ich habe mir im Ink Atelier mein erstes Tattoo stechen lassen und fand es ganz wunderbar. Celia hat mich großartig beraten und mir alles erklärt. Ich habe mich sofort gut aufgehoben gefühlt. Das Resultat ist wunderschön und ich bin vollkommen zufrieden." },
    { author: "Conny P.", text: "Bin zwar Tattooerfahren,aber es war mein erster Besuch bei Celia in diesem wunderschönen Studio.\nHerzlicher Empfang und gleich die Hauspuschen wie von Omi wecken ein heimisches Gefühl.\nHabe ein wunderschönes Ergebnis mit nach Hause genommen und habe schon die nächsten Ideen im Kopf. Danke für Deine tolle Arbeit und Deine Herzlichkeit❤" },
    { author: "Sophia E.", text: "Ich kann das Ink Atelier nur wärmstens empfehlen! Ich habe mich sofort wohl, sicher und gut aufgehoben gefühlt. Ich wurde herzlich empfangen, meine Wünsche wurden berücksichtigt und die drei Stunden vergingen wie im Flug. Mit meinen Tattoos bin ich mehr als zufrieden und ich freue mich jetzt schon auf den nächsten Termin bei Celia! Dankeschön <3" },
    { author: "Helena J.", text: "ich kann das studio zu 100% weiterempfehlen, die atmosphäre ist total warm und einladend und Celia ist unglaublich talentiert!! sie hat meine ersten tattoos gestochen und ich würde immer wieder zu ihr gehen 💗" },
    { author: "Laina K.", text: "Dieses Tattoo Studio und die tollen Professionellen Arbeiten von Celia, lassen keine Wünsche offen." },
  ],
};

function Rezensionen() {
  const [i, setI] = React.useState(0);
  const step = (d) => setI((p) => (p + d + REVIEWS.items.length) % REVIEWS.items.length);
  return (
    <Section tone="quiet">
      <Reveal><Label>rezensionen</Label></Reveal>

      <div className="ia-split" style={{
        display: "grid", gridTemplateColumns: "0.5fr 1fr", gap: "clamp(2rem, 5vw, 5rem)",
        alignItems: "start", marginTop: "clamp(2.5rem, 5vw, 4rem)",
      }}>
        {/* The aggregate — the more persuasive half, and the half that does
            not need five reviews to be worth showing. */}
        <Reveal>
          <div style={{
            fontFamily: "var(--font-display)", fontWeight: "var(--w-regular)",
            fontSize: "clamp(3rem, 7vw, 4.5rem)", lineHeight: 1,
            letterSpacing: "var(--tracking-tight)", color: "var(--ink)",
          }}><Txt k="rezensionen.rating" /></div>
          <div style={{ marginTop: "1rem" }}>
            <Label><Txt k="rezensionen.count" /> rezensionen</Label>
          </div>
          <div style={{ marginTop: "0.6rem" }}>
            <a className="ia-link ia-link--quiet" href={REVIEWS.profileUrl}
               target="_blank" rel="noopener noreferrer"
               style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", letterSpacing: "var(--tracking-wider)", color: "var(--ink-mute)" }}>
              auf google ansehen ↗
            </a>
          </div>
        </Reveal>

        <Reveal delay={80}>
          {/* Every review sits in the same grid cell and only the current
              one is visible. The block is therefore as tall as the longest
              review, so the arrows below never jump — they stay under the
              cursor while someone clicks through. Long reviews are set one
              size smaller, or the longest would make the box so tall that
              every short one floats in empty space. pre-line keeps the line
              breaks a reviewer wrote. */}
          <div className="ia-reviews" style={{ display: "grid" }}>
            {REVIEWS.items.map((it, k) => (
              <figure key={k} aria-hidden={k !== i} style={{
                gridArea: "1 / 1", margin: 0, visibility: k === i ? "visible" : "hidden",
              }}>
                <blockquote style={{
                  fontFamily: "var(--font-display)", fontWeight: "var(--w-regular)",
                  fontSize: it.text.length > 220 ? "clamp(0.98rem, 1.5vw, 1.15rem)" : "clamp(1.05rem, 2vw, 1.45rem)",
                  lineHeight: "var(--leading-snug)",
                  color: "var(--ink)", margin: 0, whiteSpace: "pre-line",
                }}>{it.text}</blockquote>
                <figcaption style={{ marginTop: "1.6rem", display: "flex", gap: "0.8rem", flexWrap: "wrap" }}>
                  <Label tone="ink">{it.author}</Label>
                  <Label>· google</Label>
                </figcaption>
              </figure>
            ))}
          </div>
          <div style={{
            display: "flex", alignItems: "center", justifyContent: "space-between",
            // No rule above the counter: it read as cutting the reviews
            // off (Celia, 2026-10-13).
            marginTop: "clamp(2rem, 4vw, 3rem)",
          }}>
            <Label>{String(i + 1).padStart(2, "0")} / {String(REVIEWS.items.length).padStart(2, "0")}</Label>
            <span style={{ display: "flex", gap: "0.75rem" }}>
              <StripArrow onClick={() => step(-1)} label="zurück">←</StripArrow>
              <StripArrow onClick={() => step(1)} label="weiter">→</StripArrow>
            </span>
          </div>
        </Reveal>
      </div>

      <p className="ia-review-note" style={{
        fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", lineHeight: "var(--leading-normal)",
        letterSpacing: "0.04em", color: "var(--ink-mute)", maxWidth: "70ch",
        margin: "clamp(2.5rem, 5vw, 3.5rem) 0 0",
      }}>
        eine auswahl unserer google-rezensionen, wortgetreu übernommen. wir prüfen nicht, ob sie von personen stammen, die bei uns tätowiert wurden.
      </p>
    </Section>
  );
}

/* ---- Termin ----------------------------------------------------------
   The process, not a booking platform and not a second artist directory.
   It answers "what do I send, and then what happens", and ends with the
   direct channels. */

function Termin() {
  return (
    <div>
      <Section first tone="dark">
        <Head
          tone="onInk"
          label="termin"
          title={<Txt k="termin.title" />}
          lead={<Txt k="termin.lead" />}
        />

        <div className="ia-cards" style={{
          display: "grid", gridTemplateColumns: "1fr 1fr 1fr",
          gap: "clamp(1.5rem, 3vw, 3rem)", marginTop: "clamp(3rem, 6vw, 5rem)",
        }}>
          {[1, 2, 3].map((n, i) => (
            <Reveal key={n} delay={i * 90}>
              <div style={{ paddingTop: "1.4rem", borderTop: "1px solid var(--line-on-ink)" }}>
                <Label tone="onInk" style={{ marginBottom: "1.4rem" }}>{String(n).padStart(2, "0")}</Label>
                <h3 style={{
                  fontFamily: "var(--font-display)", fontWeight: "var(--w-regular)",
                  fontSize: "1.25rem", color: "var(--text-on-ink)", margin: "0 0 0.8rem",
                }}><Txt k={`termin.step${n}.title`} /></h3>
                <p style={{ fontSize: "var(--text-sm)", lineHeight: "var(--leading-normal)", color: "var(--text-on-ink-mute)", margin: 0, maxWidth: "34ch" }}><Txt k={`termin.step${n}.text`} multi /></p>
              </div>
            </Reveal>
          ))}
        </div>

        {/* The action. Plain lines, not cards — the artist profiles live on
            the Artists page and are not repeated here. */}
        <Reveal delay={260} style={{ marginTop: "clamp(3.5rem, 7vw, 6rem)" }}>
          <Label tone="onInk" style={{ marginBottom: "1.6rem" }}>direkt anfragen</Label>
          <div>
            {ARTISTS.map((a) => (
              <div key={a.name} className="ia-book-row" style={{
                display: "flex", alignItems: "center", justifyContent: "space-between",
                gap: "1.5rem", flexWrap: "wrap",
                padding: "1.2rem 0", borderTop: "1px solid var(--line-on-ink)",
              }}>
                <span className="ia-book-face" style={{ display: "flex", alignItems: "center", gap: "1.2rem", flexWrap: "wrap" }}>
                  {/* A face with every name. Naming someone and showing
                      nothing of them reads as a list of strangers — and the
                      portrait is small enough that this is still not a
                      second profile card. In colour here, black and white
                      only on the artists' own cards (Celia, 2026-10-06). */}
                  <span style={{ width: "54px", height: "68px", flex: "none", overflow: "hidden", display: "block" }}>
                    <PlacePhoto place={`artists.${a.name}.portrait`} className="ia-photo" alt={a.name} sizes="54px"
                                style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                  </span>
                  <span className="ia-book-who" style={{ display: "flex", flexDirection: "column", gap: "0.35rem" }}>
                    <Hand text={a.name} style={{ fontSize: "1.9rem", color: "var(--text-on-ink)" }} />
                    {handleOf(a) ? (
                      <a
                        className="ia-link ia-link--quiet"
                        href={instagramUrl(handleOf(a))}
                        target="_blank" rel="noopener noreferrer"
                        style={{
                          fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)",
                          letterSpacing: "var(--tracking-wider)", color: "var(--text-on-ink-mute)",
                        }}
                      >@<Txt k={`artists.${a.name}.instagram`} kind="handle" /></a>
                    ) : (
                      <Label tone="onInk"><Txt k={`artists.${a.name}.role`} /></Label>
                    )}
                  </span>
                </span>
                <ArrowLink tone="onInk" href={contactHref(a)}>
                  {enquiresByMail(a) ? "per mail anfragen" : "per dm anfragen"}
                </ArrowLink>
              </div>
            ))}
          </div>
          <div style={{ marginTop: "2rem" }}>
            <ArrowLink tone="onInk" onClick={() => go("artists")}>artists ansehen</ArrowLink>
          </div>
        </Reveal>
      </Section>

      {/* A landscape photo on the white part, centred above the questions:
          booking, a design, a consultation (Celia, 2026-10-08). */}
      <Section style={{ paddingBottom: 0 }}>
        <Reveal y={28} style={{ maxWidth: "var(--container-narrow)", margin: "0 auto" }}>
          <div className="ia-photo-zoom">
            <PlacePhoto place="termin.photo" style={{ aspectRatio: "3 / 2" }} sizes="(max-width: 760px) 92vw, 720px" />
          </div>
          {/* A line under it (Celia, 2026-10-10); empty hides it. */}
          {(T("termin.photo.caption").trim() || window.IA_EDIT_MODE) && (
            <Txt k="termin.photo.caption" as="p" placeholder="eine zeile unter dem foto — anklicken und schreiben" style={{
              margin: "0.9rem 0 0", textAlign: "center", fontSize: "var(--text-sm)", color: "var(--ink-soft)",
            }} />
          )}
        </Reveal>
      </Section>

      <FAQ />
    </div>
  );
}

/* ---- FAQ -------------------------------------------------------------- */

function FAQ() {
  // As many places as there are faq.N.question texts; one whose question
  // is left empty is not shown, so Celia can have fewer without anyone
  // touching the code. In ?edit the list always ends in one free place:
  // a question written there is a new one, and the next free place
  // appears (edit/apply.mjs adds them to content.js).
  const nums = [...new Set([...Object.keys(window.CONTENT.texts), ...Object.keys(window.IA_TEXT || {})]
    .map((k) => /^faq\.(\d+)\.question$/.exec(k)).filter(Boolean).map((m) => +m[1]))].sort((a, b) => a - b);
  const last = nums[nums.length - 1] || 0;
  if (window.IA_EDIT_MODE && T(`faq.${last}.question`).trim()) nums.push(last + 1);
  const qs = nums.filter((n) => T(`faq.${n}.question`).trim() || window.IA_EDIT_MODE);
  const [open, setOpen] = React.useState(0);
  return (
    <Section narrow>
      <Reveal><Label style={{ marginBottom: "clamp(2rem, 4vw, 3rem)" }}>faq</Label></Reveal>
      <div>
        {qs.map((n, i) => {
          const isOpen = open === i;
          return (
            <Reveal key={n} delay={i * 40}>
              <div style={{ borderTop: "1px solid var(--line)" }}>
                <button
                  onClick={() => setOpen(isOpen ? -1 : i)}
                  aria-expanded={isOpen}
                  style={{
                    width: "100%", display: "flex", justifyContent: "space-between", alignItems: "center", gap: "1.5rem",
                    background: "none", border: "none", cursor: "pointer", textAlign: "left", padding: "1.5rem 0",
                    fontFamily: "var(--font-display)", fontSize: "clamp(0.95rem, 1.8vw, 1.15rem)",
                    lineHeight: "var(--leading-snug)", color: "var(--ink)",
                  }}
                >
                  <Txt k={`faq.${n}.question`} placeholder="neue frage — anklicken und schreiben" />
                  <span aria-hidden="true" style={{
                    flex: "none", color: "var(--ink-mute)", fontSize: "1.1rem",
                    transition: "transform var(--dur-base) var(--ease-soft)",
                    transform: isOpen ? "rotate(45deg)" : "none",
                  }}>+</span>
                </button>
                <div style={{ maxHeight: isOpen ? "600px" : "0", overflow: "hidden", transition: "max-height var(--dur-slow) var(--ease-calm)" }}>
                  <p style={{ fontSize: "var(--text-sm)", lineHeight: "var(--leading-relaxed)", color: "var(--ink-soft)", margin: "0 0 1.6rem", maxWidth: "58ch" }}><Txt k={`faq.${n}.answer`} multi placeholder="antwort — anklicken und schreiben" /></p>
                </div>
              </div>
            </Reveal>
          );
        })}
      </div>
    </Section>
  );
}

/* ---- Impressum & Datenschutz ------------------------------------------
   Both made by Celia with the eRecht24 generator (2026-10-13), adjusted
   where the generator didn't fit what the site actually does — see the
   notes at each page. Every text is in content.js (impressum.*,
   datenschutz.*), so she checks and corrects them in ?edit. Until she
   signs off, a banner says "entwurf" (legal.entwurf); deleting that text
   is the sign-off and removes it. A section whose text is empty is not
   shown; a one-element entry is a numbered group heading.

   Reachable from the footer only; deliberately not in the main nav. */

function LegalPage({ label, title, sections }) {
  /* No Reveal anywhere on this page. Legal text is reference material —
     you land on it, scan it, maybe ctrl-F it. Fading it in on scroll makes
     it feel like marketing and gets in the way of reading. It just renders. */
  const grouped = sections.some((e) => e.length === 1);
  const H = grouped ? "h3" : "h2";
  const shown = sections.filter(([, body]) => typeof body !== "string" || T(body).trim() || window.IA_EDIT_MODE);
  return (
    <Section first narrow>
      <Label style={{ marginBottom: "1.6rem" }}>{label}</Label>
      <h1 style={{
        fontFamily: "var(--font-display)", fontWeight: "var(--w-regular)",
        fontSize: "clamp(1.65rem, 3.4vw, 2.6rem)", lineHeight: "var(--leading-tight)",
        letterSpacing: "var(--tracking-tight)", margin: 0, maxWidth: "26ch",
      }}>{title}</h1>

      {(T("legal.entwurf").trim() || window.IA_EDIT_MODE) && (
        <div style={{
          marginTop: "clamp(2rem, 4vw, 3rem)", padding: "1rem 1.2rem",
          border: "1px solid var(--line-strong)",
        }}>
          <Label tone="ink"><Txt k="legal.entwurf" placeholder="freigegeben — kein hinweis mehr" /></Label>
        </div>
      )}

      <div style={{ marginTop: "clamp(2.5rem, 5vw, 4rem)" }}>
        {shown.map(([heading, body]) => body === undefined ? (
          <h2 key={heading} style={{
            fontFamily: "var(--font-display)", fontWeight: "var(--w-regular)",
            fontSize: "clamp(1.3rem, 2.4vw, 1.6rem)", margin: "clamp(2.5rem, 5vw, 3.5rem) 0 0.4rem", color: "var(--ink)",
          }}>{heading}</h2>
        ) : (
          <div key={heading} style={{ paddingTop: "1.6rem", marginBottom: "1.6rem", borderTop: "1px solid var(--line)" }}>
            <H style={{
              fontFamily: "var(--font-display)", fontWeight: "var(--w-regular)",
              fontSize: "1.15rem", margin: "0 0 0.8rem", color: "var(--ink)",
            }}>{heading}</H>
            <p style={{ fontSize: "var(--text-sm)", lineHeight: "var(--leading-relaxed)", color: "var(--ink-soft)", margin: 0, maxWidth: "62ch" }}>
              {typeof body === "string" ? <Txt k={body} multi /> : body}
            </p>
          </div>
        ))}
      </div>
    </Section>
  );
}

/* A section is [heading, content key] — or [heading, element] for a line
   that follows the site's data. The Impressum is the one Celia made with
   the eRecht24 generator (2026-10-13), which asks for its source line to
   stay. Kept from her own version of 2026-10-12: the second email. The
   generator's "[Telefonnummer]" is left out (no phone on the site, her
   wish), "Tätowierer" is "Tätowiererin" (Celia), and the number is a
   USt-IdNr. — DE plus nine digits; a Wirtschafts-IdNr. would carry a
   "-00001"-style suffix. */
function Impressum() {
  return (
    <LegalPage
      label="impressum"
      title="Angaben gemäß § 5 DDG"
      sections={[
        ["Diensteanbieter", "impressum.anbieter"],
        ["Kontakt", "impressum.kontakt"],
        ["Umsatzsteuer-Identifikationsnummer gemäß § 27a UStG", "impressum.ustid"],
        ["Berufsbezeichnung und berufsrechtliche Regelungen", "impressum.beruf"],
        ["Angaben zur Berufshaftpflichtversicherung", "impressum.versicherung"],
        // The EU's online dispute platform closed on 2025-07-20 (Regulation
        // (EU) 2024/3228): no link to it any more.
        ["Verbraucherstreitbeilegung / Universalschlichtungsstelle", "impressum.streit"],
        // The photographer is named here for every photo on the site, and
        // under the Studio photo (Celia, 2026-10-11).
        ["Bildnachweis", "impressum.bildnachweis"],
        ["Quelle", "impressum.quelle"],
      ]}
    />
  );
}

/* eRecht24's text (Celia, 2026-10-13), with these changes:
   - Instagram and Google Maps are plain links here, nothing embedded; the
     generator only offers them as embedded plugins (with consent, which
     would need a banner), so one section on the links replaces both.
   - Hosting: the generator wants payment for Cloudflare, so that section
     is ours: Cloudflare hosts the site and forwards the domain's email
     (since 2026-10-13).
   - Ours too: the Google reviews shown with shortened names.
   - Left out: "Analyse-Tools" (there are none), the phone placeholder.
     Fixed: the postcode (14467). */
function Datenschutz() {
  return (
    <LegalPage
      label="datenschutz"
      title="Datenschutzerklärung"
      sections={[
        ["1. Datenschutz auf einen Blick"],
        ["Allgemeine Hinweise", "datenschutz.allgemein"],
        ["Wer ist verantwortlich für die Datenerfassung auf dieser Website?", "datenschutz.wer"],
        ["Wie erfassen wir Ihre Daten?", "datenschutz.wie"],
        ["Wofür nutzen wir Ihre Daten?", "datenschutz.wofuer"],
        ["Welche Rechte haben Sie bezüglich Ihrer Daten?", "datenschutz.rechte"],
        ["2. Allgemeine Hinweise und Pflichtinformationen"],
        ["Datenschutz", "datenschutz.datenschutz"],
        ["Hinweis zur verantwortlichen Stelle", "datenschutz.verantwortlicher"],
        ["Speicherdauer", "datenschutz.speicherdauer"],
        ["Allgemeine Hinweise zu den Rechtsgrundlagen der Datenverarbeitung auf dieser Website", "datenschutz.rechtsgrundlagen"],
        ["Empfänger von personenbezogenen Daten", "datenschutz.empfaenger"],
        ["Widerruf Ihrer Einwilligung zur Datenverarbeitung", "datenschutz.widerruf"],
        ["Widerspruchsrecht gegen die Datenerhebung in besonderen Fällen sowie gegen Direktwerbung (Art. 21 DSGVO)", "datenschutz.widerspruch"],
        ["Beschwerderecht bei der zuständigen Aufsichtsbehörde", "datenschutz.beschwerde"],
        ["Recht auf Datenübertragbarkeit", "datenschutz.uebertragbarkeit"],
        ["Auskunft, Berichtigung und Löschung", "datenschutz.auskunft"],
        ["Recht auf Einschränkung der Verarbeitung", "datenschutz.einschraenkung"],
        ["3. Datenerfassung auf dieser Website"],
        ["Hosting", "datenschutz.hosting"],
        ["Anfrage per E-Mail, Telefon oder Telefax", "datenschutz.anfrage"],
        ["Bewertungen", "datenschutz.bewertungen"],
        ["4. Links zu Instagram und Google Maps"],
        ["Instagram und Google Maps", "datenschutz.links"],
        ["Quelle", "datenschutz.quelle"],
      ]}
    />
  );
}

/* ---- App -------------------------------------------------------------- */

const PAGES = {
  start: Start,
  arbeiten: Arbeiten,
  artists: Artists,
  studio: Studio,
  "studio/small-businesses": SmallBusinesses,
  termin: Termin,
  impressum: Impressum,
  datenschutz: Datenschutz,
};

function App() {
  const route = useRoute();
  // ?edit: re-render when a photo is swapped (edit/editor.js).
  const [, setEditRev] = React.useState(0);
  React.useEffect(() => {
    const bump = () => setEditRev((n) => n + 1);
    window.addEventListener("ia-edits", bump);
    return () => window.removeEventListener("ia-edits", bump);
  }, []);
  const Page = PAGES[route] || Start;
  return (
    <div>
      <Nav route={route} />
      {/* Keyed so each route remounts: scroll reveals and the filmstrip
          both need a fresh start rather than reused state. */}
      <main key={route}>
        <Page route={route} />
      </main>
      <Footer />
    </div>
  );
}

Object.assign(window, { Studio, MAKERS, CARE, shownMakers, MakerRow, SmallBusinesses, Besuch, REVIEWS, Rezensionen, Termin, FAQ, LegalPage, Impressum, Datenschutz, PAGES, App });
