801 lines
16 KiB
Typst
801 lines
16 KiB
Typst
// ==================================================
|
|
// 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(kinds: none) = {
|
|
let ca = cur_activity()
|
|
|
|
set ref(
|
|
supplement: "증빙 자료.",
|
|
)
|
|
|
|
align(bottom)[
|
|
#hl[]
|
|
*활동 증빙 자료:*
|
|
#underline[#ref(label("evidence) " + ca.name))]
|
|
#if kinds != none {
|
|
text(fill: color-ink-helper)[(#kinds)]
|
|
}
|
|
]
|
|
}
|
|
|
|
#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: 2, 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 template function
|
|
#let table_of_contents() = context {
|
|
let cp = color-palette.get()
|
|
let m = meta.get()
|
|
|
|
page(
|
|
footer: footer_template(),
|
|
)[
|
|
#show outline.entry.where(level: 1): it => link(it.element.location())[
|
|
#block(above: 25pt)[
|
|
#grid(
|
|
columns: (1fr, auto),
|
|
align: horizon,
|
|
text(font: font-subhead, size: 15pt, weight: 700)[
|
|
#it.indented(it.prefix(), heading_text(it.body()))
|
|
],
|
|
text(size: 10pt, fill: color-ink-helper)[
|
|
#it.page()
|
|
],
|
|
)
|
|
|
|
#let desc = it.element.supplement
|
|
|
|
#if desc != [domain] and desc != [evidence] {
|
|
text(fill: color-ink-helper, size: 10pt)[
|
|
#it.element.supplement
|
|
]
|
|
}
|
|
|
|
#v(10pt)
|
|
|
|
#line(length: 100%, stroke: (paint: cp.accent-faint, thickness: 0.5pt))
|
|
]
|
|
]
|
|
|
|
#heading(level: 1, outlined: false, numbering: none)[#m.cur_domain. #m.domain_name]
|
|
#label("table_of_contents")
|
|
|
|
#outline(title: none, depth: 1)
|
|
]
|
|
}
|
|
|
|
// 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, supplement: [domain])[
|
|
#title
|
|
]
|
|
|
|
#content
|
|
]
|
|
|
|
// Activity cover template function
|
|
#let activity_cover(name: "", hook: "", content) = context {
|
|
let cp = color-palette.get()
|
|
|
|
page(
|
|
header: header_template(),
|
|
footer: footer_template(),
|
|
fill: cp.cover-surface,
|
|
)[
|
|
#show heading.where(level: 1): set block(above: 0em, below: 25pt)
|
|
#heading(level: 1, supplement: hook)[
|
|
#name
|
|
]
|
|
#label(heading_text(name))
|
|
|
|
#block_quote()[
|
|
#hook
|
|
]
|
|
|
|
#v(25pt)
|
|
|
|
#content
|
|
]
|
|
}
|
|
|
|
#let activity_table() = context {
|
|
let cp = color-palette.get()
|
|
let ca = cur_activity()
|
|
let level1s = query(heading.where(level: 1, outlined: true))
|
|
let n = counter(heading).get().at(0)
|
|
let sel = heading.where(level: 2, outlined: true).after(ca.location)
|
|
if n < level1s.len() {
|
|
sel = sel.before(level1s.at(n).location(), inclusive: false)
|
|
}
|
|
|
|
page(
|
|
header: header_template(show_activity: true),
|
|
footer: footer_template(),
|
|
)[
|
|
#show outline: it => [
|
|
#line(length: 100%, stroke: (paint: cp.accent-faint, thickness: 0.5pt))
|
|
|
|
#it
|
|
]
|
|
|
|
#show outline.entry.where(level: 2): it => link(it.element.location())[
|
|
#set par(
|
|
spacing: 1em,
|
|
)
|
|
|
|
#block(above: 15pt)[
|
|
#grid(
|
|
columns: (1fr, auto),
|
|
align: horizon,
|
|
text(size: 12pt, weight: 700)[
|
|
#it.body()
|
|
],
|
|
text(size: 8pt, fill: color-ink-helper)[
|
|
#it.page()
|
|
],
|
|
)
|
|
|
|
#let desc = it.element.supplement
|
|
|
|
#par(
|
|
spacing: 0pt,
|
|
leading: 0.8em,
|
|
)[
|
|
#text(fill: color-ink-helper, size: 10pt)[
|
|
#it.element.supplement
|
|
]
|
|
]
|
|
|
|
#line(length: 100%, stroke: (paint: cp.accent-faint, thickness: 0.5pt))
|
|
]
|
|
]
|
|
|
|
#heading(level: 2, outlined: false)[
|
|
시행착오 목차
|
|
]
|
|
|
|
#outline(title: none, target: sel)
|
|
]
|
|
}
|
|
|
|
// Activity process
|
|
#let activity_process(title: "", hook: [], content) = page(
|
|
header: header_template(show_activity: true),
|
|
footer: footer_template(),
|
|
)[
|
|
#kicker("시행착오")
|
|
|
|
#heading(level: 2, supplement: hook)[
|
|
#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))
|
|
},
|
|
supplement: [evidence],
|
|
)
|
|
#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,
|
|
)
|
|
|
|
// render content
|
|
doc
|
|
}
|