// Functions #let format-time(time) = { let parts = time.split(":") let hours = int(parts.at(0)) let minutes = int(parts.at(1)) let period = if hours < 12 { "AM" } else { "PM" } hours = if hours == 0 { 12 } else if hours > 12 { hours - 12 } else { hours } let formatted-minutes = if minutes < 10 { "0" + str(minutes) } else { str(minutes) } str(hours) + ":" + formatted-minutes + " " + period } #let group-by(arr, key-func) = { let result = (:) for item in arr { let key = key-func(item) if key not in result.keys() { result.insert(key, ()) } result.at(key) = result.at(key) + (item,) } result } #let pluralize(word) = { if word.ends-with("s") or word.ends-with("sh") or word.ends-with("ch") { word + "es" } else { word + "s" } } #let current-chapter-title() = context { let headings = query(heading.where(level: 1)) let current-location = here() let relevant-headings = headings.filter(heading => heading.location().page() <= current-location.page()) if relevant-headings == () { "" } else { relevant-headings.last().body } } // Setup page and header #set text(size: 18pt, font: "Pixelify Sans") #set page( margin: ( top: 2cm, bottom: 1.5cm, x: 1cm, ), header: context { let i = here().page() if query(heading).any(it => it.location().page() == i) { return } let before = query(selector(heading).before(here())) if before != () { let headings = before.filter(it => it.level <= 2) let level1 = headings.filter(it => it.level == 1).last() let level2 = headings.filter(it => it.level == 2).last() let title = if level2 == none { level1.body } else { [#level1.body — #level2.body] } grid( columns: (1fr, 1fr), align: (left, right), [#i], title, ) } }, ) // Front page #align(center)[ #image("hope.png", width: 70%) #v(2em) ] #align(center)[ #image("halftone-hackerquarterly.png", width: 100%) #v(2em) #set text(size: 1.25em) Program Guide ] // TOC #set heading() #outline() #pagebreak() // Main content taken from schedule.json #let data = json("schedule.json") #let conference = data.schedule.conference #let days = conference.days #for day in days [ = Day #day.index #let events-by-type = group-by( day .rooms .keys() .map(room => { let events = day.rooms.at(room) (room, events) }), event => event.at(1).at(0).type, ) #for (event-type, rooms) in events-by-type [ == #pluralize(event-type) #for (room, events) in rooms [ #for event in events [ #box( [ #line(length: 100%, stroke: (dash: "dashed")) _#format-time(event.start): [#event.title]_ #if event.abstract != none [ #text(size: 0.75em, event.abstract) ] #if event.persons.len() > 0 [ _Speakers: #( event.persons.map(person => { if "public_name" in person.keys() { person.public_name } else { "Unknown" } }).join(", ") )_ #linebreak() _Location: #room _ ] ], ) #v(0.5em) ] ] ] #pagebreak() ] // Last page #align(center)[ #image("hope16_schedule.png", width: 100%) https://schedule.hope.net/hope16/schedule #v(2em) #box([ #set text(font: "JetBrains Mono") #align(left)[ \$ typst \--version\ typst 0.13.1 #image("git.png", height: 150pt) ] ]) ]