You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
kicad_vortrag/template.typ

170 lines
5.6 KiB

// ============================================================================
// template.typ — technical, minimal presentation template
//
// Usage:
// #import "template.typ": *
// #show: presentation.with(title: "...", subtitle: "...", author: "...")
//
// #slide(title: "Title")[ ... ] // two-part slide: title part | content part
// #slide[ ... ] // single-part, full-width slide
// #img("assets/fig.svg") // centered image, optional caption
// ============================================================================
// Palette: warm paper, petrol accent, terracotta counterpoint.
// All text/background pairs keep >= 4.5:1 contrast; decorative
// elements (bars, markers, watermarks) never carry information.
#let paper = rgb("#faf8f3")
#let ink = rgb("#2d2a26")
#let accent = rgb("#22617f")
#let accent-soft = rgb("#4c8fad")
#let ember = rgb("#a85430")
#let wash = rgb("#edf2f5")
#let mono = "DejaVu Sans Mono"
// Short vertical accent rule (petrol -> terracotta).
#let hairline-v = 3.5pt + gradient.linear(accent, ember, angle: 90deg)
// Horizontal hairline for title/section slides.
#let hairline-h = 1pt + gradient.linear(accent, ember, angle: 0deg)
#let slide-counter = counter("slide")
#let deck-title = state("deck-title", none)
// Footer: deck title bottom-left, slide counter bottom-right.
#let slide-footer() = context {
let n = slide-counter.get().first()
let total = slide-counter.final().first()
set text(font: mono, size: 0.55em, fill: luma(150))
place(bottom + left, dy: -1em, deck-title.get())
place(bottom + right, dy: -1em, {
text(fill: accent, weight: "bold", str(n))
text(fill: luma(165), " / " + str(total))
})
}
// Tinted content panel with a colored spine — used for paired columns
// (Grok = ember, Mistral = petrol). The spine is decorative reinforcement;
// the column headers carry the information.
#let panel(body, c: accent) = block(
width: 100%,
fill: c.lighten(95%),
stroke: (left: 2.5pt + c),
inset: (x: 0.55em, y: 0.45em),
radius: (top-right: 2pt, bottom-right: 2pt),
{
set par(spacing: 0.85em)
body
},
)
// Thin gradient band across the top edge of every page.
#let top-band = place(top + left, rect(width: 100%, height: 2.5pt, fill: gradient.linear(accent, ember, angle: 0deg)))
// Import an image, centered; optional caption (never numbered).
#let img(path, width: 82%, caption: none) = {
let im = image(path, width: width)
if caption == none {
align(center, im)
} else {
align(center, figure(im, caption: caption, numbering: none))
}
}
// Two-part slide: a narrow title part on the left, content on the right.
// Without `title`, the body takes the full width.
#let slide(body, title: none, ..args) = {
slide-counter.update(n => n + 1)
page(
footer: slide-footer(),
..args,
if title == none {
body
} else {
grid(
columns: (0.3fr, 1.1fr),
column-gutter: 2.2em,
align(top, block(
stroke: (left: hairline-v),
inset: (left: 0.9em, top: 0.4em, bottom: 0.1em),
text(font: mono, weight: "bold", size: 0.85em, tracking: 0.1em, fill: accent, upper(title)),
)),
align(top, body),
)
},
)
}
// Section divider slide, with a large decorative ghost numeral.
#let section-counter = counter("section")
#let section(name, note: none) = {
section-counter.step()
slide[
#align(horizon)[
#block(width: 72%, align(left)[
#context {
let n = section-counter.get().first()
place(top + right, dy: -1.1em, dx: 0.2em,
text(font: mono, size: 4.2em, weight: "bold", fill: accent.lighten(86%))[
0#str(n)
])
}
#text(font: mono, size: 0.65em, weight: "bold", tracking: 0.2em, fill: accent)[SECTION]
#v(0.4em)
#text(size: 2em, weight: "bold")[#name]
#v(0.55em)
#box(width: 4.5em, align(left, line(length: 100%, stroke: hairline-h)))
#if note != none [#v(0.5em); #text(font: mono, size: 0.7em, fill: luma(110))[#note]]
])
]
]
}
// Title slide.
#let title-slide(title, subtitle: none, author: none, date: none) = {
align(center + horizon)[
#block(width: 72%, align(left)[
#text(size: 2.1em, weight: "bold")[#title]
#if subtitle != none [#v(0.4em); #text(size: 1.15em, fill: luma(90))[#subtitle]]
#v(0.9em)
#line(length: 100%, stroke: hairline-h)
#v(0.6em)
#let meta = if author == none { date } else if date == none { author } else { author + " · " + date }
#text(font: mono, size: 0.7em, fill: luma(110))[#meta]
])
]
pagebreak()
}
// Main presentation wrapper. All options are named; the document content is
// bound to `doc` by the show rule.
#let presentation(
title: "Presentation",
subtitle: none,
author: none,
date: auto,
doc,
) = {
set page(paper: "presentation-16-9", margin: (x: 3em, y: 2.6em), fill: paper, footer: none, background: top-band)
set text(size: 20pt, fill: ink)
set par(justify: false, leading: 0.68em)
show strong: set text(fill: accent)
show list: set list(marker: (
text(fill: accent-soft)[•],
text(fill: accent-soft)[–],
))
show raw: set text(font: mono, size: 0.85em)
show raw.where(block: true): it => block(width: 100%, fill: wash, inset: 0.9em, radius: 0pt, it)
deck-title.update(title)
let d = datetime.today()
let mm = if d.month() < 10 { "0" + str(d.month()) } else { str(d.month()) }
let dd = if d.day() < 10 { "0" + str(d.day()) } else { str(d.day()) }
let the-date = if date == auto {
str(d.year()) + "-" + mm + "-" + dd
} else {
date
}
title-slide(title, subtitle: subtitle, author: author, date: the-date)
doc
}