// ================================================== // Config Variables // ================================================== #let font-body = "Pretendard" #let font-serif = ("Source Serif 4", "Source Han Serif K") #let font-display = ("Source Serif 4 Display", "Source Han Serif K") #let font-subhead = ("Source Serif 4 Subhead", "Source Han Serif K") #let font-caption = ("Source Serif 4 Caption", "Source Han Serif K") #let font-code = ("Source Code Pro", "Pretendard") #let color-surface = white #let color-ink = color.oklch(22%, 0.01, 250deg) #let color-ink-inverse = color.oklch(97%, 0.01, 250deg) #let color-ink-secondary = color.oklch(60%, 0.01, 250deg) #let color-ink-helper = color.oklch(50%, 0.01, 250deg) #let color-ink-faint = color.oklch(70%, 0.01, 250deg) #let color-rule-faint = color.oklch(85%, 0.01, 250deg) #let bleed = 1.5cm #let page-paper = "a4" #let page-height = 297mm #let page-margin = (x: 2.8cm, y: 4.2cm) // ================================================== // Metadata // ================================================== #let meta = state( "meta", ( cur_domain: 0, total_domain: 0, domain_name: "", domain_detail: "", date: "", author: "", ), ) #let color-palette = state( "color-palette", ( cover-surface: white, callout-surface: white, callout-border: black, rule: black, problem: black, accent: black, accent-faint: white, ), ) #let evidence-data = state("evidence-data", none) // ================================================== // Util functions // ================================================== #let pad_zeros(num, width) = { let s = str(num) "0" * calc.max(0, width - s.len()) + s } #let make_palette(hue) = ( cover-surface: color.oklch(97%, 0.01, hue), callout-surface: color.oklch(97%, 0.02, hue), callout-border: color.oklch(85%, 0.05, hue), rule: color.oklch(90%, 0.02, hue), problem: color.oklch(75%, 0.04, hue), accent: color.oklch(40%, 0.09, hue), accent-faint: color.oklch(70%, 0.02, hue), ) #let fill_break() = context { v(1fr) } #let heading_text(it) = { if type(it) == str { it } else if it == linebreak() { " " } else if it.has("text") { it.text } else if it.has("children") { it.children.map(heading_text).join("") } else { "" } } // Must be called from inside a context block. #let cur_activity() = { let m = meta.get() let level1s = query(heading.where(level: 1, outlined: true)) let n = counter(heading).get().at(0) let cur = level1s.at(n - 1) let prefix = str(m.cur_domain) + "." + str(n) + ". " ( prefix: prefix, name: heading_text(cur.body), location: cur.location(), ) } // ================================================== // Body components // ================================================== #let kicker(color: color-ink-helper, content) = { set text( font: font-body, weight: 700, size: 10pt, tracking: 1pt, fill: color, ) block(below: 1.5em)[ #content ] } #let callout(title: "", bg_color: none, border_color: none, body) = context { let cp = color-palette.get() let bg_c = if bg_color == none { cp.callout-surface } else { bg_color } let border_c = if border_color == none { cp.callout-border } else { border_color } block( width: 100%, inset: (x: 12pt, y: 18pt), spacing: 1.6em, // radius: 4pt, fill: bg_c, stroke: (paint: border_c), )[ #if title != "" { kicker(color: cp.accent)[#title] } #body ] } #let block_quote(color: none, content) = context { let gutter = 15pt let line-width = 2pt let c = if color != none { color } else { color-palette.get().accent } layout(size => { let body-width = size.width - line-width - gutter let body-size = measure( content, width: body-width, height: size.height, ) grid( columns: (line-width, 1fr), gutter: gutter, align: horizon, line( stroke: ( paint: c, thickness: line-width, // cap: "round", ), angle: 90deg, length: body-size.height + 15pt, ), )[ #content ] }) } #let problem(content) = context { let c = color-palette.get().problem block_quote(color: c)[ #kicker(color: c)[문제] #content ] } #let solution(content) = context { let c = color-palette.get().accent block_quote(color: c)[ #kicker(color: c)[해결책] #content ] } #let hl(content) = context { let cp = color-palette.get() let body = box( inset: (x: if content == [] { 0pt } else { 10pt }), )[#content] set text( font: font-body, weight: 700, fill: cp.accent, tracking: 1pt, size: 10pt, ) grid( columns: (1fr, auto, 1fr), align: horizon, line(length: 100%, stroke: (paint: cp.rule)), body, line(length: 100%, stroke: (paint: cp.rule)), ) } #let block_equation(title: "", description: "", content) = context { let cp = color-palette.get() callout()[ #set par( spacing: 1em, ) #if title != "" { kicker(color: cp.accent)[#title] } #content #if description != "" { set text( size: 9pt, fill: color-ink-helper, ) align(right)[#description] } ] } #let u_link(url, content) = link(url)[ #underline( content, ) ] #let steps(..layers) = context { let cp = color-palette.get() let radius = 12pt let gutter = 12pt let gap = 24pt let indent = radius + gutter for (idx, (title, body)) in layers.pos().enumerate() { let is_last = idx == layers.len() - 1 block( above: if idx == 0 { auto } else { 0pt }, below: if is_last { auto } else { 0pt }, inset: (left: indent), stroke: (left: (paint: cp.accent, thickness: 1pt)), width: 100%, )[ #place( left + top, dx: -indent - radius, )[ #circle(radius: radius, fill: cp.accent)[ #set align(center + horizon) #set text(font: font-serif, fill: color-ink-inverse, size: 12pt, weight: 700) #{ idx + 1 } ] ] #v(radius - 12pt / 2) #text(weight: 700)[ #title #linebreak() ] #body #v(gap) ] } } #let selection(choices) = context { let cp = color-palette.get() for (name, selected, body) in choices { let status = if selected [채택] else [기각] let bg_color = if selected { cp.callout-surface } else { color-surface } let border_color = if selected { cp.callout-border } else { color-rule-faint } let name_color = if selected { cp.accent } else { cp.accent-faint } let body_color = if selected { color-ink } else { color-ink-faint } callout(bg_color: bg_color, border_color: border_color)[ #text( weight: 700, fill: name_color, )[ #{ name }#h(0.5em)#text(size: 10pt)[#status] #linebreak() ] #text( fill: body_color, )[ #body ] ] } } #let ref_evidence() = { let ca = cur_activity() set ref( supplement: "증빙 자료.", ) align(bottom)[ #hl[] *활동 증빙 자료:* #underline[#ref(label("Evidence) " + ca.name))] ] } #let add_evidence(activity_ref: none, ..content) = context { let name = str(activity_ref.target) let data = ( ( name, content.pos().flatten(), ), ).to-dict() evidence-data.update(old => { if old == none { data } else { old + data } }) } #let header_template(dark: false, font_color: none, show_activity: false) = place( top + left, dy: 1cm, dx: -bleed, )[ #let fc = if font_color != none { font_color } else if dark == true { color-ink-helper } else { color-ink-helper } #block(width: 100% + 2 * bleed)[ #text( font: font-caption, size: 10pt, fill: fc, tracking: 0pt, )[ #context { let m = meta.get() link((page: 1, x: 0pt, y: 0pt))[#m.cur_domain. #m.domain_name] h(1fr) if show_activity { let ca = cur_activity() link( ca.location, ca.prefix + ca.name, ) } } ] ] ] #let footer_template(dark: false, font_color: none) = context { let fc = if font_color != none { font_color } else if dark == true { color-ink-helper } else { color-ink-helper } align(center)[ #set text( size: 10pt, fill: fc, ) #counter(page).display("1") ] } // ================================================== // Page template // ================================================== // Cover template function #let cover() = context { let cp = color-palette.get() page( fill: cp.cover-surface, )[ #set par(spacing: 0pt) #v(3cm) #context { let m = meta.get() align(center)[ #set text( fill: cp.accent, ) #text(font: font-caption)[ #pad_zeros(m.cur_domain, 2) / #pad_zeros(m.total_domain, 2) ] #v(12pt) #text(font: font-display, weight: 700, size: 40pt)[#m.domain_name] #v(20pt) #text(font: font-subhead, size: 15pt)[#m.domain_detail] ] align(center + bottom)[ #text(weight: 700, size: 10pt, tracking: 1pt)[ #m.date #v(10pt) #upper(m.author) ] ] } #v(1cm) ] } // Table of contents templage function #let table_of_contents() = page( footer: footer_template(), )[ #context { let m = meta.get() heading(level: 1, outlined: false, numbering: none)[#m.cur_domain. #m.domain_name] } #outline(title: none, depth: 2) ] // Domain frame template function #let domain_frame( title: "", bg_img_path: none, cover_color: white.transparentize(50%), font_color: color-ink, chrome_color: color-ink-helper, content, ) = page( background: if bg_img_path != none { stack( image(bg_img_path, width: 100.5%, height: 100.5%, fit: "cover"), move( dy: -50%, rect(width: 100.5%, height: 100.5%, fill: cover_color), ), ) } else { none }, header: header_template(font_color: chrome_color), footer: footer_template(font_color: chrome_color), )[ #set text(fill: font_color) #show strong: set text(fill: font_color) #heading(level: 1)[ #title ] #content ] // Activity cover template function #let activity_cover(name: "", hook: "", content) = page( header: header_template(), footer: footer_template(), )[ #heading(level: 1)[ #name ] #label(heading_text(name)) #block_quote()[ #hook ] #content ] // Activity intro template function #let activity_intro(title: "", content) = page( header: header_template(show_activity: true), footer: footer_template(), )[ #kicker("프로젝트 소개") #heading(level: 2)[ #title ] #content ] // Activity process #let activity_process(title: "", content) = page( header: header_template(show_activity: true), footer: footer_template(), )[ #kicker("시행착오") #heading(level: 2)[ #title ] #content ] // Activity result #let activity_result(title: "", content) = page( header: header_template(show_activity: true), footer: footer_template(), )[ #kicker("결과") #heading(level: 2)[ #title ] #content ] #let evidence() = context { let cp = color-palette.get() let ed = evidence-data.get() page( header: header_template(), footer: footer_template(), )[ #set heading(numbering: (..nums) => { numbering("A", nums.at(1)) }) #show heading.where(level: 2): it => { let num = counter(heading).display( (..nums) => numbering("A", nums.at(1)), ) block[ #num. #it.body ] } #show table.cell.where(x: 0): set text( weight: 700, fill: cp.accent, ) #heading(level: 1, numbering: none)[ 증빙 자료 ] #let cnt = ed.len() #for (name, data) in ed [ #heading(level: 2, supplement: "evidence")[#name] #label("Evidence) " + name) #table( columns: (10em, 1fr), ..data, ) #{ cnt -= 1 } #if cnt > 0 { pagebreak() } ] ] } // ================================================== // Style Settings // ================================================== #let base( cur_domain: 0, total_domain: 0, domain_name: "", domain_detail: "", date: "", author: "", main-hue: 0deg, doc, ) = { // meta data meta.update(m => ( m + ( cur_domain: cur_domain, total_domain: total_domain, domain_name: domain_name, domain_detail: domain_detail, date: date, author: author, ) )) // color palette let cp = make_palette(main-hue) color-palette.update(m => (m + cp)) // document & page config set document( title: str(cur_domain) + ". " + domain_name, description: domain_detail, author: author, ) set page( paper: page-paper, margin: page-margin, fill: color-surface, ) // text config set text( font: font-body, weight: 400, fill: color-ink, size: 12pt, ) set par( leading: 1.2em, spacing: 1.8em, ) show raw: set text(font: font-code) show strong: set text(fill: cp.accent) // underline config set underline( offset: 2pt, stroke: (paint: cp.accent, thickness: 1pt), ) // list config show list: set par(leading: 0.8em) set list( marker: ([--], [•]), indent: 0.6em, body-indent: 0.8em, spacing: 0.8em, ) show enum: set par(leading: 0.8em) set enum( indent: 0.6em, body-indent: 0.8em, spacing: 0.8em, ) // image config show image: set align(center) show image: set par( spacing: 1.6em, ) // figure config show figure: set text( fill: color-ink-helper, size: 10pt, ) show figure.caption: it => { "[ " + it.body + " ]" } // table config show table: set text(size: 10pt) set table( inset: 0.8em, align: left + top, stroke: (paint: cp.rule), ) // heading config show heading.where(level: 1): set heading(numbering: (..nums) => ( str(cur_domain) + "." + nums.pos().map(str).join(".") + "." )) show heading.where(level: 1): set block(above: 0em, below: 50pt) show heading.where(level: 1): set text( font: font-display, weight: 700, size: 27pt, tracking: 0pt, ) show heading.where(level: 2): set block(above: 0em, below: 50pt) show heading.where(level: 2): set par(leading: 1em) show heading.where(level: 2): set text( font: font-subhead, weight: 700, size: 18pt, tracking: 0pt, fill: color-ink, ) // outline config set outline( indent: 2em, ) show outline: set par(leading: 1em) show outline.entry: it => link( it.element.location(), )[ #grid( columns: (1fr, auto), it.indented(it.prefix(), heading_text(it.body())), it.page(), ) ] show outline.entry.where(level: 1): it => { set text( font: font-subhead, size: 15pt, weight: 700, ) block(above: 50pt)[ #it #line(length: 100%, stroke: (paint: color-ink-secondary, thickness: 1pt)) ] } show outline.entry.where(level: 2): it => { set text( font: font-body, size: 12pt, weight: 400, fill: color-ink-secondary, ) if it.element.supplement.text != "evidence" { block(above: 20pt)[ #it ] } } // render content doc }