/* Ink Atelier — website (part 1): router, primitives, nav, footer.

   ROUND 2 — rebuilt to Celia's feedback of 2026-08-20: cooler colours,
   rectangular photography, typewriter type, a front page that holds
   almost nothing, and real pages behind a nav rather than one long
   scroll. See DESIGN-DIRECTION.md for the verbatim feedback, what was
   assumed, and what is still open.

   Structural labels (nav, field labels, the studio name) are written
   here; every text Celia can change is in content.js — see <Txt>. */

/* ---- Routing --------------------------------------------------------
   Hash routing, not history routing, so the site deploys to any static
   host — GitHub Pages included — with no rewrite configuration. */

/* Four pages, each answering a different question: what (arbeiten),
   who (artists), where and who-we-are (studio), how (termin).
   The old separate "kontakt" page was folded into studio — address and
   opening hours are facts about the place, so they live with the place. */
const ROUTES = ["start", "arbeiten", "artists", "studio", "termin"];

/* Reachable and linkable, but deliberately not in the nav — legally
   required, not something anyone browses to. Footer only. */
const LEGAL_ROUTES = ["impressum", "datenschutz"];

/* Unterkategorien, per Celia 2026-08-23: "kann man Unterkategorien machen
   zum Studio zB Small Businesses?". Real routes, not anchors, so each one
   can be linked and shared. They appear in a sub-nav on the Studio pages,
   never in the main nav — the main nav stays four items.
   The first entry is the section's own overview page. */
const SUB_ROUTES = {
  studio: [
    { slug: "", label: "studio" },
    { slug: "small-businesses", label: "small businesses" },
  ],
};

const subRoutePaths = Object.entries(SUB_ROUTES).flatMap(([parent, subs]) =>
  subs.filter((x) => x.slug).map((x) => parent + "/" + x.slug));

const ALL_ROUTES = ROUTES.concat(LEGAL_ROUTES, subRoutePaths);

/* "studio/small-businesses" → "studio", for nav highlighting. */
const parentRoute = (route) => route.split("/")[0];

function currentRoute() {
  const raw = window.location.hash.replace(/^#\/?/, "").split("?")[0].trim();
  return ALL_ROUTES.includes(raw) ? raw : "start";
}

function go(route) {
  window.location.hash = route === "start" ? "#/" : "#/" + route;
}

function useRoute() {
  const [route, setRoute] = React.useState(currentRoute);
  React.useEffect(() => {
    const onHash = () => {
      setRoute(currentRoute());
      window.scrollTo({ top: 0, behavior: "auto" });
    };
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, []);
  return route;
}

/* Has the page been scrolled past `at` pixels? Drives the nav treatment. */
function useScrolled(at = 24) {
  const [scrolled, setScrolled] = React.useState(false);
  React.useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > at);
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, [at]);
  return scrolled;
}

/* ---- Primitives ------------------------------------------------------ */

/* The props a component passes on, minus the ones it uses itself. Not
   `({ a, ...rest })`: Babel turns that into a top-level `_excluded` list,
   and as parts, sections and app share one global scope, a later file's
   list replaced an earlier one's — Reveal lost its className (so the
   contact block never got its phone layout) and leaked `delay` into the
   DOM (found 2026-10-13). */
function omit(props, keys) {
  const out = Object.assign({}, props);
  keys.forEach((k) => { delete out[k]; });
  return out;
}

/* Reveal on scroll — a gentle rise and fade, slower than round 1. */
function Reveal(props) {
  const { children, delay = 0, y = 22, style } = props;
  const rest = omit(props, ["children", "delay", "y", "style"]);
  const ref = React.useRef(null);
  const [shown, setShown] = React.useState(false);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (typeof IntersectionObserver === "undefined") { setShown(true); return; }
    const io = new IntersectionObserver((es) => {
      es.forEach((e) => { if (e.isIntersecting) { setShown(true); io.disconnect(); } });
    }, { threshold: 0.12 });
    io.observe(el);
    // Safety net: never leave content invisible if the observer misfires.
    const t = setTimeout(() => setShown(true), 1400);
    return () => { io.disconnect(); clearTimeout(t); };
  }, []);
  return (
    <div ref={ref} style={{
      opacity: shown ? 1 : 0,
      transform: shown ? "none" : `translateY(${y}px)`,
      transition: `opacity var(--dur-slow) var(--ease-soft) ${delay}ms, transform var(--dur-slow) var(--ease-soft) ${delay}ms`,
      ...style,
    }} {...rest}>{children}</div>
  );
}

/* ---- Content ---------------------------------------------------------
   Every text Celia can change lives in content.js. T() reads one — or, in
   ?edit mode, her unsaved edit of it (window.IA_TEXT, filled by
   edit/editor.js). A missing key renders empty and is collected in
   window.IA_MISSING, which the tests check. */
function T(k) {
  const e = window.IA_TEXT;
  if (e && Object.prototype.hasOwnProperty.call(e, k)) return e[k];
  const v = window.CONTENT && window.CONTENT.texts[k];
  if (typeof v === "string") return v;
  (window.IA_MISSING = window.IA_MISSING || new Set()).add(k);
  return "";
}

/* One text from content.js. `paras` splits it into <p>s at blank lines;
   `multi` keeps its line breaks. The wrapper is rendered in every mode,
   so ?edit lays out exactly like the site.

   In ?edit mode the wrapper carries data-t for edit/editor.js, and it is
   keyed on the text: typing in the page rewrites the DOM inside it, so a
   finished edit has to remount it — React patching text nodes the
   browser has already replaced would leave stale text on screen. */
function Txt(props) {
  const { k, as: Tag = "span", paras, multi, kind, placeholder, pStyle, pGap = "0.9rem", style } = props;
  const rest = omit(props, ["k", "as", "paras", "multi", "kind", "placeholder", "pStyle", "pGap", "style"]);
  const v = T(k);
  const rev = (window.IA_TEXT_REV && window.IA_TEXT_REV[k]) || 0;
  // `kind` ("handle", "url") tells the editor how to tidy what is typed;
  // `placeholder` is what an empty text shows in edit mode.
  const edit = window.IA_EDIT_MODE ? {
    "data-t": k, "data-multi": paras || multi ? "" : undefined,
    "data-kind": kind, "data-placeholder": placeholder || "leer — anklicken zum schreiben",
  } : null;
  const ws = paras || multi ? { whiteSpace: "pre-line" } : null;
  return (
    <Tag key={rev + "|" + v} {...edit} {...rest} style={ws || style ? { ...ws, ...style } : undefined}>
      {paras
        ? v.split(/\n{2,}/).map((p, i) => (
            <p key={i} style={{ whiteSpace: "pre-line", ...pStyle, margin: i ? `${pGap} 0 0` : 0 }}>{p}</p>
          ))
        : v}
    </Tag>
  );
}

/* Handwriting. Reserved for artist names — the one thing Celia singled
   out on the inklabs reference. Not used for headlines any more; that
   was most of what made round 1 read "verspielt". */
function Hand({ text, style }) {
  return (
    <span style={{ fontFamily: "var(--font-hand)", lineHeight: 0.95, display: "inline-block", ...style }}>
      {text}
    </span>
  );
}

/* Tracked mono label. Written lowercase, no text-transform — see
   tokens/typography.css. */
function Label(props) {
  const { children, tone = "muted", style } = props;
  const rest = omit(props, ["children", "tone", "style"]);
  const colors = {
    muted: "var(--ink-mute)",
    ink: "var(--ink)",
    onInk: "var(--text-on-ink-mute)",
    onPhoto: "rgba(255,255,255,0.75)",
  };
  return (
    <span style={{
      display: "inline-block",
      fontFamily: "var(--font-mono)",
      fontSize: "var(--text-xs)",
      letterSpacing: "var(--tracking-wider)",
      color: colors[tone] || colors.muted,
      ...style,
    }} {...rest}>{children}</span>
  );
}

/* Rectangular hairline button. No pill, no fill by default, no lift —
   the references express action with a border and a label, nothing more. */
function Btn(props) {
  const { children, variant = "outline", onClick, href, style } = props;
  const rest = omit(props, ["children", "variant", "onClick", "href", "style"]);
  const variants = {
    outline: { background: "transparent", color: "var(--ink)", borderColor: "var(--line-strong)" },
    solid:   { background: "var(--ink)", color: "var(--paper)", borderColor: "var(--ink)" },
    onPhoto: { background: "transparent", color: "#fff", borderColor: "rgba(255,255,255,0.55)" },
    onInk:   { background: "transparent", color: "var(--text-on-ink)", borderColor: "var(--line-on-ink)" },
  };
  const [hover, setHover] = React.useState(false);
  const hovered = {
    outline: { background: "var(--ink)", color: "var(--paper)", borderColor: "var(--ink)" },
    solid:   { background: "var(--slate)", borderColor: "var(--slate)" },
    onPhoto: { background: "#fff", color: "var(--ink)", borderColor: "#fff" },
    onInk:   { background: "var(--text-on-ink)", color: "var(--ink)", borderColor: "var(--text-on-ink)" },
  };
  // A button that navigates somewhere is an anchor. Keeps middle-click,
  // "open in new tab" and screen-reader semantics intact.
  const Tag = href ? "a" : "button";
  const external = href && /^https?:/.test(href);
  return (
    <Tag
      href={href}
      target={external ? "_blank" : undefined}
      rel={external ? "noopener noreferrer" : undefined}
      onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        textDecoration: "none",
        display: "inline-flex", alignItems: "center", justifyContent: "center", gap: "0.9em",
        fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", fontWeight: "var(--w-regular)",
        letterSpacing: "var(--tracking-wide)", lineHeight: 1,
        padding: "1.05rem 2rem", border: "1px solid transparent", borderRadius: 0,
        cursor: "pointer", whiteSpace: "nowrap",
        transition: "background var(--dur-base) var(--ease-calm), color var(--dur-base) var(--ease-calm), border-color var(--dur-base) var(--ease-calm)",
        ...variants[variant],
        ...(hover ? hovered[variant] : null),
        ...style,
      }}
      {...rest}
    >{children}</Tag>
  );
}

/* A text link that reads as a link without a colour: label plus arrow,
   underline grows on hover. */
function ArrowLink({ children, onClick, href, tone, style }) {
  const shared = {
    fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)",
    letterSpacing: "var(--tracking-wide)",
    color: tone === "onInk" ? "var(--text-on-ink)" : tone === "onPhoto" ? "#fff" : "var(--ink)",
    ...style,
  };
  // Anything leaving the site is a real anchor, so it can be opened in a
  // new tab, copied, and read correctly by a screen reader.
  if (href) {
    const external = /^https?:/.test(href);
    return (
      <a
        className="ia-link ia-link--quiet"
        href={href}
        target={external ? "_blank" : undefined}
        rel={external ? "noopener noreferrer" : undefined}
        style={shared}
      >{children} <span aria-hidden="true">{external ? "↗" : "→"}</span></a>
    );
  }
  return (
    <span
      className="ia-link ia-link--quiet"
      onClick={onClick}
      role="link"
      tabIndex={0}
      onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick && onClick(); } }}
      style={shared}
    >{children} <span aria-hidden="true">→</span></span>
  );
}

/* The wordmark: lowercase, gently tracked mono over a hairline sub-line.

   NO MARK BESIDE THE NAME. Per Celia, 2026-08-23: "blobmark dings bei
   InkAtelier wegmachen sonst denkt man das es zum logo gehört" — sitting
   next to the words it read as part of the logo, which it is not. The blob
   survives as the favicon only; see DESIGN-DIRECTION.md for the open
   question that leaves behind. */
function Wordmark({ size = "1rem", tone = "ink", sub = true, onClick }) {
  const onPhoto = tone === "onPhoto";
  const color = onPhoto ? "#fff" : "var(--ink)";
  const subColor = onPhoto ? "rgba(255,255,255,0.7)" : "var(--ink-mute)";
  return (
    <span
      onClick={onClick}
      style={{
        display: "inline-block", cursor: onClick ? "pointer" : "default",
        textAlign: "center", whiteSpace: "nowrap",
      }}
    >
      <span style={{
        display: "block", fontFamily: "var(--font-display)", fontSize: size,
        letterSpacing: "var(--tracking-widest)", color, lineHeight: 1,
        // Tracking adds space after the last letter; pull it back so the
        // lockup optically centres.
        textIndent: "var(--tracking-widest)",
      }}>ink atelier</span>
      {sub && (
        <span style={{
          display: "block", marginTop: "0.6em", fontFamily: "var(--font-mono)",
          fontSize: "0.55em", letterSpacing: "0.24em",
          color: subColor, lineHeight: 1, textIndent: "0.24em",
        }}>potsdam</span>
      )}
    </span>
  );
}

/* The sub-nav for a section that has Unterkategorien. Sits directly under
   the page heading, quiet — it is a filter within a page, not navigation
   competing with the main nav. */
function SubNav({ parent, route }) {
  const subs = SUB_ROUTES[parent];
  if (!subs) return null;
  const current = route === parent ? "" : route.slice(parent.length + 1);
  return (
    <div style={{
      display: "flex", gap: "clamp(1.2rem, 2.5vw, 2.2rem)", flexWrap: "wrap",
      paddingBottom: "1.2rem", borderBottom: "1px solid var(--line)",
      marginBottom: "clamp(2.5rem, 5vw, 4rem)",
    }}>
      {subs.map((x) => {
        const active = current === x.slug;
        return (
          <span
            key={x.slug || "index"}
            onClick={() => go(x.slug ? parent + "/" + x.slug : parent)}
            style={{
              fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)",
              letterSpacing: "var(--tracking-wider)", cursor: "pointer",
              color: active ? "var(--ink)" : "var(--ink-mute)",
              borderBottom: `1px solid ${active ? "var(--ink)" : "transparent"}`,
              paddingBottom: "0.35rem",
              transition: "color var(--dur-base) var(--ease-calm), border-color var(--dur-base) var(--ease-calm)",
            }}
          >{x.label}</span>
        );
      })}
    </div>
  );
}

/* Section heading block: label, sentence-case mono headline, optional lead. */
function Head({ label, title, lead, tone, align = "left", max = "22ch", style }) {
  const onDark = tone === "onInk";
  return (
    <div style={{ textAlign: align, ...style }}>
      {label && <Reveal><Label tone={onDark ? "onInk" : "muted"} style={{ marginBottom: "1.6rem" }}>{label}</Label></Reveal>}
      <Reveal delay={60}>
        <h2 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: max,
          marginInline: align === "center" ? "auto" : undefined,
          color: onDark ? "var(--text-on-ink)" : "var(--ink)",
        }}>{title}</h2>
      </Reveal>
      {lead && (
        <Reveal delay={120}>
          <p style={{
            fontSize: "var(--text-md)", lineHeight: "var(--leading-normal)",
            color: onDark ? "var(--text-on-ink-mute)" : "var(--ink-soft)",
            maxWidth: "46ch", margin: "1.5rem 0 0",
            marginInline: align === "center" ? "auto" : undefined,
          }}>{lead}</p>
        </Reveal>
      )}
    </div>
  );
}

/* Standard page section. `first` adds clearance for the fixed nav. */
function Section({ children, tone = "paper", first = false, narrow = false, style }) {
  const tones = {
    paper: { background: "var(--surface-page)", color: "var(--ink)" },
    quiet: { background: "var(--surface-quiet)", color: "var(--ink)" },
    warm:  { background: "var(--surface-warm)", color: "var(--text-on-ink)" },
    dark:  { background: "var(--surface-dark)", color: "var(--text-on-ink)" },
  };
  return (
    <section style={{
      padding: `${first ? "calc(var(--space-9) + 3rem)" : "clamp(4.5rem, 10vw, 10rem)"} var(--gutter) clamp(4.5rem, 10vw, 10rem)`,
      ...tones[tone], ...style,
    }}>
      {/* Narrow sections keep the same left edge as wide ones rather than
          centring — one gutter for the whole site, as in her references. */}
      <div style={{
        maxWidth: narrow ? "var(--container-narrow)" : "var(--container)",
        margin: narrow ? "0 auto 0 max(0px, calc((100% - var(--container)) / 2))" : "0 auto",
      }}>
        {children}
      </div>
    </section>
  );
}

/* ---- Nav -------------------------------------------------------------
   Coral-style: centred wordmark with links either side. On the front
   page it overlays the full-bleed image in white until you scroll. */

function Nav({ route }) {
  const scrolled = useScrolled(24);
  const [open, setOpen] = React.useState(false);
  const overlay = route === "start" && !scrolled && !open;

  React.useEffect(() => { setOpen(false); }, [route]);
  React.useEffect(() => {
    document.body.style.overflow = open ? "hidden" : "";
    return () => { document.body.style.overflow = ""; };
  }, [open]);

  const left = ["arbeiten", "artists"];
  const right = ["studio", "termin"];

  const linkStyle = (a) => ({
    fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)",
    letterSpacing: "var(--tracking-wide)",
    color: overlay ? "#fff" : "var(--ink)",
    // Over a photo the links need more presence than they do on paper —
    // the hero is in colour now and its top edge can be very bright.
    opacity: parentRoute(route) === a ? 1 : overlay ? 0.85 : 0.62,
    cursor: "pointer", padding: "0.4rem 0",
    borderBottom: `1px solid ${parentRoute(route) === a ? "currentColor" : "transparent"}`,
    transition: "opacity var(--dur-base) var(--ease-calm), border-color var(--dur-base) var(--ease-calm), color var(--dur-base) var(--ease-calm)",
  });

  return (
    <React.Fragment>
    <header style={{
      position: "fixed", top: 0, left: 0, right: 0, zIndex: 50,
      display: "grid", gridTemplateColumns: "1fr auto 1fr", alignItems: "center",
      gap: "1rem", padding: "1.15rem var(--gutter)",
      background: overlay ? "transparent" : "var(--surface-nav)",
      backdropFilter: overlay ? "none" : "blur(14px)",
      WebkitBackdropFilter: overlay ? "none" : "blur(14px)",
      borderBottom: `1px solid ${overlay ? "transparent" : "var(--line)"}`,
      transition: "background var(--dur-base) var(--ease-calm), border-color var(--dur-base) var(--ease-calm)",
    }}>
      <nav className="ia-nav-links">
        {left.map((a) => <span key={a} style={linkStyle(a)} onClick={() => go(a)}>{a}</span>)}
      </nav>

      {/* Explicit column: on mobile the two link navs are display:none, and
          without this the wordmark would auto-place into column 1. */}
      <span style={{ gridColumn: 2, justifySelf: "center" }}>
        <Wordmark size="0.9375rem" sub={false} tone={overlay ? "onPhoto" : "ink"} onClick={() => go("start")} />
      </span>

      <nav className="ia-nav-links" style={{ justifyContent: "flex-end" }}>
        {right.map((a) => <span key={a} style={linkStyle(a)} onClick={() => go(a)}>{a}</span>)}
      </nav>

      {/* Mobile: the wordmark stays centred, the toggle takes the right cell
          and an empty left cell keeps it there. */}
      <span className="ia-nav-toggle" style={{
        gridColumn: 3, justifySelf: "end", alignItems: "center", cursor: "pointer",
        fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)",
        letterSpacing: "var(--tracking-wide)",
        color: overlay ? "#fff" : "var(--ink)",
      }} onClick={() => setOpen(!open)}>{open ? "schließen" : "menü"}</span>

    </header>

    {/* The menu panel is a SIBLING of <header>, not a child. The header
        carries backdrop-filter, and a non-none backdrop-filter makes an
        element the containing block for its fixed-position descendants —
        so nested here the panel was sized to the 60px header instead of
        the viewport, and the page showed straight through it. */}
    {open && (
      <div
        id="ia-menu"
        style={{
          position: "fixed", inset: 0, zIndex: 45,
          background: "var(--paper)",
          padding: "calc(60px + clamp(2rem, 8vh, 4rem)) var(--gutter) var(--gutter)",
          display: "flex", flexDirection: "column",
          overflowY: "auto",
        }}
      >
        <nav style={{ display: "flex", flexDirection: "column" }}>
          {ROUTES.filter((r) => r !== "start").map((a, i) => (
            <span key={a} onClick={() => go(a)} style={{
              display: "flex", alignItems: "baseline", gap: "1.1rem",
              fontFamily: "var(--font-display)",
              fontSize: "clamp(1.5rem, 7vw, 2.1rem)",
              letterSpacing: "0.02em",
              color: "var(--ink)", opacity: route === a ? 1 : 0.45,
              padding: "1.05rem 0", borderTop: "1px solid var(--line)", cursor: "pointer",
            }}>
              <span style={{
                fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)",
                letterSpacing: "var(--tracking-wide)", color: "var(--ink-mute)",
              }}>{String(i + 1).padStart(2, "0")}</span>
              {a}
            </span>
          ))}
        </nav>

        {/* Pinned to the bottom of the panel — the two things someone
            opening the menu on a phone actually wants. */}
        <div style={{ marginTop: "auto", paddingTop: "3rem" }}>
          <Label style={{ marginBottom: "1rem" }}>folgen</Label>
          <div style={{ display: "flex", gap: "1.5rem", flexWrap: "wrap" }}>
            <a className="ia-link" href={instagramUrl(STUDIO.instagram)} target="_blank" rel="noopener noreferrer"
               style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-sm)" }}>instagram ↗</a>
          </div>
          <div style={{ display: "flex", gap: "1.5rem", flexWrap: "wrap", marginTop: "1.6rem" }}>
            {LEGAL_ROUTES.map((r) => (
              <span key={r} className="ia-link ia-link--quiet" onClick={() => go(r)}
                    style={{ fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)", letterSpacing: "var(--tracking-wider)", color: "var(--ink-mute)", cursor: "pointer" }}>{r}</span>
            ))}
          </div>
        </div>
      </div>
    )}
    </React.Fragment>
  );
}

/* ---- Footer ---------------------------------------------------------- */

function Footer() {
  const link = {
    fontFamily: "var(--font-mono)", fontSize: "var(--text-sm)",
    color: "var(--ink-soft)", cursor: "pointer", display: "block", padding: "0.35rem 0",
  };
  return (
    <footer style={{ background: "var(--surface-quiet)", padding: "clamp(3.5rem, 7vw, 6rem) var(--gutter) 2.5rem" }}>
      <div className="ia-foot" style={{
        maxWidth: "var(--container)", margin: "0 auto", display: "grid",
        gridTemplateColumns: "2fr 1fr 1fr", gap: "2.5rem", alignItems: "start",
        paddingBottom: "3rem", borderBottom: "1px solid var(--line)",
      }}>
        <div>
          <Wordmark size="1.05rem" onClick={() => go("start")} />
          <p style={{ fontSize: "var(--text-sm)", color: "var(--ink-mute)", maxWidth: "30ch", marginTop: "1.6rem", lineHeight: "var(--leading-normal)" }}>
            <Txt k="footer.tagline" />
          </p>
        </div>
        {/* No column headings. "seiten"/"übersicht" labelled a list that is
            obviously a list of pages; none of the reference footers do it. */}
        <div>
          {["arbeiten", "artists", "studio", "termin"].map((a) => (
            <span key={a} style={link} onClick={() => go(a)}>{a}</span>
          ))}
        </div>
        <div>
          <a style={link} href={instagramUrl(STUDIO.instagram)} target="_blank" rel="noopener noreferrer">instagram ↗</a>
          <span style={link} onClick={() => go("studio")}>anfahrt</span>
        </div>
      </div>
      <div style={{
        maxWidth: "var(--container)", margin: "1.8rem auto 0", display: "flex",
        justifyContent: "space-between", flexWrap: "wrap", gap: "0.6rem",
        fontFamily: "var(--font-mono)", fontSize: "var(--text-xs)",
        letterSpacing: "0.06em", color: "var(--ink-mute)",
      }}>
        <span>© 2026 ink atelier potsdam</span>
        <span style={{ display: "flex", gap: "1.2rem" }}>
          <span className="ia-link ia-link--quiet" style={{ cursor: "pointer" }} onClick={() => go("impressum")}>impressum</span>
          <span className="ia-link ia-link--quiet" style={{ cursor: "pointer" }} onClick={() => go("datenschutz")}>datenschutz</span>
        </span>
      </div>
    </footer>
  );
}

Object.assign(window, {
  ROUTES, LEGAL_ROUTES, SUB_ROUTES, ALL_ROUTES, parentRoute, SubNav, currentRoute, go, useRoute, useScrolled,
  omit, T, Txt, Reveal, Hand, Label, Btn, ArrowLink, Wordmark, Head, Section, Nav, Footer,
});
