#let theme = ( bg-color: rgb("#fdf6e3"), fg-color: rgb("#073642"), accent-color: rgb("#268bd2"), border-color: rgb("#073642"), heading-font: "Atkinson Hyperlegible", body-font: "DejaVu Sans", border-radius: 0.8cm, border-stroke: 4pt, // Fixed sizes (fallback if responsive is disabled) title-size: 20pt, label-size: 12pt, body-size: 12pt, info-text-size: 12pt, // Responsive sizing configuration title-size-max: 28pt, title-size-min: 14pt, title-base-length: 45, // Character count baseline body-size-max: 14pt, body-size-min: 9pt, body-base-length: 200, // Character count baseline info-size-max: 11pt, info-size-min: 9pt, info-base-length: 60, // Character count baseline ) // Default logo configuration #let default-logo = ( path: none, height: 4cm, fit: "contain", rotation: 10deg, offset: (x: 0cm, y: 0cm), pin-position: none, // "top-left", "top-center", "top-right", etc. ) // Konfiguriert die Logos #let configure-logo(logo-config) = { if logo-config == none { return default-logo } if type(logo-config) == str { // String = nur Pfad return default-logo + (path: logo-config) } return default-logo + logo-config } // Hilfsfunktion um ein einzelnes Logo zu positionieren #let place-logo(logo, position, dx: 0cm, dy: 0cm) = { let logo = configure-logo(logo) if logo.path == none { return } place(position, dx: logo.offset.x + dx, dy: logo.offset.y + dy)[ #rotate(logo.rotation)[ #image(logo.path, height: logo.height, fit: logo.fit) ] ] } // Positioniert mehrere Logos basierend auf Layout und Anzahl #let position-logos(logos, layout: "horizontal") = { let logos = if type(logos) == array { logos.map(configure-logo) } else if type(logos) == dictionary { (configure-logo(logos),) } else { () } if layout == "horizontal" { // Logos nebeneinander oben if logos.len() == 1 { place-logo(logos.first(), top + center) } else if logos.len() == 2 { place-logo(logos.at(0), top + left, dx: 1.5cm, dy: 0.7cm) place-logo(logos.at(1), top + right, dx: -1.5cm, dy: 0.7cm) } else if logos.len() == 3 { place-logo(logos.at(0), top + left, dx: 1.5cm, dy: 0.7cm) place-logo(logos.at(1), top + center, dy: 0.7cm) place-logo(logos.at(2), top + right, dx: -1.5cm, dy: 0.7cm) } } else if layout == "vertical" { // Logos auf der rechten Seite positioniert, vertikal gestapelt und zentral angeordnet let card-height = 14cm let logo-height = 4cm // Standard logo height if logos.len() == 1 { // Single logo: centered vertically let dy = (card-height - logo-height) / 2 place-logo(logos.first(), top + right, dx: -1cm, dy: dy) } else if logos.len() == 2 { // Two logos: vertically spaced around center let total-height = 2 * logo-height let start-dy = (card-height - total-height) / 2 place-logo(logos.at(0), top + right, dx: -1cm, dy: start-dy) place-logo(logos.at(1), top + right, dx: -1cm, dy: start-dy + logo-height) } else { // 3+ logos: evenly distributed vertically around center let total-height = logos.len() * logo-height let start-dy = (card-height - total-height) / 2 for (i, logo) in logos.enumerate() { let dy = start-dy + i * logo-height place-logo(logo, top + right, dx: -1cm, dy: dy) } } } } // Hilfsfunktion um responsive Schriftgröße zu berechnen basierend auf Textlänge #let calculate-responsive-size(text-content, max-size: 28pt, min-size: 14pt, base-length: 50) = { // Zähle Zeichen in Text let text-length = if type(text-content) == str { text-content.len() } else if type(text-content) == content { // Für Content-Blöcke: Schätzung der Länge repr(text-content).len() } else { 0 } // Berechne Reduktionsfaktor let ratio = if text-length > base-length { (base-length / text-length) } else { 1.0 } // Skaliere die Größe, halte sie in Grenzen let range = max-size - min-size let scaled = min-size + range * ratio // Stelle sicher, dass die Größe in Grenzen bleibt if scaled > max-size { max-size } else if scaled < min-size { min-size } else { scaled } } // Hilfsfunktion um responsive Abstände basierend auf Textmenge zu berechnen #let calculate-responsive-spacing(about-content, max-spacing: 1.2em, min-spacing: 0.6em, base-length: 200) = { let text-length = if type(about-content) == str { about-content.len() } else if type(about-content) == content { repr(about-content).len() } else { 0 } // Reduziere Abstand wenn viel Text vorhanden let ratio = if text-length > base-length { (base-length / text-length) } else { 1.0 } let range = max-spacing - min-spacing let scaled = min-spacing + range * ratio if scaled > max-spacing { max-spacing } else if scaled < min-spacing { min-spacing } else { scaled } } // Hilfsfunktion um den Inhalt zu arrangieren #let arrange-content(title: [], when: [], where: [], about: [], layout: "horizontal", current-theme: theme) = { let left-padding = if layout == "vertical" { 1.5cm } else { 1.5cm } let right-padding = if layout == "vertical" { 5cm } else { 1.5cm } let top-padding = if layout == "vertical" { 0.8cm } else { 5.5cm } // Berechne responsive Schriftgrößen let responsive-title-size = calculate-responsive-size( title, max-size: current-theme.title-size-max, min-size: current-theme.title-size-min, base-length: current-theme.title-base-length ) let responsive-body-size = calculate-responsive-size( about, max-size: current-theme.body-size-max, min-size: current-theme.body-size-min, base-length: current-theme.body-base-length ) let responsive-info-size = calculate-responsive-size( when + where, max-size: current-theme.info-size-max, min-size: current-theme.info-size-min, base-length: current-theme.info-base-length ) // Berechne responsive Abstände let responsive-spacing = calculate-responsive-spacing( about, max-spacing: 1.2em, min-spacing: 0.5em, base-length: current-theme.body-base-length ) // Berechne maximale Höhe für Beschreibungstext basierend auf verfügbarem Platz // Card: 14cm x 14cm, Padding: 1.5cm L+R, 1.5cm B, 5.5cm T (horizontal) // Ungefähre verfügbare Höhe: 14cm - 5.5cm - 1.5cm (Title/Info space) - 1.5cm (padding) = 5.5cm let max-about-height = 5.5cm pad(top: top-padding, bottom: 1.5cm, left: left-padding, right: right-padding)[ #stack( dir: ttb, spacing: responsive-spacing, // Title mit responsiver Größe if title != [] { text(size: responsive-title-size, weight: "bold", font: current-theme.heading-font, title) }, // Info-Block für wann/wo mit responsiver Größe if when != [] or where != [] { block( inset: 0.8em, radius: 0.4em, width: 100%, fill: current-theme.bg-color.mix(current-theme.accent-color).darken(10%), stack( dir: ttb, spacing: 0.4em, if when != [] { align(center + horizon, stack( dir: ltr, spacing: 0.5em, align(center + horizon, emoji.calendar), text(size: responsive-info-size, when), )) }, if where != [] { align(center + horizon, stack( dir: ltr, spacing: 0.5em, align(center + horizon, emoji.pin), text(size: responsive-info-size, where), )) }, ), ) }, // Beschreibungstext mit responsiver Größe und maximaler Höhe if about != [] { let about-align = if layout == "vertical" { left } else { center } box( height: max-about-height, width: 100%, clip: true, align(about-align, text(size: responsive-body-size, about)) ) } ) ] } // Haupt-Vorlage-Funktion #let sharepic( logos: (), layout: "horizontal", title: [], when: [], where: [], about: [], theme-override: (:) ) = { // Überschreiben der Theme let current-theme = theme + theme-override set page( width: 15cm, height: 15cm, fill: current-theme.bg-color, margin: 0cm, ) set text( font: current-theme.body-font, size: 15pt, fill: current-theme.fg-color, ) align(center + horizon)[ #rect( width: 14cm, height: 14cm, radius: current-theme.border-radius, stroke: current-theme.border-stroke + current-theme.border-color, )[ // Positioniert Logos basierend auf Layout #{position-logos(logos, layout: layout)} // Inhaltsbereich #{arrange-content( title: title, when: when, where: where, about: about, layout: layout, current-theme: current-theme )} ] ] } // Verwendungsbeispiele und Dokumentation - /* #sharepic( logos: ( (path: "logo1.png", height: 4cm), (path: "connection.png", height: 4cm, rotation: -8deg), (path: "kew.jpg", height: 4cm, rotation: 8deg) ), layout: "horizontal", title: [Beispiel-Event], when: [22.10.2025, 17:00 Uhr], where: [Ort Hier], about: [Beschreibung des Events...] ) #sharepic( logos: ( (path: "logo1.png", height: 3cm), (path: "logo2.png", height: 3cm), (path: "logo3.png", height: 3cm), (path: "logo4.png", height: 3cm) ), layout: "vertical", title: [Beispiel-Event], about: [Beschreibung des Events...] ) // Einfache Verwendung mit String-Logos #sharepic( logos: ("connection.png", "kew.jpg"), layout: "horizontal", title: [Beispiel-Event] ) */