% trantor.sty -- a Metropolis-style theme for the ltx-talk class.
% LuaLaTeX only.
%
% Copyright (c) 2026 Marco Pompili, https://github.com/mrc-pop/trantor
% Received under the MIT license and redistributed here, with modifications,
% under the GNU GPL version 3 or later. See COPYING.
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{trantor}[2026-08-01 v1.0 Metropolis-like theme for ltx-talk]

% LuaTeX only (XeTeX is unsupported and the fonts need a Unicode engine).
\RequirePackage{iftex}
\RequireLuaTeX
\RequirePackage{tikz}
\RequirePackage{pifont}

% -- Fonts. Upstream sets Libertinus here and loads lum-euler for maths.
% Both are left to the document instead: this template ships OpenSans and
% RobotoMono, and lum-euler is not packaged in Debian's TeX Live. The document
% is expected to \setsansfont, \setmonofont and load a Unicode maths font
% before using anything below. \headerfont is what the theme uses for titles
% and other display text; redefine it if you want titles in a second face.
\renewcommand{\familydefault}{\sfdefault}
\linespread{1.18}
\providecommand\headerfont{\sffamily}
\makeatletter

% Tighten the space around display maths -- the default 11pt reads as a big gap
% on a slide, especially inside a list. Re-applied on every \normalsize so the
% size files don't reset it.
\g@addto@macro\normalsize{%
  \setlength\abovedisplayskip{5pt plus 2pt minus 1pt}%
  \setlength\belowdisplayskip{5pt plus 2pt minus 1pt}%
  \setlength\abovedisplayshortskip{5pt plus 2pt minus 1pt}%
  \setlength\belowdisplayshortskip{5pt plus 2pt minus 1pt}%
}

% -- Palette. Four colours drive everything the theme draws. Override any of
% them in the preamble, before or after loading trantor:
%
%   \definecolor{accent}{HTML}{BF1238}
%   \colorlet{TrantorDark}{makopurple1}
%
% accent      progress bars and emphasis
% TrantorDark header band, section dividers, standout panels, frame numbers
% TrantorLight text on dark, and anything that has to read against it
% TrantorMuted affiliations, footlines, arxiv tags. This is the lightest grey
%              that still meets WCAG AA contrast (4.5:1) on white (~4.54:1).
\providecolor{accent}{HTML}{2A7AE2}
\providecolor{TrantorDark}{HTML}{000000}
\providecolor{TrantorLight}{HTML}{FFFFFF}
\providecolor{TrantorMuted}{HTML}{767676}
\AddToHook{begindocument}{\colorlet{TrantorAccent}{accent}}

% itemize/enumerate labels in the body colour rather than the class default
\AddToHook{begindocument}{\colorlet{structure}{TrantorDark}}

% -- Header bar carrying the frame title (hidden on plain pages).
% The band is a rule of height (header height - headsep) with depth headsep, so
% headsep is the only thing that puts space under the title. geometry owns it.
\geometry{headsep=10pt}


\AddToHook{begindocument}{%
  \EditInstance{header}{std}{
    background-color  = TrantorDark,
    color             = TrantorLight,
    print-frame-title = true,
    height            = 40pt,
    font              = \headerfont\bfseries,
    left-hspace       = \dimexpr\Gm@lmargin - 0.6cm\relax,
  }%
  \EditInstance{footer}{std}{element-order = {}}%
  \EditInstance{frametitle}{header}{after-vspace = 6mm minus 3mm}%
}

% Like \frametitle, but forces a gap below the title on packed slides where the
% header's own spacing collapses. The \par gets us into vertical mode first.
\NewDocumentCommand\stitle{m}{\frametitle{#1}\par\addvspace{4mm}}

% \standoutline{...}: a centred line of text in the title's sans/size/colour,
% for emphasis within a normal (light) frame, e.g. closing a slide.
\NewDocumentCommand\standoutline{m}{%
  \par
  \begingroup
    \centering\headerfont\Large\bfseries\color{TrantorDark}#1\par
  \endgroup
}

% Tick and cross, for marking things right or wrong.
\NewDocumentCommand\yes{}{\ding{51}}
\NewDocumentCommand\no{}{\ding{55}}

% \footline{...}: a small reference or citation pinned to the bottom-left of
% the slide. Overlaid, so it can go anywhere inside the frame.
\NewDocumentCommand\footline{m}{%
  \begin{tikzpicture}[remember picture, overlay]
    \node[anchor=south west, xshift=1ex, yshift=0.7ex, font=\scriptsize, text=TrantorMuted]
      at (current page.south west) {#1};
  \end{tikzpicture}%
}

% \arxiv{1306.5001}: a small, grey "arXiv:<id>" tag linked to the abstract.
% Meant to sit inline next to a statement, e.g. a theorem.
\NewDocumentCommand\arxiv{m}{%
  \href{https://arxiv.org/abs/#1}{{\headerfont\scriptsize\color{TrantorMuted}arXiv:#1}}%
}

% -- \appendix, which ltx-talk does not provide. Frames after it keep counting
% but are left out of the total, so the denominator is the last frame of the
% talk proper. This is what appendixnumberbeamer does for beamer. The count is
% carried through the aux file, so it settles on the second run.
\newif\iftrantorAppendix
\trantorAppendixfalse
\newcommand\trantorMainFrames{\@totalframes}

% Appendix frames are numbered among themselves, as appendixnumberbeamer does,
% but by subtracting an offset rather than resetting the counter. The class
% records a property per frame keyed on that counter, so resetting it collides
% with ltx-talk's own frame.<n> labels and breaks \ref to a frame. Properly
% this belongs in the class, which could renumber and relabel together.
\NewDocumentCommand \appendix { } {%
  \global\trantorAppendixtrue
  \protected@write\@auxout{}{%
    \string\gdef\string\trantorMainFrames{\the\value{frame}}}%
}

% Numerator and denominator for the frame counter and the progress bars. These
% are count registers rather than macros because pgfmath expands its argument
% while parsing, and a macro carrying a conditional sends it into a loop.
\newcount\trantor@n
\newcount\trantor@d

\newcommand\trantor@setcounts{%
  \iftrantorAppendix
    \trantor@n=\numexpr\value{frame}-\trantorMainFrames\relax
    \trantor@d=\numexpr\@totalframes-\trantorMainFrames\relax
  \else
    \trantor@n=\value{frame}\relax
    \trantor@d=\numexpr\trantorMainFrames\relax
  \fi
}

\newcommand\trantorFrameNumber{\trantor@setcounts\the\trantor@n}
\newcommand\trantorTotalFrames{\trantor@setcounts\the\trantor@d}

\newcommand\trantor@setratio{%
  \ifnum\trantor@d>0
    \pgfmathsetmacro{\trantorratio}{min(\the\trantor@n / \the\trantor@d, 1)}%
  \else
    \def\trantorratio{0}%
  \fi
}

% -- Bottom progress bar + frame counter (hidden on plain pages).
\newif\iftrantorPlainPage
\trantorPlainPagefalse

\AddToHook{shipout/foreground}{%
  \iftrantorPlainPage\else
  \trantorArtifactBegin
  \begin{tikzpicture}[remember picture, overlay]
    \trantor@setcounts
    \trantor@setratio
    \fill[TrantorAccent]
      (current page.south west)
      rectangle
      ([xshift=\trantorratio\paperwidth, yshift=1pt] current page.south west);
    \node[anchor=south east, inner sep=5pt, font=\scriptsize, text=TrantorDark]
      at (current page.south east)
      {\the\trantor@n\,/\,\the\trantor@d};
  \end{tikzpicture}%
  \trantorArtifactEnd
  \fi
  \global\trantorPlainPagefalse
}

% -- Remember the current section title, and dim the others in the mini-TOC.
\ExplSyntaxOn
\tl_new:N \g__trantor_section_tl

\AddToHook{section/begin}{
  \cs_if_exist:NT \l__talk_section_tl {
    \tl_gset:NV \g__trantor_section_tl \l__talk_section_tl
  }
}

\cs_new_protected:Npn \__trantor_print_section: { \tl_use:N \g__trantor_section_tl }
\cs_set_eq:NN \trantorSectionTitle \__trantor_print_section:

% Likewise the subsection, for \subsectionpage. Cleared at each new section so
% that a stale subsection title cannot leak into the next one.
\tl_new:N \g__trantor_subsection_tl

\AddToHook{section/begin}{ \tl_gclear:N \g__trantor_subsection_tl }

\AddToHook{subsection/begin}{
  \cs_if_exist:NT \l__talk_subsection_tl {
    \tl_gset:NV \g__trantor_subsection_tl \l__talk_subsection_tl
  }
}

\cs_new_protected:Npn \__trantor_print_subsection: { \tl_use:N \g__trantor_subsection_tl }
\cs_set_eq:NN \trantorSubsectionTitle \__trantor_print_subsection:

% Suspend tagging around the theme's decorative / pagination graphics when
% tagging is on (a no-op otherwise), so the progress bar and frame counter stay
% out of the structure tree instead of being flagged as loose content.
\cs_new_protected:Npn \trantorArtifactBegin
  { \cs_if_exist:NT \tag_suspend:n { \tag_suspend:n { trantor } } }
\cs_new_protected:Npn \trantorArtifactEnd
  { \cs_if_exist:NT \tag_resume:n { \tag_resume:n { trantor } } }

% Soften the dimming of inactive TOC entries on the section page (0.2 -> 0.7).
% ltx-talk exposes no key for this yet, so we override the class-private
% \__talk_toc_dest:w. Revisit if a supported interface appears or the internal
% changes.
\cs_set_protected:Npn \__talk_toc_dest:w #1 . #2 . #3 . #4 . #5 \q_stop #6
  {
    \bool_lazy_or:nnTF
      {
        \bool_lazy_and_p:nn
          { \int_compare_p:nNn { \value { section } } = {#2} }
          { \int_compare_p:nNn { \value { subsection } } = 0 }
       }
       {
         \bool_lazy_and_p:nn
           { \int_compare_p:nNn { \value { section } } = {#2} }
           { \int_compare_p:nNn { \value { subsection } } = {#3} }
      }
      {#6}
      { \opacity_begin:n { 0.7 } #6 \opacity_end: }
  }
\ExplSyntaxOff

% -- Title page. Drop a logo.png next to your slides, or redefine \titlelogo.
\newcommand{\titlelogo}{%
  \IfFileExists{logo.png}
    {\includegraphics[height=1.4cm, alt={Organisation logo}]{logo.png}}{}%
}

\renewcommand{\maketitle}{%
  \global\trantorPlainPagetrue
  \begin{frame}{}%
    \thispagestyle{plain}%
    \vfill
    \hspace*{0.04\paperwidth}%
    \begin{minipage}{0.82\paperwidth}
      \raggedright
      {\color{TrantorDark}\headerfont\Large\bfseries \@title\par}%
      \ifx\@subtitle\empty\else
        \vspace{0.35em}%
        {\color{TrantorDark}\headerfont\large \@subtitle\par}%
      \fi
      \vspace{0.5em}%
      {\color{TrantorAccent}\rule{\linewidth}{0.8pt}\par}%
      \vspace{1.3em}%
      % Metadata on the left, logo on the right; bottom-aligned so the logo sits
      % level with the date and grows upward rather than below it.
      \begin{minipage}[b]{0.66\linewidth}
        \raggedright
        {\normalsize \@author\par}%
        \ifx\@institute\empty\else
          \vspace{0.2em}%
          {\small\color{TrantorMuted} \@institute\par}%
        \fi
        \vspace{0.2em}%
        {\small \@date\par}%
      \end{minipage}%
      \hfill
      \begin{minipage}[b]{0.30\linewidth}
        \raggedleft
        \titlelogo
      \end{minipage}%
    \end{minipage}%
    \hspace*{0.04\paperwidth}%
    \vspace{0.12\paperheight}%
  \end{frame}%
}

% -- Divider pages, following metropolis: on the ordinary light background, a
% block set in the middle of the slide holding the section title over a
% progress bar, and for a subsection the subsection title beneath that. The
% bar is the same progress the footer bar shows, drawn at half width. The
% footer bar and frame number stay, so these are not plain pages in the sense
% the shipout hook means.
\newlength\trantor@barwidth
\newlength\trantor@barfill

% \trantorProgressBar{<width>}: filled to the current position, grey after.
\NewDocumentCommand \trantorProgressBar { m } {%
  \setlength\trantor@barwidth{#1}%
  \trantor@setcounts
  \trantor@setratio
  \setlength\trantor@barfill{\trantorratio\trantor@barwidth}%
  \textcolor{TrantorDark}{\rule{\trantor@barfill}{0.6pt}}%
  \textcolor{TrantorDark!20}{%
    \rule{\dimexpr\trantor@barwidth-\trantor@barfill\relax}{0.6pt}}%
}

\NewDocumentCommand \trantor@divider { m } {%
  \begin{frame}{}%
    \thispagestyle{plain}%
    \vfill
    \centering
    \begin{minipage}{0.52\linewidth}%
      \raggedright
      {\headerfont\Large\bfseries\trantorSectionTitle\par}%
      \vspace{0.4em}%
      \trantorProgressBar{\linewidth}%
      \IfBlankF{#1}{%
        \par\vspace{0.8em}%
        {\headerfont\large\bfseries #1\par}%
      }%
    \end{minipage}%
    \par
    \vfill
  \end{frame}%
}

\NewDocumentCommand \sectionpage    { } { \trantor@divider {} }
\NewDocumentCommand \subsectionpage { } { \trantor@divider {\trantorSubsectionTitle} }

% \standout{...}: a full frame with the same dark panel as \sectionpage,
% and the given text centred on it.
\NewDocumentCommand \standout { +m } {%
  \global\trantorPlainPagetrue
  \begin{frame}{}%
    \thispagestyle{plain}%
    \noindent
    \begin{tikzpicture}
      \useasboundingbox (0,0) rectangle (\linewidth,-\textheight);
      \fill[TrantorDark]
        (-\Gm@lmargin, \paperheight)
        rectangle
        (\dimexpr\paperwidth-\Gm@lmargin\relax, -\paperheight);
      \node[text=TrantorLight, anchor=center, inner sep=0pt]
        at (0.5\linewidth, -0.5\textheight) {%
        \begin{minipage}{0.8\linewidth}
          \centering
          \headerfont\LARGE\bfseries
          #1
        \end{minipage}%
      };
    \end{tikzpicture}%
  \end{frame}%
}

\makeatother
