feat: add standalone PDF build infrastructure for Kits

Created independent build system for Hardware Kits PDF output:
- Added PDF profile (_quarto/profiles/pdf/_quarto.yml)
- Copied tex files with teal branding (#0d9488)
- Added inject_parts.lua filter for part summaries
- Updated Makefile with html and pdf targets

Usage: make pdf (or quarto render --profile pdf)
This commit is contained in:
Vijay Janapa Reddi
2025-12-27 17:59:58 -05:00
parent a558b9a3b6
commit a9a1c562c3
8 changed files with 1469 additions and 5 deletions

View File

@@ -4,17 +4,24 @@
# Part of the MLSysBook ecosystem
# =============================================================================
.PHONY: all build preview clean help
.PHONY: all build html pdf preview clean help
# Default target
all: build
all: html
# Build the kits site
# Build HTML site (default)
html: build
build:
@echo "🔧 Building Hardware Kits site..."
@echo "🔧 Building Hardware Kits HTML site..."
quarto render
@echo "✅ Build complete: _build/"
# Build PDF
pdf:
@echo "📄 Building Hardware Kits PDF..."
quarto render --profile pdf
@echo "✅ PDF build complete: _build/pdf/"
# Preview the site locally
preview:
@echo "👀 Starting preview server..."
@@ -32,7 +39,9 @@ help:
@echo "Hardware Kits Build System"
@echo ""
@echo "Available targets:"
@echo " make build - Build the site to _build/"
@echo " make build - Build HTML site to _build/"
@echo " make html - Build HTML site to _build/ (same as build)"
@echo " make pdf - Build PDF to _build/pdf/"
@echo " make preview - Start local preview server"
@echo " make clean - Remove build artifacts"
@echo " make help - Show this help message"

View File

@@ -0,0 +1,103 @@
# =============================================================================
# HARDWARE KITS PDF PROFILE
# =============================================================================
# This profile builds the Hardware Kits labs as a standalone PDF.
# Usage: quarto render --profile pdf
# =============================================================================
project:
type: book
output-dir: _build/pdf
book:
title: "Hardware Kits"
subtitle: "Hands-On Embedded ML Labs"
author: "Marcelo Rovai with Prof. Vijay Janapa Reddi"
date: today
chapters:
# Frontmatter
- index.qmd
- contents/getting-started.qmd
- contents/platforms.qmd
- contents/ide-setup.qmd
# Arduino Labs
- part: "Arduino Nicla Vision"
chapters:
- contents/arduino/nicla_vision/nicla_vision.qmd
- contents/arduino/nicla_vision/setup/setup.qmd
- contents/arduino/nicla_vision/image_classification/image_classification.qmd
- contents/arduino/nicla_vision/object_detection/object_detection.qmd
- contents/arduino/nicla_vision/kws/kws.qmd
- contents/arduino/nicla_vision/motion_classification/motion_classification.qmd
# Seeed XIAO Labs
- part: "Seeed XIAO ESP32S3"
chapters:
- contents/seeed/xiao_esp32s3/xiao_esp32s3.qmd
- contents/seeed/xiao_esp32s3/setup/setup.qmd
- contents/seeed/xiao_esp32s3/image_classification/image_classification.qmd
- contents/seeed/xiao_esp32s3/object_detection/object_detection.qmd
- contents/seeed/xiao_esp32s3/kws/kws.qmd
- contents/seeed/xiao_esp32s3/motion_classification/motion_classification.qmd
# Grove Vision Labs
- part: "Grove Vision AI V2"
chapters:
- contents/seeed/grove_vision_ai_v2/grove_vision_ai_v2.qmd
- contents/seeed/grove_vision_ai_v2/setup_and_no_code_apps/setup_and_no_code_apps.qmd
- contents/seeed/grove_vision_ai_v2/image_classification/image_classification.qmd
- contents/seeed/grove_vision_ai_v2/object_detection/object_detection.qmd
# Raspberry Pi Labs
- part: "Raspberry Pi"
chapters:
- contents/raspi/raspi.qmd
- contents/raspi/setup/setup.qmd
- contents/raspi/image_classification/image_classification.qmd
- contents/raspi/object_detection/object_detection.qmd
- contents/raspi/llm/llm.qmd
- contents/raspi/vlm/vlm.qmd
# Shared Resources
- part: "Shared Resources"
chapters:
- contents/shared/shared.qmd
- contents/shared/kws_feature_eng/kws_feature_eng.qmd
- contents/shared/dsp_spectral_features_block/dsp_spectral_features_block.qmd
format:
pdf:
documentclass: scrbook
classoption:
- openany
- twoside
papersize: letter
fontsize: 10pt
# LaTeX includes
include-in-header: tex/header-includes.tex
include-before-body: tex/before-body-includes.tex
include-after-body: tex/after-body-includes.tex
# PDF engine
pdf-engine: lualatex
# Output settings
keep-tex: false
toc: true
toc-depth: 2
number-sections: true
# Code formatting
code-copy: false
code-overflow: wrap
highlight-style: github
# Lua filters
filters:
- filters/inject_parts.lua
# Metadata for filters
part-summaries: contents/parts/summaries.yml

View File

@@ -0,0 +1,454 @@
-- ===============================================================================
-- PART STYLING LUA FILTER
-- ===============================================================================
--
-- This filter transforms \part{key:xxx} commands in QMD files into appropriate
-- LaTeX commands based on the key name, creating a structured book hierarchy.
--
-- ROUTING LOGIC:
-- 1. Book Divisions (frontmatter, main_content, backmatter, labs) → \division{title}
-- - Clean centered styling with geometric background
-- - No descriptions displayed
--
-- 2. Lab Platforms (arduino, xiao, grove, raspberry, shared) → \labdivision{title}
-- - Circuit-style neural network design with nodes and connections
-- - No descriptions displayed
--
-- 3. Numbered Parts (foundations, principles, etc.) → \part{title}
-- - Roman numeral styling (via \titleformat{\part})
-- - Includes part description
--
-- USAGE IN QMD FILES:
-- Simply use \part{key:foundations} and this filter will:
-- 1. Look up the key in part_summaries.yml
-- 2. Extract title and description
-- 3. Generate appropriate LaTeX command
-- 4. Include \setpartsummary{description} if needed
--
-- EXAMPLE TRANSFORMATIONS:
-- \part{key:foundations} → \part{Systems Foundations} + description
-- \part{key:labs} → \division{Labs} (clean geometric style)
-- \part{key:arduino} → \labdivision{Arduino Labs} (circuit-style neural network)
-- \part{key:frontmatter} → \division{Frontmatter} (clean geometric style)
-- ===============================================================================
-- 🔧 Normalize keys (lowercase, trim leading/trailing whitespace)
local function normalize(str)
return str:lower():gsub("^%s+", ""):gsub("%s+$", "")
end
-- 🗝️ Extract key from LaTeX part command
local function extract_key_from_latex(content)
-- Look for \part{key:xxx} pattern in content
local key = content:match("\\part%{key:([^}]+)%}")
return key
end
-- Helper function for formatted logging
local function log_info(message)
io.stderr:write("📄 [Part Summary Filter] " .. message .. "\n")
io.stderr:flush()
end
local function log_success(message)
io.stderr:write("✅ [Part Summary Filter] " .. message .. "\n")
io.stderr:flush()
end
local function log_warning(message)
io.stderr:write("⚠️ [Part Summary Filter] " .. message .. "\n")
io.stderr:flush()
end
local function log_error(message)
io.stderr:write("❌ [Part Summary Filter] " .. message .. "\n")
io.stderr:flush()
end
-- Roman numeral conversion
local function to_roman(num)
local values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}
local numerals = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}
local result = ""
for i, value in ipairs(values) do
while num >= value do
result = result .. numerals[i]
num = num - value
end
end
return result
end
-- Dynamic part numbering system
local current_division = nil
local part_counter = 0
-- Reset counter when new division is encountered
local function reset_part_counter(division)
if current_division ~= division then
current_division = division
part_counter = 0
log_info("🔄 New division encountered: '" .. division .. "' - resetting part counter to 0")
end
end
-- Get next part number for current division
local function get_next_part_number()
part_counter = part_counter + 1
return part_counter
end
-- Helper function to format title with part number
local function format_part_title(key, title, numbered)
-- Always return just the clean title without any "Part X —" prefix
-- The LaTeX template handles Roman numerals and "Part X" labels separately
return title
end
-- 📄 Read summaries.yml into a Lua table
local function read_summaries(path)
local summaries = {}
local file = io.open(path, "r")
if not file then
log_warning("Failed to open part summaries file: " .. path)
return summaries
end
local content = file:read("*all")
file:close()
-- Parse YAML manually for the new structure with parts array
local entries_loaded = 0
local current_key = nil
local current_title = nil
local current_type = nil
local current_numbered = nil
local description_lines = {}
local in_parts_section = false
local in_description = false
for line in content:gmatch("[^\r\n]+") do
if line:match('^parts:') then
in_parts_section = true
elseif in_parts_section and line:match('%s*%-%s*key:%s*"([^"]+)"') then
if current_key and current_title then
local description = table.concat(description_lines, " "):gsub("^%s+", ""):gsub("%s+$", "")
summaries[normalize(current_key)] = {
title = current_title,
description = description,
type = current_type or "part",
numbered = current_numbered or false
}
entries_loaded = entries_loaded + 1
log_info("📝 Loaded: '" .. current_key .. "' → '" .. current_title .. "' (type: " .. (current_type or "part") .. ", numbered: " .. tostring(current_numbered or false) .. ")")
end
current_key = line:match('%s*%-%s*key:%s*"([^"]+)"')
current_title = nil
current_type = nil
current_numbered = nil
description_lines = {}
in_description = false
elseif in_parts_section and current_key and line:match('%s*title:%s*"([^"]+)"') then
current_title = line:match('%s*title:%s*"([^"]+)"')
elseif in_parts_section and current_key and line:match('%s*type:%s*"([^"]+)"') then
current_type = line:match('%s*type:%s*"([^"]+)"')
elseif in_parts_section and current_key and line:match('%s*numbered:%s*(true|false)') then
current_numbered = line:match('%s*numbered:%s*(true|false)') == "true"
elseif in_parts_section and current_key and line:match('%s*description:%s*>?') then
in_description = true
elseif in_description and current_key then
local desc_content = line:match('%s%s%s*(.+)')
if desc_content then
table.insert(description_lines, desc_content)
elseif line:match('^%s*$') then
-- Empty line, continue
else
-- End of description
in_description = false
end
end
end
-- Handle the last entry
if current_key and current_title then
local description = table.concat(description_lines, " "):gsub("^%s+", ""):gsub("%s+$", "")
summaries[normalize(current_key)] = {
title = current_title,
description = description,
type = current_type or "part",
numbered = current_numbered or false
}
entries_loaded = entries_loaded + 1
log_info("📝 Loaded: '" .. current_key .. "' → '" .. current_title .. "' (type: " .. (current_type or "part") .. ", numbered: " .. tostring(current_numbered or false) .. ")")
end
log_success("Successfully loaded " .. entries_loaded .. " part summaries")
return summaries
end
-- Load summaries from metadata
local has_part_summaries = false
local summaries = {}
-- Validation function to check all keys in the document
local function validate_all_keys()
if not has_part_summaries then return end
local used_keys = {}
local invalid_keys = {}
-- Collect all keys used in the document
for key, _ in pairs(summaries) do
used_keys[key] = true
end
-- Check if any keys are missing from part_summaries.yml
for key, _ in pairs(used_keys) do
if not summaries[key] then
table.insert(invalid_keys, key)
end
end
-- If there are invalid keys, report them all at once
if #invalid_keys > 0 then
log_error("❌ CRITICAL ERROR: Multiple undefined keys found:")
for _, key in ipairs(invalid_keys) do
log_error(" - '" .. key .. "' not found in part_summaries.yml")
end
log_error("🔍 Available keys: frontmatter, main_content, foundations, principles, optimization, deployment, trustworthy, futures, labs, arduino, xiao, grove, raspberry, shared, backmatter")
log_error("🛑 Build stopped - fix all undefined keys before proceeding")
error("Part summary filter failed: multiple undefined keys found. Please check your .qmd files and part_summaries.yml for consistency.")
end
end
-- Pre-scan function to validate all keys before processing
local function prescan_document_keys(doc)
if not has_part_summaries then return end
log_info("🔍 Pre-scanning document for part keys...")
local found_keys = {}
local invalid_keys = {}
local key_locations = {}
-- Scan all RawBlocks for \part{key:xxx} patterns
local function scan_blocks(blocks)
for i, block in ipairs(blocks) do
if block.t == "RawBlock" and block.format == "latex" then
local key = extract_key_from_latex(block.text)
if key then
local normalized_key = normalize(key)
found_keys[normalized_key] = true
-- Check if key is valid
if not summaries[normalized_key] then
table.insert(invalid_keys, normalized_key)
key_locations[normalized_key] = i
end
end
end
-- Recursively scan nested blocks
if block.content then
scan_blocks(block.content)
end
end
end
-- Scan the document
scan_blocks(doc.blocks)
-- Report findings
if next(found_keys) then
log_info("📋 Found keys in document:")
for key, _ in pairs(found_keys) do
if summaries[key] then
log_info(" ✅ '" .. key .. "' - valid")
else
log_error(" ❌ '" .. key .. "' - INVALID (location: block " .. (key_locations[key] or "unknown") .. ")")
end
end
else
log_info("📋 No part keys found in document")
end
-- Report available keys for reference
log_info("📚 Available keys in part_summaries.yml:")
for key, _ in pairs(summaries) do
log_info(" - '" .. key .. "'")
end
-- If there are invalid keys, stop the build
if #invalid_keys > 0 then
log_error("❌ CRITICAL ERROR: Invalid keys found during pre-scan:")
for _, key in ipairs(invalid_keys) do
log_error(" - '" .. key .. "' not found in part_summaries.yml")
end
log_error("🛑 Build stopped - fix all invalid keys before proceeding")
log_error("💡 Check your .qmd files for \\part{key:" .. table.concat(invalid_keys, "} or \\part{key:") .. "} commands")
error("Part summary filter failed: invalid keys found during pre-scan. Please check your .qmd files and part_summaries.yml for consistency.")
else
log_success("✅ Pre-scan validation passed - all keys are valid")
end
end
-- Debug function to help identify the source of problematic keys
local function debug_key_source(key, el)
log_error("🔍 DEBUG: Key '" .. key .. "' found in RawBlock")
log_error("📍 RawBlock content: " .. (el.text or "nil"))
log_error("📍 RawBlock format: " .. (el.format or "nil"))
-- Try to extract more context about where this key came from
if el.text then
local context = string.sub(el.text, 1, 200) -- First 200 chars for context
log_error("📍 Context: " .. context)
end
end
-- 🏁 Main transformation function
-- This function intercepts \part{key:xxx} commands and transforms them
-- into appropriate LaTeX commands based on the routing logic above
function RawBlock(el)
if not has_part_summaries then return nil end
if el.format ~= "latex" then return nil end
local key = extract_key_from_latex(el.text)
if key then
local normalized_key = normalize(key)
if summaries[normalized_key] then
local part_entry = summaries[normalized_key]
local title = part_entry.title
local description = part_entry.description
local part_type = part_entry.type or "part"
local numbered = part_entry.numbered or false
local formatted_title = format_part_title(normalized_key, title, numbered)
local setpartsummary_cmd = "\\setpartsummary{" .. description .. "}"
local part_cmd
-- ROUTING LOGIC: Transform based on type field from YAML
-- 1. BOOK DIVISIONS: Major book structure sections
if part_type == "division" then
part_cmd = "\\division{" .. formatted_title .. "}"
local toc_cmd = "\\addtocontents{toc}{\\par\\addvspace{12pt}\\noindent\\hfil\\bfseries\\color{crimson}" .. formatted_title .. "\\color{black}\\hfil\\par\\addvspace{6pt}}"
local line_cmd = "\\addtocontents{toc}{\\par\\noindent\\hfil{\\color{crimson}\\rule{0.6\\textwidth}{0.5pt}}\\hfil\\par\\addvspace{6pt}}"
log_info("🔄 Replacing key '" .. key .. "' with division: '" .. formatted_title .. "' (with TOC entry + crimson line)")
return {
pandoc.RawBlock("latex", toc_cmd),
pandoc.RawBlock("latex", line_cmd),
pandoc.RawBlock("latex", part_cmd)
}
-- 2. LAB PLATFORMS: Circuit-style neural network design
elseif part_type == "lab" then
part_cmd = "\\labdivision{" .. formatted_title .. "}"
local toc_cmd = "\\addtocontents{toc}{\\par\\addvspace{12pt}\\noindent\\hfil\\bfseries\\color{crimson}" .. formatted_title .. "\\color{black}\\hfil\\par\\addvspace{6pt}}"
log_info("🔄 Replacing key '" .. key .. "' with lab division: '" .. formatted_title .. "' (circuit style, clean TOC entry)")
return {
pandoc.RawBlock("latex", toc_cmd),
pandoc.RawBlock("latex", part_cmd)
}
-- 3. NUMBERED PARTS: Main content sections (type: "part")
elseif part_type == "part" then
-- Reset counter if we're in a new division
reset_part_counter(part_entry.division or "mainmatter")
-- Get next part number and convert to Roman numeral
local part_number = get_next_part_number()
local roman_numeral = to_roman(part_number)
part_cmd = "\\numberedpart{" .. formatted_title .. "}" -- Use custom command instead
local toc_cmd = "\\addtocontents{toc}{\\par\\addvspace{12pt}\\noindent\\hfil\\bfseries\\color{crimson}Part~" .. roman_numeral .. "~" .. formatted_title .. "\\color{black}\\hfil\\par\\addvspace{6pt}}"
log_info("🔄 Replacing key '" .. key .. "' with numbered part: '" .. formatted_title .. "' (Part " .. roman_numeral .. ", division: " .. (part_entry.division or "mainmatter") .. ")")
return {
pandoc.RawBlock("latex", setpartsummary_cmd),
pandoc.RawBlock("latex", toc_cmd),
pandoc.RawBlock("latex", part_cmd)
}
end
else
-- Enhanced error reporting with more context
log_error("❌ CRITICAL ERROR: UNDEFINED KEY '" .. key .. "' not found in part_summaries.yml")
log_error("📍 Location: RawBlock processing")
-- Add debug information to help identify the source
debug_key_source(key, el)
log_error("🔍 Available keys: frontmatter, main_content, foundations, principles, optimization, deployment, trustworthy, futures, labs, arduino, xiao, grove, raspberry, shared, backmatter")
log_error("💡 Check your .qmd files for \\part{key:" .. key .. "} commands")
log_error("🛑 Build stopped to prevent incorrect part titles.")
-- Force immediate exit with detailed error
local error_msg = string.format(
"Part summary filter failed: undefined key '%s' in \\part{key:%s}. " ..
"Available keys: frontmatter, main_content, foundations, principles, optimization, deployment, trustworthy, futures, labs, arduino, xiao, grove, raspberry, shared, backmatter. " ..
"Please check your .qmd files and part_summaries.yml for consistency.",
key, key
)
error(error_msg)
end
end
return nil
end
-- Initialize the filter with Meta handler
function Meta(meta)
if quarto.doc.is_format("pdf") then
local filter_metadata = meta["filter-metadata"]
if filter_metadata and filter_metadata["part-summaries"] then
local config = filter_metadata["part-summaries"]
local file_path = pandoc.utils.stringify(config.file or "")
local enabled = pandoc.utils.stringify(config.enabled or "true"):lower() == "true"
if enabled and file_path ~= "" then
log_info("🚀 Initializing Part Summary Filter")
log_info("📂 Loading part summaries from: " .. file_path)
-- Add error handling for file loading
local success, result = pcall(read_summaries, file_path)
if success then
summaries = result
-- Validate that summaries were loaded properly
if type(summaries) == "table" and next(summaries) then
has_part_summaries = true
log_success("Part Summary Filter activated for PDF format")
else
log_error("❌ CRITICAL ERROR: part_summaries.yml is empty or invalid")
log_error("📍 File path: " .. file_path)
log_error("🛑 Build stopped - part_summaries.yml must contain valid entries")
error("Part summary filter failed: part_summaries.yml is empty or contains no valid entries")
end
else
log_error("❌ CRITICAL ERROR: Failed to load part_summaries.yml")
log_error("📍 File path: " .. file_path)
log_error("🔍 Error: " .. tostring(result))
log_error("🛑 Build stopped - cannot proceed without part summaries")
error("Part summary filter failed: cannot load part_summaries.yml from " .. file_path .. ". Error: " .. tostring(result))
end
else
log_warning("Part Summary Filter disabled or no file specified")
end
else
log_warning("Part Summary Filter metadata not found")
end
else
log_info("Part Summary Filter skipped (not PDF format)")
end
return meta
end
-- Return the filter in the correct order
return {
{ Meta = Meta },
{ Pandoc = function(doc)
-- Run pre-scan validation if part summaries are enabled
if has_part_summaries then
prescan_document_keys(doc)
end
return doc
end },
{ RawBlock = RawBlock }
}

View File

@@ -0,0 +1 @@
\clearpage

View File

@@ -0,0 +1,16 @@
% Disable the default title page
\renewcommand{\maketitle}{
\newgeometry{top=0.5in,bottom=1in,inner=1in,outer=1in} % Geometry for title page
\begin{titlepage}
\begin{center}
\thispagestyle{empty}
\includegraphics[trim=0 -10 0 0, clip, width=\textwidth]{cover-image-white.png} % Adjust the size and path to your image
{{\Huge\bfseries Machine Learning Systems}\\[1em] \par}
\vspace*{\fill}
{\large Written, edited and curated by \\[.1cm] Prof. Vijay Janapa Reddi \\[.2cm] Harvard University \\[1em] \normalsize {\itshape With special thanks to the community for their contributions and support.} \\[1em] \vfill \scriptsize Last Modified: \today\par \vfill}
\end{center}
\end{titlepage}
\restoregeometry % Restore original geometry
}
\let\endtitlepage\relax

11
kits/tex/copyright.tex Normal file
View File

@@ -0,0 +1,11 @@
\null\vfill
\begin{flushleft}
\thispagestyle{empty}
\textit{Here's the name of the book for the copyright page}
© Anyname, Inc.
ISBN-1234567891234
\noindent All rights reserved. No part of this publication may be produced or transmitted in any form or by any means, electronic or mechanical, including photocopying recording or any information storage and retrieval system, without the prior written permission of the publisher. For permissions contact
\end{flushleft}

9
kits/tex/cover_page.tex Normal file
View File

@@ -0,0 +1,9 @@
\begin{titlepage}
\begin{center}
\thispagestyle{empty}
\includegraphics[trim=0 -10 0 0, clip, width=\textwidth]{cover-image-white.png} % Adjust the size and path to your image
{{\Huge\bfseries Hardware Kits}\\[0.5em] \Large Hands-On Embedded ML Labs \\[1em] \par}
\vspace*{\fill}
{\large Written, edited and curated by \\[.1cm] Marcelo Rovai \\[.2cm] With Prof. Vijay Janapa Reddi \\[.2cm] Harvard University \\[1em] \normalsize {\itshape Part of the Machine Learning Systems textbook.} \\[1em] \vfill \scriptsize Last Modified: \today\par \vfill}
\end{center}
\end{titlepage}

View File

@@ -0,0 +1,861 @@
% =============================================================================
% LATEX HEADER CONFIGURATION FOR HARDWARE KITS PDF
% =============================================================================
% This file contains all LaTeX package imports, custom commands, and styling
% definitions for the PDF output of the Hardware Kits labs.
%
% Key Features:
% - Teal branding throughout (matching kits website)
% - Custom part/chapter/section styling
% - Professional table formatting with colored headers
% - Margin notes with custom styling
% - TikZ-based part dividers
% - Page numbering (Roman for frontmatter, Arabic for mainmatter)
%
% Note: This file is included via _quarto-pdf.yml and affects PDF output only.
% HTML/EPUB styling is handled separately via CSS files.
% =============================================================================
% =============================================================================
% PACKAGE IMPORTS
% =============================================================================
% Layout and positioning
% \usepackage[outercaption, ragged]{sidecap} % Commented out to make figure captions inline instead of in margin
\usepackage{adjustbox} % Adjusting box dimensions
\usepackage{afterpage} % Execute commands after page break
\usepackage{morefloats} % Increase number of floats
\usepackage{array} % Enhanced table column formatting
\usepackage{atbegshi} % Insert content at page beginning
%\usepackage{changepage} % Change page dimensions mid-document
\usepackage{emptypage} % Clear headers/footers on empty pages
% Language and text
\usepackage[english]{babel} % English language support
\usepackage{microtype} % Improved typography and hyphenation
% Captions and floats
\usepackage{caption}
% Caption styling configuration
%\captionsetup[table]{belowskip=5pt}
\captionsetup{format=plain}
\DeclareCaptionLabelFormat{mylabel}{#1
#2:\hspace{1.0ex}}
\DeclareCaptionFont{ninept}{\fontsize{7pt}{8}\selectfont #1}
% Figure captions: Small font, bold label, ragged right
\captionsetup[figure]{labelfont={bf,ninept},labelsep=space,
belowskip=2pt,aboveskip=6pt,labelformat=mylabel,
justification=raggedright,singlelinecheck=false,font={ninept}}
% Table captions: Small font, bold label, ragged right
\captionsetup[table]{belowskip=6pt,labelfont={bf,ninept},labelsep=none,
labelformat=mylabel,justification=raggedright,singlelinecheck=false,font={ninept}}
% Typography fine-tuning
\emergencystretch=5pt % Allow extra stretch to avoid overfull boxes
% Utility packages
\usepackage{etoolbox} % For patching commands and environments
% Page layout and headers
\usepackage{fancyhdr} % Custom headers and footers
\usepackage{geometry} % Page dimensions and margins
% Graphics and figures
\usepackage{graphicx} % Include graphics
\usepackage{float} % Improved float placement
\usepackage[skins,breakable]{tcolorbox} % Coloured and framed text boxes
\tcbset{before upper=\setlength{\parskip}{3pt}}
% Tables
\usepackage{longtable} % Multi-page tables
% Fonts and typography
\usepackage{fontspec} % Font selection for LuaLaTeX
\usepackage{mathptmx} % Times-like math fonts
\usepackage{newpxtext} % Palatino-like font for body text
% Colors and visual elements
\usepackage[dvipsnames]{xcolor} % Extended color support
\usepackage{tikz} % Programmatic graphics
\usetikzlibrary{positioning}
\usetikzlibrary{calc}
\usepackage{tikzpagenodes} % TikZ positioning relative to page
% Code listings
\usepackage{listings} % Code highlighting
% Hyperlinks
\usepackage{hyperref} % Clickable links in PDF
% Conditional logic
\usepackage{ifthen} % If-then-else commands
% Math symbols
\usepackage{amsmath} % AMS math extensions
\usepackage{amssymb} % AMS math symbols
\usepackage{latexsym} % Additional LaTeX symbols
\usepackage{pifont} % Zapf Dingbats symbols
\providecommand{\blacklozenge}{\ding{117}} % Black diamond symbol
% Lists
\usepackage{enumitem} % Customizable lists
% Margin notes and sidenotes
\usepackage{marginfix} % Fixes margin note overflow
\usepackage{marginnote} % Margin notes
\usepackage{sidenotes} % Academic-style sidenotes
\renewcommand\raggedrightmarginnote{\sloppy}
\renewcommand\raggedleftmarginnote{\sloppy}
% Typography improvements
\usepackage{ragged2e} % Better ragged text
\usepackage[all]{nowidow} % Prevent widows and orphans
\usepackage{needspace} % Ensure minimum space on page
% Section formatting
\usepackage[explicit]{titlesec} % Custom section titles
\usepackage{tocloft} % Table of contents formatting
% QR codes and icons
\usepackage{fontawesome5} % Font Awesome icons
\usepackage{qrcode} % QR code generation
\qrset{link, height=15mm}
% =============================================================================
% FLOAT CONFIGURATION
% =============================================================================
% Allow more floats per page to handle figure-heavy chapters
\extrafloats{100}
\setcounter{topnumber}{10} % Max floats at top of page
\setcounter{bottomnumber}{10} % Max floats at bottom of page
\setcounter{totalnumber}{20} % Max floats per page
\renewcommand{\topfraction}{.9} % Max fraction of page for top floats
\renewcommand{\bottomfraction}{.9}
\renewcommand{\textfraction}{.1} % Min fraction of page for text
\renewcommand{\floatpagefraction}{.8} % Min fraction of float page
% =============================================================================
% COLOR DEFINITIONS
% =============================================================================
% Teal - primary brand color for Hardware Kits (matching website)
\definecolor{crimson}{HTML}{0d9488}
% Quiz element colors
\definecolor{quiz-question-color1}{RGB}{225,243,248} % Light blue background
\definecolor{quiz-question-color2}{RGB}{17,158,199} % Blue border
\definecolor{quiz-answer-color1}{RGB}{250,234,241} % Light pink background
\definecolor{quiz-answer-color2}{RGB}{152,14,90} % Magenta border
% =============================================================================
% LIST FORMATTING
% =============================================================================
% Tighter list spacing for academic style
\def\tightlist{}
\setlist{itemsep=1pt, parsep=1pt, topsep=0pt,after={\vspace{0.3\baselineskip}}}
\let\tightlist\relax
\makeatletter
\@ifpackageloaded{framed}{}{\usepackage{framed}}
\@ifpackageloaded{fancyvrb}{}{\usepackage{fancyvrb}}
\makeatother
\makeatletter
%New float "codelisting" has been updated
\AtBeginDocument{%
\floatstyle{ruled}
\newfloat{codelisting}{!htb}{lop}
\floatname{codelisting}{Listing}
\floatplacement{codelisting}{!htb}
\captionsetup[codelisting]{labelfont={bf,ninept},labelformat=mylabel,
singlelinecheck=false,width=\linewidth,labelsep=none,font={ninept}}%
\renewenvironment{snugshade}{%
\def\OuterFrameSep{3pt}%
\def\FrameCommand{\fboxsep=5pt\colorbox{shadecolor}}%
\MakeFramed{\advance\hsize-\width\FrameRestore}%
\leftskip 0.5em \rightskip 0.5em%
\small% decrease font size
}{\endMakeFramed}%
}
\makeatother
%The space before and after the verbatim environment "Highlighting" has been reduced
\fvset{listparameters=\setlength{\topsep}{0pt}\setlength{\partopsep}{0pt}}
\DefineVerbatimEnvironment{Highlighting}{Verbatim}{framesep=0mm,commandchars=\\\{\}}
\makeatletter
\renewcommand\fs@ruled{\def\@fs@cfont{\bfseries}\let\@fs@capt\floatc@ruled
\def\@fs@pre{\hrule height.8pt depth0pt \kern2pt}%
\def\@fs@post{\kern2pt\hrule\relax}%
\def\@fs@mid{\kern2pt\hrule\kern1pt}%space between float and caption
\let\@fs@iftopcapt\iftrue}
\makeatother
% =============================================================================
% HYPHENATION RULES
% =============================================================================
% Explicit hyphenation points for technical terms to avoid bad breaks
\hyphenation{
light-weight
light-weight-ed
de-vel-op-ment
un-der-stand-ing
mod-els
prin-ci-ples
ex-per-tise
com-pli-cat-ed
blue-print
per‧for‧mance
com-mu-ni-ca-tion
par-a-digms
hy-per-ten-sion
a-chieved
}
% =============================================================================
% CODE LISTING CONFIGURATION
% =============================================================================
% Settings for code blocks using listings package
\lstset{
breaklines=true, % Automatic line wrapping
breakatwhitespace=true, % Break at whitespace only
basicstyle=\ttfamily, % Monospace font
frame=none, % No frame around code
keepspaces=true, % Preserve spaces
showspaces=false, % Don't show space characters
showtabs=false, % Don't show tab characters
columns=flexible, % Flexible column width
belowskip=0pt, % Minimal spacing
aboveskip=0pt
}
% =============================================================================
% PAGE GEOMETRY
% =============================================================================
% Book dimensions optimized for print (7.5" × 9.25")
% Wide outer margin for sidenotes (1.5")
\geometry{
paperwidth=7.5in,
paperheight=9.25in,
top=1in,
bottom=1in,
inner=1in, % Inner margin (binding side)
outer=2.25in, % Outer margin (includes 1.5" for sidenotes)
footskip=30pt,
marginparwidth=1.5in, % Width for margin notes
twoside % Different left/right pages
}
% =============================================================================
% SIDENOTE STYLING
% =============================================================================
% Custom sidenote design with crimson vertical bar
\renewcommand{\thefootnote}{\textcolor{crimson}{\arabic{footnote}}}
% Save original sidenote command
\makeatletter
\@ifundefined{oldsidenote}{
\let\oldsidenote\sidenote%
}{}
\makeatother
% Redefine sidenote with vertical crimson bar
\renewcommand{\sidenote}[1]{%
\oldsidenote{%
\noindent
\color{crimson!100} % Crimson vertical line
\raisebox{0em}{%
\rule{0.5pt}{1.5em} % Thin vertical line
}
\hspace{0.3em} % Space after line
\color{black} % Reset text color
\footnotesize #1 % Sidenote content
}%
}
% =============================================================================
% FIGURE ENVIRONMENT FIX
% =============================================================================
% Fix for caption display bug on even pages
\makeatletter
\let\oldfigure\figure%
\let\endoldfigure\endfigure%
\renewenvironment{figure}[1][htbp]{%
\oldfigure[#1]%
}{%
\endoldfigure%
}
\makeatother
% =============================================================================
% PAGE HEADERS AND FOOTERS
% =============================================================================
% Ensure chapters use fancy page style (not plain)
\patchcmd{\chapter}{\thispagestyle{plain}}{\thispagestyle{fancy}}{}{}
% Main page style with crimson headers
\pagestyle{fancy}
\fancyhf{} % Clear all
\fancyhead[LE]{\small\color{crimson}\nouppercase{\rightmark}} % Left even: section
\fancyhead[RO]{\color{crimson}\thepage} % Right odd: page number
\fancyhead[LO]{\small\color{crimson}\nouppercase{\leftmark}} % Left odd: chapter
\fancyhead[RE]{\color{crimson}\thepage} % Right even: page number
\renewcommand{\headrulewidth}{0.4pt} % Thin header line
\renewcommand{\footrulewidth}{0pt} % No footer line
% Plain page style (for chapter openings)
\fancypagestyle{plain}{
\fancyhf{}
\fancyfoot[C]{\color{crimson}\thepage} % Centered page number
\renewcommand{\headrulewidth}{0pt}
\renewcommand{\footrulewidth}{0pt}
}
% =============================================================================
% KOMA-SCRIPT FONT ADJUSTMENTS
% =============================================================================
% Apply crimson color to all heading levels
\addtokomafont{disposition}{\rmfamily\color{crimson}}
\addtokomafont{chapter}{\color{crimson}}
\addtokomafont{section}{\color{crimson}}
\addtokomafont{subsection}{\color{crimson}}
% =============================================================================
% ABSTRACT ENVIRONMENT
% =============================================================================
\newenvironment{abstract}{
\chapter*{\abstractname}
\addcontentsline{toc}{chapter}{\abstractname}
\small
}{
\clearpage
}
% =============================================================================
% HYPERLINK CONFIGURATION
% =============================================================================
% Crimson-colored links throughout, two-page PDF layout
\hypersetup{
linkcolor=crimson,
citecolor=crimson,
urlcolor=crimson,
pdfpagelayout=TwoPageRight, % Two-page spread view
pdfstartview=Fit % Initial zoom fits page
}
% =============================================================================
% PART SUMMARY SYSTEM
% =============================================================================
% Allows adding descriptive text below part titles
\newcommand{\partsummary}{} % Empty by default
\newif\ifhaspartsummary%
\haspartsummaryfalse%
\newcommand{\setpartsummary}[1]{%
\renewcommand{\partsummary}{#1}%
\haspartsummarytrue%
}
% Additional colors for part page backgrounds
\definecolor{BrownLL}{RGB}{233,222,220}
\definecolor{BlueDD}{RGB}{62,100,125}
\colorlet{BlueDD}{magenta}
% ===============================================================================
% PART STYLING SYSTEM
% ===============================================================================
%
% This system provides three distinct visual styles for book organization:
%
% 1. NUMBERED PARTS (\part{title}) - For main book sections
% - Roman numerals (I, II, III, etc.) in top right corner
% - Crimson title with horizontal lines above/below
% - "Part I" label in sidebar
% - Used for: foundations, principles, optimization, deployment, etc.
%
% 2. UNNUMBERED PARTS (\part*{title}) - For special sections like "Labs"
% - Division-style geometric background (left side)
% - No Roman numerals
% - Used for: labs section
%
% 3. DIVISIONS (\division{title}) - For major book divisions
% - Clean geometric background with centered title
% - Used for: frontmatter, main_content, backmatter
%
% The Lua filter (inject-parts.lua) automatically routes parts by {key:xxx} commands
% to the appropriate LaTeX command based on the key name.
% ===============================================================================
% NUMBERED PARTS: Roman numeral styling for main book sections
\titleformat{\part}[display]
{\thispagestyle{empty}}{}{20pt}{
\begin{tikzpicture}[remember picture,overlay]
%%%
%%
\node[crimson,align=flush right,
inner sep=0,outer sep=0mm,draw=none,%
anchor=east,minimum height=31mm, text width=1.2\textwidth,
yshift=-30mm,font={%
\fontsize{98pt}{104}\selectfont\bfseries}] (BG) at (current page text area.north east){\thepart};
%
\node[black,inner sep=0mm,draw=none,
anchor=mid,text width=1.2\textwidth,
minimum height=35mm, align=right,
node distance=7mm,below=of BG,
font={\fontsize{30pt}{34}\selectfont}]
(BGG) {\hyphenchar\font=-1 \color{black}\MakeUppercase {#1}};
\draw [crimson,line width=3pt] ([yshift=0mm]BGG.north west) -- ([yshift=0mm]BGG.north east);
\draw [crimson,line width=2pt] ([yshift=0mm]BGG.south west) -- ([yshift=0mm]BGG.south east);
%
\node[fill=crimson,text=white,rotate=90,%
anchor=south west,minimum height=15mm,
minimum width=40mm,font={%
\fontsize{20pt}{20}\selectfont\bfseries}](BP) at
(current page text area.south east)
{{\sffamily Part}~\thepart};
%
\path[red](BP.north west)-|coordinate(PS)(BGG.south west);
%
\ifhaspartsummary
\node[inner sep=4pt,text width=0.7\textwidth,draw=none,fill=BrownLL!40,
align=justify,font={\fontsize{9pt}{12}\selectfont},anchor=south west]
at (PS) {\partsummary};
\fi
\end{tikzpicture}
}[]
\renewcommand{\thepart}{\Roman{part}}
% UNNUMBERED PARTS: Division-style background for special sections
\titleformat{name=\part,numberless}[display]
{\thispagestyle{empty}}{}{20pt}{
\begin{tikzpicture}[remember picture,overlay]
%%%
\coordinate(S1)at([yshift=-200mm]current page.north west);
\draw[draw=none,fill=BlueDD!7](S1)--++(45:16)coordinate(S2)-
|(S2|-current page.north west)--(current page.north west)coordinate(S3)--(S1);
%
\coordinate(E1)at([yshift=-98mm]current page.north west);
\draw[draw=none,fill=BlueDD!15](E1)--(current page.north west)coordinate(E2)
--++(0:98mm)coordinate(E3)--(E1);
%
\coordinate(D1)at([yshift=15mm]current page.south west);
\draw[draw=none,fill=BlueDD!40,opacity=0.5](D1)--++(45:5.5)coordinate(D2)
-|(D2|-current page.north west)--(current page.north west)coordinate(D3)--(D1);
%%%%
\path[red](S2)-|(S2-|current page.east)coordinate(SS2);
%PART
\node[crimson,align=flush right,inner sep=0,outer sep=0mm,draw=none,anchor=south,
font={\fontsize{48pt}{48}\selectfont\bfseries}] (BG) at ($(S2)!0.5!(SS2)$){\hphantom{Part}};
%%%
\path[green]([yshift=15mm]D2)-|coordinate(TPD)(BG.south east);
\node[inner sep=0mm,draw=none,anchor=south east,%text width=0.9\textwidth,
align=right,font={\fontsize{40pt}{40}\selectfont}]
(BGG) at (TPD) {\color{crimson}\MakeUppercase {#1}};%\MakeUppercase {}
\end{tikzpicture}
}
% Define \numberedpart command for numbered parts
\newcommand{\numberedpart}[1]{%
\clearpage
\thispagestyle{empty}
\stepcounter{part}%
\begin{tikzpicture}[remember picture,overlay]
%%%
%%
\node[crimson,align=flush right,
inner sep=0,outer sep=0mm,draw=none,%
anchor=east,minimum height=31mm, text width=1.2\textwidth,
yshift=-30mm,font={%
\fontsize{98pt}{104}\selectfont\bfseries}] (BG) at (current page text area.north east){\thepart};
%
\node[black,inner sep=0mm,draw=none,
anchor=mid,text width=1.2\textwidth,
minimum height=35mm, align=right,
node distance=7mm,below=of BG,
font={\fontsize{30pt}{34}\selectfont}]
(BGG) {\hyphenchar\font=-1 \color{black}\MakeUppercase {#1}};
\draw [crimson,line width=3pt] ([yshift=0mm]BGG.north west) -- ([yshift=0mm]BGG.north east);
\draw [crimson,line width=2pt] ([yshift=0mm]BGG.south west) -- ([yshift=0mm]BGG.south east);
%
\node[fill=crimson,text=white,rotate=90,%
anchor=south west,minimum height=15mm,
minimum width=40mm,font={%
\fontsize{20pt}{20}\selectfont\bfseries}](BP) at
(current page text area.south east)
{{\sffamily Part}~\thepart};
%
\path[red](BP.north west)-|coordinate(PS)(BGG.south west);
%
\ifhaspartsummary
\node[inner sep=4pt,text width=0.7\textwidth,draw=none,fill=BrownLL!40,
align=justify,font={\fontsize{9pt}{12}\selectfont},anchor=south west]
at (PS) {\partsummary};
\fi
\end{tikzpicture}
\clearpage
}
% DIVISIONS: Clean geometric styling with subtle tech elements
% Used for frontmatter, main_content, and backmatter divisions
\newcommand{\division}[1]{%
\clearpage
\thispagestyle{empty}
\begin{tikzpicture}[remember picture,overlay]
% Clean geometric background (original design)
\coordinate(S1)at([yshift=-200mm]current page.north west);
\draw[draw=none,fill=BlueDD!7](S1)--++(45:16)coordinate(S2)-
|(S2|-current page.north west)--(current page.north west)coordinate(S3)--(S1);
\coordinate(E1)at([yshift=-98mm]current page.north west);
\draw[draw=none,fill=BlueDD!15](E1)--(current page.north west)coordinate(E2)
--++(0:98mm)coordinate(E3)--(E1);
\coordinate(D1)at([yshift=15mm]current page.south west);
\draw[draw=none,fill=BlueDD!40,opacity=0.5](D1)--++(45:5.5)coordinate(D2)
-|(D2|-current page.north west)--(current page.north west)coordinate(D3)--(D1);
% Subtle tech elements - positioned in white areas for better visibility
% Upper right white area - more visible
\draw[crimson!40, line width=0.8pt] ([xshift=140mm,yshift=-60mm]current page.north west) -- ++(40mm,0);
\draw[crimson!40, line width=0.8pt] ([xshift=150mm,yshift=-70mm]current page.north west) -- ++(30mm,0);
\draw[crimson!35, line width=0.7pt] ([xshift=160mm,yshift=-60mm]current page.north west) -- ++(0,-15mm);
\draw[crimson!35, line width=0.7pt] ([xshift=170mm,yshift=-70mm]current page.north west) -- ++(0,10mm);
% Circuit nodes - upper right
\fill[crimson!50] ([xshift=160mm,yshift=-60mm]current page.north west) circle (1.5mm);
\fill[white] ([xshift=160mm,yshift=-60mm]current page.north west) circle (0.8mm);
\fill[crimson!50] ([xshift=170mm,yshift=-70mm]current page.north west) circle (1.3mm);
\fill[white] ([xshift=170mm,yshift=-70mm]current page.north west) circle (0.6mm);
% Lower right white area - enhanced visibility
\draw[crimson!45, line width=0.9pt] ([xshift=140mm,yshift=-190mm]current page.north west) -- ++(45mm,0);
\draw[crimson!45, line width=0.9pt] ([xshift=150mm,yshift=-200mm]current page.north west) -- ++(35mm,0);
\draw[crimson!40, line width=0.8pt] ([xshift=160mm,yshift=-190mm]current page.north west) -- ++(0,-20mm);
\draw[crimson!40, line width=0.8pt] ([xshift=170mm,yshift=-200mm]current page.north west) -- ++(0,15mm);
% Additional connecting lines in lower right
\draw[crimson!35, line width=0.7pt] ([xshift=130mm,yshift=-180mm]current page.north west) -- ++(25mm,0);
\draw[crimson!35, line width=0.7pt] ([xshift=145mm,yshift=-180mm]current page.north west) -- ++(0,-25mm);
% Circuit nodes - lower right (more prominent)
\fill[crimson!55] ([xshift=160mm,yshift=-190mm]current page.north west) circle (1.6mm);
\fill[white] ([xshift=160mm,yshift=-190mm]current page.north west) circle (0.9mm);
\fill[crimson!55] ([xshift=170mm,yshift=-200mm]current page.north west) circle (1.4mm);
\fill[white] ([xshift=170mm,yshift=-200mm]current page.north west) circle (0.7mm);
\fill[crimson!50] ([xshift=145mm,yshift=-180mm]current page.north west) circle (1.2mm);
\fill[white] ([xshift=145mm,yshift=-180mm]current page.north west) circle (0.6mm);
% Title positioned in center - clean and readable
\node[inner sep=0mm,draw=none,anchor=center,text width=0.8\textwidth,
align=center,font={\fontsize{40pt}{40}\selectfont}]
(BGG) at (current page.center) {\color{crimson}\MakeUppercase {#1}};
\end{tikzpicture}
\clearpage
}
% LAB DIVISIONS: Circuit-style neural network design for lab sections
% Used specifically for lab platform sections (arduino, xiao, grove, etc.)
\newcommand{\labdivision}[1]{%
\clearpage
\thispagestyle{empty}
\begin{tikzpicture}[remember picture,overlay]
% Circuit background with subtle gradient
\coordinate(S1)at([yshift=-200mm]current page.north west);
\draw[draw=none,fill=BlueDD!5](S1)--++(45:16)coordinate(S2)-
|(S2|-current page.north west)--(current page.north west)coordinate(S3)--(S1);
% TOP AREA: Circuit lines in upper white space
\draw[crimson!50, line width=1.5pt] ([xshift=30mm,yshift=-40mm]current page.north west) -- ++(60mm,0);
\draw[crimson!40, line width=1pt] ([xshift=120mm,yshift=-50mm]current page.north west) -- ++(50mm,0);
\draw[crimson!50, line width=1.5pt] ([xshift=40mm,yshift=-70mm]current page.north west) -- ++(40mm,0);
% Connecting lines in top area
\draw[crimson!30, line width=1pt] ([xshift=60mm,yshift=-40mm]current page.north west) -- ++(0,-20mm);
\draw[crimson!30, line width=1pt] ([xshift=145mm,yshift=-50mm]current page.north west) -- ++(0,10mm);
% Neural nodes in top area
\fill[crimson!70] ([xshift=60mm,yshift=-40mm]current page.north west) circle (2.5mm);
\fill[white] ([xshift=60mm,yshift=-40mm]current page.north west) circle (1.5mm);
\fill[crimson!60] ([xshift=145mm,yshift=-50mm]current page.north west) circle (2mm);
\fill[white] ([xshift=145mm,yshift=-50mm]current page.north west) circle (1mm);
\fill[crimson!80] ([xshift=80mm,yshift=-70mm]current page.north west) circle (2mm);
\fill[white] ([xshift=80mm,yshift=-70mm]current page.north west) circle (1mm);
% BOTTOM AREA: Circuit lines in lower white space
\draw[crimson!50, line width=1.5pt] ([xshift=20mm,yshift=-200mm]current page.north west) -- ++(70mm,0);
\draw[crimson!40, line width=1pt] ([xshift=110mm,yshift=-210mm]current page.north west) -- ++(60mm,0);
\draw[crimson!50, line width=1.5pt] ([xshift=35mm,yshift=-230mm]current page.north west) -- ++(45mm,0);
% Connecting lines in bottom area
\draw[crimson!30, line width=1pt] ([xshift=55mm,yshift=-200mm]current page.north west) -- ++(0,-20mm);
\draw[crimson!30, line width=1pt] ([xshift=140mm,yshift=-210mm]current page.north west) -- ++(0,15mm);
% Neural nodes in bottom area
\fill[crimson!70] ([xshift=55mm,yshift=-200mm]current page.north west) circle (2.5mm);
\fill[white] ([xshift=55mm,yshift=-200mm]current page.north west) circle (1.5mm);
\fill[crimson!60] ([xshift=140mm,yshift=-210mm]current page.north west) circle (2mm);
\fill[white] ([xshift=140mm,yshift=-210mm]current page.north west) circle (1mm);
\fill[crimson!80] ([xshift=80mm,yshift=-230mm]current page.north west) circle (2mm);
\fill[white] ([xshift=80mm,yshift=-230mm]current page.north west) circle (1mm);
% SIDE AREAS: Subtle circuit elements on left and right edges
\draw[crimson!30, line width=1pt] ([xshift=15mm,yshift=-120mm]current page.north west) -- ++(20mm,0);
\draw[crimson!30, line width=1pt] ([xshift=175mm,yshift=-130mm]current page.north west) -- ++(15mm,0);
\fill[crimson!50] ([xshift=25mm,yshift=-120mm]current page.north west) circle (1.5mm);
\fill[white] ([xshift=25mm,yshift=-120mm]current page.north west) circle (0.8mm);
\fill[crimson!50] ([xshift=185mm,yshift=-130mm]current page.north west) circle (1.5mm);
\fill[white] ([xshift=185mm,yshift=-130mm]current page.north west) circle (0.8mm);
% Title positioned in center - CLEAN AREA
\node[inner sep=0mm,draw=none,anchor=center,text width=0.8\textwidth,
align=center,font={\fontsize{44pt}{44}\selectfont\bfseries}]
(BGG) at (current page.center) {\color{crimson}\MakeUppercase {#1}};
\end{tikzpicture}
\clearpage
}
% Define \lab command for lab styling (different visual treatment)
\newcommand{\lab}[1]{%
\begin{tikzpicture}[remember picture,overlay]
%%%
% Different background pattern for labs
\coordinate(S1)at([yshift=-200mm]current page.north west);
\draw[draw=none,fill=BlueDD!15](S1)--++(45:16)coordinate(S2)-
|(S2|-current page.north west)--(current page.north west)coordinate(S3)--(S1);
%
\coordinate(E1)at([yshift=-98mm]current page.north west);
\draw[draw=none,fill=BlueDD!25](E1)--(current page.north west)coordinate(E2)
--++(0:98mm)coordinate(E3)--(E1);
%
\coordinate(D1)at([yshift=15mm]current page.south west);
\draw[draw=none,fill=BlueDD!60,opacity=0.7](D1)--++(45:5.5)coordinate(D2)
-|(D2|-current page.north west)--(current page.north west)coordinate(D3)--(D1);
%%%%
\path[red](S2)-|(S2-|current page.east)coordinate(SS2);
%LAB - Different styling
\node[crimson,align=flush right,inner sep=0,outer sep=0mm,draw=none,anchor=south,
font={\fontsize{48pt}{48}\selectfont\bfseries}] (BG) at ($(S2)!0.5!(SS2)$){\hphantom{Workshop}};
%%%
\path[green]([yshift=15mm]D2)-|coordinate(TPD)(BG.south east);
\node[inner sep=0mm,draw=none,anchor=south east,%text width=0.9\textwidth,
align=right,font={\fontsize{40pt}{40}\selectfont}]
(BGG) at (TPD) {\color{crimson}\MakeUppercase {#1}};%\MakeUppercase {}
\end{tikzpicture}
\thispagestyle{empty}
\clearpage
}
% =============================================================================
% SECTION FORMATTING
% =============================================================================
% All section levels use crimson color and are ragged right
% Section (Large, bold, crimson)
\titleformat{\section}
{\normalfont\Large\bfseries\color{crimson}\raggedright}
{\thesection}
{0.5em}
{#1}
\titlespacing*{\section}{0pc}{14pt plus 4pt minus 4pt}{6pt plus 2pt minus 2pt}[0pc]
% Subsection (large, bold, crimson)
\titleformat{\subsection}
{\normalfont\large\bfseries\color{crimson}\raggedright}
{\thesubsection}
{0.5em}
{#1}
\titlespacing*{\subsection}{0pc}{12pt plus 4pt minus 4pt}{5pt plus 1pt minus 2pt}[0pc]
% Subsubsection (normal size, bold, crimson)
\titleformat{\subsubsection}
{\normalfont\normalsize\bfseries\color{crimson}\raggedright}
{\thesubsubsection}
{0.5em}
{#1}
\titlespacing*{\subsubsection}{0pc}{12pt plus 4pt minus 4pt}{5pt plus 1pt minus 2pt}[0pc]
% Paragraph (run-in, bold, crimson, ends with period)
\titleformat{\paragraph}[runin]
{\normalfont\normalsize\bfseries\color{crimson}}
{\theparagraph}
{0.5em}
{#1}
[\textbf{.}]
\titlespacing*{\paragraph}{0pc}{6pt plus 2pt minus 2pt}{0.5em}[0pc]
% Subparagraph (run-in, italic, crimson, ends with period)
\titleformat{\subparagraph}[runin]
{\normalfont\normalsize\itshape\color{crimson}}
{\thesubparagraph}
{0.5em}
{#1}
[\textbf{.}]
\titlespacing*{\subparagraph}{0pc}{6pt plus 2pt minus 2pt}{0.5em}[0pc]
% =============================================================================
% CHAPTER FORMATTING
% =============================================================================
% Numbered chapters: "Chapter X" prefix, huge crimson title
\titleformat{\chapter}[display]
{\normalfont\huge\bfseries\color{crimson}}
{\chaptername\ \thechapter}
{20pt}
{\Huge #1}
[]
% Unnumbered chapters: no prefix, huge crimson title
\titleformat{name=\chapter,numberless}
{\normalfont\huge\bfseries\color{crimson}}
{}
{0pt}
{\Huge #1}
[]
\renewcommand{\chaptername}{Chapter}
% =============================================================================
% TABLE OF CONTENTS FORMATTING
% =============================================================================
\setcounter{tocdepth}{2} % Show chapters, sections, subsections
% TOC spacing adjustments for number widths and indentation
\setlength{\cftchapnumwidth}{2em} % Chapter number width
\setlength{\cftsecnumwidth}{2.75em} % Section number width
\setlength{\cftsubsecnumwidth}{3.25em} % Subsection number width
\setlength{\cftsubsubsecnumwidth}{4em} % Subsubsection number width
\setlength{\cftsubsecindent}{4.25em} % Subsection indent
\setlength{\cftsubsubsecindent}{7.5em} % Subsubsection indent
% Chapter entries in TOC: bold crimson with "Chapter" prefix
\renewcommand{\cftchapfont}{\bfseries\color{crimson}}
\renewcommand{\cftchappresnum}{\color{crimson}Chapter~}
% Custom formatting for division entries (styled like parts)
\newcommand{\divisionchapter}[1]{%
\addvspace{12pt}%
\noindent\hfil\bfseries\color{crimson}#1\hfil\par%
\addvspace{6pt}%
}
% Adjust TOC spacing for "Chapter" prefix
\newlength{\xtraspace}
\settowidth{\xtraspace}{\cftchappresnum\cftchapaftersnum}
\addtolength{\cftchapnumwidth}{\xtraspace}
% Unnumbered chapters with TOC entry
\newcommand{\likechapter}[1]{%
\chapter*{#1}
\addcontentsline{toc}{chapter}{\textcolor{crimson}{#1}}
}
% =============================================================================
% PAGE NUMBERING SYSTEM
% =============================================================================
% Implements traditional book numbering:
% - Roman numerals (i, ii, iii...) for frontmatter
% - Arabic numerals (1, 2, 3...) for mainmatter
% Automatically switches at first numbered chapter
\makeatletter
\newif\if@firstnumbered%
\@firstnumberedtrue%
\newif\if@firstunnumbered%
\@firstunnumberedtrue%
\newcounter{lastRomanPage}
\setcounter{lastRomanPage}{1}
% Start document with Roman numerals (frontmatter)
\AtBeginDocument{
\pagenumbering{roman}
\renewcommand{\thepage}{\roman{page}}
}
% Intercept chapter command
\let\old@chapter\chapter%
\renewcommand{\chapter}{%
\@ifstar{\unnumbered@chapter}{\numbered@chapter}%
}
% Numbered chapters: switch to Arabic on first occurrence
\newcommand{\numbered@chapter}[1]{%
\if@firstnumbered%
\cleardoublepage%
\setcounter{lastRomanPage}{\value{page}}%
\pagenumbering{arabic}%
\@firstnumberedfalse%
\else
\setcounter{page}{\value{page}}%
\fi
\setcounter{sidenote}{1} % Reset footnote counter per chapter
\old@chapter{#1}%
}
% Unnumbered chapters: stay in Roman numerals
\newcommand{\unnumbered@chapter}[1]{%
\if@firstunnumbered%
\clearpage
\setcounter{lastRomanPage}{\value{page}}%
\pagenumbering{roman}%
\@firstunnumberedfalse%
\fi
\setcounter{sidenote}{1}
\old@chapter*{#1}%
}
\makeatother
% =============================================================================
% TABLE SIZING AND SPACING
% =============================================================================
% Make tables slightly smaller to fit more content
\AtBeginEnvironment{longtable}{\scriptsize}
% Increase vertical spacing in table cells (default is 1.0)
\renewcommand{\arraystretch}{1.3}
% Prefer placing tables at the top of pages
\makeatletter
\renewcommand{\fps@table}{t} % Default placement: top of page
\makeatother
% =============================================================================
% LONGTABLE PAGE BREAKING FIXES (Windows compatibility)
% =============================================================================
% Prevent "Infinite glue shrinkage" errors on Windows LaTeX builds
% by giving longtable more flexibility in page breaking
% Allow more flexible page breaking (vs strict \flushbottom)
\raggedbottom
% Process more rows before attempting page break (default is 20)
\setcounter{LTchunksize}{50}
% Add extra stretch for longtable environments specifically
\AtBeginEnvironment{longtable}{%
\setlength{\emergencystretch}{3em}%
\setlength{\parskip}{0pt plus 1pt}%
}
% =============================================================================
% TABLE STYLING - Clean tables with crimson borders
% =============================================================================
% Professional table appearance with:
% - Clean white background (no colored rows)
% - Crimson-colored borders
% - Good spacing for readability
%
% Note: Headers are automatically bolded by Quarto when using **text** in source
\usepackage{booktabs} % Professional table rules (\toprule, \midrule, \bottomrule)
\usepackage{colortbl} % For colored borders (\arrayrulecolor)
% Global table styling - crimson borders
\setlength{\arrayrulewidth}{0.5pt} % Thinner borders than default
%\arrayrulecolor{crimson} % Crimson borders matching brand
\setcounter{chapter}{0}