<?xml version="1.0" encoding="utf-8"?>
<!-- common.xsl · In the Gesture SSG · shared shell, rendering, helpers -->
<xsl:stylesheet version="3.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:ig="https://inthegesture.art/ns"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    exclude-result-prefixes="xs ig map"
    expand-text="yes">

  <xsl:output method="html" html-version="5" indent="yes"/>

  <!-- Paths, resolved relative to this stylesheet -->
  <xsl:param name="nav-doc" select="doc(resolve-uri('../xml/nav.xml', static-base-uri()))"/>
  <xsl:param name="works-doc" select="doc(resolve-uri('../xml/works.xml', static-base-uri()))"/>
  <xsl:param name="gestures-doc" select="doc(resolve-uri('../xml/gestures.xml', static-base-uri()))"/>

  <!-- Site identity for canonical URLs and JSON-LD -->
  <xsl:param name="site" select="'https://inthegesture.art/'"/>
  <xsl:param name="author-given" select="'Franklin'"/>
  <xsl:param name="author-family" select="'Einspruch'"/>
  <xsl:param name="author-url" select="'https://franklin.art/'"/>

  <!-- Open Graph: shared fallback image and site description.
       build.sh warns if assets/in-the-gesture.png is missing; keep the
       two paths in step if the image is ever renamed. -->
  <xsl:param name="default-og-image" select="$site || 'assets/in-the-gesture.png'"/>
  <xsl:param name="site-description"
      select="'A daily log of gestures, kept by Franklin Einspruch.'"/>

  <xsl:function name="ig:author-map" as="map(*)">
    <xsl:sequence select="map{'@type':'Person',
                              'givenName':$author-given,
                              'familyName':$author-family,
                              'url':$author-url}"/>
  </xsl:function>

  <!-- ===================== helpers ===================== -->

  <!-- A <time> value as xs:dateTime. Times are full xsd:dateTime with an
       offset (2026-09-02T15:57:00-04:00), enforced by xml/gestures.rng. -->
  <xsl:function name="ig:dt" as="xs:dateTime">
    <xsl:param name="time" as="xs:string"/>
    <xsl:sequence select="xs:dateTime($time)"/>
  </xsl:function>

  <!-- Filename slug: local wall time to the second, no offset.
       2026-09-02T15:57:00-04:00 → 2026-09-02T155700 -->
  <xsl:function name="ig:slug" as="xs:string">
    <xsl:param name="time" as="xs:string"/>
    <xsl:sequence select="format-dateTime(ig:dt($time),
        '[Y0001]-[M01]-[D01]T[H01][m01][s01]')"/>
  </xsl:function>

  <!-- Human-readable stamp in local wall time: September 2, 2026, 3:57 pm
       (am/pm computed here; Saxon's [Pn] gives "p.m.") -->
  <xsl:function name="ig:when" as="xs:string">
    <xsl:param name="time" as="xs:string"/>
    <xsl:variable name="dt" select="ig:dt($time)"/>
    <xsl:sequence select="format-dateTime($dt, '[MNn] [D], [Y], [h]:[m01] ')
        || (if (hours-from-dateTime($dt) lt 12) then 'am' else 'pm')"/>
  </xsl:function>

  <!-- Medium line: "pencil on paper", or just "ash" for a work with no
       support (sculpture). Every work has a medium; the support is optional,
       and " on " appears only when there is one. -->
  <xsl:function name="ig:medium-line" as="xs:string">
    <xsl:param name="work" as="element(work)"/>
    <xsl:sequence select="string-join((
      string-join($work/mediums/medium, ', '),
      string-join($work/supports/support[normalize-space()], ', ')[.]), ' on ')"/>
  </xsl:function>

  <!-- Dimension line: "5.75 × 9.75 inches", or height × width × depth
       ("36 × 12 × 10 inches") when the work has a <depth> -->
  <xsl:function name="ig:dim-line" as="xs:string?">
    <xsl:param name="work" as="element(work)"/>
    <xsl:if test="$work/dimensions">
      <xsl:sequence select="string-join(
        $work/dimensions/(height, width, depth)[normalize-space()], ' × ')
        || ' ' || $work/dimensions/units"/>
    </xsl:if>
  </xsl:function>

  <!-- A work's <date> is the date it was finished. While it is in
       progress, the date is left empty or left out, and this returns ().
       (xsl/assemble.xsl halts the build on a date that isn't YYYY-MM-DD.) -->
  <xsl:function name="ig:finished" as="xs:date?">
    <xsl:param name="work" as="element(work)"/>
    <xsl:sequence select="$work/date[normalize-space()]/xs:date(normalize-space())"/>
  </xsl:function>

  <!-- Date line for a tombstone: the finished date in $picture, else "in progress" -->
  <xsl:function name="ig:date-line" as="xs:string">
    <xsl:param name="work" as="element(work)"/>
    <xsl:param name="picture" as="xs:string"/>
    <xsl:sequence select="(ig:finished($work) ! format-date(., $picture), 'in progress')[1]"/>
  </xsl:function>

  <!-- Capture filename backing a page's og:image, if any.
       Gesture: its own image, else a non-video figure.
       Work: latest non-video figure toward it, else latest toward-gesture image. -->
  <xsl:function name="ig:og-capture" as="xs:string?">
    <xsl:param name="g" as="element(gesture)?"/>
    <xsl:param name="w" as="element(work)?"/>
    <xsl:choose>
      <xsl:when test="exists($g)">
        <xsl:sequence select="($g/image/file/string(),
                               $g/figure[not(file/type = 'video')]/file/name/string())[1]"/>
      </xsl:when>
      <xsl:when test="exists($w)">
        <xsl:variable name="figs" select="$gestures-doc/gestures/gesture
            /figure[work = $w/id][not(file/type = 'video')]/file/name"/>
        <xsl:variable name="twd" select="$gestures-doc/gestures/gesture
            [toward = $w/id]/image/file"/>
        <xsl:sequence select="($figs[last()]/string(), $twd[last()]/string())[1]"/>
      </xsl:when>
    </xsl:choose>
  </xsl:function>

  <!-- One-line og:description: gesture text truncated at a word break,
       else caption, else toward-line; for a work, its tombstone. -->
  <xsl:function name="ig:og-desc" as="xs:string?">
    <xsl:param name="g" as="element(gesture)?"/>
    <xsl:param name="w" as="element(work)?"/>
    <xsl:variable name="raw" as="xs:string?" select="
      if (exists($g)) then (
        normalize-space(string($g/text)),
        $g/(image | video)/caption/normalize-space(.),
        for $t in $g/(toward | figure/work)
          return 'Toward ' || $works-doc/works/work[id = $t]/title
      )[. ne ''][1]
      else if (exists($w)) then
        string-join((ig:medium-line($w), ig:dim-line($w),
                     ig:date-line($w, '[Y]')), ', ')
        => (function($s) { upper-case(substring($s, 1, 1)) || substring($s, 2) })()
      else ()"/>
    <xsl:sequence select="
      if (empty($raw)) then ()
      else if (string-length($raw) le 200) then $raw
      else replace(substring($raw, 1, 199), '\s\S*$', '') || '…'"/>
  </xsl:function>

  <!-- ===================== page shell ===================== -->
  <!-- $root: relative path from the page to the site root, e.g. '../' -->
  <xsl:template name="ig:page">
    <xsl:param name="title" as="xs:string"/>
    <xsl:param name="root" as="xs:string" select="'../'"/>
    <xsl:param name="body" as="item()*"/>
    <xsl:param name="jsonld" as="map(*)?" select="()"/>
    <!-- Open Graph subject: pass one of $gesture / $work,
         or $path (site-relative, e.g. 'html/log.html') for index pages -->
    <xsl:param name="gesture" as="element(gesture)?" select="()"/>
    <xsl:param name="work" as="element(work)?" select="()"/>
    <xsl:param name="path" as="xs:string?" select="()"/>

    <xsl:variable name="og-path" as="xs:string?" select="
      if (exists($gesture)) then 'g/' || ig:slug($gesture/time) || '.html'
      else if (exists($work)) then 'html/works/' || $work/id || '.html'
      else $path"/>
    <xsl:variable name="og-type" as="xs:string"
        select="if (exists($gesture)) then 'article' else 'website'"/>
    <xsl:variable name="og-capture" as="xs:string?"
        select="ig:og-capture($gesture, $work)"/>
    <xsl:variable name="og-desc" as="xs:string"
        select="(ig:og-desc($gesture, $work), $site-description)[1]"/>

    <html lang="en">
      <xsl:if test="exists($og-path)">
        <xsl:attribute name="prefix" select="'og: https://ogp.me/ns#'
            || (if ($og-type eq 'article')
                then ' article: https://ogp.me/ns/article#' else '')"/>
      </xsl:if>
      <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <title>{$title} · In the Gesture</title>
        <xsl:if test="exists($og-path)">
          <meta name="description" content="{$og-desc}"/>
          <meta property="og:site_name" content="In the Gesture"/>
          <meta property="og:locale" content="en_US"/>
          <meta property="og:title" content="{$title}"/>
          <meta property="og:type" content="{$og-type}"/>
          <meta property="og:url" content="{$site}{$og-path}"/>
          <meta property="og:description" content="{$og-desc}"/>
          <meta property="og:image" content="{if (exists($og-capture))
              then $site || 'captures/' || $og-capture
              else $default-og-image}"/>
          <meta property="og:image:alt" content="{if (exists($og-capture))
              then ($gesture/(image | video)/caption/normalize-space(.), $title)[1]
              else 'In the Gesture'}"/>
          <xsl:if test="exists($gesture)">
            <meta property="article:published_time"
                  content="{ig:dt($gesture/time)}"/>
          </xsl:if>
        </xsl:if>
        <link rel="icon" type="image/png" href="{$root}assets/favicon.png"/>
        <link rel="stylesheet" href="{$root}css/styles.css"/>
        <!-- Feed autodiscovery: lets readers find xml/rss.xml from any page -->
        <link rel="alternate" type="application/rss+xml"
              title="In the Gesture" href="{$root}xml/rss.xml"/>
        <xsl:if test="exists($jsonld)">
          <script type="application/ld+json">
            <xsl:sequence select="serialize($jsonld,
                map{'method':'json', 'indent':true()})"/>
          </script>
        </xsl:if>
      </head>
      <body>
        <xsl:copy-of select="$nav-doc/nav"/>
        <main>
          <xsl:sequence select="$body"/>
        </main>
      </body>
    </html>
  </xsl:template>

  <!-- ===================== gesture rendering ===================== -->
  <!-- mode "render" turns a <gesture>'s children into HTML.
       Tunnel $root carries the page's path back to site root. -->

  <xsl:mode name="render" on-no-match="shallow-copy"/>

  <xsl:template match="text" mode="render">
    <xsl:apply-templates mode="render"/>
  </xsl:template>

  <!-- Captions may carry inline markup (<i>, <em>, <br/>). Visible
       captions keep it; attributes (alt here, og:* and JSON-LD elsewhere)
       take the caption's plain string value. -->
  <xsl:template match="image" mode="render">
    <xsl:param name="root" as="xs:string" select="'../'" tunnel="yes"/>
    <figure>
      <img src="{$root}captures/{file}" alt="{normalize-space(caption)}"/>
      <figcaption><xsl:apply-templates select="caption/node()" mode="render"/></figcaption>
    </figure>
  </xsl:template>

  <xsl:template match="video" mode="render">
    <xsl:param name="root" as="xs:string" select="'../'" tunnel="yes"/>
    <figure>
      <video controls="controls" src="{$root}captures/{file}" preload="metadata"/>
      <figcaption><xsl:apply-templates select="caption/node()" mode="render"/></figcaption>
    </figure>
  </xsl:template>

  <!-- A figure toward a work -->
  <xsl:template match="figure" mode="render">
    <xsl:param name="root" as="xs:string" select="'../'" tunnel="yes"/>
    <xsl:variable name="w" select="$works-doc/works/work[id = current()/work]"/>
    <figure>
      <xsl:choose>
        <xsl:when test="file/type = 'video'">
          <video controls="controls" src="{$root}captures/{file/name}" preload="metadata"/>
        </xsl:when>
        <xsl:otherwise>
          <img src="{$root}captures/{file/name}" alt="{$w/title}"/>
        </xsl:otherwise>
      </xsl:choose>
      <figcaption>Toward <a href="{$root}html/works/{work}.html"><i>{$w/title}</i></a></figcaption>
    </figure>
  </xsl:template>

  <!-- The toward line on a gesture -->
  <xsl:template match="toward" mode="render">
    <xsl:param name="root" as="xs:string" select="'../'" tunnel="yes"/>
    <xsl:variable name="w" select="$works-doc/works/work[id = current()/string()]"/>
    <p class="toward">Toward <a href="{$root}html/works/{.}.html"><i>{$w/title}</i></a></p>
  </xsl:template>

  <!-- time and place are rendered by callers, not in the flow -->
  <xsl:template match="time | place" mode="render"/>

  <!-- ===================== log rendering ===================== -->
  <!-- mode "log": full text; images as thumbnails from thumbnails/
       (same filename as in captures/, made by build.sh), each linking
       to its gesture's page; videos as placeholders -->

  <xsl:mode name="log" on-no-match="shallow-copy"/>

  <xsl:template match="text" mode="log">
    <xsl:apply-templates mode="log"/>
  </xsl:template>

  <xsl:template match="image" mode="log">
    <xsl:param name="root" as="xs:string" select="'../'" tunnel="yes"/>
    <figure class="thumb">
      <a href="{$root}g/{ig:slug(ancestor::gesture/time)}.html">
        <img src="{$root}thumbnails/{file}" alt="{normalize-space(caption)}" loading="lazy"/>
      </a>
      <xsl:if test="caption">
        <figcaption><xsl:apply-templates select="caption/node()" mode="render"/></figcaption>
      </xsl:if>
    </figure>
  </xsl:template>

  <xsl:template match="video" mode="log">
    <p class="media-placeholder">[video<xsl:if test="caption">: <xsl:apply-templates
        select="caption/node()" mode="render"/></xsl:if>]</p>
  </xsl:template>

  <xsl:template match="figure" mode="log">
    <xsl:param name="root" as="xs:string" select="'../'" tunnel="yes"/>
    <xsl:variable name="w" select="$works-doc/works/work[id = current()/work]"/>
    <xsl:choose>
      <xsl:when test="file/type = 'video'">
        <p class="media-placeholder">[video{
          if ($w) then ': toward ' || $w/title else ''}]</p>
      </xsl:when>
      <xsl:otherwise>
        <figure class="thumb">
          <a href="{$root}g/{ig:slug(ancestor::gesture/time)}.html">
            <img src="{$root}thumbnails/{file/name}" alt="{$w/title}" loading="lazy"/>
          </a>
          <xsl:if test="$w">
            <figcaption>Toward <a href="{$root}html/works/{work}.html"><i>{$w/title}</i></a></figcaption>
          </xsl:if>
        </figure>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template match="toward" mode="log">
    <xsl:apply-templates select="." mode="render"/>
  </xsl:template>

  <xsl:template match="time | place" mode="log"/>

</xsl:stylesheet>
