% 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 and loads lum-euler; both are left to the % document, which must \setsansfont, \setmonofont and load a maths font. \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}% } % -- Colours. Set your own accent anywhere in the preamble with, e.g., % \definecolor{accent}{HTML}{BF1238} % \definecolor{TrantorDark}{HTML}{23373B} % Four colour palette: % 1. accent progress bars, title rule % 2. TrantorDark headings, section dividers, standout panels, frame numbers % 3. TrantorLight text set against TrantorDark % 4. TrantorMuted affiliation, footline, arxiv. 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). % headsep is what pads under the title; the height key alone only pads above. \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:" 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: frames after it are numbered among themselves and left out of % the total. The count travels through the aux file, so it settles on the % second run. \newif\iftrantorAppendix \trantorAppendixfalse \newcommand\trantorMainFrames{\@totalframes} \NewDocumentCommand \appendix { } {% \global\trantorAppendixtrue \protected@write\@auxout{}{% \string\gdef\string\trantorMainFrames{\the\value{frame}}}% } % Count registers, not macros: pgfmath expands while parsing, and a macro % carrying a conditional loops. \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 } % Fraction of the way through the talk, shared by the footer bar and % \trantorProgressBar. The zero test must stay outside pgfmath, which % evaluates both arms of a ternary. \newcommand\trantor@setratio{% \trantor@setcounts \ifnum\trantor@d>0 \pgfmathsetmacro{\trantorratio}{min(\the\trantor@n / \the\trantor@d, 1)}% \else \def\trantorratio{0}% \fi } % \trantorProgressBar{}: a rule of the given width, filled to the % current position and grey the rest of the way. \newlength\trantor@barwidth \newlength\trantor@barfill \NewDocumentCommand \trantorProgressBar { m } {% \setlength\trantor@barwidth{#1}% \trantor@setratio % dimen = , so \trantorratio scales the width \setlength\trantor@barfill{\trantorratio\trantor@barwidth}% \textcolor{TrantorAccent}{\rule{\trantor@barfill}{0.6pt}}% \textcolor{TrantorDark!20}{% \rule{\dimexpr\trantor@barwidth-\trantor@barfill\relax}{0.6pt}}% } % -- Bottom progress bar + frame counter (hidden on plain pages). \newif\iftrantorPlainPage \trantorPlainPagefalse \AddToHook{shipout/foreground}{% \iftrantorPlainPage\else \trantorArtifactBegin \begin{tikzpicture}[remember picture, overlay] \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=\tiny, text=TrantorDark] at (current page.south east) {\the\trantor@n\,/\,\the\trantor@d}; \end{tikzpicture}% \trantorArtifactEnd \fi \global\trantorPlainPagefalse } % -- Remember the section and subsection titles, for the dividers. \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 when a new section % starts so a stale 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 } } } \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}% } % -- A full-bleed dark panel with the given body centred in a minipage of the % given width. \NewDocumentCommand \trantor@panel { m +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}{#1} \centering #2 \end{minipage}% }; \end{tikzpicture}% \end{frame}% } % -- Divider pages: the section title over a progress bar, set on the ordinary % light background. Not plain pages: the footer bar and frame number stay. \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}% #1% \end{minipage}% \par \vfill \end{frame}% } \NewDocumentCommand \sectionpage { } { \trantor@divider{} } % The subsection divider is the same, naming the subsection under the bar. \NewDocumentCommand \subsectionpage { } {% \trantor@divider{% \par\vspace{0.8em}% {\headerfont\large\bfseries\trantorSubsectionTitle\par}% }% } % \standout{...}: a full-bleed dark panel with the given text centred on it. \NewDocumentCommand \standout { +m } {% \trantor@panel{0.8\linewidth}{\headerfont\LARGE\bfseries #1}% } \makeatother