mirror of
https://github.com/HananoshikaYomaru/openfreebooks.git
synced 2026-11-13 13:05:51 +00:00
Add catalog subject banner feature: Introduce optional header images for subjects in the catalog, update CONTRIBUTING.md with guidelines for adding banners, and modify catalog data structure to support banner URLs. Enhance frontend components and styles to display banners effectively. Update TypeScript types and curriculum structure to accommodate new banner functionality.
This commit is contained in:
parent
8a7f6449a7
commit
7636bb4a86
35 changed files with 4140 additions and 10 deletions
|
|
@ -162,6 +162,73 @@
|
|||
z-index: 10;
|
||||
}
|
||||
|
||||
.catalog-header--has-banner {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.catalog-header--has-banner .catalog-header__head,
|
||||
.catalog-header--has-banner .catalog-header__filters {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.catalog-header--has-banner .catalog-header__head {
|
||||
margin-top: -3rem;
|
||||
}
|
||||
|
||||
.catalog-header--has-banner .catalog-header__filters {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.catalog-header__banner {
|
||||
position: relative;
|
||||
width: calc(100% + 64px);
|
||||
max-height: 220px;
|
||||
margin: 0 -32px;
|
||||
aspect-ratio: 2 / 1;
|
||||
overflow: hidden;
|
||||
background: var(--card-bg);
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
mask-image: linear-gradient(
|
||||
to bottom,
|
||||
#000 0%,
|
||||
#000 38%,
|
||||
rgb(0 0 0 / 0.55) 72%,
|
||||
transparent 100%
|
||||
);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to bottom,
|
||||
#000 0%,
|
||||
#000 38%,
|
||||
rgb(0 0 0 / 0.55) 72%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
transparent 58%,
|
||||
color-mix(in srgb, var(--card-bg) 45%, transparent) 80%,
|
||||
var(--card-bg) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.catalog-header__head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
@ -907,6 +974,22 @@ a.catalog-chapter-card {
|
|||
gap: 18px;
|
||||
}
|
||||
|
||||
.catalog-header--has-banner {
|
||||
padding: 0 0 16px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.catalog-header--has-banner .catalog-header__head,
|
||||
.catalog-header--has-banner .catalog-header__filters {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.catalog-header__banner {
|
||||
width: calc(100% + 40px);
|
||||
margin: 0 -20px;
|
||||
}
|
||||
|
||||
.catalog-header__head {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
|||
164
themes/openfreebooks/sass/_faq.scss
Normal file
164
themes/openfreebooks/sass/_faq.scss
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
.faq-page {
|
||||
padding: 54px 0 72px;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.faq-page__title {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.faq-page__intro {
|
||||
margin: 0 0 32px;
|
||||
max-width: var(--prose-width);
|
||||
color: var(--soft-ink);
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.72;
|
||||
}
|
||||
|
||||
.faq-jump {
|
||||
margin: 0 0 40px;
|
||||
padding: 20px 24px;
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 12px;
|
||||
background: var(--card-bg);
|
||||
box-shadow: 0 10px 28px var(--shadow-soft);
|
||||
}
|
||||
|
||||
.faq-jump__label {
|
||||
margin: 0 0 12px;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.faq-jump__list {
|
||||
margin: 0;
|
||||
padding: 0 0 0 1.25rem;
|
||||
max-width: var(--prose-width);
|
||||
color: var(--soft-ink);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.faq-jump__link {
|
||||
color: var(--accent);
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.faq-jump__link:hover,
|
||||
.faq-jump__link:focus-visible {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.faq-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
max-width: var(--prose-width);
|
||||
}
|
||||
|
||||
.faq-item {
|
||||
scroll-margin-top: 5rem;
|
||||
}
|
||||
|
||||
.faq-item__question {
|
||||
margin: 0 0 14px;
|
||||
font-family: var(--font-heading);
|
||||
font-size: clamp(1.2rem, 2vw, 1.45rem);
|
||||
font-weight: 400;
|
||||
line-height: 1.35;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.faq-item__answer {
|
||||
color: var(--soft-ink);
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.72;
|
||||
}
|
||||
|
||||
.faq-item__answer p {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
.faq-item__answer p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.faq-item__links {
|
||||
margin-top: 1rem !important;
|
||||
}
|
||||
|
||||
.faq-item__links a {
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.faq-item__links a:hover,
|
||||
.faq-item__links a:focus-visible {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.faq-compare__peers {
|
||||
margin: 20px 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.faq-compare__card {
|
||||
padding: 18px 20px;
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 12px;
|
||||
background: var(--card-bg);
|
||||
box-shadow: 0 10px 28px var(--shadow-soft);
|
||||
}
|
||||
|
||||
.faq-compare__name {
|
||||
margin: 0 0 8px;
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1.15rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.faq-compare__name a {
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.faq-compare__name a:hover,
|
||||
.faq-compare__name a:focus-visible {
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.faq-compare__role {
|
||||
margin: 0 0 8px;
|
||||
font-size: 0.98rem;
|
||||
}
|
||||
|
||||
.faq-compare__contrast {
|
||||
margin: 0;
|
||||
font-size: 0.98rem;
|
||||
}
|
||||
|
||||
.faq-compare__ofb {
|
||||
margin: 0;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.faq-page__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
@import "credits";
|
||||
@import "contributing";
|
||||
@import "about-manifest";
|
||||
@import "faq";
|
||||
@import "book";
|
||||
@import "catalog";
|
||||
@import "search";
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
"homeUrl": {{ get_url(path="/") | json_encode() | safe }},
|
||||
"browseUrl": {{ config.extra.browse_url | json_encode() | safe }},
|
||||
"aboutUrl": {{ config.extra.about_url | json_encode() | safe }},
|
||||
"faqUrl": {{ config.extra.faq_url | json_encode() | safe }},
|
||||
"searchUrl": {{ config.extra.search_url | json_encode() | safe }},
|
||||
"githubUrl": {{ config.extra.github_url | json_encode() | safe }}
|
||||
}
|
||||
|
|
@ -56,6 +57,7 @@
|
|||
<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.faq_url | safe }}">FAQ</a>
|
||||
<a class="footer-link" href="{{ config.extra.contributing_url | safe }}">Contribute</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>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@
|
|||
{% for subject in catalog.subjects %}
|
||||
{
|
||||
"id": {{ subject.id | json_encode() | safe }},
|
||||
"name": {{ subject.name | json_encode() | safe }},
|
||||
"name": {{ subject.name | json_encode() | safe }}{% if subject.banner %},
|
||||
"banner": {{ subject.banner | json_encode() | safe }}{% endif %},
|
||||
"strands": {% if subject.id == "math" %}{{ math_catalog.strands | json_encode() | safe }}{% else %}[]{% endif %}{% if subject.id == "math" and math_catalog.graph %},
|
||||
"graph": {{ math_catalog.graph | json_encode() | safe }}{% endif %}
|
||||
}{% if not loop.last %},{% endif %}
|
||||
|
|
|
|||
87
themes/openfreebooks/templates/faq.html
Normal file
87
themes/openfreebooks/templates/faq.html
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ page.title }} | {{ config.extra.site_title }}{% endblock title %}
|
||||
{% block meta_description %}
|
||||
<meta name="description" content="Frequently asked questions about {{ config.extra.site_title }} — free, open textbooks, how we differ from other sites, and how to use the catalog." />
|
||||
{% endblock meta_description %}
|
||||
|
||||
{% block content %}
|
||||
{% set faq = load_data(path="data/faq.json", format="json") %}
|
||||
<section class="faq-page">
|
||||
<div class="container">
|
||||
<p class="section-kicker">Help</p>
|
||||
<h1 class="section-title faq-page__title">{{ page.title }}</h1>
|
||||
<p class="faq-page__intro">{{ faq.intro }}</p>
|
||||
|
||||
<nav class="faq-jump" aria-label="On this page">
|
||||
<p class="faq-jump__label">On this page</p>
|
||||
<ol class="faq-jump__list">
|
||||
{% for item in faq.items %}
|
||||
<li>
|
||||
<a class="faq-jump__link" href="#faq-{{ item.id }}">{{ item.question }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="faq-list">
|
||||
{% for item in faq.items %}
|
||||
<article class="faq-item" id="faq-{{ item.id }}">
|
||||
<h2 class="faq-item__question">{{ item.question }}</h2>
|
||||
|
||||
{% if item.type == "comparison" %}
|
||||
<div class="faq-item__answer faq-compare">
|
||||
<p>{{ item.intro }}</p>
|
||||
<ul class="faq-compare__peers">
|
||||
{% for peer in item.peers %}
|
||||
<li class="faq-compare__card">
|
||||
<h3 class="faq-compare__name">
|
||||
<a href="{{ peer.url | safe }}" rel="noopener noreferrer">{{ peer.name }}</a>
|
||||
</h3>
|
||||
<p class="faq-compare__role">{{ peer.role }}</p>
|
||||
<p class="faq-compare__contrast">{{ peer.contrast }}</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p class="faq-compare__ofb">
|
||||
<strong>{{ config.extra.site_title }}</strong> — {{ item.ofb }}
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="faq-item__answer">
|
||||
{% for paragraph in item.answer %}
|
||||
<p>{{ paragraph }}</p>
|
||||
{% endfor %}
|
||||
|
||||
{% if item.id == "open-github" %}
|
||||
<p class="faq-item__links">
|
||||
<a href="{{ config.extra.contributing_url | safe }}">Contributing guide</a>
|
||||
·
|
||||
<a href="{{ config.extra.github_url | safe }}">GitHub repository</a>
|
||||
</p>
|
||||
{% elif item.id == "about-manifesto" %}
|
||||
<p class="faq-item__links">
|
||||
<a href="{{ config.extra.about_url | safe }}">Read the manifesto</a>
|
||||
</p>
|
||||
{% elif item.id == "subjects" or item.id == "find-chapters" or item.id == "catalog-curriculum" %}
|
||||
<p class="faq-item__links">
|
||||
<a href="{{ config.extra.browse_url | safe }}">Browse the catalog</a>
|
||||
{% if item.id == "find-chapters" %}
|
||||
·
|
||||
<a href="{{ config.extra.search_url | safe }}">Search chapters</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="faq-page__actions">
|
||||
<a class="button button-primary" href="{{ config.extra.browse_url | safe }}">Browse books</a>
|
||||
<a class="button button-secondary" href="{{ config.extra.contributing_url | safe }}">Contribute</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock content %}
|
||||
|
|
@ -23,6 +23,10 @@
|
|||
<span class="contributing-quick-nav__label">Chapter graph</span>
|
||||
<span class="contributing-quick-nav__text">Branches & prerequisites on the map</span>
|
||||
</a>
|
||||
<a class="contributing-quick-nav__card" href="#catalog-banner">
|
||||
<span class="contributing-quick-nav__label">Catalog banner</span>
|
||||
<span class="contributing-quick-nav__text">Optional subject header image</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<section id="overview" class="contributing-section">
|
||||
|
|
@ -133,6 +137,29 @@ bun run build # production build</code></pre>
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section id="catalog-banner" class="contributing-section">
|
||||
<h2 class="contributing-section__title">Catalog subject banner</h2>
|
||||
<div class="contributing-prose">
|
||||
<p>
|
||||
Optional image at the top of the catalog header for a subject (List and Map). Subjects without
|
||||
<code>banner</code> in <code>data/catalog.json</code> stay plain.
|
||||
</p>
|
||||
<ol>
|
||||
<li>Prepare a <strong>2:1</strong> image (e.g. 2000×1000).</li>
|
||||
<li>
|
||||
Optimize to WebP in <code>static/catalog/banners/{subject-id}.webp</code> — use
|
||||
<code>ffmpeg</code> with <code>libwebp</code>, or <code>cwebp -resize 1400 0 -q 82 …</code>
|
||||
if ffmpeg lacks WebP support.
|
||||
</li>
|
||||
<li>
|
||||
Add <code>"banner": "/catalog/banners/{subject-id}.webp"</code> on the subject in
|
||||
<code>data/catalog.json</code>.
|
||||
</li>
|
||||
<li>Verify at <code>/catalog/?subject=…</code> after <code>bun run build:js && zola serve</code>.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="contributing-section contributing-section--cta">
|
||||
<h2 class="contributing-section__title">Ready to open a pull request?</h2>
|
||||
<p class="contributing-page__lead">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue