% beamer2html.sty -- beamer templates for decks that beamer2html also turns
% into an HTML slide deck (title slides, title bar, footer, layout, plots,
% animations, shapes, videos).
%
% Copyright (C) 2026 Arun Periyal
% This work may be distributed and/or modified under the conditions of the
% LaTeX Project Public License, either version 1.3c of this license or (at
% your option) any later version. The latest version of this license is in
% https://www.latex-project.org/lppl.txt
% This work has the LPPL maintenance status `maintained'.
% The Current Maintainer of this work is Arun Periyal.
%
% Template 1 : \SeminarTitleFrame    (thesis / extension seminar)
% Template 2 : \ConferenceTitleFrame (conference talk, two authors)
% Content    : ordinary  \begin{frame}{Title}{Subtitle} ... \end{frame}
%              get a blue title bar and a footer automatically.
%
% Fill the fields with the setter macros (see main.tex) before \begin{document}.

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{beamer2html}[2026/11/12 v1.0 beamer templates for beamer2html]

\RequirePackage{xcolor}
\RequirePackage{graphicx}
\RequirePackage{tikz}
\RequirePackage{pifont}
\RequirePackage{etoolbox}

% ---------------------------------------------------------------- colours
\definecolor{bannerblue}{RGB}{31,73,125}
\definecolor{bannertext}{RGB}{255,255,255}

% ---------------------------------------------------------------- fields
% Placeholders shown until the real values are set.
\newcommand{\@ttTitle}{[Presentation Title]}
\newcommand{\@ttSeminar}{[Seminar Name]}
\newcommand{\@ttDegree}{[Degree Name]}
\newcommand{\@ttPresenter}{[Presenter Name]}
\newcommand{\@ttRollNo}{[Roll Number]}
\newcommand{\@ttSupervisor}{[Supervisor Name]}
\newcommand{\@ttDepartment}{[Department Name]}
\newcommand{\@ttInstitute}{[Institute Name]}
\newcommand{\@ttLogo}{figures/logo}
\newcommand{\@ttAuthorOne}{[Author 1]}
\newcommand{\@ttAuthorTwo}{[Author 2]}
\newcommand{\@ttConference}{[Conference Name]}
\newcommand{\@ttShortTitle}{[Short Title]}
\newcommand{\@ttFootAuthor}{[Presenter Name]}

\newcommand{\SetTitle}[1]{\renewcommand{\@ttTitle}{#1}}
\newcommand{\SetSeminar}[1]{\renewcommand{\@ttSeminar}{#1}}
\newcommand{\SetDegree}[1]{\renewcommand{\@ttDegree}{#1}}
\newcommand{\SetPresenter}[1]{\renewcommand{\@ttPresenter}{#1}}
\newcommand{\SetRollNo}[1]{\renewcommand{\@ttRollNo}{#1}}
\newcommand{\SetSupervisor}[1]{\renewcommand{\@ttSupervisor}{#1}}
\newcommand{\SetDepartment}[1]{\renewcommand{\@ttDepartment}{#1}}
\newcommand{\SetInstitute}[1]{\renewcommand{\@ttInstitute}{#1}}
\newcommand{\SetLogo}[1]{\renewcommand{\@ttLogo}{#1}}
\newcommand{\SetAuthorOne}[1]{\renewcommand{\@ttAuthorOne}{#1}}
\newcommand{\SetAuthorTwo}[1]{\renewcommand{\@ttAuthorTwo}{#1}}
\newcommand{\SetConference}[1]{\renewcommand{\@ttConference}{#1}}
\newcommand{\SetShortTitle}[1]{\renewcommand{\@ttShortTitle}{#1}}
\newcommand{\SetFootAuthor}[1]{\renewcommand{\@ttFootAuthor}{#1}}

% ---------------------------------------------------------------- pieces
% Full-width blue banner carrying the title, pinned to the top edge of the
% page; the same vertical space is reserved in the frame body below it.
\newsavebox{\@ttBannerBox}
\newlength{\@ttBannerHt}
\newcommand{\@ttBanner}[1]{%
  \sbox{\@ttBannerBox}{%
    \tikz\node[fill=bannerblue, text=bannertext, minimum width=\paperwidth,
               text width=0.92\paperwidth, align=center, outer sep=0pt,
               inner ysep=#1, font=\LARGE\bfseries] {\@ttTitle};}%
  \setlength{\@ttBannerHt}{\ht\@ttBannerBox}%
  \addtolength{\@ttBannerHt}{\dp\@ttBannerBox}%
  \begin{tikzpicture}[remember picture, overlay]
    \node[anchor=north, inner sep=0pt, outer sep=0pt]
      at (current page.north) {\usebox{\@ttBannerBox}};
  \end{tikzpicture}%
  \par\vspace*{\dimexpr\@ttBannerHt-\baselineskip\relax}%
}

% Logo, or a dashed placeholder box if the file does not exist yet.
\newcommand{\@ttLogoBox}[1]{%
  \IfFileExists{\@ttLogo.pdf}{\includegraphics[height=#1]{\@ttLogo}}{%
  \IfFileExists{\@ttLogo.png}{\includegraphics[height=#1]{\@ttLogo}}{%
  \IfFileExists{\@ttLogo.jpg}{\includegraphics[height=#1]{\@ttLogo}}{%
    \tikz\node[draw=gray, dashed, minimum width=#1, minimum height=#1,
               font=\scriptsize\color{gray}]{Logo};}}}%
}

% ---------------------------------------------------------------- Template 1
\newcommand{\SeminarTitleFrame}{%
  {\setbeamertemplate{headline}{}%
   \setbeamertemplate{footline}{}%
   \begin{frame}[plain,t]
     \@ttBanner{8pt}
     \vfill
     \centering
     \small
     \@ttSeminar\\
     presented for the degree of\\
     \textbf{\@ttDegree}\\[0.4em]
     Presented by,\\
     \textbf{\@ttPresenter}\\
     \textbf{\@ttRollNo}\\
     Under the guidance of\\
     \textbf{\@ttSupervisor}\\[0.4em]
     \@ttLogoBox{1.3cm}\\[0.2em]
     \@ttDepartment\\
     \@ttInstitute
     \vspace*{0.4em}
   \end{frame}}%
}

% ---------------------------------------------------------------- Template 2
\newcommand{\ConferenceTitleFrame}{%
  {\setbeamertemplate{headline}{}%
   \setbeamertemplate{footline}{}%
   \begin{frame}[plain,t]
     \@ttBanner{18pt}
     \vfill
     \centering
     \begin{minipage}[t]{0.45\textwidth}
       \centering\large\textbf{\@ttAuthorOne}
     \end{minipage}%
     \hfill
     \begin{minipage}[t]{0.45\textwidth}
       \centering\large\textbf{\@ttAuthorTwo}
     \end{minipage}
     \vfill
     {\color{bannerblue}\rule{0.6\textwidth}{0.6pt}}\\[0.8em]
     {\large\itshape \@ttConference}
     \vfill\vfill
   \end{frame}}%
}

% ---------------------------------------------------------------- Content
% Title area at the top of every ordinary frame.
%   \TitleBarOn / \TitleBarOff   blue bar with white title  /  plain
%                                underlined black title (no bar)
%   \SetTitleHeight{<length>}    height of the title area (and the bar);
%                                grows by itself if a title needs more room
%   \SectionNameOn / \SectionNameOff
%                                current section name at the top right
% All of these may also be used mid-document; they affect later frames.
\newif\if@ttbar       \@ttbartrue
\newif\if@ttsecname   \@ttsecnametrue
\newlength{\@ttTitleHt}  \setlength{\@ttTitleHt}{1.1cm}
\newcommand{\TitleBarOn}{\@ttbartrue}
\newcommand{\TitleBarOff}{\@ttbarfalse}
\newcommand{\SectionNameOn}{\@ttsecnametrue}
\newcommand{\SectionNameOff}{\@ttsecnamefalse}
\newcommand{\SetTitleHeight}[1]{\setlength{\@ttTitleHt}{#1}}
% side margin of the title bar and the footer (the slide body's margins are
% beamer's: \setbeamersize{text margin left=..., text margin right=...})
\newlength{\@ttBarMargin}  \setlength{\@ttBarMargin}{0.4cm}
\newcommand{\SetBarMargin}[1]{\setlength{\@ttBarMargin}{#1}}

% \SetTextMargins{left}{right}: side margins of the slide body, also between
% frames (beamer's \setbeamersize works only in the preamble, through
% geometry; between frames the page offset and text width are set directly)
\newcommand{\SetTextMargins}[2]{%
  \ifx\document\@notprerr                     % after \begin{document}
    \global\beamer@leftmargin=#1\relax
    \global\beamer@rightmargin=#2\relax
    \xdef\Gm@lmargin{\the\dimexpr#1\relax}%   % beamer places head/foot with these
    \xdef\Gm@rmargin{\the\dimexpr#2\relax}%   % (geometry keeps them as macros)
    \global\oddsidemargin=\dimexpr#1-1in\relax
    \global\evensidemargin=\oddsidemargin
    \global\textwidth=\dimexpr\paperwidth-#1-#2\relax
    \global\linewidth=\textwidth
    \global\columnwidth=\textwidth
    \global\hsize=\textwidth
  \else
    \setbeamersize{text margin left=#1, text margin right=#2}%
  \fi}

\newsavebox{\@ttTmpBox}
\newlength{\@ttRuleWd}
\newsavebox{\@ttSecBox}
\newsavebox{\@ttTitleBox}
\newlength{\@ttAreaHt}
\newlength{\@ttTitleWd}
\setbeamertemplate{frametitle}{%
  % section name (only after the first \section)
  \sbox{\@ttSecBox}{}%
  \if@ttsecname\ifnum\c@section>0
    \sbox{\@ttSecBox}{\usebeamerfont{section name}%
      \if@ttbar\color{bannertext!85!bannerblue}\else\color{bannerblue}\fi
      \insertsectionhead}%
    % long section names wrap, right-aligned, within 30% of the slide width
    \ifdim\wd\@ttSecBox>0.3\paperwidth
      \sbox{\@ttSecBox}{\parbox{0.3\paperwidth}{\raggedleft
        \usebeamerfont{section name}%
        \if@ttbar\color{bannertext!85!bannerblue}\else\color{bannerblue}\fi
        \insertsectionhead\par}}%
    \fi
  \fi\fi
  % the title gets whatever width the section name leaves free
  \setlength{\@ttTitleWd}{\dimexpr\paperwidth-2\@ttBarMargin\relax}%
  \ifdim\wd\@ttSecBox>0pt
    \addtolength{\@ttTitleWd}{-\wd\@ttSecBox}%
    \addtolength{\@ttTitleWd}{-0.6cm}%
  \fi
  % title (+ subtitle) block
  \sbox{\@ttTitleBox}{\parbox{\@ttTitleWd}{\raggedright
    \usebeamerfont{frametitle}%
    % "(cont.)" of split frames in the title's colour (beamer would use blue)
    \if@ttbar\setbeamercolor{frametitle continuation}{fg=bannertext}%
    \else\setbeamercolor{frametitle continuation}{fg=black}\fi
    \if@ttbar\color{bannertext}\insertframetitle
    \else
      % underline: a rule as wide as the title (or the full title width
      % when the title wraps onto several lines)
      \sbox{\@ttTmpBox}{\insertframetitle}%
      \setlength{\@ttRuleWd}{\wd\@ttTmpBox}%
      \ifdim\@ttRuleWd>\@ttTitleWd \setlength{\@ttRuleWd}{\@ttTitleWd}\fi
      \color{black}\insertframetitle\par\nointerlineskip
      \vspace{1.5pt}\rule{\@ttRuleWd}{0.8pt}%
    \fi\par
    \ifx\insertframesubtitle\@empty\else
      {\usebeamerfont{framesubtitle}%
       \if@ttbar\color{bannertext!80!bannerblue}\else\color{darkgray}\fi
       \insertframesubtitle\par}%
    \fi}}%
  % area height = requested height, grown if the title needs more room
  \setlength{\@ttAreaHt}{\@ttTitleHt}%
  \ifdim\dimexpr\ht\@ttTitleBox+\dp\@ttTitleBox+0.3cm\relax>\@ttAreaHt
    \setlength{\@ttAreaHt}{\dimexpr\ht\@ttTitleBox+\dp\@ttTitleBox+0.3cm\relax}%
  \fi
  \nointerlineskip
  \begin{beamercolorbox}[wd=\paperwidth,sep=0pt]{}%
  \begin{tikzpicture}[inner sep=0pt, outer sep=0pt]
    \useasboundingbox (0,0) rectangle (\paperwidth,-\@ttAreaHt);
    \if@ttbar
      \fill[bannerblue] (0,0) rectangle (\paperwidth,-\@ttAreaHt);
    \fi
    \node[anchor=west] at (\@ttBarMargin,-0.5\@ttAreaHt) {\usebox{\@ttTitleBox}};
    \node[anchor=east] at (\paperwidth-\@ttBarMargin,-0.5\@ttAreaHt)
      {\usebox{\@ttSecBox}};
  \end{tikzpicture}%
  \end{beamercolorbox}%
  \par\vspace{0.3em}%
}
\setbeamerfont{frametitle}{size=\Large, series=\bfseries}
\setbeamerfont{framesubtitle}{size=\small, series=\mdseries, shape=\itshape}
\setbeamerfont{section name}{size=\small, shape=\itshape}

% Footer: presenter | short title | slide number, under a thin blue rule.
%   \FooterOn          full footer (default)
%   \FooterNumberOnly  no rule or text, just the slide number at bottom right
%   \FooterOff         no footer at all
% Like the title options, these may be used mid-document.
\newcommand{\@ttFootMode}{full}
\newcommand{\FooterOn}{\renewcommand{\@ttFootMode}{full}}
\newcommand{\FooterNumberOnly}{\renewcommand{\@ttFootMode}{number}}
\newcommand{\FooterOff}{\renewcommand{\@ttFootMode}{off}}
\newcommand{\@ttFootFull}{full}
\newcommand{\@ttFootNumber}{number}

\setbeamertemplate{footline}{%
  \ifx\@ttFootMode\@ttFootFull
    {\color{bannerblue}\rule{\paperwidth}{0.5pt}}\par\nointerlineskip
    \vspace{0.4ex}%
    \begin{beamercolorbox}[wd=\paperwidth,ht=2.2ex,dp=1ex,
                           leftskip=\@ttBarMargin,rightskip=\@ttBarMargin]{footline}%
      \usebeamerfont{footline}%
      \@ttFootAuthor\hfill\@ttShortTitle\hfill
      \insertframenumber\,/\,\inserttotalframenumber
    \end{beamercolorbox}%
  \else\ifx\@ttFootMode\@ttFootNumber
    \begin{beamercolorbox}[wd=\paperwidth,ht=2.2ex,dp=1.4ex,
                           leftskip=\@ttBarMargin,rightskip=\@ttBarMargin]{footline}%
      \usebeamerfont{footline}\hfill\insertframenumber
    \end{beamercolorbox}%
  \fi\fi
}
\setbeamercolor{footline}{fg=bannerblue}
\setbeamerfont{footline}{size=\scriptsize}

% Body styling to match the banner colour.
\setbeamercolor{structure}{fg=bannerblue}
\setbeamertemplate{itemize items}[circle]
\setbeamertemplate{enumerate items}[default]
\setbeamercolor{block title}{fg=bannertext, bg=bannerblue}
\setbeamercolor{block body}{bg=bannerblue!6}
\setbeamertemplate{blocks}[rounded][shadow=false]

% Overview (table of contents): bold entries with a shadowed-square bullet,
% evenly spaced (beamer puts \vfill between entries; \strut keeps every
% entry the same height), in black like the reference slide.
\setbeamertemplate{section in toc}{%
  \leavevmode\hspace*{1.2em}%
  {\color{bannerblue}\ding{113}}\hspace{0.6em}%
  \textbf{\inserttocsection}\strut
}
\setbeamertemplate{subsection in toc}{}
\setbeamerfont{section in toc}{size=\large}
\setbeamercolor{section in toc}{fg=black}

%   \OverviewFrame[<title>]   -- default title "Overview"
\newcommand{\OverviewFrame}[1][Overview]{%
  \begin{frame}[t]{#1}
    \vspace*{0.8em}%
    \tableofcontents
  \end{frame}%
}

%   \ThankYouFrame[<text>]    -- closing slide, default "Thank You"
\newcommand{\ThankYouFrame}[1][Thank You]{%
  {\setbeamertemplate{footline}{}%
   \begin{frame}[plain]
     \centering
     {\Huge\bfseries\color{bannerblue}#1}\\[1em]
     {\large Questions?}
   \end{frame}}%
}

% References.
%   \ReferencesFrame[<title>]  -- default title "References"
% Needs biblatex loaded in the preamble (see main.tex). Long lists continue
% on extra slides automatically, titled "<title> (cont.)". Only cited entries
% are listed; add \nocite{*} to list everything in the .bib file.
\setbeamertemplate{bibliography item}{\insertbiblabel}
\setbeamercolor{bibliography item}{fg=bannerblue}
\setbeamercolor{bibliography entry author}{fg=black}
\setbeamercolor{bibliography entry title}{fg=black}
\setbeamercolor{bibliography entry location}{fg=darkgray}
\setbeamercolor{bibliography entry note}{fg=darkgray}
% "(cont.)" from the second part of a split frame on; the first keeps its title
\setbeamertemplate{frametitle continuation}{%
  \ifnum\insertcontinuationcount>1 (cont.)\fi}
\newcommand{\ReferencesFrame}[1][References]{%
  {\@ttsecnamefalse   % not part of any section
   \begin{frame}[t,allowframebreaks]{#1}
     \footnotesize
     \printbibliography[heading=none]
   \end{frame}}%
}

% Dashed box standing in for a figure that has not been added yet:
%   \FigurePlaceholder[<width>]{<height>}{<label>}
\newcommand{\FigurePlaceholder}[3][\linewidth]{%
  \tikz\node[draw=gray, dashed, minimum width=#1, minimum height=#2,
             outer sep=0pt, font=\small\color{gray}]{#3};%
}

% ---------------------------------------------------------------- Rows
%   \begin{rows}
%     \begin{row}{0.4}  ... \end{row}    % 40% of the slide body height
%     \begin{row}[t]{0.6} ... \end{row}  % content at the top (c: centre, b: bottom)
%   \end{rows}
% The body is the space between the title area and the footer. A row can
% hold columns (grid layout) and take an overlay spec: \begin{row}<2->{0.6}.
% beamer typesets the body before the title, so the title's height is taken
% from \SetTitleHeight (a title that grows past it makes the rows overflow).
\newdimen\@ttBodyHt
\newcommand{\@ttRowsFudge}{0.6em}       % beamer's own space around the body

\newenvironment{rows}{%
  \par
  \global\@ttBodyHt=\dimexpr\textheight-\@ttRowsFudge\relax
  \ifx\insertframetitle\@empty\else
    \global\advance\@ttBodyHt by -\dimexpr\@ttTitleHt+0.3em\relax
  \fi
  \nointerlineskip
}{\par}

\newenvironment<>{row}[2][c]{%
  \begin{actionenv}#3%
  \par\nointerlineskip
  \begin{lrbox}{\@ttCellBox}%
  \begin{minipage}[t][#2\@ttBodyHt][#1]{\textwidth}%
}{%
  \end{minipage}\end{lrbox}%
  \leavevmode\@ttPutCell{row}%
  \par\end{actionenv}%
}

% ---------------------------------------------------------------- Dividers
%   \showdividers / \hidedividers   dashed outlines (with their size) around
%                                   every row and column, as an editing aid.
% Like the other switches they work in the preamble or between frames. The
% outlines are drawn over the content, so the layout is identical either way.
\definecolor{dividercolor}{RGB}{211,54,130}
\newif\if@ttdividers
\newcommand{\showdividers}{\@ttdividerstrue}
\newcommand{\hidedividers}{\@ttdividersfalse}
\newsavebox{\@ttCellBox}

% Output \@ttCellBox; with dividers on, outline it and label it with its
% share of the text width (col) or of the body height (row).
\newcommand{\@ttPutCell}[1]{%
  \if@ttdividers
    \rlap{\begin{tikzpicture}[overlay, inner sep=1pt]
      \draw[dividercolor, densely dashed, line width=0.6pt]
        (0,-\dp\@ttCellBox) rectangle (\wd\@ttCellBox,\ht\@ttCellBox);
      \ifstrequal{#1}{row}%                       % row labels top right,
        {\def\@ttAnchor{north east}\def\@ttX{\wd\@ttCellBox}}%   % column labels
        {\def\@ttAnchor{north west}\def\@ttX{0pt}}%              % top left
      \node[anchor=\@ttAnchor, font=\tiny\sffamily, text=dividercolor,
            fill=white, fill opacity=0.8, text opacity=1]
        at (\@ttX,\ht\@ttCellBox) {#1\ \@ttCellPercent{#1}\%};
    \end{tikzpicture}}%
  \fi
  \usebox{\@ttCellBox}%
}
\newcommand{\@ttCellPercent}[1]{%
  \ifstrequal{#1}{row}%
    {\the\numexpr 100*(\ht\@ttCellBox+\dp\@ttCellBox)/(\@ttBodyHt/100)/100\relax}%
    {\the\numexpr 100*\wd\@ttCellBox/(\textwidth/100)/100\relax}%
}

% beamer's column environment (beamerbaseframecomponents.sty), with its
% minipage collected in a box so that \@ttPutCell can outline it
\renewenvironment<>{beamer@columnenv}[2][\beamer@colmode]{%
  \beamer@colclose\def\beamer@colclose{}%
  \begin{actionenv}#3%
    \setkeys{beamer@col}{#1}%
    \begin{lrbox}{\@ttCellBox}%
    \begin{minipage}[\beamer@colalign]{#2}%
      \leavevmode\raggedright\beamer@colheadskip\ignorespaces}
  {\end{minipage}\end{lrbox}\@ttPutCell{col}\hfill\end{actionenv}\@ignoretrue}
\let\endcolumn\endbeamer@columnenv

% ---------------------------------------------------------------- Research progress
%   \begin{progress}                    % [nolegend] hides the legend
%     \task[done]{Literature review}{Jan 2022 -- Jun 2022}
%     \task[ongoing]{Flexible cylinder with grooves}{2023 -- now}
%     \task<2->[planned]{Thesis writing}{2025}   % <spec>: reveal on a later step
%   \end{progress}
% A vertical timeline: dates | status marker on a line | task. Status:
% done (default), ongoing, planned. Needs two runs (latexmk does this) to draw
% the connecting line.
\definecolor{statusdone}{RGB}{46,125,50}
\definecolor{statusongoing}{RGB}{230,145,0}
\definecolor{statusplanned}{RGB}{150,150,150}
\newcounter{@ttprog}                     % progress environments so far
\newcounter{@tttask}                     % tasks in the current one
\newif\if@ttlegend
\newlength{\@ttWhenWd}

\newcommand{\@ttMarker@done}{%
  \fill[statusdone] (0,0) circle (0.55em);
  \node[text=white, font=\tiny] at (0,0) {\ding{51}};}
\newcommand{\@ttMarker@ongoing}{%
  \fill[white] (0,0) circle (0.55em);
  \fill[statusongoing] (0,0) -- (90:0.55em) arc (90:270:0.55em) -- cycle;
  \draw[statusongoing, line width=0.9pt] (0,0) circle (0.5em);}
\newcommand{\@ttMarker@planned}{%
  \fill[white] (0,0) circle (0.55em);
  \draw[statusplanned, line width=0.9pt] (0,0) circle (0.5em);}
\newcommand{\@ttLegendMarker}[1]{%
  \tikz[baseline=-0.6ex, scale=0.8]{\csname @ttMarker@#1\endcsname}}

\newenvironment{progress}[1][]{%
  \stepcounter{@ttprog}\setcounter{@tttask}{0}%
  \ifstrequal{#1}{nolegend}{\@ttlegendfalse}{\@ttlegendtrue}%
  \setlength{\@ttWhenWd}{0.22\linewidth}%
  \par\vspace{0.3em}%
}{%
  \if@ttlegend
    \par\vspace{0.8em}\hspace*{\@ttWhenWd}\hspace{0.5em}%
    {\footnotesize
     \@ttLegendMarker{done}~Done\quad
     \@ttLegendMarker{ongoing}~Ongoing\quad
     \@ttLegendMarker{planned}~Planned}%
  \fi
  \par
}

% \task[status]{title}{when}; with beamer's <> the overlay spec is #4
\newcommand<>{\task}[3][done]{%
  \stepcounter{@tttask}%
  \par\noindent
  \uncover#4{%
    \makebox[\@ttWhenWd][r]{\small\color{darkgray}#3}\hspace{0.5em}%
    \begin{tikzpicture}[remember picture, baseline=-0.6ex]
      \coordinate (tt-p\the\value{@ttprog}-t\the\value{@tttask}) at (0,0);
      \ifcsname @ttMarker@#1\endcsname\csname @ttMarker@#1\endcsname
      \else\csname @ttMarker@planned\endcsname\fi
      \ifnum\value{@tttask}>1                  % line up to the previous task
        \draw[overlay, gray!50, line width=1pt]
          ([yshift=-0.55em]tt-p\the\value{@ttprog}-t\the\numexpr\value{@tttask}-1\relax)
          -- (0,0.55em);
      \fi
    \end{tikzpicture}\hspace{0.6em}%
    \parbox[t]{\dimexpr\linewidth-\@ttWhenWd-3.5em\relax}{\strut#2\strut}%
  }\par\vspace{0.55em}%
}

% ---------------------------------------------------------------- Literature review
%   \begin{litreview}{0.2\textwidth}            % left column width (default 0.3)
%     \study{key1,key2}{What these studies found.}
%     \study<2->{key3}{...}                     % <spec>: reveal on a later step
%     \studytext{Author (2024)}{...}            % left text given directly
%   \end{litreview}
% Two columns separated by thick rules: bold "Author et al. (Year)" for each
% key (from the .bib; 2 authors: "A & B"), centred, and the summary. Needs
% biblatex; the studies are cited, so they appear on the References slide.
\RequirePackage{array}
\newlength{\@ttLitLeft}
\newcommand{\@ttLitRule}{\noalign{\color{black}\hrule height 1.1pt}}
\newcommand{\@ttLitPad}{\rule{0pt}{2em}}        % space above the text in a row

% width of the left column: \begin{litreview}{0.2\textwidth} (any length),
% or the older \begin{litreview}[0.2] (a fraction); default 0.3\linewidth
\newenvironment{litreview}[1][0.3]{%
  \@ifnextchar\bgroup{\@ttLitBegin}{\@ttLitBegin{#1\linewidth}}%
}{%
  \end{tabular}\par
}
\newcommand{\@ttLitBegin}[1]{%
  \par\noindent
  \setlength{\@ttLitLeft}{\dimexpr#1-2\tabcolsep\relax}%
  \small
  \begin{tabular}{@{}>{\centering\arraybackslash}m{\@ttLitLeft}
                     >{\raggedright\arraybackslash}%
                     m{\dimexpr\linewidth-\@ttLitLeft-2\tabcolsep\relax}@{}}
  \@ttLitRule
}

% one "Author et al. (Year)" line per key (\@centercr: \\ would end the row)
\newcommand{\@ttLitCite}[1]{%
  \@ttLitSep\def\@ttLitSep{\@centercr}%
  \AtNextCite{\defcounter{maxnames}{2}\defcounter{minnames}{1}%
              \renewcommand*{\finalnamedelim}{\addspace\&\space}}%
  \citeauthor{#1}~(\citeyear{#1})}
\newcommand{\@ttLitRow}[3]{%          #1 overlay spec, #2 left, #3 summary
  \uncover#1{\@ttLitPad\textbf{#2}}\vspace{0.9em} &
  \uncover#1{\@ttLitPad#3}\vspace{0.9em} \\
  \@ttLitRule}
\newcommand<>{\study}[2]{%
  \@ttLitRow{#3}{\def\@ttLitSep{}\forcsvlist{\@ttLitCite}{#1}}{#2}}
\newcommand<>{\studytext}[2]{\@ttLitRow{#3}{#1}{#2}}

% ---------------------------------------------------------------- Animations
%   \begin{animation}[fps=3, frames=3000:5000:50]   % first:last:step
%     \animpanel{Bare cylinder}{figures/.../bare/frame_\N}
%     \animpanel{Helical grooves}{figures/.../helical/frame_\N}
%   \end{animation}
% Panels play in sync, side by side, with their labels above; \N is the
% frame number. Options: fps (3), frames (required), width (of each panel;
% default: equal shares of the line), controls (all; play,step,stop,speed,
% none, or hover: buttons only while the mouse is over the animation in the
% HTML deck -- none in the PDF), autoplay (true), loop (true), subcaptions
% ((a), (b), ... before the labels), dt (show the time below the panels:
% frame number x dt), timelabel ($t = $), timedigits (2), timeunit (),
% timebar (a progress bar below the panels; with dt, first/last times at its ends),
% time (true; time=false hides the current time, the timebar keeps its ends).
% columns (all in one row; columns=3: rows of 3, each label on its panel).
% \animpanel[frames=a:b:s, dt=]{label}{path}: a panel with its own range (and
% time step); the panels still step together, a shorter one holds its last
% frame, and each panel then shows its own time below it.
% Put it in a figure for \caption / \label. Needs animate.
\ExplSyntaxOn
\seq_new:N  \g__tt_anim_labels_seq
\seq_new:N  \g__tt_anim_paths_seq
\seq_new:N  \g__tt_anim_popts_seq      % [options] of each \animpanel
\seq_new:N  \l__tt_anim_pfirst_seq     % per panel: first frame, step,
\seq_new:N  \l__tt_anim_pstep_seq      % number of frames, dt
\seq_new:N  \l__tt_anim_pcount_seq
\seq_new:N  \l__tt_anim_pdt_seq
\int_new:N  \l__tt_anim_count_int      % frames of the animation (longest panel)
\int_new:N  \l__tt_anim_gcount_int     % frames of the frames= range
\bool_new:N \l__tt_anim_ptime_bool     % panels differ: time below each panel
\tl_new:N   \l__tt_anim_pframes_tl
\tl_new:N   \l__tt_anim_pdtv_tl
\tl_new:N   \l__tt_anim_fps_tl
\tl_new:N   \l__tt_anim_frames_tl
\tl_new:N   \l__tt_anim_width_tl
\tl_new:N   \l__tt_anim_controls_tl
\tl_new:N   \l__tt_anim_out_tl
\bool_new:N \l__tt_anim_autoplay_bool
\bool_new:N \l__tt_anim_loop_bool
\bool_new:N \l__tt_anim_sub_bool
\dim_new:N  \l__tt_anim_w_dim
\dim_new:N  \l__tt_anim_gap_dim
\int_new:N  \l__tt_anim_n_int
\bool_new:N \l__tt_anim_hover_bool
\tl_new:N   \l__tt_anim_dt_tl
\tl_new:N   \l__tt_anim_timelabel_tl
\tl_new:N   \l__tt_anim_timeunit_tl
\int_new:N  \l__tt_anim_timedigits_int
\int_new:N  \l__tt_anim_cols_int
\bool_new:N \l__tt_anim_grid_bool
\bool_new:N \l__tt_anim_bar_bool
\bool_new:N \l__tt_anim_time_bool
\int_new:N  \l__tt_anim_first_int
\int_new:N  \l__tt_anim_last_int
\dim_new:N  \l__tt_anim_barw_dim

\keys_define:nn { tt / animation }
  {
    fps         .tl_set:N   = \l__tt_anim_fps_tl,
    fps         .initial:n  = 3,
    frames      .tl_set:N   = \l__tt_anim_frames_tl,
    width       .tl_set:N   = \l__tt_anim_width_tl,
    controls    .tl_set:N   = \l__tt_anim_controls_tl,
    controls    .initial:n  = all,
    controls    .default:n  = all,
    autoplay    .bool_set:N = \l__tt_anim_autoplay_bool,
    autoplay    .initial:n  = true,
    autoplay    .default:n  = true,
    loop        .bool_set:N = \l__tt_anim_loop_bool,
    loop        .initial:n  = true,
    loop        .default:n  = true,
    subcaptions .bool_set:N = \l__tt_anim_sub_bool,
    subcaptions .initial:n  = false,
    subcaptions .default:n  = true,
    dt          .tl_set:N   = \l__tt_anim_dt_tl,
    timelabel   .tl_set:N   = \l__tt_anim_timelabel_tl,
    timelabel   .initial:n  = { $t = $ },
    timedigits  .int_set:N  = \l__tt_anim_timedigits_int,
    timedigits  .initial:n  = 2,
    timeunit    .tl_set:N   = \l__tt_anim_timeunit_tl,
    timebar     .bool_set:N = \l__tt_anim_bar_bool,
    timebar     .initial:n  = false,
    timebar     .default:n  = true,
    time        .bool_set:N = \l__tt_anim_time_bool,
    time        .initial:n  = true,
    time        .default:n  = true,
    columns     .int_set:N  = \l__tt_anim_cols_int,
    columns     .initial:n  = 0,
  }

\keys_define:nn { tt / animpanel }        % \animpanel[frames=a:b:s, dt=]
  {
    frames .tl_set:N = \l__tt_anim_pframes_tl,
    dt     .tl_set:N = \l__tt_anim_pdtv_tl,
  }
\cs_generate_variant:Nn \seq_set_split:Nnn { NVV }
\NewDocumentEnvironment { animation } { O{} +b }
  {
    \keys_set:nn { tt / animation } {#1}
    \bool_set_false:N \l__tt_anim_hover_bool
    \str_if_eq:VnT \l__tt_anim_controls_tl { hover }   % hover: no buttons in the PDF
      { \bool_set_true:N \l__tt_anim_hover_bool }
    \seq_gclear:N \g__tt_anim_labels_seq
    \seq_gclear:N \g__tt_anim_paths_seq
    \seq_gclear:N \g__tt_anim_popts_seq
    \hbox_set:Nn \l_tmpa_box                   % collect the panels, print nothing
      {
        \DeclareDocumentCommand \animpanel { O{} m m }
          {
            \seq_gput_right:Nn \g__tt_anim_popts_seq {##1}
            \seq_gput_right:Nn \g__tt_anim_labels_seq {##2}
            \seq_gput_right:Nn \g__tt_anim_paths_seq {##3}
          }
        #2
      }
    \__tt_anim_typeset:
  }
  { }

% each panel's range: its own [frames=, dt=] or the animation's; the
% animation runs for the longest panel, shorter ones hold their last frame
\cs_new_protected:Npn \__tt_anim_panel_ranges:
  {
    \seq_clear:N \l__tt_anim_pfirst_seq
    \seq_clear:N \l__tt_anim_pstep_seq
    \seq_clear:N \l__tt_anim_pcount_seq
    \seq_clear:N \l__tt_anim_pdt_seq
    \bool_set_false:N \l__tt_anim_ptime_bool
    \int_set_eq:NN \l__tt_anim_count_int \l__tt_anim_gcount_int
    \seq_map_inline:Nn \g__tt_anim_popts_seq
      {
        \tl_clear:N \l__tt_anim_pframes_tl
        \tl_clear:N \l__tt_anim_pdtv_tl
        \keys_set:nn { tt / animpanel } {##1}
        \tl_if_blank:VTF \l__tt_anim_pframes_tl
          {
            \seq_put_right:Nx \l__tt_anim_pfirst_seq { \int_use:N \l__tt_anim_first_int }
            \seq_put_right:Nx \l__tt_anim_pstep_seq { \int_use:N \l_tmpb_int }
            \seq_put_right:Nx \l__tt_anim_pcount_seq { \int_use:N \l__tt_anim_gcount_int }
          }
          {
            \bool_set_true:N \l__tt_anim_ptime_bool
            \seq_set_split:NVV \l_tmpb_seq \c_colon_str \l__tt_anim_pframes_tl
            \tl_set:Nx \l_tmpa_tl { \seq_item:Nn \l_tmpb_seq { 3 } }
            \tl_set:Nx \l_tmpb_tl { \tl_if_blank:VTF \l_tmpa_tl { 1 } { \l_tmpa_tl } }
            \seq_put_right:Nx \l__tt_anim_pfirst_seq { \int_eval:n { \seq_item:Nn \l_tmpb_seq { 1 } } }
            \seq_put_right:NV \l__tt_anim_pstep_seq \l_tmpb_tl
            \seq_put_right:Nx \l__tt_anim_pcount_seq
              { \int_eval:n { ( \seq_item:Nn \l_tmpb_seq { 2 } - \seq_item:Nn \l_tmpb_seq { 1 } )
                              / \l_tmpb_tl + 1 } }
            \int_set:Nn \l__tt_anim_count_int
              { \int_max:nn { \l__tt_anim_count_int } { \seq_item:Nn \l__tt_anim_pcount_seq { -1 } } }
          }
        \tl_if_blank:VTF \l__tt_anim_pdtv_tl
          { \seq_put_right:NV \l__tt_anim_pdt_seq \l__tt_anim_dt_tl }
          {
            \bool_set_true:N \l__tt_anim_ptime_bool
            \seq_put_right:NV \l__tt_anim_pdt_seq \l__tt_anim_pdtv_tl
          }
      }
  }

\bool_new:N \l__tt_anim_inbox_bool
\cs_new_protected:Npn \__tt_anim_typeset:
  {
    \bool_gset_true:N \g__tt_anim_page_bool      % shapes over it: see \__tt_over:N
    \bool_gset_true:N \g__tt_anim_seen_bool
    % inside a box (annotate): typeset in a line-wide minipage
    \mode_if_horizontal:TF { \bool_set_true:N \l__tt_anim_inbox_bool }
      { \bool_set_false:N \l__tt_anim_inbox_bool }
    \bool_if:NT \l__tt_anim_inbox_bool { \begin{minipage}{\linewidth} }
    \@ifpackageloaded { animate } { }
      { \PackageError { beamer2html } { animation~needs~\string\usepackage{animate} } { } }
    % frames = first:last[:step]
    \seq_set_split:NVV \l_tmpa_seq \c_colon_str \l__tt_anim_frames_tl   % ':' is a letter here
    \int_set:Nn \l_tmpa_int { \seq_item:Nn \l_tmpa_seq { 1 } }
    \int_set:Nn \l__tt_anim_first_int { \l_tmpa_int }
    \int_set:Nn \l__tt_anim_last_int { \seq_item:Nn \l_tmpa_seq { 2 } }
    \dim_set:Nn \l__tt_anim_barw_dim { 0.6 \linewidth }
    \tl_set:Nx \l_tmpa_tl { \seq_item:Nn \l_tmpa_seq { 3 } }
    \int_set:Nn \l_tmpb_int { \tl_if_blank:VTF \l_tmpa_tl { 1 } { \l_tmpa_tl } }
    \int_set:Nn \l__tt_anim_n_int { \seq_count:N \g__tt_anim_paths_seq }
    \int_set:Nn \l__tt_anim_gcount_int
      { ( \l__tt_anim_last_int - \l__tt_anim_first_int ) / \l_tmpb_int + 1 }
    \__tt_anim_panel_ranges:
    % columns = N: panels in rows of N, each with its label on top (grid);
    % otherwise one row with the labels above it
    \bool_set:Nn \l__tt_anim_grid_bool
      { \int_compare_p:nNn \l__tt_anim_cols_int > 0
        && \int_compare_p:nNn \l__tt_anim_cols_int < \l__tt_anim_n_int }
    \bool_if:NF \l__tt_anim_grid_bool
      { \int_set_eq:NN \l__tt_anim_cols_int \l__tt_anim_n_int }
    % panel width and the gap between panels (per row)
    \tl_if_blank:VTF \l__tt_anim_width_tl
      {
        \dim_set:Nn \l__tt_anim_gap_dim { 0.02 \linewidth }
        \dim_set:Nn \l__tt_anim_w_dim
          { ( \linewidth - \l__tt_anim_gap_dim * ( \l__tt_anim_cols_int - 1 ) ) / \l__tt_anim_cols_int }
      }
      {
        \dim_set:Nn \l__tt_anim_w_dim { \l__tt_anim_width_tl }
        \dim_set:Nn \l__tt_anim_gap_dim
          {
            \int_compare:nNnTF \l__tt_anim_cols_int > 1
              { ( \linewidth - \l__tt_anim_w_dim * \l__tt_anim_cols_int ) / ( \l__tt_anim_cols_int - 1 ) }
              { 0pt }
          }
        \dim_compare:nNnT \l__tt_anim_gap_dim < { 0pt } { \dim_zero:N \l__tt_anim_gap_dim }
        \bool_if:NT \l__tt_anim_grid_bool         % grid rows: packed and centred
          { \dim_set:Nn \l__tt_anim_gap_dim { \dim_min:nn { \l__tt_anim_gap_dim } { 0.02 \linewidth } } }
      }
    % labels row (in a grid, every panel carries its own label)
    \par \noindent
    \bool_if:NF \l__tt_anim_grid_bool {
    \hbox_to_wd:nn { \linewidth }
      {
        \seq_map_indexed_inline:Nn \g__tt_anim_labels_seq
          {
            \int_compare:nNnF {##1} = { 1 } { \skip_horizontal:N \l__tt_anim_gap_dim }
            \parbox[b]{ \l__tt_anim_w_dim }
              {
                \centering \small
                \bool_if:NT \l__tt_anim_sub_bool { ( \int_to_alph:n {##1} ) ~ }
                ##2
              }
          }
        \hfil
      }
    \par \nointerlineskip \vspace { 0.25em }
    }
    % the animation: one \multiframe frame holds every panel
    \tl_set:Nx \l__tt_anim_out_tl
      {
        \exp_not:N \begin { animateinline }
          [ \bool_if:NT \l__tt_anim_autoplay_bool { autoplay , }
            \bool_if:NT \l__tt_anim_loop_bool { loop , }
            \bool_if:NF \l__tt_anim_hover_bool
              {
                \str_if_eq:VnF \l__tt_anim_controls_tl { none }
                  { controls = { \exp_not:V \l__tt_anim_controls_tl } , }
              }
            buttonsize = 0.8em , buttonfg = 0.12~0.29~0.49 ]
          { \exp_not:V \l__tt_anim_fps_tl }
        % \iK counts the frames of the animation; each panel works out its own
        % frame number \N from it, \iN is the frames= range's (shared time)
        \exp_not:N \multiframe { \int_use:N \l__tt_anim_count_int } { iK = 0 + 1 }
          {
            \exp_not:N \edef \exp_not:N \iN
              { \exp_not:N \int_eval:n { \int_use:N \l__tt_anim_first_int + \int_use:N \l_tmpb_int
                  * \exp_not:N \int_min:nn { \exp_not:N \iK } { \int_eval:n { \l__tt_anim_gcount_int - 1 } } } }
            \bool_if:nTF { \l__tt_anim_bar_bool || \l__tt_anim_grid_bool || \l__tt_anim_ptime_bool
                           || ! \tl_if_blank_p:V \l__tt_anim_dt_tl }
              {                                    % panels, time bar, time below them
                \exp_not:N \parbox { \dim_use:N \linewidth }
                  {
                    \exp_not:N \centering
                    \seq_map_indexed_function:NN \g__tt_anim_paths_seq \__tt_anim_panel:nn
                    \bool_if:NT \l__tt_anim_bar_bool
                      { \exp_not:N \par \exp_not:N \vspace { 0.3em } \exp_not:N \__tt_anim_bar: }
                    \bool_if:nT { \l__tt_anim_time_bool && ! \l__tt_anim_ptime_bool
                                   && ! \tl_if_blank_p:V \l__tt_anim_dt_tl }
                      { \exp_not:N \par \exp_not:N \vspace { 0.15em } \exp_not:N \__tt_anim_time: }
                  }
              }
              { \seq_map_indexed_function:NN \g__tt_anim_paths_seq \__tt_anim_panel:nn }
          }
        \exp_not:N \end { animateinline }
      }
    \tl_use:N \l__tt_anim_out_tl
    \par
    \bool_if:NT \l__tt_anim_inbox_bool { \end{minipage} }
  }
% time bar: a track filled up to the current frame; with dt, the first and
% last times at its ends
\cs_new_protected:Npn \__tt_anim_bar:
  {
    \bool_if:nT { ! \l__tt_anim_ptime_bool && ! \tl_if_blank_p:V \l__tt_anim_dt_tl }
      {
        { \scriptsize \color{darkgray}
          \__tt_anim_fixed:nn { \l__tt_anim_first_int * \l__tt_anim_dt_tl } { \l__tt_anim_timedigits_int } }
        \hspace { 0.6em }
      }
    \begin{tikzpicture}[baseline=-0.6ex]
      \fill [ gray!25 ] (0pt, -1.3pt) rectangle (\dim_use:N \l__tt_anim_barw_dim, 1.3pt);
      \fill [ bannerblue ] (0pt, -1.3pt) rectangle
        ( \fp_to_dim:n
            {
              \int_compare:nNnTF \l__tt_anim_count_int < { 2 } { \l__tt_anim_barw_dim }
                { \l__tt_anim_barw_dim * \iK / ( \l__tt_anim_count_int - 1 ) }
            } , 1.3pt );
    \end{tikzpicture}
    \bool_if:nT { ! \l__tt_anim_ptime_bool && ! \tl_if_blank_p:V \l__tt_anim_dt_tl }
      {
        \hspace { 0.6em }
        { \scriptsize \color{darkgray}
          \__tt_anim_fixed:nn { \l__tt_anim_last_int * \l__tt_anim_dt_tl } { \l__tt_anim_timedigits_int } }
      }
  }
% the time of the current frame: timelabel, \iN x dt with timedigits
% decimals, timeunit
\cs_new_protected:Npn \__tt_anim_time:
  {
    { \small \l__tt_anim_timelabel_tl \unskip \nobreakspace
      \__tt_anim_fixed:nn { \iN * \l__tt_anim_dt_tl } { \l__tt_anim_timedigits_int }
      \tl_if_blank:VF \l__tt_anim_timeunit_tl { \nobreakspace \l__tt_anim_timeunit_tl } }
  }
% a panel's own time (panels with different ranges): first, step, number of
% frames, dt -- frame (first + step x min(\iK, n-1)) x dt
\cs_new_protected:Npn \__tt_anim_ptime:nnnn #1#2#3#4
  {
    { \small \l__tt_anim_timelabel_tl \unskip \nobreakspace
      \__tt_anim_fixed:nn { ( #1 + #2 * \int_min:nn { \iK } { #3 - 1 } ) * (#4) }
        { \l__tt_anim_timedigits_int }
      \tl_if_blank:VF \l__tt_anim_timeunit_tl { \nobreakspace \l__tt_anim_timeunit_tl } }
  }
% #1 rounded to #2 decimals, always printed with #2 decimals (150 -> 150.00)
\cs_new_protected:Npn \__tt_anim_fixed:nn #1 #2
  {
    \int_set:Nn \l_tmpa_int { \fp_eval:n { round( (#1) * 10 ^ (#2) ) } }
    \int_compare:nNnT \l_tmpa_int < { 0 } { - }
    \int_set:Nn \l_tmpb_int { \int_abs:n { \l_tmpa_int } }
    \int_eval:n { \int_div_truncate:nn { \l_tmpb_int } { \fp_eval:n { 10 ^ (#2) } } }
    \int_compare:nNnT { #2 } > { 0 }
      {
        .
        \tl_set:Nx \l_tmpa_tl { \int_eval:n { \int_mod:nn { \l_tmpb_int } { \fp_eval:n { 10 ^ (#2) } } } }
        \prg_replicate:nn { (#2) - \tl_count:N \l_tmpa_tl } { 0 }
        \l_tmpa_tl
      }
  }
% one panel inside the \multiframe body (expanded into \l__tt_anim_out_tl)
\cs_new:Npn \__tt_anim_panel:nn #1 #2
  {
    \bool_if:nTF { \l__tt_anim_grid_bool || \l__tt_anim_ptime_bool }
      {
        \int_compare:nNnF {#1} = { 1 }
          {
            \bool_if:nTF
              { \l__tt_anim_grid_bool
                && \int_compare_p:nNn { \int_mod:nn { #1 - 1 } { \l__tt_anim_cols_int } } = { 0 } }
              { \exp_not:N \par \exp_not:N \vspace { 0.4em } }      % next row
              { \exp_not:N \hspace* { \dim_use:N \l__tt_anim_gap_dim } }
          }
        \exp_not:N \parbox [ t ] { \dim_use:N \l__tt_anim_w_dim }
          {
            \exp_not:N \centering \exp_not:N \small
            \bool_if:NT \l__tt_anim_grid_bool               % label on the panel
              {
                \bool_if:NT \l__tt_anim_sub_bool { ( \int_to_alph:n {#1} ) ~ }
                \seq_item:Nn \g__tt_anim_labels_seq {#1}
                \exp_not:N \par \exp_not:N \vspace { 0.15em }
              }
            \__tt_anim_frame:n {#1}
            \exp_not:N \includegraphics [ width = \dim_use:N \l__tt_anim_w_dim ] { \exp_not:n {#2} }
            \bool_if:nT { \l__tt_anim_time_bool && \l__tt_anim_ptime_bool
                          && ! \tl_if_blank_p:e { \seq_item:Nn \l__tt_anim_pdt_seq {#1} } }
              {                                              % its own time below it
                \exp_not:N \par \exp_not:N \vspace { 0.1em }
                \exp_not:N \__tt_anim_ptime:nnnn
                  { \seq_item:Nn \l__tt_anim_pfirst_seq {#1} } { \seq_item:Nn \l__tt_anim_pstep_seq {#1} }
                  { \seq_item:Nn \l__tt_anim_pcount_seq {#1} } { \seq_item:Nn \l__tt_anim_pdt_seq {#1} }
              }
          }
      }
      {
        \int_compare:nNnF {#1} = { 1 } { \exp_not:N \hspace* { \dim_use:N \l__tt_anim_gap_dim } }
        \__tt_anim_frame:n {#1}
        \exp_not:N \includegraphics [ width = \dim_use:N \l__tt_anim_w_dim ] { \exp_not:n {#2} }
      }
  }
% \N for panel #1 at animation frame \iK: first + step x min(\iK, n-1)
\cs_new:Npn \__tt_anim_frame:n #1
  {
    \exp_not:N \edef \exp_not:N \N
      { \exp_not:N \int_eval:n { \seq_item:Nn \l__tt_anim_pfirst_seq {#1}
          + \seq_item:Nn \l__tt_anim_pstep_seq {#1}
          * \exp_not:N \int_min:nn { \exp_not:N \iK } { \int_eval:n { \seq_item:Nn \l__tt_anim_pcount_seq {#1} - 1 } } } }
  }
\ExplSyntaxOff

% ---------------------------------------------------------------- Videos
%   \slidevideo[width=0.7\linewidth, poster=figures/flow.png]{figures/flow.mp4}
% In the HTML deck, a video player. It pauses when you leave the slide, and
% with autoplay it plays whenever its slide (or step) comes up. Options:
% width (\linewidth), height, poster (an image shown until it plays, and in
% the PDF), autoplay, loop, muted (browsers play sound only after a click or
% key press on the page), controls (true; false: click the video or press P
% to play / pause), start, end (in seconds: play only that part).
% \slidevideo<2->{...}: shown from step 2 on. Put it in a figure for \caption.
% In the PDF: the poster -- or a box with the file name -- linking to the
% video; with \usepackage{multimedia}, a \movie instead, which plays in the
% PDF viewers that support it (Okular, ...).
\ExplSyntaxOn
\dim_new:N \l__tt_video_w_dim
\dim_new:N \l__tt_video_h_dim
\box_new:N \l__tt_video_box
\clist_new:N \l__tt_video_movie_clist
\tl_new:N \l__tt_video_file_tl
\keys_define:nn { tt / video }
  {
    width    .tl_set:N   = \l__tt_video_width_tl,
    width    .initial:n  = \linewidth,
    height   .tl_set:N   = \l__tt_video_height_tl,
    poster   .tl_set:N   = \l__tt_video_poster_tl,
    autoplay .bool_set:N = \l__tt_video_autoplay_bool,
    autoplay .initial:n  = false,
    autoplay .default:n  = true,
    loop     .bool_set:N = \l__tt_video_loop_bool,
    loop     .initial:n  = false,
    loop     .default:n  = true,
    muted    .bool_set:N = \l__tt_video_muted_bool,
    muted    .initial:n  = false,
    muted    .default:n  = true,
    controls .bool_set:N = \l__tt_video_controls_bool,
    controls .initial:n  = true,
    controls .default:n  = true,
    start    .tl_set:N   = \l__tt_video_start_tl,
    end      .tl_set:N   = \l__tt_video_end_tl,
  }
\NewDocumentCommand \slidevideo { d<> O{} m }
  {
    \group_begin:
    \keys_set:nn { tt / video } {#2}
    \tl_set:Nn \l__tt_video_file_tl {#3}           % the extension may be left out
    \clist_map_inline:nn { .mp4, .webm, .m4v, .mov, .ogv }
      {
        \file_if_exist:nT { #3 ##1 }
          { \tl_set:Nn \l__tt_video_file_tl { #3 ##1 } \clist_map_break: }
      }
    \IfNoValueTF {#1}
      { \__tt_video:V \l__tt_video_file_tl }
      {                                              % hidden: its space only, no link
        \alt<#1> { \__tt_video:V \l__tt_video_file_tl }
          { \phantom { \__tt_video:V \l__tt_video_file_tl } }
      }
    \group_end:
  }
\cs_new_protected:Npn \__tt_video:n #1
  {
    \dim_set:Nn \l__tt_video_w_dim { \l__tt_video_width_tl }
    \tl_if_empty:NTF \l__tt_video_height_tl
      { \dim_set:Nn \l__tt_video_h_dim { 0.5625 \l__tt_video_w_dim } }   % 16:9
      { \dim_set:Nn \l__tt_video_h_dim { \l__tt_video_height_tl } }
    \hbox_set:Nn \l__tt_video_box
      {
        \tl_if_empty:NTF \l__tt_video_poster_tl
          {                                          % a box with a play sign
            \tikz
              {
                \node [ draw = gray, minimum~width = \l__tt_video_w_dim,
                        minimum~height = \l__tt_video_h_dim,
                        inner~sep = 0pt, outer~sep = 0pt ] (v) { };
                \fill [ gray!70 ] (v.center) ++ (-0.5em, 0.7em) -- ++ (1.3em, -0.7em)
                  -- ++ (-1.3em, -0.7em) -- cycle;
                \node [ above, font = \scriptsize\ttfamily, text = gray ] at (v.south)
                  { \tl_to_str:n {#1} };
              }
          }
          {
            \tl_if_empty:NTF \l__tt_video_height_tl
              { \includegraphics [ width = \l__tt_video_w_dim ] { \l__tt_video_poster_tl } }
              { \includegraphics [ width = \l__tt_video_w_dim, height = \l__tt_video_h_dim,
                                   keepaspectratio ] { \l__tt_video_poster_tl } }
          }
      }
    \cs_if_exist:NTF \movie                         % multimedia: play in the PDF
      {
        \clist_clear:N \l__tt_video_movie_clist
        \bool_if:NT \l__tt_video_autoplay_bool
          { \clist_put_right:Nn \l__tt_video_movie_clist { autostart } }
        \bool_if:NT \l__tt_video_loop_bool
          { \clist_put_right:Nn \l__tt_video_movie_clist { loop } }
        \bool_if:NT \l__tt_video_controls_bool
          { \clist_put_right:Nn \l__tt_video_movie_clist { showcontrols } }
        \tl_if_empty:NF \l__tt_video_start_tl
          { \clist_put_right:Nx \l__tt_video_movie_clist { start = \l__tt_video_start_tl s } }
        \tl_if_empty:NF \l__tt_video_end_tl
          {
            \clist_put_right:Nx \l__tt_video_movie_clist
              { duration = \fp_eval:n { \l__tt_video_end_tl
                  - ( \tl_if_empty:NTF \l__tt_video_start_tl { 0 } { \l__tt_video_start_tl } ) } s }
          }
        \use:e
          {
            \exp_not:N \movie [ \clist_use:Nn \l__tt_video_movie_clist { , } ]
              { \exp_not:N \box_use:N \exp_not:N \l__tt_video_box } { \exp_not:n {#1} }
          }
      }
      {                                              % ":" is a letter here: hyperref
        \use:e                                       % needs a real one to see run:
          { \exp_not:N \href { run \c_colon_str \exp_not:n {#1} } }
          { \box_use:N \l__tt_video_box }
      }
  }
\cs_generate_variant:Nn \__tt_video:n { V }
\ExplSyntaxOff

% ---------------------------------------------------------------- Plots
%   \begin{slideplot}[xlabel={$t$}, ylabel={$y/D$}, height=5cm]
%     \plotline[x=time, y=aleDisp_y, label={$U_r = 4$}]{data/U4/data_dy.csv}
%     \plotline<2->[x=time, y=aleDisp_y, label={$U_r = 6$}]{data/U6/data_dy.csv}
%   \end{slideplot}
% Lines from data files: columns separated by commas (.csv) or spaces, a
% header row with the column names (optional), lines starting with # or %
% skipped. In the PDF, pgfplots draws them; in the HTML deck, the plot is
% interactive: values under the pointer, drag to zoom in, double-click to
% zoom out, click a legend entry to hide / show its line.
% slideplot options: width (\linewidth), height (half the width), xlabel,
%   ylabel, xmin, xmax, ymin, ymax, grid (true), legend (north east, north
%   west, south east, south west: inside the axes; outer east: to the right
%   of them, outer north: in rows above them -- the plot stays within width;
%   none).
% \plotline options: x, y (the column's name, or its number counting from 1;
%   default 1 and 2), label (legend entry; lines with the same label share
%   one colour and one entry), color (plotcolor1, 2, ... in turn), style
%   (solid, dashed, dotted), xscale, yscale (the x / y values are multiplied
%   by them: other units, e.g. xscale=1000 for s -> ms, or yscale=-1 to flip),
%   offset (added to the x values, after xscale: lines that cover the same
%   span at different times, e.g. offset=-100, start together), sep
%   (comma, semicolon, tab, space: instead of guessing from the file name),
%   pdfevery=N (the PDF plots every N-th point only -- for long series that
%   TeX is slow with or runs out of memory on; the HTML always has all of
%   them).
% \plotline<2->: the line is drawn from step 2 on; the axes stay the same.
%
% Panels: \plotpanel[title, xlabel, ylabel, xmin, xmax, ymin, ymax] starts a
% panel; the \plotline lines after it are drawn in it. A panel's xmin, ...
% are for an axis it does not share (share=none, or y for xmin / xmax); they
% override the slideplot's.
%   \begin{slideplot}[columns=3, xlabel={$t$}, ylabel={$y/D$}, legend=outer north]
%     \plotpanel[title={$U_r = 4.0$}]
%       \plotline[x=time, y=disp, label=Helical]{data/U4.csv}
%     \plotpanel[title={$U_r = 5.4$}]
%       ...
%   \end{slideplot}
% rows=R and / or columns=C lay them out, row by row (only one given: the
% other from the number of panels; neither: one column). share=x, y, both,
% none: a shared axis has one range (from all panels, unless given), tick
% labels on the outer panels only and, in the HTML deck, linked zoom. By
% default: one column -> x, one row -> y, a grid -> both. xlabel / ylabel of
% the slideplot are written once, centred; those of a panel on that panel.
% One legend for all panels. \plotpanel<2->: the panel from step 2 on.
\RequirePackage{pgfplots}
\pgfplotsset{compat=1.18}
\usepgfplotslibrary{groupplots}
\definecolor{plotcolor1}{RGB}{31,73,125}     % bannerblue
\definecolor{plotcolor2}{RGB}{213,94,0}      % vermilion
\definecolor{plotcolor3}{RGB}{0,158,115}     % green
\definecolor{plotcolor4}{RGB}{204,121,167}   % purple
\definecolor{plotcolor5}{RGB}{230,159,0}     % orange
\definecolor{plotcolor6}{RGB}{86,180,233}    % sky blue
\definecolor{plotcolor7}{RGB}{110,110,110}   % gray
\ExplSyntaxOn
\dim_new:N \l__tt_plot_w_dim
\dim_new:N \l__tt_plot_h_dim
\dim_new:N \l__tt_plot_aw_dim                    % one panel's axes
\dim_new:N \l__tt_plot_ah_dim
\dim_new:N \l__tt_plot_hsep_dim
\dim_new:N \l__tt_plot_vsep_dim
\dim_new:N \g__tt_plot_key_dim                   % widest legend label
\clist_new:N \l__tt_plot_axis_clist
\tl_new:N \l__tt_plot_draw_tl
\tl_new:N \l__tt_plot_table_tl
\tl_new:N \l__tt_plot_color_tl
\tl_new:N \l__tt_plot_mode_tl                    % what \plotline / \plotpanel do:
                                                 % survey, draw (one axis), grid
\tl_new:N \g__tt_plot_opts_tl
\tl_new:N \l__tt_plot_keytab_tl
\tl_new:N \l__tt_plot_cval_tl
\tl_new:N \l__tt_plot_row_tl
\tl_new:N \l__tt_plot_xv_tl
\tl_new:N \l__tt_plot_yv_tl
\str_new:N \l__tt_plot_ckey_str
\str_new:N \l__tt_plot_sep_str
\bool_new:N \l__tt_plot_key_bool
\bool_new:N \l__tt_plot_multi_bool               % with \plotpanel
\bool_new:N \l__tt_plot_scan_bool                % read the data for shared ranges
\bool_new:N \l__tt_plot_sharex_bool
\bool_new:N \l__tt_plot_sharey_bool
\bool_new:N \l__tt_plot_first_bool
\bool_new:N \l__tt_plot_spaces_bool
\bool_new:N \g__tt_plot_hidden_bool              % this panel: not shown at this step
\bool_new:N \g__tt_plot_drop_bool                % no panel to draw into
\bool_new:N \g__tt_plot_titles_bool
\bool_new:N \g__tt_plot_pxlabel_bool
\bool_new:N \g__tt_plot_pylabel_bool
\int_new:N \g__tt_plot_line_int
\int_new:N \g__tt_plot_colors_int
\int_new:N \g__tt_plot_keys_int
\int_new:N \g__tt_plot_panels_int
\int_new:N \g__tt_plot_panel_int
\int_new:N \l__tt_plot_rows_int
\int_new:N \l__tt_plot_cols_int
\int_new:N \l__tt_plot_n_int                     % panels drawn
\int_new:N \l__tt_plot_keycols_int
\int_new:N \l__tt_plot_k_int
\int_new:N \l__tt_plot_xi_int
\int_new:N \l__tt_plot_yi_int
\prop_new:N \g__tt_plot_labels_prop              % label -> colour
\prop_new:N \g__tt_plot_cache_prop               % file and columns -> data ranges
\seq_new:N \g__tt_plot_linecolor_seq             % each line's colour
\seq_new:N \g__tt_plot_linekey_seq               % 1: the line has its label's entry
\seq_new:N \g__tt_plot_keys_seq                  % {label}{colour}{style}{spec}
\seq_new:N \l__tt_plot_row_seq
\fp_new:N \g__tt_plot_xlo_fp
\fp_new:N \g__tt_plot_xhi_fp
\fp_new:N \g__tt_plot_ylo_fp
\fp_new:N \g__tt_plot_yhi_fp
\fp_new:N \l__tt_plot_sxlo_fp
\fp_new:N \l__tt_plot_sxhi_fp
\fp_new:N \l__tt_plot_sylo_fp
\fp_new:N \l__tt_plot_syhi_fp
\fp_new:N \l__tt_plot_off_fp
\fp_new:N \l__tt_plot_xs_fp
\fp_new:N \l__tt_plot_ys_fp
\fp_new:N \l__tt_plot_lo_fp
\fp_new:N \l__tt_plot_hi_fp
\fp_new:N \l__tt_plot_x_fp
\fp_new:N \l__tt_plot_y_fp
\prop_new:N \g__tt_plot_written_prop             % ranges written to the .aux
\ior_new:N \g__tt_plot_ior
\box_new:N \l__tt_plot_measure_box
\cs_generate_variant:Nn \str_case:nnF { V }
\cs_generate_variant:Nn \str_case:nn { V }
\cs_generate_variant:Nn \prop_gput:Nnn { NVV , NVn }
\prg_generate_conditional_variant:Nnn \prop_if_in:Nn { NV } { T , F , TF }
\prg_generate_conditional_variant:Nnn \prop_get:NnN { NV } { T , F , TF }
\keys_define:nn { tt / plot }
  {
    width   .tl_set:N   = \l__tt_plot_width_tl,
    width   .initial:n  = \linewidth,
    height  .tl_set:N   = \l__tt_plot_height_tl,
    xlabel  .tl_set:N   = \l__tt_plot_xlabel_tl,
    ylabel  .tl_set:N   = \l__tt_plot_ylabel_tl,
    xmin    .tl_set:N   = \l__tt_plot_xmin_tl,
    xmax    .tl_set:N   = \l__tt_plot_xmax_tl,
    ymin    .tl_set:N   = \l__tt_plot_ymin_tl,
    ymax    .tl_set:N   = \l__tt_plot_ymax_tl,
    grid    .bool_set:N = \l__tt_plot_grid_bool,
    grid    .initial:n  = true,
    grid    .default:n  = true,
    legend  .tl_set:N   = \l__tt_plot_legend_tl,
    legend  .initial:n  = { north~east },
    rows    .tl_set:N   = \l__tt_plot_rows_tl,
    columns .tl_set:N   = \l__tt_plot_columns_tl,
    share   .tl_set:N   = \l__tt_plot_share_tl,
  }
\keys_define:nn { tt / plotpanel }
  {
    title   .tl_set:N   = \l__tt_plotpanel_title_tl,
    xlabel  .tl_set:N   = \l__tt_plotpanel_xlabel_tl,
    ylabel  .tl_set:N   = \l__tt_plotpanel_ylabel_tl,
    xmin    .tl_set:N   = \l__tt_plotpanel_xmin_tl,
    xmax    .tl_set:N   = \l__tt_plotpanel_xmax_tl,
    ymin    .tl_set:N   = \l__tt_plotpanel_ymin_tl,
    ymax    .tl_set:N   = \l__tt_plotpanel_ymax_tl,
  }
\keys_define:nn { tt / plotline }
  {
    x        .tl_set:N  = \l__tt_plotline_x_tl,
    x        .initial:n = 1,
    y        .tl_set:N  = \l__tt_plotline_y_tl,
    y        .initial:n = 2,
    label    .tl_set:N  = \l__tt_plotline_label_tl,
    color    .tl_set:N  = \l__tt_plotline_color_tl,
    style    .tl_set:N  = \l__tt_plotline_style_tl,
    style    .initial:n = solid,
    sep      .tl_set:N  = \l__tt_plotline_sep_tl,
    offset   .tl_set:N  = \l__tt_plotline_offset_tl,
    xscale   .tl_set:N  = \l__tt_plotline_xscale_tl,
    yscale   .tl_set:N  = \l__tt_plotline_yscale_tl,
    pdfevery .tl_set:N  = \l__tt_plotline_every_tl,
    pdfevery .initial:n = 1,
  }
% axis style shared by single plots and panels
\tl_const:Nn \c__tt_plot_style_tl
  {
    enlarge~x~limits = false, scaled~ticks = false,
    xticklabel~style = { /pgf/number~format/fixed },
    yticklabel~style = { /pgf/number~format/fixed },
    tick~label~style = { font = \footnotesize },
    label~style = { font = \small }, title~style = { font = \small },
    legend~style = { font = \footnotesize, cells = { anchor = west } },
    major~grid~style = { gray!20 },
  }
\NewDocumentEnvironment { slideplot } { O{} +b }
  {
    \group_begin:
    \keys_set:nn { tt / plot } {#1}
    \dim_set:Nn \l__tt_plot_w_dim { \l__tt_plot_width_tl }
    \tl_if_empty:NTF \l__tt_plot_height_tl
      { \dim_set:Nn \l__tt_plot_h_dim { 0.5 \l__tt_plot_w_dim } }
      { \dim_set:Nn \l__tt_plot_h_dim { \l__tt_plot_height_tl } }
    \seq_set_split:Nnn \l__tt_plot_row_seq { \plotpanel } {#2}
    \int_gset:Nn \g__tt_plot_panels_int { \seq_count:N \l__tt_plot_row_seq - 1 }
    \bool_set:Nn \l__tt_plot_multi_bool { \int_compare_p:nNn \g__tt_plot_panels_int > 0 }
    \bool_if:NT \l__tt_plot_multi_bool { \__tt_plot_layout: }
    \bool_set:Nn \l__tt_plot_scan_bool               % a shared range not given
      {
        \l__tt_plot_multi_bool &&
        (
          ( \l__tt_plot_sharex_bool &&
            ( \tl_if_empty_p:N \l__tt_plot_xmin_tl || \tl_if_empty_p:N \l__tt_plot_xmax_tl ) )
          || ( \l__tt_plot_sharey_bool &&
            ( \tl_if_empty_p:N \l__tt_plot_ymin_tl || \tl_if_empty_p:N \l__tt_plot_ymax_tl ) )
        )
      }
    \__tt_plot_survey:n {#2}
    \par \centering
    \bool_if:NTF \l__tt_plot_multi_bool
      { \__tt_plot_grid:n {#2} }
      { \__tt_plot_single:n {#2} }
    \par
    \group_end:
  }
  { }
\NewDocumentCommand \plotpanel { d<> O{} }
  {
    \str_case:Vn \l__tt_plot_mode_tl
      {
        { survey } { \__tt_plot_survey_panel:n {#2} }
        { grid }   { \__tt_plot_draw_panel:nn {#1} {#2} }
      }
  }
\NewDocumentCommand \plotline { d<> O{} m }
  {
    \str_case:Vn \l__tt_plot_mode_tl
      {
        { survey } { \__tt_plot_survey_line:nnn {#1} {#2} {#3} }
        { draw }   { \__tt_plot_draw_line:nnn {#1} {#2} {#3} }
        { grid }   { \__tt_plot_draw_line:nnn {#1} {#2} {#3} }
      }
  }
% ---- first pass: the body run in a box. Counts the panels, gives each line
% its colour (one per label), collects the legend entries and, for shared
% axes, the data ranges.
\cs_new_protected:Npn \__tt_plot_survey:n #1
  {
    \int_gzero:N \g__tt_plot_line_int
    \int_gzero:N \g__tt_plot_colors_int
    \int_gzero:N \g__tt_plot_keys_int
    \prop_gclear:N \g__tt_plot_labels_prop
    \seq_gclear:N \g__tt_plot_linecolor_seq
    \seq_gclear:N \g__tt_plot_linekey_seq
    \seq_gclear:N \g__tt_plot_keys_seq
    \dim_gzero:N \g__tt_plot_key_dim
    \bool_gset_false:N \g__tt_plot_titles_bool
    \bool_gset_false:N \g__tt_plot_pxlabel_bool
    \bool_gset_false:N \g__tt_plot_pylabel_bool
    \fp_gset:Nn \g__tt_plot_xlo_fp { inf }
    \fp_gset:Nn \g__tt_plot_xhi_fp { -inf }
    \fp_gset:Nn \g__tt_plot_ylo_fp { inf }
    \fp_gset:Nn \g__tt_plot_yhi_fp { -inf }
    \tl_set:Nn \l__tt_plot_mode_tl { survey }
    \hbox_set:Nn \l__tt_plot_measure_box {#1}
  }
\cs_new_protected:Npn \__tt_plot_survey_panel:n #1
  {
    \group_begin:
    \keys_set:nn { tt / plotpanel } {#1}
    \tl_if_empty:NF \l__tt_plotpanel_title_tl { \bool_gset_true:N \g__tt_plot_titles_bool }
    \tl_if_empty:NF \l__tt_plotpanel_xlabel_tl { \bool_gset_true:N \g__tt_plot_pxlabel_bool }
    \tl_if_empty:NF \l__tt_plotpanel_ylabel_tl { \bool_gset_true:N \g__tt_plot_pylabel_bool }
    \group_end:
  }
\cs_new_protected:Npn \__tt_plot_survey_line:nnn #1#2#3
  {
    \int_gincr:N \g__tt_plot_line_int
    \group_begin:
    \keys_set:nn { tt / plotline } {#2}
    \tl_if_empty:NTF \l__tt_plotline_label_tl
      {
        \__tt_plot_new_color:
        \seq_gput_right:NV \g__tt_plot_linecolor_seq \l__tt_plot_color_tl
        \seq_gput_right:Nn \g__tt_plot_linekey_seq { 0 }
      }
      {
        \prop_get:NVNTF \g__tt_plot_labels_prop \l__tt_plotline_label_tl \l__tt_plot_color_tl
          {                                          % a label seen before: its colour
            \seq_gput_right:NV \g__tt_plot_linecolor_seq \l__tt_plot_color_tl
            \seq_gput_right:Nn \g__tt_plot_linekey_seq { 0 }
          }
          {
            \__tt_plot_new_color:
            \prop_gput:NVV \g__tt_plot_labels_prop \l__tt_plotline_label_tl \l__tt_plot_color_tl
            \seq_gput_right:NV \g__tt_plot_linecolor_seq \l__tt_plot_color_tl
            \seq_gput_right:Nn \g__tt_plot_linekey_seq { 1 }
            \int_gincr:N \g__tt_plot_keys_int
            \seq_gput_right:Ne \g__tt_plot_keys_seq
              {
                { \exp_not:V \l__tt_plotline_label_tl } { \l__tt_plot_color_tl }
                { \l__tt_plotline_style_tl } { \IfNoValueF {#1} { \exp_not:n {#1} } }
              }
            \hbox_set:Nn \l_tmpa_box { \footnotesize \l__tt_plotline_label_tl }
            \dim_gset:Nn \g__tt_plot_key_dim
              { \dim_max:nn { \g__tt_plot_key_dim } { \box_wd:N \l_tmpa_box } }
          }
      }
    \bool_if:NT \l__tt_plot_scan_bool
      { \file_if_exist:nT {#3} { \__tt_plot_scan:n {#3} } }
    \group_end:
  }
\cs_new_protected:Npn \__tt_plot_new_color:
  {
    \tl_if_empty:NTF \l__tt_plotline_color_tl
      {
        \int_gincr:N \g__tt_plot_colors_int
        \tl_set:Ne \l__tt_plot_color_tl
          { plotcolor \int_eval:n { \int_mod:nn { \g__tt_plot_colors_int - 1 } { 7 } + 1 } }
      }
      { \tl_set_eq:NN \l__tt_plot_color_tl \l__tt_plotline_color_tl }
  }
% ---- data ranges of a line (x with offset; y within xmin..xmax), read from
% its file once, then kept in the .aux (with the file's date: a changed file
% is read again)
\cs_new_protected:cpn { tt@plotrange } #1#2
  { \prop_gput:Nnn \g__tt_plot_cache_prop {#1} {#2} }
\cs_new_protected:Npn \__tt_plot_scan:n #1
  {
    \str_set:Ne \l__tt_plot_ckey_str
      {
        \file_timestamp:n {#1} | #1 | \l__tt_plotline_x_tl | \l__tt_plotline_y_tl | \l__tt_plotline_sep_tl |
        \l__tt_plotline_offset_tl | \l__tt_plotline_xscale_tl | \l__tt_plotline_yscale_tl |
        \l__tt_plot_xmin_tl | \l__tt_plot_xmax_tl
      }
    \prop_get:NVNF \g__tt_plot_cache_prop \l__tt_plot_ckey_str \l__tt_plot_cval_tl
      {
        \__tt_plot_scan_file:n {#1}
        \tl_set:Ne \l__tt_plot_cval_tl
          {
            { \fp_use:N \l__tt_plot_sxlo_fp } { \fp_use:N \l__tt_plot_sxhi_fp }
            { \fp_use:N \l__tt_plot_sylo_fp } { \fp_use:N \l__tt_plot_syhi_fp }
          }
        \prop_gput:NVV \g__tt_plot_cache_prop \l__tt_plot_ckey_str \l__tt_plot_cval_tl
      }
    \prop_if_in:NVF \g__tt_plot_written_prop \l__tt_plot_ckey_str
      {
        \prop_gput:NVn \g__tt_plot_written_prop \l__tt_plot_ckey_str { }
        \iow_now:Ne \@auxout
          {
            \token_to_str:N \tt@plotrange
              { \l__tt_plot_ckey_str } { \exp_not:V \l__tt_plot_cval_tl }
          }
      }
    \exp_after:wN \__tt_plot_merge:nnnn \l__tt_plot_cval_tl
  }
\cs_new_protected:Npn \__tt_plot_merge:nnnn #1#2#3#4
  {
    \fp_gset:Nn \g__tt_plot_xlo_fp { min ( \g__tt_plot_xlo_fp , #1 ) }
    \fp_gset:Nn \g__tt_plot_xhi_fp { max ( \g__tt_plot_xhi_fp , #2 ) }
    \fp_gset:Nn \g__tt_plot_ylo_fp { min ( \g__tt_plot_ylo_fp , #3 ) }
    \fp_gset:Nn \g__tt_plot_yhi_fp { max ( \g__tt_plot_yhi_fp , #4 ) }
  }
\cs_new_protected:Npn \__tt_plot_scan_file:n #1
  {
    \fp_set:Nn \l__tt_plot_sxlo_fp { inf }
    \fp_set:Nn \l__tt_plot_sxhi_fp { -inf }
    \fp_set:Nn \l__tt_plot_sylo_fp { inf }
    \fp_set:Nn \l__tt_plot_syhi_fp { -inf }
    \fp_set:Nn \l__tt_plot_off_fp
      { \tl_if_empty:NTF \l__tt_plotline_offset_tl { 0 } { \l__tt_plotline_offset_tl } }
    \fp_set:Nn \l__tt_plot_xs_fp
      { \tl_if_empty:NTF \l__tt_plotline_xscale_tl { 1 } { \l__tt_plotline_xscale_tl } }
    \fp_set:Nn \l__tt_plot_ys_fp
      { \tl_if_empty:NTF \l__tt_plotline_yscale_tl { 1 } { \l__tt_plotline_yscale_tl } }
    \fp_set:Nn \l__tt_plot_lo_fp
      { \tl_if_empty:NTF \l__tt_plot_xmin_tl { -inf } { \l__tt_plot_xmin_tl } }
    \fp_set:Nn \l__tt_plot_hi_fp
      { \tl_if_empty:NTF \l__tt_plot_xmax_tl { inf } { \l__tt_plot_xmax_tl } }
    \str_set:Nn \l__tt_plot_sep_str { , }
    \bool_set_false:N \l__tt_plot_spaces_bool
    \str_case:VnF \l__tt_plotline_sep_tl
      {
        { comma }     { }
        { semicolon } { \str_set:Nn \l__tt_plot_sep_str { ; } }
        { tab }       { \bool_set_true:N \l__tt_plot_spaces_bool }  % tabs are read as spaces
        { space }     { \bool_set_true:N \l__tt_plot_spaces_bool }
      }
      { \regex_match:nnF { (?i) \.csv \Z } {#1} { \bool_set_true:N \l__tt_plot_spaces_bool } }
    \__tt_plot_scan_column:NV \l__tt_plot_xi_int \l__tt_plotline_x_tl
    \__tt_plot_scan_column:NV \l__tt_plot_yi_int \l__tt_plotline_y_tl
    \bool_set_true:N \l__tt_plot_first_bool
    \ior_open:Nn \g__tt_plot_ior {#1}
    \ior_str_map_inline:Nn \g__tt_plot_ior { \__tt_plot_scan_row:n {##1} }
    \ior_close:N \g__tt_plot_ior
  }
\cs_new_protected:Npn \__tt_plot_scan_column:Nn #1#2      % a number: that column
  {                                                      % (else 0: from the header)
    \regex_match:nnTF { \A \d+ \Z } {#2}
      { \int_set:Nn #1 {#2} }
      { \int_zero:N #1 }
  }
\cs_generate_variant:Nn \__tt_plot_scan_column:Nn { NV }
\cs_new_protected:Npn \__tt_plot_scan_row:n #1
  {
    \tl_set:Ne \l__tt_plot_row_tl { \tl_trim_spaces:n {#1} }
    \tl_if_empty:NF \l__tt_plot_row_tl
      {
        \bool_lazy_or:nnF
          { \str_if_eq_p:ee { \str_head:N \l__tt_plot_row_tl } { \c_hash_str } }
          { \str_if_eq_p:ee { \str_head:N \l__tt_plot_row_tl } { \c_percent_str } }
          { \__tt_plot_scan_fields: }
      }
  }
\cs_new_protected:Npn \__tt_plot_scan_fields:
  {
    \bool_if:NT \l__tt_plot_spaces_bool
      { \tl_replace_all:Nnn \l__tt_plot_row_tl { ~ } { , } }
    \exp_args:NNVV \seq_set_split:Nnn \l__tt_plot_row_seq \l__tt_plot_sep_str \l__tt_plot_row_tl
    \bool_if:NT \l__tt_plot_spaces_bool { \seq_remove_all:Nn \l__tt_plot_row_seq { } }
    \bool_if:NTF \l__tt_plot_first_bool
      {
        \bool_set_false:N \l__tt_plot_first_bool
        \tl_set:Ne \l__tt_plot_xv_tl { \seq_item:Nn \l__tt_plot_row_seq { 1 } }
        \regex_match:nVTF { \A [\+\-]? (\d+\.?\d*|\.\d+) ([eE][\+\-]?\d+)? \Z } \l__tt_plot_xv_tl
          { \__tt_plot_scan_point: }              % no header row
          {
            \seq_map_indexed_inline:Nn \l__tt_plot_row_seq
              {
                \int_compare:nNnT \l__tt_plot_xi_int = 0
                  { \str_if_eq:nVT {##2} \l__tt_plotline_x_tl { \int_set:Nn \l__tt_plot_xi_int {##1} } }
                \int_compare:nNnT \l__tt_plot_yi_int = 0
                  { \str_if_eq:nVT {##2} \l__tt_plotline_y_tl { \int_set:Nn \l__tt_plot_yi_int {##1} } }
              }
          }
      }
      { \__tt_plot_scan_point: }
  }
\cs_new_protected:Npn \__tt_plot_scan_point:
  {
    \bool_lazy_and:nnT
      { \int_compare_p:nNn { \int_min:nn \l__tt_plot_xi_int \l__tt_plot_yi_int } > 0 }
      {
        \int_compare_p:nNn { \seq_count:N \l__tt_plot_row_seq }
          > { \int_max:nn \l__tt_plot_xi_int \l__tt_plot_yi_int - 1 }
      }
      {
        \tl_set:Ne \l__tt_plot_xv_tl { \seq_item:Nn \l__tt_plot_row_seq { \l__tt_plot_xi_int } }
        \tl_set:Ne \l__tt_plot_yv_tl { \seq_item:Nn \l__tt_plot_row_seq { \l__tt_plot_yi_int } }
        \bool_lazy_or:nnF { \tl_if_empty_p:N \l__tt_plot_xv_tl } { \tl_if_empty_p:N \l__tt_plot_yv_tl }
          {
            \fp_set:Nn \l__tt_plot_x_fp { \l__tt_plot_xv_tl * \l__tt_plot_xs_fp + \l__tt_plot_off_fp }
            \fp_compare:nNnTF \l__tt_plot_x_fp < \l__tt_plot_sxlo_fp
              { \fp_set_eq:NN \l__tt_plot_sxlo_fp \l__tt_plot_x_fp }
              {
                \fp_compare:nNnT \l__tt_plot_x_fp > \l__tt_plot_sxhi_fp
                  { \fp_set_eq:NN \l__tt_plot_sxhi_fp \l__tt_plot_x_fp }
              }
            \fp_compare:nNnT \l__tt_plot_x_fp > \l__tt_plot_sxhi_fp   % the first point
              { \fp_set_eq:NN \l__tt_plot_sxhi_fp \l__tt_plot_x_fp }
            \bool_lazy_and:nnT
              { ! \fp_compare_p:nNn \l__tt_plot_x_fp < \l__tt_plot_lo_fp }
              { ! \fp_compare_p:nNn \l__tt_plot_x_fp > \l__tt_plot_hi_fp }
              {
                \fp_set:Nn \l__tt_plot_y_fp { \l__tt_plot_yv_tl * \l__tt_plot_ys_fp }
                \fp_compare:nNnTF \l__tt_plot_y_fp < \l__tt_plot_sylo_fp
                  { \fp_set_eq:NN \l__tt_plot_sylo_fp \l__tt_plot_y_fp }
                  {
                    \fp_compare:nNnT \l__tt_plot_y_fp > \l__tt_plot_syhi_fp
                      { \fp_set_eq:NN \l__tt_plot_syhi_fp \l__tt_plot_y_fp }
                  }
                \fp_compare:nNnT \l__tt_plot_y_fp > \l__tt_plot_syhi_fp
                  { \fp_set_eq:NN \l__tt_plot_syhi_fp \l__tt_plot_y_fp }
              }
          }
      }
  }
% ---- legend entries per row that fit in a width
\cs_new_protected:Npn \__tt_plot_keycols:n #1
  {
    \int_set:Nn \l__tt_plot_keycols_int
      {
        \int_max:nn { 1 }
          {
            \int_min:nn { \g__tt_plot_keys_int }
              {
                \int_div_truncate:nn { \dim_to_decimal_in_sp:n {#1} }
                  { \dim_to_decimal_in_sp:n { \g__tt_plot_key_dim + 1.6cm } }
              }
          }
      }
  }
\cs_new:Npn \__tt_plot_keyrows:
  {
    \int_div_truncate:nn { \g__tt_plot_keys_int + \l__tt_plot_keycols_int - 1 }
      { \l__tt_plot_keycols_int }
  }
% ---- one axis
\cs_new_protected:Npn \__tt_plot_single:n #1
  {
    % a legend outside the axes takes its room from the width / height
    \str_if_eq:VnT \l__tt_plot_legend_tl { outer~east }
      { \dim_sub:Nn \l__tt_plot_w_dim { \g__tt_plot_key_dim + 1.4cm } }
    \str_if_eq:VnT \l__tt_plot_legend_tl { outer~north }
      {                                              % as many per row as fit
        \__tt_plot_keycols:n { \l__tt_plot_w_dim }
        \dim_sub:Nn \l__tt_plot_h_dim { 1.6em * \__tt_plot_keyrows: + 0.6em }
      }
    \clist_set:Ne \l__tt_plot_axis_clist
      {
        width = \dim_use:N \l__tt_plot_w_dim,
        height = \dim_use:N \l__tt_plot_h_dim,
        enlarge~y~limits = 0.05,
        \exp_not:V \c__tt_plot_style_tl
        \bool_if:NT \l__tt_plot_grid_bool { grid = major, }
        \str_case:VnF \l__tt_plot_legend_tl
          {
            { none } { }
            { outer~east } { legend~style = { at = { (1.03, 1) }, anchor = north~west }, }
            { outer~north }
              {
                legend~columns = \int_use:N \l__tt_plot_keycols_int,
                legend~style = { at = { (0.5, 1.03) }, anchor = south,
                                 /tikz/every~even~column/.append~style = { column~sep = 0.8em } },
              }
          }
          { legend~pos = \tl_use:N \l__tt_plot_legend_tl, }
        \tl_if_empty:NF \l__tt_plot_xlabel_tl { xlabel = { \exp_not:V \l__tt_plot_xlabel_tl }, }
        \tl_if_empty:NF \l__tt_plot_ylabel_tl { ylabel = { \exp_not:V \l__tt_plot_ylabel_tl }, }
        \tl_if_empty:NF \l__tt_plot_xmin_tl { xmin = \l__tt_plot_xmin_tl, }
        \tl_if_empty:NF \l__tt_plot_xmax_tl { xmax = \l__tt_plot_xmax_tl, }
        \tl_if_empty:NF \l__tt_plot_ymin_tl { ymin = \l__tt_plot_ymin_tl, }
        \tl_if_empty:NF \l__tt_plot_ymax_tl { ymax = \l__tt_plot_ymax_tl, }
      }
    \tl_set:Nn \l__tt_plot_mode_tl { draw }
    \int_gzero:N \g__tt_plot_line_int
    \bool_gset_false:N \g__tt_plot_hidden_bool
    \bool_gset_false:N \g__tt_plot_drop_bool
    \__tt_plot_axis:Vn \l__tt_plot_axis_clist {#1}
  }
\cs_new_protected:Npn \__tt_plot_axis:nn #1#2
  { \begin{tikzpicture} \begin{axis} [#1] #2 \end{axis} \end{tikzpicture} }
\cs_generate_variant:Nn \__tt_plot_axis:nn { V }
% ---- panels: a groupplot
% rows x columns (one given: the other from the number of panels; neither:
% one column) and the shared axes
\cs_new_protected:Npn \__tt_plot_layout:
  {
    \int_set:Nn \l__tt_plot_cols_int
      {
        \tl_if_blank:VTF \l__tt_plot_columns_tl
          {
            \tl_if_blank:VTF \l__tt_plot_rows_tl { 1 }
              {
                \int_div_truncate:nn { \g__tt_plot_panels_int + \l__tt_plot_rows_tl - 1 }
                  { \l__tt_plot_rows_tl }
              }
          }
          { \l__tt_plot_columns_tl }
      }
    \int_set:Nn \l__tt_plot_rows_int
      {
        \tl_if_blank:VTF \l__tt_plot_rows_tl
          {
            \int_div_truncate:nn { \g__tt_plot_panels_int + \l__tt_plot_cols_int - 1 }
              { \l__tt_plot_cols_int }
          }
          { \l__tt_plot_rows_tl }
      }
    \int_set:Nn \l__tt_plot_n_int
      { \int_min:nn { \g__tt_plot_panels_int } { \l__tt_plot_rows_int * \l__tt_plot_cols_int } }
    \int_compare:nNnT { \g__tt_plot_panels_int } > { \l__tt_plot_n_int }
      {
        \msg_warning:nneee { beamer2html } { plot-panels }
          { \int_use:N \g__tt_plot_panels_int } { \int_use:N \l__tt_plot_rows_int }
          { \int_use:N \l__tt_plot_cols_int }
      }
    % shared axes: one column -> x, one row -> y, a grid -> both
    \tl_if_blank:VT \l__tt_plot_share_tl
      {
        \tl_set:Ne \l__tt_plot_share_tl
          {
            \int_compare:nNnTF \l__tt_plot_cols_int = 1
              { \int_compare:nNnTF \l__tt_plot_rows_int > 1 { x } { both } }
              { \int_compare:nNnTF \l__tt_plot_rows_int = 1 { y } { both } }
          }
      }
    \bool_set:Nn \l__tt_plot_sharex_bool
      { \str_if_eq_p:Vn \l__tt_plot_share_tl { x } || \str_if_eq_p:Vn \l__tt_plot_share_tl { both } }
    \bool_set:Nn \l__tt_plot_sharey_bool
      { \str_if_eq_p:Vn \l__tt_plot_share_tl { y } || \str_if_eq_p:Vn \l__tt_plot_share_tl { both } }
  }
\cs_new_protected:Npn \__tt_plot_grid:n #1
  {
    % sizes: the axes of all panels alike; tick labels, labels, titles and
    % the legend fitted in around them
    \dim_set:Nn \l__tt_plot_hsep_dim
      {
        \bool_if:NTF \l__tt_plot_sharey_bool { 0.45cm }        % room for x tick labels
          { 2.6em + \bool_if:NTF \g__tt_plot_pylabel_bool { 1.6em } { 0pt } }
      }
    \dim_set:Nn \l__tt_plot_vsep_dim
      {
        \bool_if:NTF \l__tt_plot_sharex_bool { 0.25cm }
          { 1.6em + \bool_if:NTF \g__tt_plot_pxlabel_bool { 1.6em } { 0pt } }
        + \bool_if:NTF \g__tt_plot_titles_bool { 1.6em } { 0pt }
      }
    \str_if_eq:VnT \l__tt_plot_legend_tl { outer~north }
      { \__tt_plot_keycols:n { \l__tt_plot_w_dim } }
    \dim_set:Nn \l__tt_plot_aw_dim
      {
        (
          \l__tt_plot_w_dim - 2.6em
          - \bool_lazy_or:nnTF { ! \tl_if_empty_p:N \l__tt_plot_ylabel_tl }
              { \g__tt_plot_pylabel_bool } { 1.6em } { 0pt }
          - \l__tt_plot_hsep_dim * \int_eval:n { \l__tt_plot_cols_int - 1 }
          - \str_if_eq:VnTF \l__tt_plot_legend_tl { outer~east }
              { ( \g__tt_plot_key_dim + 1.4cm ) } { 0pt }
        ) / \l__tt_plot_cols_int
      }
    \dim_set:Nn \l__tt_plot_ah_dim
      {
        (
          \l__tt_plot_h_dim - 1.6em
          - \bool_lazy_or:nnTF { ! \tl_if_empty_p:N \l__tt_plot_xlabel_tl }
              { \g__tt_plot_pxlabel_bool } { 1.6em } { 0pt }
          - \bool_if:NTF \g__tt_plot_titles_bool { 1.6em } { 0.3em }
          - \l__tt_plot_vsep_dim * \int_eval:n { \l__tt_plot_rows_int - 1 }
          - \str_if_eq:VnTF \l__tt_plot_legend_tl { outer~north }
              { ( 1.6em * \__tt_plot_keyrows: + 0.8em ) } { 0pt }
        ) / \l__tt_plot_rows_int
      }
    \dim_set:Nn \l__tt_plot_aw_dim { \dim_max:nn { \l__tt_plot_aw_dim } { 1cm } }
    \dim_set:Nn \l__tt_plot_ah_dim { \dim_max:nn { \l__tt_plot_ah_dim } { 0.8cm } }
    \clist_set:Ne \l__tt_plot_axis_clist
      {
        group~style =
          {
            group~size = { \int_use:N \l__tt_plot_cols_int \c_space_tl by \c_space_tl \int_use:N \l__tt_plot_rows_int },
            horizontal~sep = \dim_use:N \l__tt_plot_hsep_dim,
            vertical~sep = \dim_use:N \l__tt_plot_vsep_dim,
          },
        scale~only~axis, width = \dim_use:N \l__tt_plot_aw_dim,
        height = \dim_use:N \l__tt_plot_ah_dim,
        \exp_not:V \c__tt_plot_style_tl
        \bool_if:NT \l__tt_plot_grid_bool { grid = major, }
        \bool_if:NTF \l__tt_plot_sharex_bool
          {
            \__tt_plot_limit:nNn { xmin } \l__tt_plot_xmin_tl { \g__tt_plot_xlo_fp }
            \__tt_plot_limit:nNn { xmax } \l__tt_plot_xmax_tl { \g__tt_plot_xhi_fp }
          }
          {
            \tl_if_empty:NF \l__tt_plot_xmin_tl { xmin = \l__tt_plot_xmin_tl, }
            \tl_if_empty:NF \l__tt_plot_xmax_tl { xmax = \l__tt_plot_xmax_tl, }
          }
        \bool_if:NTF \l__tt_plot_sharey_bool
          {
            enlarge~y~limits = false,                % 5% added here
            \__tt_plot_limit:nNn { ymin } \l__tt_plot_ymin_tl
              { \g__tt_plot_ylo_fp - 0.05 * ( \g__tt_plot_yhi_fp - \g__tt_plot_ylo_fp ) }
            \__tt_plot_limit:nNn { ymax } \l__tt_plot_ymax_tl
              { \g__tt_plot_yhi_fp + 0.05 * ( \g__tt_plot_yhi_fp - \g__tt_plot_ylo_fp ) }
          }
          {
            enlarge~y~limits = 0.05,
            \tl_if_empty:NF \l__tt_plot_ymin_tl { ymin = \l__tt_plot_ymin_tl, }
            \tl_if_empty:NF \l__tt_plot_ymax_tl { ymax = \l__tt_plot_ymax_tl, }
          }
      }
    \tl_set:Nn \l__tt_plot_mode_tl { grid }
    \int_gzero:N \g__tt_plot_line_int
    \int_gzero:N \g__tt_plot_panel_int
    \bool_gset_true:N \g__tt_plot_drop_bool          % lines before the first panel
    \bool_gset_false:N \g__tt_plot_hidden_bool
    \__tt_plot_group:Vn \l__tt_plot_axis_clist {#1}
  }
% key = given value, or the one from the data (if there was data)
\cs_new:Npn \__tt_plot_limit:nNn #1#2#3
  {
    \tl_if_empty:NTF #2
      {
        \fp_compare:nT { abs ( \g__tt_plot_xlo_fp ) < inf && abs ( \g__tt_plot_ylo_fp ) < inf }
          { #1 = \fp_eval:n {#3}, }
      }
      { #1 = \exp_not:V #2, }
  }
\cs_new_protected:Npn \__tt_plot_group:nn #1#2
  {
    \begin{tikzpicture}
      \begin{groupplot} [#1]
        #2
        \prg_replicate:nn { \l__tt_plot_rows_int * \l__tt_plot_cols_int - \l__tt_plot_n_int }
          { \nextgroupplot [ group/empty~plot ] }
      \end{groupplot}
      \__tt_plot_labels:
      \__tt_plot_legend:
    \end{tikzpicture}
  }
\cs_generate_variant:Nn \__tt_plot_group:nn { V }
\cs_new_protected:Npn \__tt_plot_draw_panel:nn #1#2
  {
    \int_gincr:N \g__tt_plot_panel_int
    \int_compare:nNnTF \g__tt_plot_panel_int > \l__tt_plot_n_int
      { \bool_gset_true:N \g__tt_plot_drop_bool }
      {
        \bool_gset_false:N \g__tt_plot_drop_bool
        \IfNoValueTF {#1}
          { \bool_gset_false:N \g__tt_plot_hidden_bool }
          { \alt<#1> { \bool_gset_false:N \g__tt_plot_hidden_bool } { \bool_gset_true:N \g__tt_plot_hidden_bool } }
        \group_begin:
        \keys_set:nn { tt / plotpanel } {#2}
        \tl_gset:Ne \g__tt_plot_opts_tl
          {
            \bool_if:NTF \g__tt_plot_hidden_bool { hide~axis, }
              {
                \tl_if_empty:NF \l__tt_plotpanel_title_tl
                  { title = { \exp_not:V \l__tt_plotpanel_title_tl }, }
              }
            \tl_if_empty:NF \l__tt_plotpanel_xlabel_tl
              { xlabel = { \exp_not:V \l__tt_plotpanel_xlabel_tl }, }
            \tl_if_empty:NF \l__tt_plotpanel_ylabel_tl
              { ylabel = { \exp_not:V \l__tt_plotpanel_ylabel_tl }, }
            \bool_if:NF \l__tt_plot_sharex_bool       % the panel's own range
              {
                \tl_if_empty:NF \l__tt_plotpanel_xmin_tl { xmin = \l__tt_plotpanel_xmin_tl, }
                \tl_if_empty:NF \l__tt_plotpanel_xmax_tl { xmax = \l__tt_plotpanel_xmax_tl, }
              }
            \bool_if:NF \l__tt_plot_sharey_bool
              {
                \tl_if_empty:NF \l__tt_plotpanel_ymin_tl { ymin = \l__tt_plotpanel_ymin_tl, }
                \tl_if_empty:NF \l__tt_plotpanel_ymax_tl { ymax = \l__tt_plotpanel_ymax_tl, }
              }
            \bool_if:NT \l__tt_plot_sharex_bool       % tick labels on the outer panels
              {
                \int_compare:nNnF { \g__tt_plot_panel_int + \l__tt_plot_cols_int } > \l__tt_plot_n_int
                  { xticklabels = { }, }
              }
            \bool_if:NT \l__tt_plot_sharey_bool
              {
                \int_compare:nNnF { \int_mod:nn { \g__tt_plot_panel_int - 1 } { \l__tt_plot_cols_int } } = 0
                  { yticklabels = { }, }
              }
          }
        \group_end:
        \exp_args:NV \__tt_plot_next:n \g__tt_plot_opts_tl
      }
  }
\cs_new_protected:Npn \__tt_plot_next:n #1 { \nextgroupplot [#1] }
% the slideplot's xlabel / ylabel, centred on all panels
\cs_new_protected:Npn \__tt_plot_labels:
  {
    \use:e
      {
        \exp_not:N \path (group~c1r1.north~west) --
          (group~c \int_min:nn { \l__tt_plot_cols_int } { \l__tt_plot_n_int } r1.north~east)
          coordinate [ midway ] (tt-plot-top) ;
        \exp_not:N \path (group~c1r1.north~west) --
          (group~c1r \int_div_truncate:nn { \l__tt_plot_n_int + \l__tt_plot_cols_int - 1 }
                                          { \l__tt_plot_cols_int } .south~west)
          coordinate [ midway ] (tt-plot-left) ;
      }
    \tl_if_empty:NF \l__tt_plot_xlabel_tl
      {
        \node [ anchor = north, font = \small, inner~sep = 2pt ]
          at (tt-plot-top |- current~bounding~box.south) { \l__tt_plot_xlabel_tl } ;
      }
    \tl_if_empty:NF \l__tt_plot_ylabel_tl
      {
        \node [ anchor = south, rotate = 90, font = \small, inner~sep = 2pt ]
          at (current~bounding~box.west |- tt-plot-left) { \l__tt_plot_ylabel_tl } ;
      }
  }
% one legend for all panels: a box with a line sample and label per entry
\cs_new_protected:Npn \__tt_plot_legend:
  {
    \bool_lazy_or:nnF
      { \int_compare_p:nNn \g__tt_plot_keys_int = 0 }
      { \str_if_eq_p:Vn \l__tt_plot_legend_tl { none } }
      {
        \str_if_eq:VnF \l__tt_plot_legend_tl { outer~north }
          { \int_set:Nn \l__tt_plot_keycols_int { 1 } }
        \tl_clear:N \l__tt_plot_keytab_tl
        \int_zero:N \l__tt_plot_k_int
        \seq_map_inline:Nn \g__tt_plot_keys_seq { \__tt_plot_key_cell:nnnn ##1 }
        \int_set:Nn \l_tmpa_int { \int_min:nn { \l__tt_plot_cols_int } { \l__tt_plot_n_int } }
        \int_set:Nn \l_tmpb_int
          { \int_div_truncate:nn { \l__tt_plot_n_int + \l__tt_plot_cols_int - 1 } { \l__tt_plot_cols_int } }
        \str_case:Vn \l__tt_plot_legend_tl
          {
            { outer~east }
              {
                \__tt_plot_key_node:nn { anchor = north~west, xshift = 0.25cm }
                  { current~bounding~box.east |- group~c \int_use:N \l_tmpa_int r1.north~east }
              }
            { outer~north }
              {
                \__tt_plot_key_node:nn { anchor = south, yshift = 0.1cm }
                  { tt-plot-top |- current~bounding~box.north }
              }
            { north~east }
              {
                \__tt_plot_key_node:nn { anchor = north~east, xshift = -4pt, yshift = -4pt }
                  { group~c \int_use:N \l_tmpa_int r1.north~east }
              }
            { north~west }
              {
                \__tt_plot_key_node:nn { anchor = north~west, xshift = 4pt, yshift = -4pt }
                  { group~c1r1.north~west }
              }
            { south~east }
              {
                \__tt_plot_key_node:nn { anchor = south~east, xshift = -4pt, yshift = 4pt }
                  {
                    group~c \int_eval:n { \int_mod:nn { \l__tt_plot_n_int - 1 } { \l__tt_plot_cols_int } + 1 }
                    r \int_use:N \l_tmpb_int .south~east
                  }
              }
            { south~west }
              {
                \__tt_plot_key_node:nn { anchor = south~west, xshift = 4pt, yshift = 4pt }
                  { group~c1r \int_use:N \l_tmpb_int .south~west }
              }
          }
      }
  }
\cs_new_protected:Npn \__tt_plot_key_cell:nnnn #1#2#3#4
  {
    \int_incr:N \l__tt_plot_k_int
    \int_compare:nNnT \l__tt_plot_k_int > 1
      {
        \int_compare:nNnTF { \int_mod:nn { \l__tt_plot_k_int - 1 } { \l__tt_plot_keycols_int } } = 0
          { \tl_put_right:Nn \l__tt_plot_keytab_tl { \\ } }
          { \tl_put_right:Nn \l__tt_plot_keytab_tl { & } }
      }
    \tl_if_blank:nTF {#4}
      { \tl_put_right:Nn \l__tt_plot_keytab_tl { \__tt_plot_key:nnn {#1} {#2} {#3} } }
      {                                              % its line comes at a step
        \tl_put_right:Nn \l__tt_plot_keytab_tl
          { \alt<#4> { \__tt_plot_key:nnn {#1} {#2} {#3} } { \phantom { \__tt_plot_key:nnn {#1} {#2} {#3} } } }
      }
  }
\cs_new_protected:Npn \__tt_plot_key:nnn #1#2#3
  {
    \tikz [ baseline = -0.55ex ] \draw [ #2, line~width = 0.8pt, #3 ] (0,0) -- (0.5cm,0) ;
    \hspace { 0.35em } #1
  }
\cs_new_protected:Npn \__tt_plot_key_node:nn #1#2
  {
    \use:e
      {
        \exp_not:N \node
          [ draw, fill = white, inner~sep = 2.5pt, font = \exp_not:N \footnotesize, #1 ]
          at (#2)
          {
            \exp_not:N \begin{tabular}
              {
                @{} l
                \int_compare:nNnT \l__tt_plot_keycols_int > 1
                  { * { \int_eval:n { \l__tt_plot_keycols_int - 1 } } { @{ \exp_not:N \hspace { 0.9em } } l } }
                @{}
              }
              \exp_not:V \l__tt_plot_keytab_tl
            \exp_not:N \end{tabular}
          } ;
      }
  }
% ---- drawing a line (one axis, or the current panel)
\cs_new_protected:Npn \__tt_plot_draw_line:nnn #1#2#3
  {
    \int_gincr:N \g__tt_plot_line_int
    \bool_if:NF \g__tt_plot_drop_bool
      {
        \file_if_exist:nTF {#3}
          {
            \group_begin:
            \keys_set:nn { tt / plotline } {#2}
            \tl_set:Ne \l__tt_plot_color_tl
              { \seq_item:Nn \g__tt_plot_linecolor_seq { \g__tt_plot_line_int } }
            % the table: separator, columns by name or number (pgfplots: from 0)
            \tl_clear:N \l__tt_plot_table_tl
            \tl_if_empty:NTF \l__tt_plotline_sep_tl
              {
                \regex_match:nnT { (?i) \.csv \Z } {#3}
                  { \tl_put_right:Nn \l__tt_plot_table_tl { col~sep = comma, } }
              }
              {
                \str_if_eq:VnF \l__tt_plotline_sep_tl { space }
                  { \tl_put_right:Ne \l__tt_plot_table_tl { col~sep = \l__tt_plotline_sep_tl, } }
              }
            \__tt_plot_coord:nVVV { x } \l__tt_plotline_x_tl         % x * xscale + offset
              \l__tt_plotline_xscale_tl \l__tt_plotline_offset_tl
            \__tt_plot_coord:nVVV { y } \l__tt_plotline_y_tl         % y * yscale
              \l__tt_plotline_yscale_tl \c_empty_tl
            % a legend entry (one axis): the first line of a label, unless legend=none
            \bool_set:Nn \l__tt_plot_key_bool
              {
                ! \l__tt_plot_multi_bool
                && \int_compare_p:nNn
                     { \seq_item:Nn \g__tt_plot_linekey_seq { \g__tt_plot_line_int } } = 1
                && ! \str_if_eq_p:Vn \l__tt_plot_legend_tl { none }
              }
            \tl_set:Ne \l__tt_plot_draw_tl
              {
                color = \l__tt_plot_color_tl, line~width = 0.8pt, mark = none,
                \l__tt_plotline_style_tl,
                each~nth~point = \l__tt_plotline_every_tl,
                filter~discard~warning = false, unbounded~coords = jump
                \bool_if:NF \l__tt_plot_key_bool { , forget~plot }
              }
            \bool_if:NTF \g__tt_plot_hidden_bool
              { \__tt_plot_add:nVn { draw = none, forget~plot } \l__tt_plot_table_tl {#3} }
              {
                \IfNoValueTF {#1}
                  { \__tt_plot_line:n {#3} }
                  {                                  % hidden: drawn invisibly, so
                    \alt<#1> { \__tt_plot_line:n {#3} }  % the axes do not change
                      { \__tt_plot_add:nVn { draw = none, forget~plot } \l__tt_plot_table_tl {#3} }
                  }
              }
            \group_end:
          }
          { \msg_warning:nnn { beamer2html } { plot-file } {#3} }
      }
  }
\msg_new:nnn { beamer2html } { plot-file } { \plotline:~data~file~'#1'~not~found. }
\msg_new:nnn { beamer2html } { plot-panels }
  { slideplot:~#1~panels~do~not~fit~in~#2~x~#3~(the~last~ones~left~out). }
\cs_new_protected:Npn \__tt_plot_column:nn #1#2
  {
    \regex_match:nnTF { \A \d+ \Z } {#2}
      { \tl_put_right:Ne \l__tt_plot_table_tl { , #1~index = \int_eval:n { #2 - 1 } } }
      { \tl_put_right:Nn \l__tt_plot_table_tl { , #1 = {#2} } }
  }
\cs_generate_variant:Nn \__tt_plot_column:nn { nV }
% a coordinate: the column (#2), times #3, plus #4 (either may be empty)
\cs_new_protected:Npn \__tt_plot_coord:nnnn #1#2#3#4
  {
    \bool_lazy_and:nnTF { \tl_if_empty_p:n {#3} } { \tl_if_empty_p:n {#4} }
      { \__tt_plot_column:nn {#1} {#2} }
      {
        \regex_match:nnTF { \A \d+ \Z } {#2}
          { \tl_set:Ne \l__tt_plot_cell_tl { \exp_not:N \thisrowno { \int_eval:n { #2 - 1 } } } }
          { \tl_set:Nn \l__tt_plot_cell_tl { \thisrow {#2} } }
        \tl_put_right:Ne \l__tt_plot_table_tl
          {
            , #1~expr = \exp_not:V \l__tt_plot_cell_tl
              \tl_if_empty:nF {#3} { * ( \exp_not:n {#3} ) }
              \tl_if_empty:nF {#4} { + ( \exp_not:n {#4} ) }
          }
      }
  }
\cs_generate_variant:Nn \__tt_plot_coord:nnnn { nVVV }
\tl_new:N \l__tt_plot_cell_tl
\cs_new_protected:Npn \__tt_plot_line:n #1
  {
    \__tt_plot_add:VVn \l__tt_plot_draw_tl \l__tt_plot_table_tl {#1}
    \bool_if:NT \l__tt_plot_key_bool
      { \exp_args:NV \addlegendentry \l__tt_plotline_label_tl }
  }
\cs_new_protected:Npn \__tt_plot_add:nnn #1#2#3
  { \addplot [#1] table [#2] {#3} ; }
\cs_generate_variant:Nn \__tt_plot_add:nnn { VVn, nVn }
\ExplSyntaxOff

% ---------------------------------------------------------------- Captions
%   \begin{figure}                       % (or table) -- centred, does not float
%     \includegraphics[width=0.8\linewidth]{figures/...}
%     \caption{What it shows.}\label{fig:name}
%   \end{figure}
%   ... see Fig.~\ref{fig:name} ...
% Numbered "Figure n: caption" / "Table n: caption"; numbers run through the
% deck. A short caption is centred, a long one left-aligned (beamer's rule).
\setbeamertemplate{caption}{%
  \raggedright
  {\usebeamercolor[fg]{caption name}\usebeamerfont*{caption name}%
   \insertcaptionname~\insertcaptionnumber
   \usebeamertemplate{caption label separator}}%
  \insertcaption\par}
\setbeamerfont{caption}{size=\small}
% caption size for the whole deck: \SetCaptionSize{\footnotesize}
% (same as \setbeamerfont{caption}{size=\footnotesize}; the HTML reads both)
\newcommand{\SetCaptionSize}[1]{\setbeamerfont{caption}{size=#1}}
\setbeamerfont{caption name}{series=\bfseries}
\setbeamercolor{caption name}{fg=bannerblue}
\setlength{\abovecaptionskip}{4pt}
\setlength{\belowcaptionskip}{2pt}

% ---------------------------------------------------------------- Tables
%   \begin{slidetable}[striped]{lcc}    % column spec as for tabular
%     Case    & $A^*/D$ & $St$ \\       % first row = header
%     Bare    & 0.92    & 0.20 \\
%     Helical & 0.10    & 0.18 \\
%   \end{slidetable}
% Styles: plain (default; top/bottom rules, thin rule under the header),
% lines (a rule between every row), header (blue header band), striped
% (blue header band and shaded alternate rows). The table is centred; the
% style draws all rules, so \hline in the body is ignored. (Rows end with
% \\[0pt] and \rowcolor gets its optional overhangs, so a cell starting with
% "[" is never taken as an optional argument.)
\RequirePackage{colortbl}
% row spacing of slide tables; \renewcommand{\slidetablestretch}{1.0} before a
% table (or in the preamble) for denser tables
\newcommand{\slidetablestretch}{1.3}
\definecolor{tablestripe}{RGB}{232,237,244}   % light bannerblue
\definecolor{tableline}{RGB}{170,170,170}

\ExplSyntaxOn
\seq_new:N  \l__tt_rows_seq
\seq_new:N  \l__tt_cells_seq
\tl_new:N   \l__tt_out_tl
\int_new:N  \l__tt_row_int
\bool_new:N \l__tt_band_bool                  % header band (header, striped)
\bool_new:N \l__tt_stripe_bool                % shaded alternate rows (striped)
\bool_new:N \l__tt_lines_bool                 % rule between rows (lines)

\cs_new:Npn \__tt_rule:nn #1 #2         % colour, thickness
  { \noalign { \group_begin: \color{#1} \hrule height #2 \group_end: } }

\NewDocumentEnvironment { slidetable } { O{plain} m +b }
  {
    \bool_set_false:N \l__tt_band_bool
    \bool_set_false:N \l__tt_stripe_bool
    \bool_set_false:N \l__tt_lines_bool
    \str_case:nnF {#1}
      {
        { plain }   { }
        { lines }   { \bool_set_true:N \l__tt_lines_bool }
        { header }  { \bool_set_true:N \l__tt_band_bool }
        { striped } { \bool_set_true:N \l__tt_band_bool \bool_set_true:N \l__tt_stripe_bool }
      }
      { \PackageWarning{beamer2html}{slidetable:~unknown~style~'#1'~(plain,~lines,~header,~striped)} }
    \__tt_build:n {#3}
    \par \centering
    \renewcommand{\arraystretch}{\slidetablestretch}
    \begin{tabular}{#2} \l__tt_out_tl \end{tabular}
    \par \vspace{0.5em}
  }
  { }

\cs_new_protected:Npn \__tt_build:n #1
  {
    \seq_set_split:Nnn \l__tt_rows_seq { \\ } {#1}
    \seq_remove_all:Nn \l__tt_rows_seq { }
    \int_set:Nn \l__tt_row_int { \seq_count:N \l__tt_rows_seq }
    \tl_clear:N \l__tt_out_tl
    \bool_if:NF \l__tt_band_bool
      { \tl_put_right:Nn \l__tt_out_tl { \__tt_rule:nn {black} {1.1pt} } }
    \seq_map_indexed_inline:Nn \l__tt_rows_seq
      {
        \tl_set:Nn \l_tmpa_tl {##2}
        \tl_remove_all:Nn \l_tmpa_tl { \hline }
        \seq_set_split:NnV \l__tt_cells_seq { & } \l_tmpa_tl
        \int_compare:nNnTF {##1} = { 1 }
          { \__tt_header: }
          { \__tt_row:n {##1} }
      }
    \bool_if:NTF \l__tt_band_bool
      { \tl_put_right:Nn \l__tt_out_tl { \__tt_rule:nn {bannerblue} {1pt} } }
      { \tl_put_right:Nn \l__tt_out_tl { \__tt_rule:nn {black} {1.1pt} } }
  }

\cs_new_protected:Npn \__tt_header:
  {
    \bool_if:NT \l__tt_band_bool
      { \tl_put_right:Nn \l__tt_out_tl { \rowcolor{bannerblue}[\tabcolsep][\tabcolsep] } }
    \seq_map_indexed_inline:Nn \l__tt_cells_seq
      {
        \int_compare:nNnF {##1} = { 1 } { \tl_put_right:Nn \l__tt_out_tl { & } }
        \bool_if:NTF \l__tt_band_bool
          { \tl_put_right:Nn \l__tt_out_tl { { \color{white} \bfseries ##2 } } }
          { \tl_put_right:Nn \l__tt_out_tl { { \bfseries ##2 } } }
      }
    \tl_put_right:Nn \l__tt_out_tl { \\[0pt] }
    \bool_if:NF \l__tt_band_bool
      { \tl_put_right:Nn \l__tt_out_tl { \__tt_rule:nn {black} {0.6pt} } }
  }

\cs_new_protected:Npn \__tt_row:n #1           % #1: row number (header = 1)
  {
    \bool_lazy_and:nnT { \l__tt_stripe_bool } { \int_if_odd_p:n {#1} }
      { \tl_put_right:Nn \l__tt_out_tl { \rowcolor{tablestripe}[\tabcolsep][\tabcolsep] } }
    \seq_map_indexed_inline:Nn \l__tt_cells_seq
      {
        \int_compare:nNnF {##1} = { 1 } { \tl_put_right:Nn \l__tt_out_tl { & } }
        \tl_put_right:Nn \l__tt_out_tl { ##2 }
      }
    \tl_put_right:Nn \l__tt_out_tl { \\[0pt] }
    \bool_lazy_and:nnT { \l__tt_lines_bool } { \int_compare_p:nNn {#1} < { \l__tt_row_int } }
      { \tl_put_right:Nn \l__tt_out_tl { \__tt_rule:nn {tableline} {0.4pt} } }
  }
\ExplSyntaxOff

% ---------------------------------------------------------------- Publications
%   \begin{publications}
%     \pubsection{Journal articles}
%     \pub[published]{bibkey}                 % entry from the .bib file
%     \pubtext[preparation]{Authors. Title.}  % not in the .bib file (yet)
%   \end{publications}
% Status badges: published, accepted, review, submitted, preparation.
% Needs biblatex. The entries are cited in their own refsection, so they are
% not added to the References slide.
\definecolor{pubpublished}{RGB}{46,125,50}
\definecolor{pubaccepted}{RGB}{0,121,107}
\definecolor{pubreview}{RGB}{230,145,0}
\definecolor{pubsubmitted}{RGB}{31,73,125}
\definecolor{pubpreparation}{RGB}{130,130,130}
\newcommand{\@ttPubLabel@published}{Published}
\newcommand{\@ttPubLabel@accepted}{Accepted}
\newcommand{\@ttPubLabel@review}{Under review}
\newcommand{\@ttPubLabel@submitted}{Submitted}
\newcommand{\@ttPubLabel@preparation}{In preparation}

\newcommand{\@ttPubBadge}[1]{%
  \ifcsname @ttPubLabel@#1\endcsname
    \tikz[baseline=(b.base)]\node(b)[fill=pub#1, text=white, rounded corners=2pt,
      inner xsep=3pt, inner ysep=1.5pt, font=\scriptsize\sffamily]
      {\csname @ttPubLabel@#1\endcsname};%
  \fi}

\newenvironment{publications}{%
  \par\begin{refsection}%
  \begin{list}{}{\leftmargin=0pt\itemindent=0pt\labelwidth=0pt\labelsep=0pt
                 \itemsep=0.45em\parsep=0pt\topsep=0.2em}%
  \small
}{%
  \end{list}\end{refsection}\par
}
\newcommand{\pubsection}[1]{%
  \item[]\vspace{0.4em}{\normalsize\bfseries\color{bannerblue}#1}}
\newcommand{\pub}[2][]{\item[]\@ttPubBadge{#1}\ \fullcite{#2}}
\newcommand{\pubtext}[2][]{\item[]\@ttPubBadge{#1}\ #2}

% \cmd{shape} -> \shape in typewriter type (for slides about LaTeX itself)
\newcommand{\cmd}[1]{\texttt{\textbackslash #1}}

% ---------------------------------------------------------------- Shapes
%   \begin{annotate}[grid]            shapes over an element: fractions of its
%     \includegraphics[...]{...}      width/height, origin bottom left
%     \shape<2->[color=green!60!black]{rect}{0.45,0.05}{0.56,0.95}
%   \end{annotate}
%   \slideshape[...]{arrow}{0.1,0.2}{0.3,0.4}   fractions of the whole slide
%   \slidegrid                                  coordinate grid on this slide
% Kinds: rect, ellipse (circle), line, arrow: {x1,y1}{x2,y2} (ellipse: its
% bounding box); text: {x,y}{text}; axes: {x,y}{length} -- a coordinate
% indicator, length a fraction of the width (xlabel=$x$, ylabel=$y$,
% zlabel= adds an oblique z axis, oblique=its angle from x (225), rotate=
% degrees; an empty label leaves that axis out). Options: color (red), width (1pt),
% fill (none), opacity (of the fill: 0.25, text 1), dashed, rounded (3pt),
% border (text only: a frame, in the text colour or border=<colour>), label,
% labelpos (above/below/left/right), textsize (\small). <spec> as in beamer.
% Shapes never change the layout. annotate shows a grid (0.1 steps) with
% the grid option or while \showdividers is on, to read off coordinates.
\usetikzlibrary{arrows.meta}
\newif\ifttsh@dashed
\def\ttsh@none{none}
\def\ttsh@true{true}
\newcommand{\@ttShapeStyle}{%
  \tikzset{ttshape/.style={draw=\ttsh@color, line width=\ttsh@width,
                           rounded corners=\ttsh@rounded}}%
  \ifttsh@dashed\tikzset{ttshape/.append style={dashed}}\fi
  \ifx\ttsh@fill\ttsh@none\else
    \tikzset{ttshape/.append style={fill=\ttsh@fill,
      fill opacity=\ifx\ttsh@opacity\@empty 0.25\else\ttsh@opacity\fi}}\fi}
\newcommand{\@ttShapeLabel}[3]{% anchor, x, y
  \ifx\ttsh@label\@empty\else
    \node[anchor=#1, text=\ttsh@color, font=\ttsh@size, inner sep=2pt]
      at (#2,#3) {\ttsh@label};\fi}
\newcommand{\@ttShapeLabelAt}{%
  \ifcase\ttsh@pos\relax
       \@ttShapeLabel{south}{\ttsh@xc}{\ttsh@ymax}%
  \or  \@ttShapeLabel{north}{\ttsh@xc}{\ttsh@ymin}%
  \or  \@ttShapeLabel{east}{\ttsh@xmin}{\ttsh@yc}%
  \or  \@ttShapeLabel{west}{\ttsh@xmax}{\ttsh@yc}\fi}
\newcommand{\@ttShapeRect}{\@ttShapeStyle
  \draw[ttshape] (\ttsh@xa,\ttsh@ya) rectangle (\ttsh@xb,\ttsh@yb);\@ttShapeLabelAt}
\newcommand{\@ttShapeEllipse}{\@ttShapeStyle
  \draw[ttshape] (\ttsh@xc,\ttsh@yc) ellipse [x radius=\ttsh@rx, y radius=\ttsh@ry];%
  \@ttShapeLabelAt}
\newcommand{\@ttShapeLine}[1]{\@ttShapeStyle
  \draw[ttshape, fill=none, #1] (\ttsh@xa,\ttsh@ya) -- (\ttsh@xb,\ttsh@yb);\@ttShapeLabelAt}
% text: fill = background (opaque unless opacity is given), border = frame
% (bare: in the text colour) drawn with width / dashed / rounded
\newcommand{\@ttShapeText}[1]{%
  \tikzset{ttshape/.style={text=\ttsh@color, font=\ttsh@size, inner sep=1pt}}%
  \ifx\ttsh@fill\ttsh@none\else
    \tikzset{ttshape/.append style={inner sep=2.5pt, fill=\ttsh@fill, text opacity=1,
      fill opacity=\ifx\ttsh@opacity\@empty 1\else\ttsh@opacity\fi}}\fi
  \ifx\ttsh@border\ttsh@none\else
    \ifx\ttsh@border\ttsh@true\let\ttsh@border\ttsh@color\fi
    \tikzset{ttshape/.append style={inner sep=2.5pt, draw=\ttsh@border,
      line width=\ttsh@width, rounded corners=\ttsh@rounded}}%
    \ifttsh@dashed\tikzset{ttshape/.append style={dashed}}\fi\fi
  \node[ttshape] at (\ttsh@xa,\ttsh@ya) {#1};}
% axes: arrows from (\ttsh@xa,\ttsh@ya), \ttsh@len long, x at \ttsh@rot degrees,
% y at +90, an oblique z (0.7 long, at +\ttsh@obl); an axis with an empty
% label is left out; the labels sit just beyond the arrow tips
\newcommand{\@ttShapeAxis}[3]{% angle, length factor, label
  \pgfmathsetmacro\ttsh@a{#1}\pgfmathsetmacro\ttsh@b{#1+180}%
  \draw[ttshape, fill=none, -{Stealth}] (\ttsh@xa,\ttsh@ya) -- ++(\ttsh@a:#2*\ttsh@len)
    node[anchor=\ttsh@b, text=\ttsh@color, font=\ttsh@size, inner sep=1.5pt] {#3};}
\newcommand{\@ttShapeAxes}{\@ttShapeStyle
  \ifx\ttsh@xlabel\@empty\else\@ttShapeAxis{\ttsh@rot}{1}{\ttsh@xlabel}\fi
  \ifx\ttsh@ylabel\@empty\else\@ttShapeAxis{\ttsh@rot+90}{1}{\ttsh@ylabel}\fi
  \ifx\ttsh@zlabel\@empty\else\@ttShapeAxis{\ttsh@rot+\ttsh@obl}{0.7}{\ttsh@zlabel}\fi}
% grid over \ttsh@W x \ttsh@H (origin bottom left, shifted by \ttsh@yo);
% numbers outside an element, inside the page edge for \slidegrid
\def\ttsh@xanchor{north}\def\ttsh@yanchor{east}
\newcommand{\@ttShapeGrid}{%
  \foreach \ttsh@i in {0,...,10}{%
    \pgfmathsetmacro\ttsh@f{\ttsh@i/10}%
    \ifodd\ttsh@i\def\ttsh@lw{0.2pt}\else\def\ttsh@lw{0.4pt}\fi
    \draw[dividercolor, line width=\ttsh@lw, opacity=0.6]
      (\ttsh@f*\ttsh@W,\ttsh@yo) -- ++(0,\ttsh@H)
      (0,\ttsh@yo+\ttsh@f*\ttsh@H) -- ++(\ttsh@W,0);
    \ifodd\ttsh@i\else
      \node[font=\tiny\sffamily, text=dividercolor, fill=white, fill opacity=0.7,
            text opacity=1, inner sep=0.5pt, anchor=\ttsh@xanchor]
        at (\ttsh@f*\ttsh@W,\ttsh@yo) {\pgfmathprintnumber[fixed,precision=1]{\ttsh@f}};
      \node[font=\tiny\sffamily, text=dividercolor, fill=white, fill opacity=0.7,
            text opacity=1, inner sep=0.5pt, anchor=\ttsh@yanchor]
        at (0,\ttsh@yo+\ttsh@f*\ttsh@H) {\pgfmathprintnumber[fixed,precision=1]{\ttsh@f}};
    \fi}}
% #1: the shapes; a picture whose origin is the current point
\newcommand{\@ttShapePicture}[1]{%
  \begin{tikzpicture}[overlay]#1\end{tikzpicture}}
% the shapes alone, in a picture of the annotated element's size
\newcommand{\@ttShapesPicture}[2]{% shapes, grid?
  \begin{tikzpicture}
    \path (0,0) rectangle (\ttsh@W,\ttsh@H);
    #2#1
  \end{tikzpicture}}
\newcommand{\@ttAnnotatePicture}[3]{% box, shapes, grid?
  \begin{tikzpicture}[baseline=(ttbox.south)]
    \node[inner sep=0pt, anchor=south west] (ttbox) at (0,0) {\usebox{#1}};
    \begin{scope}[overlay]#3#2\end{scope}
  \end{tikzpicture}}

\ExplSyntaxOn
% animate shows its frames as PDF widget annotations, which viewers draw above
% all page content. Shapes and slide shapes on a page with an
% animation therefore become an annotation of their own (a read-only stamp
% whose appearance is the drawing), added after animate's so it is drawn on
% top. pdfTeX only; elsewhere, and on pages without animations, the drawing
% stays ordinary page content.
\bool_new:N \g__tt_anim_page_bool     % this page has an animation
\bool_new:N \g__tt_anim_seen_bool     % the current annotate holds one
\box_new:N  \l__tt_over_box       % \__tt_over:N's own
\box_new:N  \l__tt_over_in_box    % the drawing handed to it
\dim_const:Nn \c__tt_over_margin_dim { 30pt }   % room for labels outside the box
\cs_new_protected:Npn \__tt_over:N #1
  {
    \bool_lazy_and:nnTF { \sys_if_engine_pdftex_p: } { \g__tt_anim_page_bool }
      {
        \hbox_set:Nn \l__tt_over_box
          { \skip_horizontal:N \c__tt_over_margin_dim \box_use:N #1
            \skip_horizontal:N \c__tt_over_margin_dim }
        \box_set_ht:Nn \l__tt_over_box { \box_ht:N #1 + \c__tt_over_margin_dim }
        \box_set_dp:Nn \l__tt_over_box { \box_dp:N #1 + \c__tt_over_margin_dim }
        \hbox:n
          {
            \skip_horizontal:n { -\c__tt_over_margin_dim }
            \tex_immediate:D \tex_pdfxform:D \l__tt_over_box
            \tex_pdfannot:D
              width  \dim_eval:n { \box_wd:N #1 + 2 \c__tt_over_margin_dim }
              height \dim_eval:n { \box_ht:N #1 + \c__tt_over_margin_dim }
              depth  \dim_eval:n { \box_dp:N #1 + \c__tt_over_margin_dim }
              { /Subtype /Stamp /F~196 /AP << /N~\int_use:N \tex_pdflastxform:D \c_space_tl 0~R >> }
            \skip_horizontal:n { \c__tt_over_margin_dim + \box_wd:N #1 }
          }
      }
      { \box_use:N #1 }
  }
\dim_new:N  \l__tt_sh_w_dim
\dim_new:N  \l__tt_sh_h_dim
\dim_new:N  \l__tt_sh_yo_dim
\tl_new:N   \g__tt_sh_ann_tl
\tl_new:N   \g__tt_sh_page_tl
\bool_new:N \l__tt_sh_grid_bool
\newsavebox \l__tt_sh_box
\tl_new:N   \l__tt_sh_pos_tl

\keys_define:nn { tt / shape }
  {
    color    .tl_set:N   = \ttsh@color,
    color    .initial:n  = red,
    width    .tl_set:N   = \ttsh@width,
    width    .initial:n  = 1pt,
    fill     .tl_set:N   = \ttsh@fill,
    fill     .initial:n  = none,
    opacity  .tl_set:N   = \ttsh@opacity,
    opacity  .initial:n  = ,
    border   .tl_set:N   = \ttsh@border,
    border   .initial:n  = none,
    border   .default:n  = true,
    dashed   .code:n     = { \str_if_eq:nnTF {#1} {false}
                               { \ttsh@dashedfalse } { \ttsh@dashedtrue } },
    dashed   .default:n  = true,
    rounded  .tl_set:N   = \ttsh@rounded,
    rounded  .initial:n  = 0pt,
    rounded  .default:n  = 3pt,
    label    .tl_set:N   = \ttsh@label,
    label    .initial:n  = ,
    labelpos .tl_set:N   = \l__tt_sh_pos_tl,
    labelpos .initial:n  = above,
    textsize .tl_set:N   = \ttsh@size,
    textsize .initial:n  = \small,
    xlabel   .tl_set:N   = \ttsh@xlabel,
    xlabel   .initial:n  = $x$,
    ylabel   .tl_set:N   = \ttsh@ylabel,
    ylabel   .initial:n  = $y$,
    zlabel   .tl_set:N   = \ttsh@zlabel,
    zlabel   .initial:n  = ,
    oblique  .tl_set:N   = \ttsh@obl,
    oblique  .initial:n  = 225,
    rotate   .tl_set:N   = \ttsh@rot,
    rotate   .initial:n  = 0,
  }
\keys_define:nn { tt / annotate }
  {
    grid .bool_set:N = \l__tt_sh_grid_bool,
    grid .default:n  = true,
    grid .initial:n  = false,
  }

% fraction pair "x,y" -> \ttsh@x<#2>, \ttsh@y<#2> in pt
\cs_new_protected:Npn \__tt_sh_point:nn #1#2
  {
    \seq_set_split:Nnn \l_tmpa_seq { , } {#1}
    \tl_set:cx { ttsh@x #2 }
      { \fp_to_dim:n { ( \seq_item:Nn \l_tmpa_seq { 1 } ) * \l__tt_sh_w_dim } }
    \tl_set:cx { ttsh@y #2 }
      { \fp_to_dim:n { ( \seq_item:Nn \l_tmpa_seq { 2 } ) * \l__tt_sh_h_dim
                       + \l__tt_sh_yo_dim } }
  }
\cs_new_protected:Npn \__tt_sh_box:
  {
    \tl_set:Nx \ttsh@xmin { \fp_to_dim:n { min(\ttsh@xa, \ttsh@xb) } }
    \tl_set:Nx \ttsh@xmax { \fp_to_dim:n { max(\ttsh@xa, \ttsh@xb) } }
    \tl_set:Nx \ttsh@ymin { \fp_to_dim:n { min(\ttsh@ya, \ttsh@yb) } }
    \tl_set:Nx \ttsh@ymax { \fp_to_dim:n { max(\ttsh@ya, \ttsh@yb) } }
    \tl_set:Nx \ttsh@xc   { \fp_to_dim:n { (\ttsh@xa + \ttsh@xb) / 2 } }
    \tl_set:Nx \ttsh@yc   { \fp_to_dim:n { (\ttsh@ya + \ttsh@yb) / 2 } }
    \tl_set:Nx \ttsh@rx   { \fp_to_dim:n { abs(\ttsh@xb - \ttsh@xa) / 2 } }
    \tl_set:Nx \ttsh@ry   { \fp_to_dim:n { abs(\ttsh@yb - \ttsh@ya) / 2 } }
    \tl_set:Nx \ttsh@pos
      { \str_case:VnF \l__tt_sh_pos_tl
          { {above} {0} {below} {1} {left} {2} {right} {3} } {0} }
  }
\cs_generate_variant:Nn \str_case:nnF { VnF }
% one shape, drawn inside a tikzpicture: {options}{kind}{point}{point or text}
\cs_new_protected:Npn \__tt_sh_draw:nnnn #1#2#3#4
  {
    \group_begin:
    \keys_set:nn { tt / shape } {#1}
    \__tt_sh_point:nn {#3} { a }
    \str_if_eq:nnTF {#2} { text }
      { \@ttShapeText {#4} }
      {
    \str_if_eq:nnTF {#2} { axes }                % {origin}{length: fraction of the width}
      {
        \tl_set:Nx \ttsh@len { \fp_to_dim:n { (#4) * \l__tt_sh_w_dim } }
        \@ttShapeAxes
      }
      {
        \__tt_sh_point:nn {#4} { b }
        \__tt_sh_box:
        \str_case:nnF {#2}
          {
            { rect }    { \@ttShapeRect }
            { ellipse } { \@ttShapeEllipse }
            { circle }  { \@ttShapeEllipse }
            { line }    { \@ttShapeLine { } }
            { arrow }   { \@ttShapeLine { -{Stealth} } }
          }
          { \msg_warning:nnn { beamer2html } { shape } {#2} }
      }
      }
    \group_end:
  }
\msg_new:nnn { beamer2html } { shape }
  { unknown~shape~'#1'~(use~rect,~ellipse,~circle,~line,~arrow,~text~or~axes) }

% \shape / \slideshape: queue the shape (only on the overlays of <spec>)
\cs_new_protected:Npn \__tt_sh_queue:Nnnnnn #1#2#3#4#5#6
  {
    \IfNoValueTF {#2}
      { \tl_gput_right:Nn #1 { \__tt_sh_draw:nnnn {#3} {#4} {#5} {#6} } }
      { \only<#2>{ \tl_gput_right:Nn #1 { \__tt_sh_draw:nnnn {#3} {#4} {#5} {#6} } } }
  }
\NewDocumentCommand \shape { d<> O{} m m m }
  { \unskip \__tt_sh_queue:Nnnnnn \g__tt_sh_ann_tl {#1} {#2} {#3} {#4} {#5} \ignorespaces }
\NewDocumentCommand \slideshape { d<> O{} m m m }
  { \__tt_sh_queue:Nnnnnn \g__tt_sh_page_tl {#1} {#2} {#3} {#4} {#5} \ignorespaces }
\NewDocumentCommand \slidegrid { }
  {
    \tl_gput_right:Nn \g__tt_sh_page_tl
      { \group_begin: \def\ttsh@xanchor{south} \def\ttsh@yanchor{west}
        \@ttShapeGrid \group_end: }
    \ignorespaces
  }

\NewDocumentEnvironment { annotate } { O{} }
  {
    \keys_set:nn { tt / annotate } {#1}
    \if@ttdividers \bool_set_true:N \l__tt_sh_grid_bool \fi
    \tl_gclear:N \g__tt_sh_ann_tl
    \bool_gset_false:N \g__tt_anim_seen_bool
    \begin{lrbox}{\l__tt_sh_box} \ignorespaces
  }
  {
    \unskip \end{lrbox}
    \dim_set:Nn \l__tt_sh_w_dim { \wd \l__tt_sh_box }
    \dim_set:Nn \l__tt_sh_h_dim { \ht \l__tt_sh_box + \dp \l__tt_sh_box }
    \dim_zero:N \l__tt_sh_yo_dim
    \tl_set:Nx \ttsh@W  { \dim_use:N \l__tt_sh_w_dim }
    \tl_set:Nx \ttsh@H  { \dim_use:N \l__tt_sh_h_dim }
    \tl_set:Nn \ttsh@yo { 0pt }
    \leavevmode
    \bool_lazy_and:nnTF { \sys_if_engine_pdftex_p: } { \g__tt_anim_seen_bool }
      {                                          % over an animation: the content,
        \hbox_overlap_right:n                    % then the shapes as a stamp
          { \@ttAnnotatePicture { \l__tt_sh_box } { } { } }
        \hbox_set:Nn \l__tt_over_in_box
          {
            \@ttShapesPicture { \g__tt_sh_ann_tl }
              { \bool_if:NT \l__tt_sh_grid_bool { \@ttShapeGrid } }
          }
        \__tt_over:N \l__tt_over_in_box
      }
      {
        \@ttAnnotatePicture { \l__tt_sh_box } { \g__tt_sh_ann_tl }
          { \bool_if:NT \l__tt_sh_grid_bool { \@ttShapeGrid } }
      }
    \tl_gclear:N \g__tt_sh_ann_tl
  }

% slide shapes go on top of the finished page (origin: its top left corner)
\AddToHook { shipout / foreground }
  {
    \tl_if_empty:NF \g__tt_sh_page_tl
      {
        \dim_set_eq:NN \l__tt_sh_w_dim \paperwidth
        \dim_set_eq:NN \l__tt_sh_h_dim \paperheight
        \dim_set:Nn \l__tt_sh_yo_dim { -\paperheight }
        \tl_set:Nx \ttsh@W  { \dim_use:N \paperwidth }
        \tl_set:Nx \ttsh@H  { \dim_use:N \paperheight }
        \tl_set:Nx \ttsh@yo { \dim_eval:n { -\paperheight } }
        \bool_lazy_and:nnTF { \sys_if_engine_pdftex_p: } { \g__tt_anim_page_bool }
          {                                      % a page-sized picture, as a stamp
            \hbox_set:Nn \l__tt_over_in_box
              {
                \begin{tikzpicture}
                  \path (0,-\paperheight) rectangle (\paperwidth,0);
                  \g__tt_sh_page_tl
                \end{tikzpicture}
              }
            \put(0,0){ \rlap { \raisebox { -\paperheight } { \__tt_over:N \l__tt_over_in_box } } }
          }
          { \put(0,0){ \@ttShapePicture { \g__tt_sh_page_tl } } }
        \tl_gclear:N \g__tt_sh_page_tl
      }
    \bool_gset_false:N \g__tt_anim_page_bool
  }
\ExplSyntaxOff

% ---------------------------------------------------------------- Flow diagrams
% TikZ styles for flowcharts in the deck's colours:
%   \begin{tikzpicture}[flow]
%     \node[flowstart]                     (s) {Start};
%     \node[flowbox, below=of s]           (a) {Mesh};
%     \node[flowdecision, below=of a]      (d) {Converged?};
%     \draw[flowarrow] (s) -- (a);
%     \draw[flowarrow] (a) -- (d);
%     \draw[flowarrow] (d.east) -- ++(6mm,0) |- node[flowlabel, near start] {no} (a);
%   \end{tikzpicture}
% flow (on the picture): node distance 6mm and 10mm, \small. Nodes: flowbox
% (a step), flowstart (start/end), flowdecision (a question), flowio (input/
% output). flowarrow for the arrows, flowlabel for text on them. Step by
% step: \node<2->[...] (takes its space only from step 2), or visible on=<2->
% (overlay-beamer-styles: keeps its space, so nothing moves).
\usetikzlibrary{shapes.geometric, shapes.misc, positioning, graphs}
\IfFileExists{tikzlibraryoverlay-beamer-styles.code.tex}
  {\usetikzlibrary{overlay-beamer-styles}}{}
\tikzset{
  flow/.style={node distance=6mm and 10mm, font=\small},
  flownode/.style={draw=bannerblue, line width=0.8pt, align=center, text=black,
                   minimum height=8mm, inner sep=4pt},
  flowbox/.style={flownode, rectangle, rounded corners=2pt, fill=bannerblue!8,
                  minimum width=24mm},
  flowstart/.style={flownode, rounded rectangle, fill=bannerblue!22, minimum width=20mm},
  flowdecision/.style={flownode, diamond, aspect=2, inner sep=1pt,
                       draw=statusongoing!80!black, fill=statusongoing!15},
  flowio/.style={flownode, trapezium, trapezium left angle=70, trapezium right angle=110,
                 trapezium stretches body, fill=white, minimum width=22mm},
  flowarrow/.style={draw=black!70, line width=0.8pt, -{Stealth[length=2.2mm]}},
  flowlabel/.style={font=\footnotesize, fill=white, inner sep=1pt, text=black!70},
}

\endinput
