Update .gitignore to include additional static assets for KaTeX, add KaTeX and Commander dependencies in package.json and bun.lock, enhance footer with an About link in base.html, and improve work card links in index.html. Update Vite configuration for asset handling and add new components in SASS files.

This commit is contained in:
Hananoshika Yomaru 2026-05-18 23:35:42 -07:00
parent 3e6c53b928
commit 0d36f3a2d6
114 changed files with 9743 additions and 14 deletions

View file

@ -0,0 +1,89 @@
.about-page {
padding: 54px 0 72px;
border-top: 1px solid var(--line);
}
.about-page__title {
margin-bottom: 20px;
}
.about-page__intro {
margin: 0 0 12px;
max-width: var(--prose-width);
font-family: var(--font-heading);
font-size: clamp(1.35rem, 2.4vw, 1.75rem);
font-weight: 400;
line-height: 1.35;
color: var(--ink);
}
.about-page__lead {
margin: 0 0 40px;
max-width: var(--prose-width);
color: var(--soft-ink);
font-size: 1.05rem;
line-height: 1.72;
}
.manifest-principles {
margin: 0 0 48px;
}
.manifest-principles__sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.manifest-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
gap: 20px;
margin: 0;
padding: 0;
list-style: none;
}
.manifest-card {
margin: 0;
padding: 24px 22px;
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 12px;
box-shadow: 0 12px 32px var(--shadow-soft);
}
.manifest-card__title {
margin: 0 0 12px;
font-family: var(--font-heading);
font-size: clamp(1.5rem, 2.5vw, 1.85rem);
font-weight: 400;
line-height: 1.15;
color: var(--ink);
}
.manifest-card__body {
margin: 0;
max-width: 42ch;
color: var(--soft-ink);
font-size: 0.98rem;
line-height: 1.65;
}
.about-page__actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
@media (max-width: 640px) {
.manifest-grid {
grid-template-columns: 1fr;
}
}

View file

@ -0,0 +1,259 @@
.book-page {
padding: 48px 0 96px;
}
.book-breadcrumb {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 28px;
font-family: var(--mono);
font-size: 0.72rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.book-breadcrumb a {
color: var(--soft-ink);
text-decoration: none;
}
.book-breadcrumb a:hover,
.book-breadcrumb a:focus-visible {
color: var(--accent);
}
.book-breadcrumb__sep {
color: var(--muted);
}
.book-header {
max-width: var(--prose-width);
margin-bottom: 56px;
}
.book-subtitle {
margin: 12px 0 0;
font-family: var(--mono);
font-size: 0.85rem;
letter-spacing: 0.06em;
color: var(--accent);
}
.book-intro {
margin: 20px 0 0;
font-size: 1.05rem;
line-height: 1.65;
color: var(--soft-ink);
}
.book-strands {
display: grid;
gap: 40px;
}
.book-strand__title {
margin: 0 0 16px;
font-size: 1.5rem;
}
.book-chapter-list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 10px;
}
.book-chapter-link {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 16px 20px;
border: 1px solid var(--card-border);
border-radius: 12px;
background: var(--card-bg);
text-decoration: none;
color: var(--ink);
transition: border-color 200ms ease, box-shadow 200ms ease;
}
a.book-chapter-link:hover,
a.book-chapter-link:focus-visible {
border-color: var(--accent);
box-shadow: 0 6px 20px var(--shadow-soft);
}
.book-chapter-link--disabled {
opacity: 0.72;
cursor: default;
}
.book-chapter-item--planned .book-chapter-link__title {
color: var(--soft-ink);
}
.book-chapter-link__title {
font-family: var(--font-heading);
font-size: 1.15rem;
}
.book-chapter-badge {
flex-shrink: 0;
font-family: var(--mono);
font-size: 0.65rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 4px 10px;
border-radius: 999px;
}
.book-chapter-badge--live {
color: var(--accent);
background: rgba(154, 107, 46, 0.12);
}
.book-chapter-badge--planned {
color: var(--muted);
background: rgba(118, 118, 118, 0.12);
}
.book-chapter {
max-width: var(--prose-width);
}
.book-chapter__header {
margin-bottom: 32px;
}
.book-chapter__lead {
margin: 16px 0 0;
font-size: 1.05rem;
line-height: 1.65;
color: var(--soft-ink);
}
.book-prose {
font-size: 1rem;
line-height: 1.7;
color: var(--ink);
}
.book-prose > p {
margin: 0 0 1.1em;
}
.book-prose__heading {
margin: 2em 0 0.6em;
font-size: 1.35rem;
}
.book-formula {
margin: 0;
font-family: var(--mono);
font-size: 1.1rem;
letter-spacing: 0.02em;
}
.book-formula--inline {
font-size: 0.95rem;
}
.book-callout {
margin: 1.5em 0;
padding: 18px 20px;
border-left: 3px solid var(--accent);
border-radius: 0 10px 10px 0;
background: var(--card-bg);
}
.book-callout--formula {
text-align: center;
border-left: none;
border: 1px solid var(--card-border);
border-radius: 12px;
}
.book-callout--dse {
border-left-color: var(--muted);
}
.book-callout__label {
margin: 0 0 8px;
font-family: var(--mono);
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--accent);
}
.book-table-wrap {
margin: 1.25em 0 1.5em;
overflow-x: auto;
}
.book-table {
width: 100%;
border-collapse: collapse;
font-size: 0.92rem;
border: 1px solid var(--line);
}
.book-table th,
.book-table td {
padding: 12px 14px;
text-align: left;
border: 1px solid var(--line);
vertical-align: top;
}
.book-table thead th {
font-family: var(--mono);
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ink);
background: rgba(154, 107, 46, 0.08);
}
.book-table tbody td:first-child {
font-family: var(--mono);
font-size: 0.88rem;
}
.book-table tbody tr:nth-child(even) td {
background: rgba(154, 107, 46, 0.04);
}
.book-prose .katex {
font-size: 1.05em;
}
.book-formula .katex-display {
margin: 0;
}
.math-widget-mount {
margin-top: 40px;
max-width: 960px;
}
.book-page--chapter .book-callout--dse {
max-width: var(--prose-width);
margin-top: 2rem;
}
@media (min-width: 900px) {
.book-page--chapter .book-chapter {
max-width: none;
}
.book-prose {
max-width: var(--prose-width);
}
}

View file

@ -320,6 +320,24 @@ h3 {
line-height: 1.7;
}
.work-card--link .work-card__anchor,
.work-card--link .work-card__anchor:any-link {
display: block;
color: inherit;
text-decoration: none;
}
.work-card__anchor h3,
.work-card__anchor p,
.work-card__anchor .card-subtitle {
text-decoration: none;
}
.work-card__anchor:hover h3,
.work-card__anchor:focus-visible h3 {
color: var(--accent);
}
.card-subtitle {
font-style: italic;
color: var(--muted);

View file

@ -0,0 +1,247 @@
.math-widget {
--math-grid: #e5e5e5;
--math-axis: #a8a29e;
--math-curve: #9a6b2e;
--math-vertex: #4a6b4a;
--math-root: #2563eb;
--math-canvas-bg: #ffffff;
padding: 28px;
border: 1px solid var(--card-border);
border-radius: 14px;
background: var(--card-bg);
box-shadow: 0 10px 32px var(--shadow-soft);
}
[data-theme="dark"] .math-widget {
--math-grid: #3a3834;
--math-axis: #8f8a80;
--math-curve: #c4924a;
--math-vertex: #84cc16;
--math-root: #60a5fa;
--math-canvas-bg: #242120;
}
.math-widget__title {
margin: 0 0 20px;
padding-bottom: 12px;
font-size: 1.35rem;
border-bottom: 1px solid var(--line);
}
.math-widget__layout {
display: grid;
gap: 28px;
}
.math-widget__equation-box {
margin-bottom: 20px;
padding: 16px;
text-align: center;
border-radius: 10px;
background: rgba(154, 107, 46, 0.06);
}
.math-widget__equation-label {
display: block;
margin-bottom: 8px;
font-family: var(--mono);
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
}
.math-widget__equation {
font-size: 1.1rem;
}
.math-widget__equation .katex {
font-size: 1.15em;
}
.math-widget__controls {
min-width: 0;
}
.math-slider-row {
margin-bottom: 16px;
}
.math-slider-label {
display: flex;
justify-content: space-between;
gap: 12px;
margin-bottom: 8px;
font-size: 0.88rem;
font-weight: 500;
color: var(--soft-ink);
}
.math-slider-value {
font-family: var(--mono);
color: var(--accent);
font-weight: 700;
}
.math-slider {
width: 100%;
}
.math-slider--a {
accent-color: var(--accent);
}
.math-slider--b {
accent-color: #4a6b4a;
}
.math-slider--c {
accent-color: #2563eb;
}
.math-widget__delta-panel {
margin-top: 8px;
padding: 16px;
border: 1px solid var(--line);
border-radius: 10px;
background: rgba(154, 107, 46, 0.05);
}
.math-widget__delta-title {
margin: 0 0 10px;
font-size: 1rem;
}
.math-widget__delta-formula {
margin-bottom: 8px;
font-size: 1rem;
}
.math-widget__delta-formula--pos .katex {
color: #16a34a;
}
.math-widget__delta-formula--neg .katex {
color: #dc2626;
}
.math-widget__delta-nature {
margin: 0;
font-size: 0.9rem;
font-weight: 500;
color: var(--soft-ink);
}
.math-widget__delta-roots {
margin: 8px 0 0;
font-size: 0.85rem;
color: var(--muted);
}
.math-widget__graph {
margin: 0;
min-width: 0;
}
.math-widget__canvas-wrap {
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 16px;
border-radius: 10px;
border: 1px solid var(--line);
background: rgba(154, 107, 46, 0.04);
}
.math-widget__canvas {
display: block;
max-width: 100%;
height: auto;
border-radius: 8px;
border: 1px solid var(--line);
box-shadow: inset 0 1px 4px var(--shadow-soft);
cursor: crosshair;
touch-action: none;
}
.math-widget__canvas--active {
cursor: pointer;
}
.math-graph-tooltip {
position: absolute;
z-index: 2;
display: flex;
flex-direction: column;
gap: 2px;
padding: 8px 10px;
border-radius: 8px;
border: 1px solid var(--card-border);
background: var(--card-bg);
box-shadow: 0 6px 18px var(--shadow-soft);
pointer-events: none;
font-family: var(--mono);
font-size: 0.78rem;
line-height: 1.35;
animation: math-tooltip-in 120ms ease-out;
}
@keyframes math-tooltip-in {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
.math-graph-tooltip {
animation: none;
}
}
.math-graph-tooltip__label {
font-size: 0.62rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
}
.math-graph-tooltip__coords {
font-weight: 700;
color: var(--ink);
}
.math-graph-tooltip--vertex .math-graph-tooltip__label {
color: var(--math-vertex);
}
.math-graph-tooltip--root .math-graph-tooltip__label {
color: var(--math-root);
}
.math-graph-tooltip--curve .math-graph-tooltip__label {
color: var(--math-curve);
}
.math-widget__caption {
margin-top: 10px;
font-size: 0.82rem;
color: var(--muted);
text-align: center;
}
@media (min-width: 768px) {
.math-widget__layout {
grid-template-columns: minmax(240px, 1fr) minmax(280px, 1.2fr);
align-items: start;
}
}

View file

@ -5,3 +5,6 @@
@import "why-sections";
@import "contributors";
@import "credits";
@import "about-manifest";
@import "book";
@import "math-widget";

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import{k as v}from"./katex-Dc8nsIP1.js";var T=function(a,r,e){for(var t=e,d=0,l=a.length;t<r.length;){var n=r[t];if(d<=0&&r.slice(t,t+l)===a)return t;n==="\\"?t++:n==="{"?d++:n==="}"&&d--,t++}return-1},b=function(a){return a.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&")},k=/^\\begin{/,w=function(a,r){for(var e,t=[],d=new RegExp("("+r.map(o=>b(o.left)).join("|")+")");e=a.search(d),e!==-1;){e>0&&(t.push({type:"text",data:a.slice(0,e)}),a=a.slice(e));var l=r.findIndex(o=>a.startsWith(o.left));if(e=T(r[l].right,a,r[l].left.length),e===-1)break;var n=a.slice(0,e+r[l].right.length),i=k.test(n)?n:a.slice(r[l].left.length,e);t.push({type:"math",data:i,rawData:n,display:r[l].display}),a=a.slice(e+r[l].right.length)}return a!==""&&t.push({type:"text",data:a}),t},N=function(a,r){var e=w(a,r.delimiters);if(e.length===1&&e[0].type==="text")return null;for(var t=document.createDocumentFragment(),d=0;d<e.length;d++)if(e[d].type==="text")t.appendChild(document.createTextNode(e[d].data));else{var l=document.createElement("span"),n=e[d].data;r.displayMode=e[d].display;try{r.preProcess&&(n=r.preProcess(n)),v.render(n,l,r)}catch(i){if(!(i instanceof v.ParseError))throw i;r.errorCallback("KaTeX auto-render: Failed to parse `"+e[d].data+"` with ",i),t.appendChild(document.createTextNode(e[d].rawData));continue}t.appendChild(l)}return t},p=function(a,r){for(var e=function(l){var n=a.childNodes[l];if(n.nodeType===3){for(var i,o=(i=n.textContent)!=null?i:"",f=n.nextSibling,h=0;f&&f.nodeType===Node.TEXT_NODE;){var g;o+=(g=f.textContent)!=null?g:"",f=f.nextSibling,h++}var c=N(o,r);if(c){for(var u=0;u<h;u++)n.nextSibling.remove();l+=c.childNodes.length-1,a.replaceChild(c,n)}else l+=h}else if(n.nodeType===1){var y=" "+n.className+" ",m=!r.ignoredTags.has(n.nodeName.toLowerCase())&&r.ignoredClasses.every(E=>!y.includes(" "+E+" "));m&&p(n,r)}t=l},t=0;t<a.childNodes.length;t++)e(t)},M=function(a,r){if(!a)throw new Error("No element provided to render");var e={};Object.assign(e,r),e.delimiters=e.delimiters||[{left:"$$",right:"$$",display:!0},{left:"\\(",right:"\\)",display:!1},{left:"\\begin{equation}",right:"\\end{equation}",display:!0},{left:"\\begin{align}",right:"\\end{align}",display:!0},{left:"\\begin{alignat}",right:"\\end{alignat}",display:!0},{left:"\\begin{gather}",right:"\\end{gather}",display:!0},{left:"\\begin{CD}",right:"\\end{CD}",display:!0},{left:"\\[",right:"\\]",display:!0}],e.ignoredTags=new Set((r==null?void 0:r.ignoredTags)||["script","noscript","style","textarea","pre","code","option"]),e.ignoredClasses=e.ignoredClasses||[],e.errorCallback=e.errorCallback||console.error,e.macros=e.macros||{},p(a,e)};const x=[{left:"$$",right:"$$",display:!0},{left:"\\(",right:"\\)",display:!1},{left:"\\[",right:"\\]",display:!0}];function I(){document.querySelectorAll(".book-prose, .book-callout").forEach(s=>{M(s,{delimiters:x,throwOnError:!1,strict:"ignore"})})}export{I as renderBookMath};

View file

@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block title %}{{ page.title }} | {{ config.extra.site_title }}{% endblock title %}
{% block meta_description %}
<meta name="description" content="The guiding principles behind {{ config.extra.site_title }} — free, open, structured textbooks for every learner." />
{% endblock meta_description %}
{% block content %}
{% set manifest = load_data(path="data/manifest.json", format="json") %}
<section class="about-page">
<div class="container">
<p class="section-kicker">About</p>
<h1 class="section-title about-page__title">{{ page.title }}</h1>
<p class="about-page__intro">{{ manifest.intro }}</p>
<p class="about-page__lead">{{ manifest.lead }}</p>
{% include "partials/manifest-principles.html" %}
<div class="about-page__actions">
<a class="button button-primary" href="{{ config.extra.browse_url | safe }}">Browse books</a>
<a class="button button-secondary" href="{{ config.extra.github_url | safe }}">View on GitHub</a>
</div>
</div>
</section>
{% endblock content %}

View file

@ -43,13 +43,14 @@
<div class="container footer-inner">
<span class="footer-copy">© <span id="footer-year"></span> {{ config.extra.site_title }} — Free, open textbooks for every learner</span>
<nav class="footer-links" aria-label="Footer links">
<a class="footer-link" href="{{ config.extra.about_url | safe }}">About</a>
<a class="footer-link" href="{{ config.extra.credits_url | safe }}">Credits</a>
<a class="footer-link" href="{{ config.extra.github_url | safe }}">GitHub</a>
</nav>
</div>
</footer>
<script src="{{ get_url(path='js/bundle.js', trailing_slash=false) | safe }}" defer></script>
<script type="module" src="{{ get_url(path='js/bundle.js', trailing_slash=false) | safe }}"></script>
{% block extra_scripts %}{% endblock extra_scripts %}
</body>
</html>

View file

@ -37,10 +37,12 @@
<p class="section-kicker">Catalog</p>
<h2 class="section-title">Browse by subject</h2>
<div class="work-grid" data-scroll-reveal>
<article class="work-card" data-scroll-reveal-card>
<h3>Mathematics</h3>
<p class="card-subtitle">Coming soon</p>
<p>Algebra, calculus, statistics, and more — structured for self-study and classrooms.</p>
<article class="work-card work-card--link" data-scroll-reveal-card>
<a class="work-card__anchor" href="{{ get_url(path='/math/') | safe }}">
<h3>Mathematics</h3>
<p class="card-subtitle">DSE Compulsory — live</p>
<p>Hong Kong secondary mathematics: three curriculum strands, starting with quadratic equations.</p>
</a>
</article>
<article class="work-card" data-scroll-reveal-card>
<h3>Science</h3>
@ -67,10 +69,12 @@
<p class="section-kicker">Featured</p>
<h2 class="section-title">Featured books</h2>
<div class="work-grid" data-scroll-reveal>
<article class="work-card" data-scroll-reveal-card>
<h3>Your textbook here</h3>
<p class="card-subtitle">Placeholder</p>
<p>We are building the first open catalog. The first published books will appear in this grid.</p>
<article class="work-card work-card--link" data-scroll-reveal-card>
<a class="work-card__anchor" href="{{ get_url(path='/math/') | safe }}">
<h3>Hong Kong Mathematics</h3>
<p class="card-subtitle">DSE Compulsory Part</p>
<p>Number &amp; Algebra, Measures Shape &amp; Space, and Data Handling — with an interactive quadratic explorer.</p>
</a>
</article>
<article class="work-card" data-scroll-reveal-card>
<h3>Community contributions</h3>

View file

@ -0,0 +1,51 @@
{% extends "base.html" %}
{% block title %}{{ section.title }} | {{ config.extra.site_title }}{% endblock title %}
{% block meta_description %}
<meta name="description" content="{{ section.description | default(value=config.description) }}" />
{% endblock meta_description %}
{% block content %}
{% set curriculum = load_data(path="data/math-dse-curriculum.json", format="json") %}
<section class="book-page">
<div class="container">
<nav class="book-breadcrumb" aria-label="Breadcrumb">
<a href="{{ get_url(path='/') | safe }}">Home</a>
<span class="book-breadcrumb__sep" aria-hidden="true">/</span>
<span aria-current="page">{{ curriculum.title }}</span>
</nav>
<header class="book-header">
<p class="section-kicker">Mathematics</p>
<h1 class="section-title">{{ curriculum.title }}</h1>
<p class="book-subtitle">{{ curriculum.subtitle }}</p>
<p class="book-intro">{{ curriculum.intro }}</p>
</header>
<div class="book-strands">
{% for strand in curriculum.strands %}
<section class="book-strand" aria-labelledby="strand-{{ strand.id }}">
<h2 id="strand-{{ strand.id }}" class="book-strand__title">{{ strand.title }}</h2>
<ul class="book-chapter-list">
{% for chapter in strand.chapters %}
<li class="book-chapter-item{% if chapter.status != 'live' %} book-chapter-item--planned{% endif %}">
{% if chapter.status == 'live' %}
<a class="book-chapter-link" href="{{ get_url(path='math/' ~ chapter.slug ~ '/') | safe }}">
<span class="book-chapter-link__title">{{ chapter.title }}</span>
<span class="book-chapter-badge book-chapter-badge--live">Available</span>
</a>
{% else %}
<span class="book-chapter-link book-chapter-link--disabled">
<span class="book-chapter-link__title">{{ chapter.title }}</span>
<span class="book-chapter-badge book-chapter-badge--planned">Coming soon</span>
</span>
{% endif %}
</li>
{% endfor %}
</ul>
</section>
{% endfor %}
</div>
</div>
</section>
{% endblock content %}

View file

@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block title %}{{ section.title }} | Hong Kong Mathematics | {{ config.extra.site_title }}{% endblock title %}
{% block meta_description %}
<meta name="description" content="{{ section.description | default(value=config.description) }}" />
{% endblock meta_description %}
{% block extra_head %}
<link rel="stylesheet" href="{{ get_url(path='js/katex.css', trailing_slash=false) | safe }}" />
{% endblock extra_head %}
{% block content %}
{% set curriculum = load_data(path="data/math-dse-curriculum.json", format="json") %}
<section class="book-page book-page--chapter">
<div class="container">
<nav class="book-breadcrumb" aria-label="Breadcrumb">
<a href="{{ get_url(path='/') | safe }}">Home</a>
<span class="book-breadcrumb__sep" aria-hidden="true">/</span>
<a href="{{ get_url(path='/math/') | safe }}">{{ curriculum.title }}</a>
<span class="book-breadcrumb__sep" aria-hidden="true">/</span>
<span aria-current="page">{{ section.title }}</span>
</nav>
<article class="book-chapter">
<header class="book-chapter__header">
<p class="section-kicker">Number &amp; Algebra</p>
<h1 class="section-title">{{ section.title }}</h1>
{% if section.description %}
<p class="book-chapter__lead">{{ section.description }}</p>
{% endif %}
</header>
{% if section.extra.chapter_id == "quadratic-equations" %}
{% include "partials/math/quadratic-equations-content.html" %}
<div id="quadratic-explorer" class="math-widget-mount"></div>
{% include "partials/math/quadratic-equations-dse-callout.html" %}
{% endif %}
</article>
</div>
</section>
{% endblock content %}

View file

@ -0,0 +1,11 @@
<section class="manifest-principles" aria-labelledby="manifest-principles-heading">
<h2 id="manifest-principles-heading" class="manifest-principles__sr-only">Principles</h2>
<ul class="manifest-grid" data-scroll-reveal>
{% for principle in manifest.principles %}
<li class="manifest-card" id="{{ principle.id }}" data-scroll-reveal-card>
<h3 class="manifest-card__title">{{ principle.title }}</h3>
<p class="manifest-card__body">{{ principle.body }}</p>
</li>
{% endfor %}
</ul>
</section>

View file

@ -0,0 +1,59 @@
<div class="book-prose">
<p>
A quadratic equation in one unknown has the form \(ax^2 + bx + c = 0\), where
\(a \neq 0\). On the DSE Compulsory Part you solve these by factorisation, the quadratic
formula, or by reading information from the graph of \(y = ax^2 + bx + c\).
</p>
<h2 class="book-prose__heading">The discriminant</h2>
<p>
The <strong>discriminant</strong> \(\Delta\) (delta) is defined below. It tells you how many
<em>real</em> roots the equation has without fully solving it.
</p>
<div class="book-callout book-callout--formula" role="note">
<p class="book-callout__label">Definition</p>
<p class="book-formula">\[ \Delta = b^2 - 4ac \]</p>
</div>
<h2 class="book-prose__heading">Nature of roots</h2>
<div class="book-table-wrap">
<table class="book-table">
<thead>
<tr>
<th scope="col">Discriminant</th>
<th scope="col">Nature of roots</th>
<th scope="col">Graph of \(y = ax^2 + bx + c\)</th>
</tr>
</thead>
<tbody>
<tr>
<td>\(\Delta &gt; 0\)</td>
<td>Two distinct real roots</td>
<td>Parabola cuts the x-axis twice</td>
</tr>
<tr>
<td>\(\Delta = 0\)</td>
<td>One repeated real root (equal roots)</td>
<td>Vertex touches the x-axis</td>
</tr>
<tr>
<td>\(\Delta &lt; 0\)</td>
<td>No real roots</td>
<td>Parabola lies entirely above or below the x-axis</td>
</tr>
</tbody>
</table>
</div>
<p>
When \(\Delta \geq 0\), the quadratic formula gives the roots:
\[ x = \frac{-b \pm \sqrt{\Delta}}{2a} \]
</p>
<h2 class="book-prose__heading">Try it yourself</h2>
<p>
Use the explorer below to change \(a\), \(b\), and \(c\). Watch how \(\Delta\) and the
parabola change, and read off the nature of the roots.
</p>
</div>

View file

@ -0,0 +1,9 @@
<aside class="book-callout book-callout--dse" role="note">
<p class="book-callout__label">DSE note — Foundation vs Non-foundation</p>
<p>
For a maximum grade around <strong>Level 4</strong>, focus on solving quadratics,
interpreting \(\Delta\), and linking graphs to equations (Foundation topics). Candidates aiming at
<strong>Level 5*</strong> and above also need fluency with reverse-engineering: given the nature of
roots, solve inequalities such as \(\Delta \geq 0\) for an unknown constant.
</p>
</aside>