forked from lovis/socials-template
		
	
		
			
				
	
	
		
			347 lines
		
	
	
	
		
			9.6 KiB
		
	
	
	
		
			Typst
		
	
	
	
	
	
			
		
		
	
	
			347 lines
		
	
	
	
		
			9.6 KiB
		
	
	
	
		
			Typst
		
	
	
	
	
	
| #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]
 | |
| )
 | |
| */
 | 
