learning-website-django1-5 Exercise 1: The Theme App ====================================================== Build the shared design as a reusable app: tokens, base styles and component styles as three small stylesheets, a context processor that tells every template which site it is on, and one base template. Django 6.1.2. PART A. Give every site a look. Add this to config/sites_config.py (the family chooses the neutral colours, the accent is the site's own colour): # How each site looks (Chapter 5). "family" picks the neutral colours: the language courses use one # set, the technical courses another (Learning Website: Framework & Architecture 4). "accent" is the # site's own colour: it must be readable on the dark surfaces (a test checks the contrast). SITE_STYLE = { "languages": {"family": "language", "accent": "#a78bfa"}, "webdevelopment": {"family": "technical", "accent": "#38bdf8"}, "programming": {"family": "technical", "accent": "#44b78b"}, "systems": {"family": "technical", "accent": "#f0938a"}, "ai": {"family": "technical", "accent": "#fbbf24"}, "humanities": {"family": "language", "accent": "#fb923c"}, "lifeskills": {"family": "language", "accent": "#6ee7b7"}, "creative": {"family": "technical", "accent": "#f472b6"}, } and register the context processor in config/settings/base.py: "OPTIONS": {"context_processors": [ "django.template.context_processors.request", "apps.theme.context_processors.site", ]}, Save as apps/theme/context_processors.py: from config.sites_config import SITE_STYLE def site(request): """Make the current site's name, title, neutral colour family and accent available to every template.""" name = getattr(request, "site", None) if name is None: # error pages for hosts that are not a site return {"site_name": None, "site_title": "Learning Notes", "site_family": "technical", "site_accent": "#44b78b"} style = SITE_STYLE[name] return {"site_name": name, "site_title": request.site_info["title"], "site_family": style["family"], "site_accent": style["accent"]} PART B. The three stylesheets and the script, in apps/theme/static/theme/. The order they are loaded in matters (tokens, base, components): Save as apps/theme/static/theme/tokens.css: /* tokens.css: the ONLY place a colour is decided. Everything else reads these variables. */ :root { --bg: #0f1117; --font-body: system-ui, -apple-system, "Segoe UI", sans-serif; --font-mono: Consolas, "Cascadia Mono", monospace; --radius: 8px; --max-width: 56rem; /* --accent is set per site, on the element, from config/sites_config.py */ } /* the two neutral families that exist on the site today (measured in Framework & Architecture 4) */ [data-family="language"] { --surface: #1a1d27; --border: #2a2d3a; --text: #e2e4ec; --muted: #a0a4b8; } [data-family="technical"] { --surface: #161b22; --border: #30363d; --text: #c9d1d9; --muted: #8b949e; } /* a lesson fragment keeps its own language colour; it wins inside its own wrapper */ .hu-lesson { --accent: #5aa469; } /* Hungarian */ .de-lesson { --accent: #e0a82e; } /* German */ .jp-lesson { --accent: #d64550; } /* Japanese */ .fr-lesson { --accent: #4f8ef7; } /* French */ Save as apps/theme/static/theme/base.css: /* base.css: the page itself: typography, layout, links. Colours come from tokens.css. */ *, *::before, *::after { box-sizing: border-box; } html { background: var(--bg); } body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--font-body); line-height: 1.7; } a { color: var(--accent); } a:focus-visible, button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } .skip-link { position: absolute; left: -999px; top: 0; padding: .5rem 1rem; background: var(--accent); color: #0f1117; z-index: 10; } .skip-link:focus { left: 0; } .site-bar { display: flex; align-items: center; gap: 1.5rem; padding: .75rem 1.25rem; background: var(--surface); border-bottom: 2px solid var(--accent); } .site-bar__name { color: var(--text); font-weight: 700; text-decoration: none; } main { max-width: var(--max-width); margin: 0 auto; padding: 1.5rem 1.25rem 3rem; } .page-title { color: #fff; font-size: 1.9rem; line-height: 1.25; margin: .5rem 0 1.25rem; } .page-body { overflow-wrap: anywhere; } .site-footer { border-top: 1px solid var(--border); color: var(--muted); text-align: center; padding: 1.25rem; font-size: .85rem; } @media print { .site-bar, .site-footer, .skip-link { display: none; } body { background: #fff; color: #000; } } Save as apps/theme/static/theme/components.css: /* components.css: the shared look of the building blocks that lesson fragments already use. Every selector is a single class, so a fragment's own scoped rule (".lesson .tip-box") is more specific and wins. That is deliberate: old pages keep their look until their own CSS is removed. */ .tip-box, .warn-box, .finding-box { background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--accent); border-radius: 0 var(--radius) var(--radius) 0; padding: 1rem 1.2rem; margin: 1.2rem 0; font-size: .92rem; } .warn-box { border-left-color: #e3b341; } .finding-box { border-left-color: #a371f7; } .code-block { background: #0d1117; border: 1px solid var(--border); border-radius: var(--radius); padding: 1rem 1.2rem; margin: 1rem 0 1.5rem; overflow-x: auto; font-family: var(--font-mono); font-size: .85rem; line-height: 1.6; white-space: pre; } .code-block-wrap { position: relative; } .copy-btn { position: absolute; top: 8px; right: 8px; padding: 3px 8px; cursor: pointer; background: var(--surface); border: 1px solid var(--border); color: var(--muted); border-radius: 4px; font-size: .7rem; font-family: var(--font-body); } .copy-btn:hover { color: var(--text); } @media print { .copy-btn { display: none; } } .compare-table-wrap { overflow-x: auto; margin: 1rem 0 1.5rem; } .compare-table, .vocab-table { width: 100%; border-collapse: collapse; font-size: .86rem; } .compare-table th, .vocab-table th { background: var(--surface); color: var(--accent); text-align: left; padding: .5rem .7rem; border-bottom: 2px solid var(--border); font-size: .72rem; text-transform: uppercase; letter-spacing: .07em; } .compare-table td, .vocab-table td { padding: .5rem .7rem; border-bottom: 1px solid var(--border); vertical-align: top; } .challenge-block { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); margin: 1rem 0 1.5rem; overflow: hidden; } .challenge-block .ch-num { background: var(--accent); color: #0f1117; font-size: .7rem; font-weight: 700; text-transform: uppercase; padding: .5rem 1rem; } .challenge-block .ch-body { padding: .9rem 1.1rem; } .quick-ref { background: #0d1117; border: 1px solid var(--border); border-radius: var(--radius); padding: 1.1rem 1.3rem; margin-top: 2rem; } Save as apps/theme/static/theme/copy-code.js: /* copy-code.js: loaded once by the base template. Page bodies are inserted as HTML text, and the browser does not run a
Skip to the content