Initial commit
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
demo-dist/
|
||||
@@ -0,0 +1,151 @@
|
||||
# tlig_css
|
||||
|
||||
The TLIG stylesheet rebuilt on SASS tokens and Vite, as a replacement for
|
||||
`Avada-Child-Theme/css/tlig_en.css` (7,082 lines) and the compiled Avada
|
||||
`style.css` it fights with.
|
||||
|
||||
This first pass covers the common foundation only: fonts, colour, type scale,
|
||||
links, buttons, form fields and the footer. Page-specific styling (messages,
|
||||
bookstore, menus, the wood and scroll backgrounds) is not here yet.
|
||||
|
||||
## Commands
|
||||
|
||||
npm install
|
||||
npm run dev # demo with live reload, at localhost:5173
|
||||
npm run preview # demo as a static page, at localhost:4173
|
||||
npm run build # the deliverable: dist/tlig.css + dist/fonts/
|
||||
|
||||
There is no way to open `src/index.html` from disk — it is an ES module that
|
||||
imports `.scss`, so it needs one of the two servers above. `file://` gives a
|
||||
blank page.
|
||||
|
||||
**`npm run dev`** compiles the SASS in memory and injects the CSS through
|
||||
JavaScript. Fast, hot-reloading, and how you work. One caveat: because the CSS
|
||||
arrives after first paint, anything with a `transition` animates up from its
|
||||
unstyled state, so for a few milliseconds links are browser-blue and buttons are
|
||||
nearly transparent. That is a dev-server artifact, not the stylesheet.
|
||||
|
||||
**`npm run preview`** builds the demo to `demo-dist/` and serves it. The page
|
||||
there loads the compiled stylesheet through a real `<link rel="stylesheet">` in
|
||||
`<head>`, so it paints once, exactly as a browser will on the real site. Use
|
||||
this when judging how something actually looks, or when screenshotting.
|
||||
|
||||
**`npm run build`** emits only the deliverable — `dist/tlig.css` and the fonts,
|
||||
no HTML and no demo markup. The demo is built from `src/index.html` and the
|
||||
deliverable from `src/main.js`, so demo chrome cannot reach `dist`.
|
||||
|
||||
## The demo page
|
||||
|
||||
`npm run dev` serves `src/index.html`: the foundation rendered on the real
|
||||
ground, inside the wood background and the papyrus scroll, with the real footer
|
||||
component at the bottom. It shows the type scale, every link and button
|
||||
variant, the dark-ground mechanism, and a colour grid generated at runtime from
|
||||
the `--tlig-color-*` properties the stylesheet actually emits — so the grid
|
||||
cannot drift from the code.
|
||||
|
||||
The toolbar has three controls:
|
||||
|
||||
- **Script** — switches the sample heading between Latin, Hungarian, Cyrillic
|
||||
and Greek. No CSS changes when you do; the browser picks the font file by
|
||||
codepoint. Also addressable as `?script=bg` for linking or screenshotting.
|
||||
Greek is included deliberately: no papyrus cut exists for it, so it shows the
|
||||
fallback face honestly.
|
||||
- **Show boxes** — outlines each component so the structure is visible.
|
||||
- A live viewport width and the active breakpoint.
|
||||
|
||||
The wood and the scroll are **demo chrome**, in `src/demo/demo.scss`, not in the
|
||||
stylesheet: page layout is still on the to-do list below, and a demo is not the
|
||||
place to quietly ship it. The demo is not a build input, so none of it can reach
|
||||
`dist/tlig.css` — the build is checked for exactly that.
|
||||
|
||||
The frame is there anyway because a foundation only tells the truth on the
|
||||
ground it has to work on. Brown ink at 86% looks arbitrary on white and
|
||||
obviously right on papyrus.
|
||||
|
||||
## Layout
|
||||
|
||||
src/sass/
|
||||
abstracts/ compile-time only; emits no CSS
|
||||
_palette.scss every literal colour the live site uses
|
||||
_tokens.scss semantic tokens: what a colour is *for*
|
||||
_functions.scss token accessors
|
||||
_mixins.scss media queries, type, patterns
|
||||
base/
|
||||
_root.scss emits every token as a CSS custom property
|
||||
_fonts.scss @font-face
|
||||
_reset.scss
|
||||
_typography.scss
|
||||
_links.scss
|
||||
components/
|
||||
_button.scss _field.scss _footer.scss
|
||||
src/fonts/ papyrus-bold (4 scripts) and Lato, self-hosted
|
||||
src/demo/ demo page chrome and behaviour — never built
|
||||
src/index.html the demo page
|
||||
|
||||
## The three rules that replace the jigsaw
|
||||
|
||||
**One source of truth per value.** No rule writes a hex code. `_palette.scss`
|
||||
holds the literals, `_tokens.scss` names them by role, `base/_root.scss` emits
|
||||
them as `--tlig-*` custom properties, and every rule reads a token. `#482020`
|
||||
appears 74 times in `tlig_en.css`; it appears once here.
|
||||
|
||||
**Context re-points tokens, it does not restate rules.** The `dark-ground`
|
||||
mixin re-points nine custom properties; `.site-footer` and `.on-dark` both
|
||||
include it and declare no colours of their own. Every link, heading and button
|
||||
inside them is then correct — where the live CSS needs 24 id selectors to say
|
||||
the same thing. The list of what "dark" means exists once, so the two cannot
|
||||
drift: building the demo caught exactly that bug, an `.on-dark` that re-pointed
|
||||
the links but not `--tlig-color-heading`, rendering a heading dark-brown on
|
||||
dark-brown.
|
||||
|
||||
**Specificity goes one way.** Layer order in `main.scss` is tokens → base →
|
||||
components, and nothing later needs to outrank anything earlier. Every
|
||||
`!important` in the build — four of them — is in the single
|
||||
`prefers-reduced-motion` block, where overriding author animation is the point.
|
||||
There are none anywhere else. `tlig_en.css` has 518.
|
||||
|
||||
## What the tokens were taken from
|
||||
|
||||
Values are the ones the site renders today, not new choices:
|
||||
|
||||
| Token | Value | Source |
|
||||
|---|---|---|
|
||||
| `--tlig-color-page-ink` | `rgba(72,32,32,0.86)` | Avada `--body_typography-color` |
|
||||
| `--tlig-color-link` | `#810008` | Avada `--link_color` |
|
||||
| `--tlig-color-link-hover` | `#ca4a1f` | `tlig_en.css` global `a:hover` |
|
||||
| `--tlig-color-heading` | `#482020` | Avada `--h1_typography-color` |
|
||||
| `--tlig-color-heading-message` | `#7a0012` | `tlig_en.css` global `h2` |
|
||||
| `--tlig-color-footer-ink` | `#fbefc9` | `tlig_en.css` `#footer-container` |
|
||||
| `--tlig-color-button-bg` | `#8f0b06` | `tlig_en.css` `.btnSubmitNewsletter` |
|
||||
| `--tlig-size-base` | `18px` | Avada `--body_typography-font-size` |
|
||||
|
||||
Two deliberate departures, both noted in the source:
|
||||
|
||||
- **h2** is encoded as Trebuchet / `#7a0012` / bold. Avada's own token says Lato
|
||||
/ `#333333`, but `tlig_en.css` overrides it globally with four `!important`s,
|
||||
so the second is what actually renders.
|
||||
- **Spacing** is snapped to a 4px scale. The live CSS uses 6px, 23px, 35px and
|
||||
46px in adjacent rules; those become the nearest step.
|
||||
|
||||
## Fonts
|
||||
|
||||
`papyrus-bold` is declared four times under one family name, split by
|
||||
`unicode-range` across the Latin, Latin-Extended (Hungarian), Cyrillic and
|
||||
Cyrillic-Extended (Bulgarian) files. The browser picks the file by codepoint, so
|
||||
a Bulgarian heading needs no language class and no second stylesheet.
|
||||
|
||||
All font files are project assets. The live site points `@font-face` at absolute
|
||||
`ww3.tlig.org` URLs, which is why a staging copy renders in the fallback face;
|
||||
here Vite emits them into `dist/fonts/` and the CSS references them relatively,
|
||||
so the bundle resolves at whatever path it is dropped.
|
||||
|
||||
Lato is self-hosted from the three files already in the child theme, replacing
|
||||
the 33 `@font-face` blocks Avada generates from its Google Fonts cache.
|
||||
|
||||
## Not done yet
|
||||
|
||||
- Page-level layout: header, nav, the wood and papyrus backgrounds, `#main`
|
||||
- Message typography (`.tbody`, `.vassula`, `.jc`, `.god`, the bible-reference
|
||||
asides)
|
||||
- Menus, bookstore, search, testimonies, downloads
|
||||
- Enqueuing the built CSS from the theme, and retiring `tlig_en.css`
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "tlig-css",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"description": "TLIG stylesheet, rebuilt on SASS tokens and Vite.",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build:demo": "vite build --mode demo",
|
||||
"preview": "vite build --mode demo && vite preview --mode demo"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sass": "^1.105.0",
|
||||
"vite": "^8.3.0"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 57 KiB |
@@ -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))
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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'
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Single import surface for the abstract layer: `@use '../abstracts' as *;`
|
||||
@forward 'palette';
|
||||
@forward 'tokens';
|
||||
@forward 'functions';
|
||||
@forward 'mixins';
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@forward 'root';
|
||||
@forward 'fonts';
|
||||
@forward 'reset';
|
||||
@forward 'typography';
|
||||
@forward 'links';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
@forward 'button';
|
||||
@forward 'field';
|
||||
@forward 'footer';
|
||||
@@ -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';
|
||||
@@ -0,0 +1,69 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const here = (...p) => resolve(import.meta.dirname, ...p)
|
||||
|
||||
// Two build targets from one config:
|
||||
//
|
||||
// vite build → dist/ the deliverable: tlig.css + fonts,
|
||||
// no HTML. This is what WordPress
|
||||
// enqueues.
|
||||
// vite build --mode demo → demo-dist/ a static demo page that loads that
|
||||
// stylesheet through a real <link>,
|
||||
// with the site images beside it.
|
||||
//
|
||||
// The second exists because in dev Vite injects CSS through JavaScript, so
|
||||
// anything with a `transition` animates from its unstyled state on first paint.
|
||||
// That is a dev artifact, but it makes the demo misrepresent the stylesheet.
|
||||
// The static build paints once, the way a browser will on the real site.
|
||||
export default defineConfig(({ mode }) => {
|
||||
const isDemo = mode === 'demo'
|
||||
|
||||
return {
|
||||
root: 'src',
|
||||
|
||||
// Relative asset URLs — a url() in CSS resolves against the stylesheet's
|
||||
// own location, so the output can be dropped at any path under wp-content
|
||||
// without rebuilding.
|
||||
base: './',
|
||||
|
||||
// `site/` mirrors the live asset tree, so the demo can reference
|
||||
// /wp-content/includes/images/… exactly as a WordPress template does.
|
||||
publicDir: here('site'),
|
||||
|
||||
css: {
|
||||
devSourcemap: true,
|
||||
},
|
||||
|
||||
build: {
|
||||
outDir: isDemo ? here('demo-dist') : here('dist'),
|
||||
emptyOutDir: true,
|
||||
|
||||
// The demo needs the wood and scroll images; the deliverable does not —
|
||||
// WordPress already hosts them.
|
||||
copyPublicDir: isDemo,
|
||||
|
||||
sourcemap: true,
|
||||
cssCodeSplit: false,
|
||||
|
||||
rollupOptions: {
|
||||
// The demo is built from the page; the deliverable from the entry
|
||||
// module, so no demo markup or demo CSS can reach dist/.
|
||||
input: isDemo ? here('src/index.html') : here('src/main.js'),
|
||||
|
||||
output: {
|
||||
// Predictable names: WordPress enqueues these paths, so they must not
|
||||
// change on every build. Fonts keep their own names for the same
|
||||
// reason — a preload hint in the theme header has to stay valid.
|
||||
entryFileNames: isDemo ? 'assets/[name]-[hash].js' : 'tlig.js',
|
||||
assetFileNames: (info) => {
|
||||
const name = info.names?.[0] ?? info.name ?? ''
|
||||
if (name.endsWith('.css')) return 'tlig.css'
|
||||
if (/\.(woff2?|ttf|eot|svg)$/.test(name)) return 'fonts/[name][extname]'
|
||||
return 'assets/[name]-[hash][extname]'
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||