Initial commit

This commit is contained in:
jasa
2026-09-23 15:09:14 +02:00
commit e8a71dcb92
74 changed files with 2931 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
// Demo page behaviour. Not a build input — see src/demo/demo.scss.
import '../sass/main.scss'
import './demo.scss'
// -- Colour swatches ---------------------------------------------------------
//
// Read from the emitted custom properties rather than from a hand-kept list, so
// the demo cannot drift from the stylesheet: delete a token and its swatch
// disappears on reload.
function emittedTokens (group) {
const names = new Set()
for (const sheet of document.styleSheets) {
let rules
try { rules = sheet.cssRules } catch { continue } // cross-origin
for (const rule of rules) {
if (rule.selectorText !== ':root') continue
for (const prop of rule.style) {
if (prop.startsWith(`--tlig-${group}-`)) names.add(prop)
}
}
}
return [...names]
}
function renderSwatches () {
const root = getComputedStyle(document.documentElement)
const html = emittedTokens('color').map((name) => {
const value = root.getPropertyValue(name).trim()
const short = name.replace('--tlig-color-', '')
return `<figure class="swatch">
<div class="swatch__chip"><div class="swatch__fill" style="background:${value}"></div></div>
<figcaption class="swatch__meta">
<div class="swatch__name">${short}</div>
<div class="swatch__value">${value}</div>
</figcaption>
</figure>`
}).join('')
document.querySelector('#swatches').innerHTML = html
document.querySelector('#swatch-count').textContent = emittedTokens('color').length
}
// -- Script switcher ---------------------------------------------------------
//
// The point of this control: all four papyrus files share one family name and
// are separated by unicode-range, so switching scripts changes no CSS at all.
// The browser picks the font file by codepoint.
const SAMPLES = {
en: {
label: 'English',
heading: 'True Life in God',
line: 'Peace be with you — I am the Alpha and the Omega.',
note: 'Latin — PapyrusLETBold, the original file.',
},
hu: {
// The pangram, not the site's own title: "Igaz Élet Istenben" contains no
// codepoint above U+00FF, so it would be served by the Latin file and
// would demonstrate nothing. ő (U+0151) and ű (U+0171) are the test.
label: 'Magyar',
heading: 'Árvíztűrő tükörfúrógép',
line: 'Igaz Élet Istenben — ő and ű are what the Latin-Extended cut is for.',
note: 'Latin Extended-A — the hu file. The original PapyrusLETBold has no ő or ű.',
},
bg: {
label: 'Български',
heading: 'Истински живот в Бога',
line: 'Мир на вас — Аз съм Алфа и Омега.',
note: 'Cyrillic — the cyrillic and bulgarian cuts.',
},
ru: {
label: 'Русский',
heading: 'Истинная жизнь в Боге',
line: 'Мир вам — Я есмь Альфа и Омега.',
note: 'Cyrillic — same file as Bulgarian, no extra rule.',
},
el: {
label: 'Ελληνικά',
heading: 'Αληθινή εν Θεώ Ζωή',
line: 'Ειρήνη υμίν — Εγώ ειμι το Άλφα και το Ωμέγα.',
note: 'No papyrus cut exists for Greek — this is the fallback face, shown honestly.',
},
}
function setScript (code) {
const sample = SAMPLES[code]
if (!sample) return
document.querySelector('#script-heading').textContent = sample.heading
document.querySelector('#script-line').textContent = sample.line
document.querySelector('#script-note').textContent = sample.note
document.querySelector('#script-heading').lang = code
for (const button of document.querySelectorAll('[data-script]')) {
button.setAttribute('aria-pressed', String(button.dataset.script === code))
}
}
// -- Breakpoint readout ------------------------------------------------------
function trackViewport () {
const output = document.querySelector('#viewport')
const update = () => {
const width = window.innerWidth
const name = width >= 1200 ? 'wide' : width >= 800 ? 'tablet' : 'phone'
output.textContent = `${width}px · ${name}`
}
window.addEventListener('resize', update, { passive: true })
update()
}
// -- Wire up -----------------------------------------------------------------
renderSwatches()
trackViewport()
// `?script=bg` opens straight onto a given script, so a particular cut can be
// linked to or screenshotted without clicking.
const requested = new URLSearchParams(location.search).get('script')
setScript(requested in SAMPLES ? requested : 'en')
for (const button of document.querySelectorAll('[data-script]')) {
button.addEventListener('click', () => setScript(button.dataset.script))
}
const boxes = document.querySelector('#toggle-boxes')
boxes.addEventListener('click', () => {
const on = document.body.classList.toggle('show-boxes')
boxes.setAttribute('aria-pressed', String(on))
})
+256
View File
@@ -0,0 +1,256 @@
// -----------------------------------------------------------------------------
// Demo chrome — NOT part of the shipped stylesheet.
//
// Everything here exists to frame the foundation in something that looks like
// the real site (wood ground, papyrus scroll) and to give the page its toolbar
// and swatch grid. It is reached only from src/index.html, which is not a build
// input, so none of it can reach dist/tlig.css.
//
// The scroll frame is deliberately here and not in components/: page layout is
// still on the "not done" list, and a demo is not the place to quietly ship it.
// -----------------------------------------------------------------------------
@use '../sass/abstracts' as *;
$img: '/wp-content/includes/images';
// -- The page ground ---------------------------------------------------------
html {
background: url('#{$img}/background_planche_1920.jpg') top center / contain fixed;
}
body {
min-height: 100vh;
}
// The scroll: a head image, a vertically repeating middle, a foot image.
.scroll {
max-width: 1200px;
margin-inline: auto;
}
.scroll__head,
.scroll__foot {
background-repeat: no-repeat;
background-size: 100% 100%;
}
.scroll__head {
height: 140px;
background-image: url('#{$img}/page/background-message-entete_cnd.png.webp');
background-position: bottom;
}
.scroll__body {
margin-block-start: -1px;
padding: 0 clamp(#{space-raw('md')}, 5vw, 5rem) space('xl');
background: url('#{$img}/page/background-message-milieux1200x190-4_cnd.png.webp') repeat-y top / 100% auto;
}
.scroll__foot {
// The top 18px of this image ramp from alpha 15 up to its 242 plateau — it
// fades in. Butted against the body, that ramp composites over bare wood and
// reads as a hard line at the body's bottom edge. The overlap below is the
// measured ramp height plus a little, so the whole fade lands on papyrus.
//
// `top` cannot do this: the element is static, and only a positioned box
// moves. `position`/`z-index` pin the paint order the effect depends on —
// tree order already gives it, but silently.
position: relative;
z-index: 1;
margin-block-start: -20px;
height: 114px;
background-image: url('#{$img}/page/background-message-footer-1200x114_cnd.png.webp');
background-position: top;
}
// Below the tablet breakpoint the site swaps in narrower scroll images, which
// are not mirrored into this project. Rather than stretch the wide ones, the
// demo falls back to a plain parchment panel.
@include down('tablet') {
.scroll__head,
.scroll__foot { display: none; }
.scroll__body {
margin: space('md');
padding: space('lg');
border-radius: radius('md');
background: rgba(247, 240, 224, 0.92);
}
}
// -- Section scaffolding -----------------------------------------------------
.demo-section {
padding-block: space('xl');
& + & {
border-block-start: 1px solid rgba(145, 116, 85, 0.35);
}
}
.demo-label {
display: flex;
align-items: baseline;
gap: space('sm');
margin-block-end: space('lg');
font-family: font('body');
font-size: 11px;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
color: $brown-600;
&::after {
content: '';
flex: 1;
height: 1px;
background: currentColor;
opacity: 0.35;
}
}
.demo-note {
margin-block-start: space('sm');
font-family: font('body');
font-size: 13px;
font-style: italic;
color: $brown-600;
}
// A row of specimens that should sit side by side and wrap sanely.
.demo-row {
display: flex;
flex-wrap: wrap;
gap: space('md');
align-items: center;
}
// -- Swatches ----------------------------------------------------------------
.swatch-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: space('sm');
}
.swatch {
overflow: hidden;
border: 1px solid rgba(72, 32, 32, 0.15);
border-radius: radius('sm');
background: $white;
font-family: font('body');
font-size: 11px;
line-height: 1.5;
// Checkerboard, so an alpha token reads as translucent rather than as a
// slightly different flat colour.
&__chip {
height: 56px;
background-image:
linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%),
linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%);
background-size: 12px 12px;
background-position: 0 0, 6px 6px;
}
&__fill { height: 100%; }
&__meta { padding: space('2xs') space('xs'); }
&__name { font-weight: 700; color: $brown-900; }
&__value { color: $grey-500; font-variant-numeric: tabular-nums; }
}
// -- Type specimen table -----------------------------------------------------
.specimen {
display: grid;
grid-template-columns: 8rem 1fr;
gap: space('sm') space('lg');
align-items: baseline;
@include down('phone') {
grid-template-columns: 1fr;
gap: space('2xs');
}
}
.specimen__key {
font-family: font('body');
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: $brown-600;
}
// -- Dark-ground demo --------------------------------------------------------
.demo-dark {
padding: space('lg');
border-radius: radius('md');
background: $brown-900;
}
// -- Toolbar -----------------------------------------------------------------
.toolbar {
position: sticky;
top: 0;
z-index: 10;
display: flex;
flex-wrap: wrap;
gap: space('md');
align-items: center;
padding: space('xs') space('md');
background: rgba(30, 14, 14, 0.94);
color: #f0e6d2;
font-family: font('body');
font-size: 12px;
backdrop-filter: blur(6px);
&__brand {
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
}
&__group {
display: flex;
gap: space('2xs');
align-items: center;
}
&__spacer { flex: 1; }
button {
padding: 3px 10px;
border: 1px solid rgba(240, 230, 210, 0.3);
border-radius: radius('pill');
background: transparent;
color: inherit;
font-size: 12px;
&[aria-pressed='true'] {
background: #f0e6d2;
color: #1e0e0e;
border-color: #f0e6d2;
}
}
code {
font-variant-numeric: tabular-nums;
opacity: 0.8;
}
}
// "Show the boxes" — outlines every component so the structure is visible.
.show-boxes {
.site-footer__col,
.demo-row > *,
.field-group,
.scroll__body > * {
outline: 1px dashed rgba(202, 74, 31, 0.6);
outline-offset: 2px;
}
}
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.
+228
View File
@@ -0,0 +1,228 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>TLIG — stylesheet demo</title>
<script type="module" src="/demo/demo.js"></script>
</head>
<body>
<div class="toolbar">
<span class="toolbar__brand">tlig.css</span>
<span class="toolbar__group">
<span>Script:</span>
<button type="button" data-script="en" aria-pressed="true">English</button>
<button type="button" data-script="hu" aria-pressed="false">Magyar</button>
<button type="button" data-script="bg" aria-pressed="false">Български</button>
<button type="button" data-script="ru" aria-pressed="false">Русский</button>
<button type="button" data-script="el" aria-pressed="false">Ελληνικά</button>
</span>
<span class="toolbar__spacer"></span>
<button type="button" id="toggle-boxes" aria-pressed="false">Show boxes</button>
<code id="viewport"></code>
</div>
<div class="scroll">
<div class="scroll__head"></div>
<div class="scroll__body">
<!-- ------------------------------------------------------------------ -->
<section class="demo-section">
<h1>A stylesheet without the jigsaw</h1>
<div class="prose">
<p>Everything below is rendered by <code>src/sass/main.scss</code> — 10 kB
compiled, and every <code>!important</code> in it (four) sits in the
one <code>prefers-reduced-motion</code> block, where overriding author
animation is the point. <code>tlig_en.css</code> has 518. The wood ground and the papyrus scroll around this
text are <em>demo chrome</em>, not part of the stylesheet: page layout
is still on the to-do list, and a demo is not the place to quietly
ship it.</p>
<p>The frame is here anyway because a foundation only tells you the
truth when you see it on the ground it has to work on. Brown ink at
86% opacity looks arbitrary on white and obviously right on papyrus.</p>
</div>
</section>
<!-- ------------------------------------------------------------------ -->
<section class="demo-section">
<p class="demo-label">One family name, four files</p>
<h1 id="script-heading">True Life in God</h1>
<p class="prose" id="script-line"></p>
<p class="demo-note" id="script-note"></p>
<p class="demo-note">Use the toolbar to switch script. No CSS changes, no
language class, no second stylesheet — the four papyrus cuts share the
family name <code>papyrus-bold</code> and are separated by
<code>unicode-range</code>, so the browser picks the file by codepoint.</p>
</section>
<!-- ------------------------------------------------------------------ -->
<section class="demo-section">
<p class="demo-label">Type scale</p>
<div class="specimen">
<span class="specimen__key">h1 · 34px</span>
<h1>Papyrus-bold, tight leading</h1>
<span class="specimen__key">h2 · 22px</span>
<h2>Trebuchet, message red, bold</h2>
<span class="specimen__key">h3 · 20px</span>
<h3>Lato bold, heading ink</h3>
<span class="specimen__key">h4 · 25px</span>
<h4>Papyrus-bold, the section mark</h4>
<span class="specimen__key">h5 · 14px</span>
<h5>Lato bold, small heading</h5>
<span class="specimen__key">h6 · 12px</span>
<h6>Lato bold, uppercase</h6>
<span class="specimen__key">body · 18px</span>
<p>Lato at 18px on a 1.5 line height, in
<code>--tlig-color-page-ink</code>: a brown at 86% rather than black,
so it settles onto the papyrus instead of sitting on top of it. Here
is <strong>bold text</strong>, <em>italic text</em>, and
<small>small print in the muted token</small>.</p>
<span class="specimen__key">blockquote</span>
<blockquote>The translucent panel — <code>--tlig-color-page-panel</code>
at 30% white — with the message-red rule and the asymmetric corner the
site has always had.</blockquote>
</div>
<p class="demo-note">h2 is encoded as Trebuchet / #7a0012 / bold. Avada's
own token says Lato / #333333, but <code>tlig_en.css</code> overrides it
globally with four <code>!important</code>s — so the override is what
actually renders, and the override is what got written down.</p>
</section>
<!-- ------------------------------------------------------------------ -->
<section class="demo-section">
<p class="demo-label">Links</p>
<div class="prose">
<p>A <a href="#">default link</a> carries no underline — that is for
navigation and cards. Inside <code>.prose</code>, <a href="#">a link
underlines itself</a> without needing a class. The explicit
<a href="#" class="link-underline">.link-underline</a> variant is kept
for templates that ask for it, and <a href="#" class="link-plain">.link-plain</a>
inherits its colour, for a link wrapping a whole card.</p>
</div>
<p class="demo-note">The live rule is
<code>a:hover { color: #ca4a1f !important }</code> — one global
<code>!important</code> that every later link style then has to fight.
That is where most of the 518 in <code>tlig_en.css</code> start.</p>
</section>
<!-- ------------------------------------------------------------------ -->
<section class="demo-section">
<p class="demo-label">Buttons and fields</p>
<div class="demo-row">
<a href="#" class="button">View the magazine</a>
<a href="#" class="button button--donate">Donate</a>
<button class="button" type="button" disabled>Disabled</button>
</div>
<form class="field-group" style="margin-top:1.5rem" onsubmit="return false">
<input class="field field--attached" type="email" placeholder="Your email address" aria-label="Email address">
<button class="button button--attached" type="submit">Subscribe</button>
</form>
<p class="demo-note">Four ids and classes in the live CSS —
<code>#footer-viewMagazineSpan</code>, <code>.btnSubmitNewsletter</code>,
<code>.btnSubmitDailyMessage</code>, <code>.footer-donateSpan</code> —
repeating the same six declarations with different corner radii. One
class with modifiers replaces them.</p>
</section>
<!-- ------------------------------------------------------------------ -->
<section class="demo-section">
<p class="demo-label">Context re-points tokens</p>
<div class="demo-dark on-dark">
<h3>The same markup on a dark ground</h3>
<p>This block adds one class. It declares no colours of its own: the
<code>dark-ground</code> mixin re-points nine tokens, and this
heading, this body copy, this <a href="#">link</a>, its hover and the
button below are all correct as a result. No dark-specific rule for
any of them exists in the stylesheet.</p>
<p style="margin-top:1rem"><a href="#" class="button">A button, unchanged</a></p>
</div>
<p class="demo-note">This is the mechanism that replaces the footer's 24
id selectors: a region declares what its tokens mean, and every
component inside it is already right. The footer below includes the same
mixin — the list of what "dark" means exists once, so the two cannot
drift. An earlier draft of this page caught the bug that proves the
point: <code>.on-dark</code> had re-pointed the links but not
<code>--tlig-color-heading</code>, and the heading above rendered
dark-brown on dark-brown.</p>
</section>
<!-- ------------------------------------------------------------------ -->
<section class="demo-section">
<p class="demo-label">Colour tokens (<span id="swatch-count">0</span>)</p>
<div class="swatch-grid" id="swatches"></div>
<p class="demo-note">Generated at runtime from the <code>--tlig-color-*</code>
properties the stylesheet actually emits, not from a hand-kept list — so
this grid cannot drift. Delete a token and its swatch disappears.</p>
</section>
</div>
<div class="scroll__foot"></div>
</div>
<!-- The real footer component, not a mock ------------------------------- -->
<footer class="site-footer">
<div class="site-footer__inner">
<div class="site-footer__row">
<div class="site-footer__col site-footer__col--wide">
<p class="site-footer__title">The True Life in God magazine</p>
<p class="site-footer__text">Read the latest issue online, or order a
printed copy for your prayer group.</p>
<p style="margin-top:1rem"><a href="#" class="button">View the magazine</a></p>
</div>
<div class="site-footer__col">
<p class="site-footer__title">Newsletter</p>
<form class="field-group" onsubmit="return false">
<input class="field field--attached" type="email" placeholder="Your email address" aria-label="Email address">
<button class="button button--attached" type="submit">Subscribe</button>
</form>
<p class="site-footer__title" style="margin-top:2rem">Daily message</p>
<p><a href="#" class="button">Receive it by email</a></p>
</div>
</div>
<div class="site-footer__row">
<div class="site-footer__col">
<p class="site-footer__title">What is…</p>
<ul class="site-footer__menu">
<li><a href="#">What is True Life in God?</a></li>
<li><a href="#">Who is Vassula Rydén?</a></li>
<li><a href="#">The messages</a></li>
</ul>
</div>
<div class="site-footer__col">
<p class="site-footer__title">Support the mission</p>
<p><a href="#" class="button button--donate">Donate</a></p>
</div>
<div class="site-footer__col">
<p class="site-footer__title">Follow</p>
<ul class="site-footer__social">
<li><a href="#">Facebook</a></li>
<li><a href="#">YouTube</a></li>
<li><a href="#">Instagram</a></li>
</ul>
</div>
</div>
</div>
</footer>
</body>
</html>
+3
View File
@@ -0,0 +1,3 @@
// Entry point. Its only job today is to pull the stylesheet into the build;
// behaviour belongs in src/js/ and gets imported here as it is written.
import './sass/main.scss'
+37
View File
@@ -0,0 +1,37 @@
// -----------------------------------------------------------------------------
// Token accessors.
//
// These return `var(--tlig-…)` rather than the literal, so a token stays
// overridable at runtime. Use `*-raw()` only where a custom property cannot go
// — inside a media query condition, or in a `@font-face` block.
// -----------------------------------------------------------------------------
@use 'sass:map';
@use 'tokens' as *;
@function _var($group, $key, $map) {
@if not map.has-key($map, $key) {
@error 'Unknown #{$group} token "#{$key}". Known: #{map.keys($map)}.';
}
@return var(--#{$prefix}-#{$group}-#{$key});
}
@function color($key) { @return _var('color', $key, $colors); }
@function font($key) { @return _var('font', $key, $fonts); }
@function size($key) { @return _var('size', $key, $font-sizes); }
@function leading($key){ @return _var('leading', $key, $line-heights); }
@function weight($key) { @return _var('weight', $key, $font-weights); }
@function space($key) { @return _var('space', $key, $space); }
@function radius($key) { @return _var('radius', $key, $radii); }
// Compile-time literals, for the few places a custom property is not allowed.
@function color-raw($key) { @return map.get($colors, $key); }
@function font-raw($key) { @return map.get($fonts, $key); }
@function space-raw($key) { @return map.get($space, $key); }
@function breakpoint($key) {
@if not map.has-key($breakpoints, $key) {
@error 'Unknown breakpoint "#{$key}". Known: #{map.keys($breakpoints)}.';
}
@return map.get($breakpoints, $key);
}
+5
View File
@@ -0,0 +1,5 @@
// Single import surface for the abstract layer: `@use '../abstracts' as *;`
@forward 'palette';
@forward 'tokens';
@forward 'functions';
@forward 'mixins';
+89
View File
@@ -0,0 +1,89 @@
@use 'sass:map';
@use 'tokens' as *;
@use 'functions' as *;
// -- Media queries -----------------------------------------------------------
//
// Mobile-first: `up()` opens a floor, `down()` a ceiling, and `down()` subtracts
// 0.02px so the two never both match on the same viewport. The live CSS has
// exactly that overlap bug at 800px.
@mixin up($name) {
@media (min-width: #{breakpoint($name)}) { @content; }
}
@mixin down($name) {
@media (max-width: #{breakpoint($name) - 0.02px}) { @content; }
}
@mixin between($from, $to) {
@media (min-width: #{breakpoint($from)}) and (max-width: #{breakpoint($to) - 0.02px}) {
@content;
}
}
// -- Type --------------------------------------------------------------------
@mixin type($family: 'body', $size: 'base', $leading: 'normal', $weight: 'normal') {
font-family: font($family);
font-size: size($size);
line-height: leading($leading);
font-weight: weight($weight);
}
// Headings on this site are the display face at a fixed size and tight leading.
@mixin heading($size: '2xl') {
font-family: font('display');
font-size: size($size);
line-height: leading('tight');
font-weight: weight('normal');
color: color('heading');
}
// -- Patterns ----------------------------------------------------------------
// The translucent panel that carries text over the papyrus background.
@mixin panel($pad: 'lg', $round: 'md') {
padding: space($pad);
background-color: color('page-panel');
border-radius: radius($round);
}
// Visible only to screen readers.
@mixin visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
// A focus ring that survives on both the light page and the dark footer.
@mixin focus-ring($offset: 2px) {
outline: 2px solid currentColor;
outline-offset: $offset;
}
// Re-point the token set for a dark ground.
//
// This is the whole contract in one place: any region that includes it gets
// correct body copy, headings, links and hovers with no further declarations.
// It must cover *every* token that encodes a light-ground assumption — an
// incomplete list is how a heading ends up dark-brown on dark-brown.
@mixin dark-ground {
--#{$prefix}-color-page-ink: #{map.get($colors, 'footer-ink')};
--#{$prefix}-color-page-ink-strong: #{map.get($colors, 'footer-ink')};
--#{$prefix}-color-page-ink-muted: #{map.get($colors, 'footer-rule')};
--#{$prefix}-color-heading: #{map.get($colors, 'footer-ink')};
--#{$prefix}-color-heading-message: #{map.get($colors, 'footer-link-hover')};
--#{$prefix}-color-link: #{map.get($colors, 'footer-link')};
--#{$prefix}-color-link-hover: #{map.get($colors, 'footer-link-hover')};
--#{$prefix}-color-link-underlined: #{map.get($colors, 'footer-link')};
--#{$prefix}-color-page-rule: #{map.get($colors, 'footer-rule')};
color: color('page-ink');
}
+66
View File
@@ -0,0 +1,66 @@
// -----------------------------------------------------------------------------
// Raw palette — every literal colour the live site actually uses.
//
// Nothing outside this file may write a hex value. Rules consume the *semantic*
// tokens in `_tokens.scss`, never these names directly: a raw name says what a
// colour looks like, a semantic name says what it is for, and only the second
// survives a redesign.
//
// Source: `Avada-Child-Theme/style.css` (`:root` fusion variables) and
// `Avada-Child-Theme/css/tlig_en.css`, September 2026.
// -----------------------------------------------------------------------------
// Browns — the site's "ink"; the wood background and papyrus scroll sit under
// everything, so the darkest text tone is a brown, never black.
$brown-900: #482020; // 74 uses in tlig_en.css — the most common colour
$brown-800: #511a06; // message body text
$brown-600: #917455; // wood / border tone
$brown-300: #c8b29a; // newsletter input field
// Reds — link, heading and call-to-action family.
$red-900: #7a0012; // message headings (h2)
$red-800: #810008; // Avada --link_color
$red-780: #7e050c; // .linkUnderline
$red-700: #8f0b06; // primary button fill
$red-650: #770000; // mobile header / menu
$red-600: #951111; // blockquote rule
$red-500: #961a07; // alert
$red-400: #ca4a1f; // link hover (global `a:hover` in tlig_en.css)
// Golds — footer, accents, hover states on dark ground.
$gold-700: #d8ae0b; // donate button
$gold-600: #ffc600; // sliding-bar link hover
$gold-500: #f9d368; // footer dropdown rules
$gold-400: #ffe683; // hover on dark
$gold-300: #ffe7bf; // mobile menu hover
$gold-200: #fff8c4; // mobile menu text
$gold-100: #fbefc9; // footer text
// Neutrals.
$white: #ffffff;
$grey-050: #fbfbfb;
$grey-100: #f0f0f0;
$grey-200: #eaeaea;
$grey-300: #d6d6d6;
$grey-400: #bbbbbb;
$grey-500: #989898;
$grey-700: #333333;
$black: #0a0a0a;
// Semantic accents used by notices and status chips.
$green-600: #53961e;
$green-400: #96d865;
$blue-700: #244c80;
$sand-100: #fcf8e3;
// Alpha tints. Kept as literals rather than computed, because these exact
// values are what ships today and a rounding difference would be a visible
// change dressed up as a refactor.
$ink-86a: rgba(72, 32, 32, 0.86); // body text
$ink-50a: rgba(72, 32, 32, 0.5);
$wood-50a: rgba(145, 116, 85, 0.5);
$wood-20a: rgba(145, 116, 85, 0.2);
$white-80a: rgba(255, 255, 255, 0.8);
$white-30a: rgba(255, 255, 255, 0.3); // the translucent "paper" panels
$shadow-35a: rgba(0, 0, 0, 0.35);
$shadow-15a: rgba(0, 0, 0, 0.15);
+139
View File
@@ -0,0 +1,139 @@
// -----------------------------------------------------------------------------
// Semantic tokens.
//
// One map per concern. Each map is emitted as CSS custom properties by
// `base/_root.scss`, so a token can be re-pointed per language or per section
// at runtime without recompiling — which is how the per-language papyrus and
// the dark footer get handled without a second stylesheet.
// -----------------------------------------------------------------------------
@use 'palette' as *;
$prefix: 'tlig';
// -- Colour ------------------------------------------------------------------
$colors: (
// Page
'page-ink': $ink-86a, // body copy
'page-ink-strong': $brown-900, // headings, emphasis
'page-ink-muted': $grey-500,
'page-panel': $white-30a, // translucent card over the scroll
'page-rule': $grey-300,
// Links
'link': $red-800,
'link-hover': $red-400,
'link-underlined': $red-780, // the `.link-underline` variant
// Headings
'heading': $brown-900,
'heading-message': $red-900, // h2 inside a message
// Message typography (the scroll pages)
'message-body': $brown-800,
'message-aside': $brown-600,
// Footer — the one region on a dark ground
'footer-bg': $brown-900,
'footer-ink': $gold-100,
'footer-link': $white,
'footer-link-hover': $gold-400,
'footer-rule': $gold-500,
'footer-panel': $black,
// Buttons
'button-bg': $red-700,
'button-ink': $white,
'button-ink-hover': $gold-400,
'button-donate-bg': $gold-700,
'button-donate-ink': $white,
// Forms
'field-bg': $brown-300,
'field-ink': $white,
'field-placeholder': rgba(255, 255, 255, 0.8),
// Status
'ok': $green-600,
'ok-soft': $green-400,
'alert': $red-500,
'info': $blue-700,
'notice-bg': $sand-100,
// Chrome
'shadow': $shadow-35a,
'shadow-soft': $shadow-15a,
);
// -- Type --------------------------------------------------------------------
// `papyrus-bold` is the display face; the per-script files are bound to the
// same family name via unicode-range in `base/_fonts.scss`, so a Bulgarian
// heading needs no separate rule.
$fonts: (
'display': (papyrus-bold, 'Papyrus', fantasy),
'body': (Lato, 'Trebuchet MS', Helvetica, sans-serif),
'message': ('Trebuchet MS', Verdana, sans-serif),
'nav': (Oswald, 'Lato', sans-serif),
);
$font-sizes: (
'xs': 12px,
'sm': 14px,
'base': 18px, // Avada --body_typography-font-size
'md': 20px,
'lg': 22px,
'xl': 25px,
'2xl': 34px, // h1
);
$line-heights: (
'tight': 1.2, // headings
'snug': 1.35,
'normal': 1.5, // body
'message': 1.33, // 24px on 18px
);
$font-weights: (
'normal': 400,
'medium': 600,
'bold': 700,
);
// -- Space -------------------------------------------------------------------
// A 4px-based scale. The live CSS uses arbitrary values (6px, 23px, 46px…);
// those are rounded to the nearest step here. Every such rounding is invisible
// at a glance and is the point of the exercise — one rhythm, not forty.
$space: (
'3xs': 2px,
'2xs': 4px,
'xs': 8px,
'sm': 12px,
'md': 16px,
'lg': 24px,
'xl': 32px,
'2xl': 48px,
'3xl': 64px,
);
$radii: (
'sm': 5px,
'md': 7px,
'lg': 20px,
'pill': 999px,
);
// -- Layout ------------------------------------------------------------------
$site-width: 1200px; // Avada --site_width
// Named breakpoints. The live CSS switches at 480 and 799/800 with a one-pixel
// gap between `max-width: 800px` and `min-width: 800px` rules; that gap is
// closed here by driving everything from these two numbers.
$breakpoints: (
'phone': 480px,
'tablet': 800px,
'wide': 1200px,
);
+85
View File
@@ -0,0 +1,85 @@
// -----------------------------------------------------------------------------
// @font-face.
//
// The live site declares papyrus-bold once, in the compiled Avada stylesheet,
// pointing at absolute ww3.tlig.org URLs — which is why a staging copy renders
// with the fallback face. Here the files are project assets: Vite fingerprints
// them and rewrites the URL, so the bundle is self-contained wherever it lands.
//
// All four papyrus files share one family name and are separated by
// unicode-range. A Bulgarian or Hungarian heading therefore needs no language
// class and no second stylesheet — the browser picks the file by codepoint.
// -----------------------------------------------------------------------------
// Latin — the original PapyrusLETBold.
@font-face {
font-family: papyrus-bold;
src: url('/fonts/papyrus/papyrus-bold-latin.woff2') format('woff2'),
url('/fonts/papyrus/papyrus-bold-latin.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122,
U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
// Latin Extended-A — Hungarian needs ő and ű, which the original file lacks.
@font-face {
font-family: papyrus-bold;
src: url('/fonts/papyrus/papyrus-bold-hu.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2C60-2C7F, U+A720-A7FF;
}
// Cyrillic.
@font-face {
font-family: papyrus-bold;
src: url('/fonts/papyrus/papyrus-bold-cyrillic.woff2') format('woff2'),
url('/fonts/papyrus/papyrus-bold-cyrillic.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
// Cyrillic Extended — the Bulgarian cut covers the forms the base file misses.
@font-face {
font-family: papyrus-bold;
src: url('/fonts/papyrus/papyrus-bold-bulgarian.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
// Lato — self-hosted. The live site pulls this from Google via Avada's
// fusion-gfonts cache, 33 @font-face blocks for what is three files.
@font-face {
font-family: Lato;
src: url('/fonts/lato/Lato-Medium.woff2') format('woff2'),
url('/fonts/lato/Lato-Medium.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: Lato;
src: url('/fonts/lato/Lato-MediumItalic.woff2') format('woff2'),
url('/fonts/lato/Lato-MediumItalic.woff') format('woff');
font-weight: 400;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: Lato;
src: url('/fonts/lato/Lato-Bold.woff2') format('woff2'),
url('/fonts/lato/Lato-Bold.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}
+5
View File
@@ -0,0 +1,5 @@
@forward 'root';
@forward 'fonts';
@forward 'reset';
@forward 'typography';
@forward 'links';
+51
View File
@@ -0,0 +1,51 @@
// -----------------------------------------------------------------------------
// Links.
//
// The live rule is `a:hover { color: #ca4a1f !important; }` — one global
// `!important` that every later link style then has to fight, which is where
// most of the `!important` inflation in `tlig_en.css` starts. Here the hover
// colour is inherited from a token, so a context that needs a different hover
// (the footer, a button) simply re-points `--tlig-color-link-hover`.
// -----------------------------------------------------------------------------
@use '../abstracts' as *;
a {
color: color('link');
text-decoration: none;
transition: color 120ms ease-in-out;
&:hover,
&:focus-visible {
color: color('link-hover');
}
}
// Body copy inside prose gets an underline; navigation and cards do not.
.prose a,
.link-underline {
color: color('link-underlined');
text-decoration: underline;
text-underline-offset: 0.15em;
&:hover,
&:focus-visible {
color: color('link-hover');
}
}
// A link wrapping a card or an image should not tint its contents.
.link-plain {
color: inherit;
&:hover,
&:focus-visible {
color: inherit;
}
}
// Any region that re-points the token set gets correct links for free — this
// is what the footer does, via the same mixin.
.on-dark {
@include dark-ground;
}
+69
View File
@@ -0,0 +1,69 @@
// -----------------------------------------------------------------------------
// Reset. Deliberately small — it normalises box model and defaults, and stops.
// -----------------------------------------------------------------------------
@use '../abstracts' as *;
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
body {
margin: 0;
}
// Headings and paragraphs carry their spacing from the type scale, not from
// whatever the user agent thinks.
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd {
margin: 0;
}
ul, ol {
margin: 0;
padding: 0;
}
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
height: auto;
}
input,
button,
textarea,
select {
font: inherit;
color: inherit;
}
button {
cursor: pointer;
}
:focus-visible {
@include focus-ring;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
+23
View File
@@ -0,0 +1,23 @@
// -----------------------------------------------------------------------------
// Emit every token as a CSS custom property.
//
// This is the only place the SASS maps become CSS. Everything downstream reads
// `var(--tlig-…)`, which means a language, a section or a future dark mode can
// re-point a token in one declaration block instead of re-stating the rules.
// -----------------------------------------------------------------------------
@use 'sass:map';
@use 'sass:list';
@use '../abstracts' as *;
:root {
@each $key, $value in $colors { --#{$prefix}-color-#{$key}: #{$value}; }
@each $key, $value in $fonts { --#{$prefix}-font-#{$key}: #{list.join((), $value, $separator: comma)}; }
@each $key, $value in $font-sizes { --#{$prefix}-size-#{$key}: #{$value}; }
@each $key, $value in $line-heights{ --#{$prefix}-leading-#{$key}: #{$value}; }
@each $key, $value in $font-weights{ --#{$prefix}-weight-#{$key}: #{$value}; }
@each $key, $value in $space { --#{$prefix}-space-#{$key}: #{$value}; }
@each $key, $value in $radii { --#{$prefix}-radius-#{$key}: #{$value}; }
--#{$prefix}-site-width: #{$site-width};
}
+103
View File
@@ -0,0 +1,103 @@
// -----------------------------------------------------------------------------
// Element typography.
//
// Sizes and colours are the ones the site renders today; the difference is that
// each is stated once, from a token, with no `!important`. `tlig_en.css` sets
// h2 globally with four `!important`s to beat Avada's own h2 rule — here there
// is only one h2 rule, so nothing needs beating.
// -----------------------------------------------------------------------------
@use '../abstracts' as *;
body {
@include type('body', 'base', 'normal');
color: color('page-ink');
letter-spacing: 0;
}
h1 {
@include heading('2xl');
margin-block: space('md') space('xs');
}
// Trebuchet, deep red, bold: the message-heading look, applied globally today.
h2 {
font-family: font('message');
font-size: size('lg');
line-height: leading('normal');
font-weight: weight('bold');
color: color('heading-message');
margin-block-end: space('sm');
}
h3 {
@include type('body', 'md', 'normal', 'bold');
color: color('heading');
margin-block-end: space('xs');
}
h4 {
@include heading('xl');
margin-block: space('lg');
}
h5 {
@include type('body', 'sm', 'normal', 'bold');
color: color('heading');
}
h6 {
@include type('body', 'xs', 'normal', 'bold');
color: color('heading');
text-transform: uppercase;
letter-spacing: 0.05em;
}
p {
margin-block-end: space('md');
&:last-child {
margin-block-end: 0;
}
}
strong, b { font-weight: weight('bold'); }
em, i { font-style: italic; }
small {
font-size: size('sm');
color: color('page-ink-muted');
}
hr {
height: 1px;
margin-block: space('xl');
border: 0;
background-color: color('page-rule');
}
blockquote {
@include panel('lg', 'lg');
border-inline-start: 4px solid color('heading-message');
border-end-start-radius: space('2xl');
font-style: italic;
}
// -- Utility classes ---------------------------------------------------------
//
// The live CSS carries `.font-papyrus`, `.font34`, `.centre`, `.bold`, `.ita`
// and friends, each an `!important` one-liner. Those are template-facing, so
// they are kept — but as plain, unforced declarations.
.font-display { font-family: font('display'); }
.font-body { font-family: font('body'); }
.font-message { font-family: font('message'); }
.text-center { text-align: center; }
.text-start { text-align: start; }
.text-justify { text-align: justify; }
.text-muted { color: color('page-ink-muted'); }
.text-strong { color: color('page-ink-strong'); }
.visually-hidden { @include visually-hidden; }
+59
View File
@@ -0,0 +1,59 @@
// -----------------------------------------------------------------------------
// Buttons.
//
// The live site has `#footer-viewMagazineSpan`, `.btnSubmitNewsletter`,
// `.btnSubmitDailyMessage` and `.footer-donateSpan` — four ids and classes
// repeating the same six declarations with different corner radii. One class
// with modifiers replaces them.
// -----------------------------------------------------------------------------
@use '../abstracts' as *;
.button {
display: inline-block;
padding: space('xs') space('md');
border: 1px solid transparent;
border-radius: radius('sm');
background-color: color('button-bg');
color: color('button-ink');
font-family: font('body');
font-size: size('sm');
font-weight: weight('bold');
line-height: 1.2;
letter-spacing: 2px;
text-align: center;
text-decoration: none;
transition: color 120ms ease-in-out, background-color 120ms ease-in-out;
&:hover,
&:focus-visible {
color: color('button-ink-hover');
}
// Donate: gold ground, ink on hover rather than gold-on-gold.
&--donate {
background-color: color('button-donate-bg');
color: color('button-donate-ink');
&:hover,
&:focus-visible {
color: color('page-ink-strong');
}
}
// The half of a joined field + button pair.
&--attached {
border-start-start-radius: 0;
border-end-start-radius: 0;
}
&--block {
display: block;
width: 100%;
}
&[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
}
+36
View File
@@ -0,0 +1,36 @@
// -----------------------------------------------------------------------------
// Text inputs. Modelled on the footer newsletter field (`.inputEmail`), which
// today carries five `!important`s to override Avada's form styling.
// -----------------------------------------------------------------------------
@use '../abstracts' as *;
.field {
min-width: 0;
padding: space('2xs') space('xs');
border: 1px solid color('field-bg');
border-radius: radius('sm');
background-color: color('field-bg');
color: color('field-ink');
font-family: font('body');
font-size: size('sm');
letter-spacing: 2px;
&::placeholder {
color: color('field-placeholder');
opacity: 1;
}
&--attached {
border-start-end-radius: 0;
border-end-end-radius: 0;
}
}
// Field + button as one control.
.field-group {
display: flex;
justify-content: center;
.field { flex: 1 1 auto; max-width: 18rem; }
}
+116
View File
@@ -0,0 +1,116 @@
// -----------------------------------------------------------------------------
// Footer.
//
// The live footer is 24 id selectors (`#footer-magazine`, `#footer-rightblock`,
// `#footer-whatOptionLink1`…) with hand-tuned percentage widths and a media
// query that resets each one to 100%. This is the same layout expressed as two
// flex containers, and it re-points the link tokens once via `.on-dark` instead
// of colouring each link by hand.
// -----------------------------------------------------------------------------
@use '../abstracts' as *;
.site-footer {
// Scope the dark-ground tokens to the whole region. Everything inside —
// links, headings, buttons — reads the right colour without a single
// footer-specific colour declaration. Shared with `.on-dark` so the two can
// never drift apart.
@include dark-ground;
padding-block: space('xl');
background-color: color('footer-bg');
font-family: font('display');
}
.site-footer__inner {
width: min(100% - 2 * #{space-raw('lg')}, var(--#{$prefix}-site-width));
margin-inline: auto;
}
// Two rows: magazine + newsletter above, the three link columns below.
.site-footer__row {
display: flex;
flex-wrap: wrap;
gap: space('xl');
justify-content: space-between;
& + & {
margin-block-start: space('2xl');
}
}
.site-footer__col {
flex: 1 1 18rem;
text-align: center;
}
// The magazine block is the wide one at desktop; below the tablet breakpoint
// every column is simply full width, which the flex-basis already gives us.
.site-footer__col--wide {
flex: 1 1 28rem;
}
.site-footer__title {
font-family: font('display');
font-size: size('md');
line-height: leading('tight');
color: color('footer-ink');
margin-block-end: space('sm');
}
.site-footer__text {
font-size: size('md');
line-height: leading('snug');
}
// Social row.
.site-footer__social {
display: flex;
flex-wrap: wrap;
gap: space('md');
justify-content: center;
list-style: none;
a {
font-size: size('xl');
&:hover,
&:focus-visible {
color: color('button-donate-bg');
}
}
}
// The "what is…" dropdown: a bordered list on the dark panel colour.
.site-footer__menu {
// 80% of the column, as the live `#footer-whatDropdown` has it, but capped so
// it does not stretch on a wide screen.
max-width: min(22rem, 80%);
margin-inline: auto;
list-style: none;
text-align: start;
border: 1px solid color('footer-rule');
border-radius: radius('sm');
overflow: hidden;
li + li {
border-block-start: 1px solid color('footer-rule');
}
a {
display: block;
padding: space('xs') space('sm');
background-color: color('footer-panel');
}
}
@include down('tablet') {
.site-footer__row {
gap: space('lg');
}
.site-footer__col,
.site-footer__col--wide {
flex-basis: 100%;
}
}
+3
View File
@@ -0,0 +1,3 @@
@forward 'button';
@forward 'field';
@forward 'footer';
+14
View File
@@ -0,0 +1,14 @@
// -----------------------------------------------------------------------------
// TLIG stylesheet — entry point.
//
// Layer order is load-bearing: tokens first, then element defaults, then
// components. Nothing later in the file needs higher specificity than anything
// earlier, which is the whole reason there is not a single `!important` here.
//
// abstracts/ compile-time only, emits no CSS
// base/ custom properties, @font-face, reset, element typography, links
// components/ single-purpose blocks
// -----------------------------------------------------------------------------
@use 'base';
@use 'components';