diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..44298f7 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,21 @@ +name: Validate theme + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: npm run check + - run: git diff --exit-code -- dist/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aafcb34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.DS_Store +*.log diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5812924 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,138 @@ +# Agent instructions for `trueChristian/theme` + +## Purpose + +This repository defines the unified visual identity and mandatory global page chrome for **all True Christian Church websites**. It is not a page-specific website and must not be narrowed to one ministry, project, event, or location. + +The words **MUST**, **MUST NOT**, **SHOULD**, and **SHOULD NOT** in this file are requirements. + +## Read before editing + +Before changing this repository, read: + +1. this file; +2. `docs/source-inventory.md` for evidence and provenance; +3. `docs/header.md` and `docs/footer.md` for the global chrome contract; +4. `docs/component-inventory.md` before changing page-body component styles; +5. `src/data/site-chrome.json` for canonical labels, URLs, grouping, and order; +6. `src/data/main-site-navigation.json` when working on the canonical main-site menu; +7. the supplied header/footer evidence in `docs/source/`; +8. the visual references in `docs/reference/`. + +Do not infer a redesign from a single project page. The shared header and footer belong to the church-wide theme. + +## Sources of truth + +When sources disagree, use this order: + +1. Explicit instructions from the repository owner. +2. `src/data/site-chrome.json` for navigation and footer content. +3. `src/html/site-header.html` and `src/html/site-footer.html` for structure and accessibility. +4. `src/css/` for visual implementation. +5. `docs/reference/header-desktop.png` and `docs/reference/footer-desktop.png` for visual comparison. +6. Human-readable documentation under `docs/`. + +Do not silently change fixed labels, destinations, group order, logo proportions, footer layers, or brand colors. + +## Mandatory global header + +Every site MUST include a responsive header with the following behavior: + +- The source logo is `assets/brand/logo.jpg`. +- Render the logo at its native aspect ratio, nominally `288px × 77px`. +- The logo alternative text MUST be `A True Christian Church`. +- The logo MUST link to the applicable True Christian Church home page. +- A visible `Home` link MUST be the first textual navigation entry. +- Navigation entries belonging to the site or page being built MUST follow `Home` and use the same visual treatment. +- On the main church site, the canonical entries are `The Holy Scriptures`, `Sermons`, `Articles`, `Daily`, and `Services` with the `Join us` subtitle. Preserve the full extracted menu data in `src/data/main-site-navigation.json`. +- Desktop navigation MUST use the white bar, thin cyan top rule, uppercase widely spaced labels, and active/hover contrast shown in the reference screenshot. +- The desktop header SHOULD be sticky and reveal on upward scroll when the host framework supports it. +- Below the desktop breakpoint, use a menu toggle and off-canvas or equivalent accessible navigation. Keep the logo centered and never squeeze the complete desktop menu into a narrow row. +- Dropdown triggers MUST be keyboard operable and expose `aria-expanded`; the mobile menu MUST have an accessible close control. +- Portable implementations MUST load `dist/truechristian-theme.js` (built from `src/js/site-header.js`) or provide equivalent menu, submenu, Escape-key, focus, and breakpoint behavior. +- Set `aria-current="page"` only on the actual current route. Never hardcode `Home` as active in a reusable template. +- Do not replace the logo with plain text, a generic church icon, or a project logo. + +When a satellite site needs different page-specific links, change only the site-specific navigation data. Preserve the logo, home link, header silhouette, typography, spacing, responsive behavior, and accessible menu pattern. + +## Mandatory footer assembly + +Every site MUST finish with the complete footer assembly, directly after the page content. It has two visible bands and three named responsibilities: + +1. **Directory/link footer** — the light `tm-bottom` band containing all grouped links and social outreach. +2. **Copyright footer** — the dark band containing the copyright statement and legal/project links. +3. **Bottom footer assembly** — the complete page-bottom unit formed by the directory footer followed immediately by the copyright footer. Nothing page-specific may be placed between those two bands. + +This terminology prevents the YOOtheme region name `tm-bottom` from being confused with the entire footer. + +### Directory/link footer requirements + +- Use four desktop columns in this exact order: `Church Info`, `Projects`, `Welcome`, `Social Outreach`. +- Use the exact labels, URLs, order, and external-link behavior in `src/data/site-chrome.json`. +- The data preserves the source's root-relative URLs. On a different domain, resolve every root-relative global-footer URL against `https://truechristian.church`; the portable HTML template already does this. +- `Social Outreach` MUST contain X/Twitter, Facebook, GitHub, and Telegram icon links. +- The source skyline file is `/images/city-skyline-skyscrapers-top.jpg`. It was not supplied separately; `assets/footer/city-skyline-reference.png` is an exact-size reference crop from the owner-supplied screenshot. Preserve its `270px × 180px` slot and do not substitute unrelated stock imagery. +- On medium layouts, the four columns MAY become a two-by-two grid. On narrow layouts, stack them and center headings, links, and social controls. +- Keep the generous light-grey spacing, uppercase headings and links, circular social buttons, rounded image treatment, and subdued shadow shown in `docs/reference/footer-desktop.png`. + +### Copyright footer requirements + +- Render the copyright footer immediately after the directory footer. +- Use the exact text and link sequence from `src/data/site-chrome.json`: + `Copyright © trueChristian.Church free distribution only | Privacy Policy | Mission | Revival | We Fear God | Why we believe the Bible | Trousseau Chest`. +- `free distribution only` links to `/copyright`. +- Preserve the vertical-bar separators on wide layouts. Allow wrapping on small screens without clipping or horizontal scrolling. +- Use the dark charcoal background and light text shown in the reference. + +### Footer prohibitions + +- MUST NOT omit either visible footer band. +- MUST NOT collapse all footer content into a single undifferentiated paragraph. +- MUST NOT replace the directory with a project-specific footer. +- MUST NOT move legal links out of the copyright band. +- MUST NOT reorder or rename groups or fixed links without explicit owner instruction. +- MUST NOT place a newsletter signup, advertisement, unrelated logo, or promotional call-to-action into the global footer unless explicitly requested. + +## CSS and visual tokens + +- `src/css/` is the authoritative implementation. +- `src/css/theme.css` defines module order; the files in `dist/` are generated. +- Preserve the measured flat colors documented in `docs/brand-guide.md`. +- Keep the header and footer selectors usable without Joomla while retaining compatibility selectors for the supplied YOOtheme/UIkit structure. +- Do not paste or vendor proprietary YOOtheme CSS. Record framework references and implement church-specific CSS here. +- The supplied HTML does not expose its compiled `@font-face` declarations. Montserrat and Raleway are forensic screenshot matches, not declarations extracted from the HTML; keep that provenance explicit. +- Maintain visible keyboard focus, reduced-motion behavior, print-safe output, and documented contrast behavior. The measured inactive-nav color reproduces the source but is below WCAG AA for small text; do not claim otherwise. +- Do not use CSS to hide missing required content. + +## Assets + +- Do not stretch, recolor, crop, redraw, or add effects to `assets/brand/logo.jpg`. +- Preserve `assets/favicons/favicon.ico` byte-for-byte unless the owner supplies a replacement. +- Generated YOOtheme cache files are outputs, not source assets. +- Do not introduce unrelated page photography into the theme package. + +## Documentation expectations + +Any change to header/footer structure, links, colors, typography, or assets MUST update the corresponding documentation and reference data in the same pull request. + +If a value cannot be proven from the supplied files, document the uncertainty. Never present a guess as extracted fact. + +## Required validation + +Before committing: + +```sh +npm run check +``` + +Also verify: + +- both reference screenshots remain present; +- the logo and favicon remain present; +- the example contains the global header, directory footer, and copyright footer; +- the portable header script is present and syntax-valid; +- all fixed labels and URLs match `src/data/site-chrome.json`; +- no horizontal overflow is introduced at narrow widths; +- `dist/` was regenerated after any source CSS change. + +Keep commits coherent. Do not merge a pull request unless the owner asks for the merge. diff --git a/README.md b/README.md index 93af4cb..dfb65d9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,69 @@ -# theme -Unified Theme for Building Sites for TrueChristian Church +# True Christian Church unified theme + +This repository is the shared visual and structural source of truth for websites built for **A True Christian Church**. + +It records the church-wide brand, CSS, header, navigation, link directory, copyright footer, favicon, and implementation rules so a person or an automated agent can reproduce the same site chrome consistently. + +## Non-negotiable site chrome + +Every site using this theme must render both of these assemblies: + +1. The global header: the `A True Christian Church` logo links home, followed by `Home`, the navigation belonging to the site being built, and the services entry where applicable. +2. The complete footer stack: + - the light directory/link footer with `Church Info`, `Projects`, `Welcome`, and `Social Outreach`; + - the dark copyright/legal footer beneath it. + +The exact contracts are in [AGENTS.md](AGENTS.md), [docs/header.md](docs/header.md), and [docs/footer.md](docs/footer.md). Future agents must read `AGENTS.md` before changing this repository or applying the theme elsewhere. + +## Repository map + +| Path | Purpose | +| --- | --- | +| `src/css/` | Authoritative modular CSS source | +| `src/js/` | Accessible portable header behavior | +| `src/html/` | Reusable document-head, header, and footer markup | +| `src/data/site-chrome.json` | Canonical labels, URLs, grouping, and order | +| `src/data/main-site-navigation.json` | Full static menu data extracted from the supplied main-site header | +| `assets/brand/logo.jpg` | Source church logo, 288 × 77 | +| `assets/favicons/favicon.ico` | Supplied browser favicon | +| `assets/footer/city-skyline-reference.png` | 270 × 180 screenshot-derived footer image reference | +| `dist/` | Built, directly consumable CSS and header JavaScript | +| `docs/` | Human-readable brand and implementation guide | +| `docs/component-inventory.md` | Supplied page-source framework/CSS roles and evidence limits | +| `docs/reference/` | Supplied desktop header/footer screenshots | +| `examples/style-guide.html` | Working reference page using the package | + +## Use + +Load the built stylesheet and use the supplied structural templates: + +```html + + + +``` + +The templates deliberately carry both portable `tcc-*` classes and the relevant YOOtheme/UIkit class names from the supplied page source. + +`src/html/site-header.html` is the generic portable shell: logo, Home, a main-site top-level example, dropdown behavior, and the insertion point for the links belonging to the site being built. It is not a pre-rendered copy of the main site's content-heavy Articles mega-menu. Main-site renderers must use `src/data/main-site-navigation.json` or the retained YOOtheme source excerpt. + +## Build and validate + +The package has no npm dependencies. + +```sh +npm run build +npm run check +``` + +`npm run check` rebuilds the distribution files and verifies mandatory assets, exact fixed labels/URLs/order, source-to-template parity, key selectors, and JavaScript syntax. + +## Evidence and provenance + +The implementation was derived from the supplied True Christian Church page source, supplied header HTML, the source `logo.jpg`, the supplied favicon, and the supplied desktop header/footer screenshots. See [docs/source-inventory.md](docs/source-inventory.md). + +The supplied page source references Joomla, YOOtheme Pro 5.0.35, UIkit, and a compiled site stylesheet named `theme.13.css`. Third-party framework code is not copied into this repository. This package implements the reusable church-specific layer and records the external integration points. + +## Software license + +No software license was supplied for this repository. The website phrase `free distribution only` is preserved as required footer content; it is not treated as a software license. The repository owner should add the intended code/assets license explicitly before third-party distribution. diff --git a/assets/brand/logo.jpg b/assets/brand/logo.jpg new file mode 100644 index 0000000..36af34f Binary files /dev/null and b/assets/brand/logo.jpg differ diff --git a/assets/favicons/favicon.ico b/assets/favicons/favicon.ico new file mode 100644 index 0000000..63cd6a1 Binary files /dev/null and b/assets/favicons/favicon.ico differ diff --git a/assets/footer/city-skyline-reference.png b/assets/footer/city-skyline-reference.png new file mode 100644 index 0000000..51ebe1f Binary files /dev/null and b/assets/footer/city-skyline-reference.png differ diff --git a/dist/truechristian-theme.css b/dist/truechristian-theme.css new file mode 100644 index 0000000..55afc94 --- /dev/null +++ b/dist/truechristian-theme.css @@ -0,0 +1,607 @@ +/* True Christian Church unified theme. Generated from src/css; do not edit dist directly. */ +/* tokens.css */ +:root { + --tcc-color-white: #ffffff; + --tcc-color-cyan: #00cadb; + --tcc-color-directory: #f7f7f7; + --tcc-color-charcoal: #303033; + --tcc-color-ink: #2d2e33; + --tcc-color-nav-muted: #b4b5ba; + --tcc-color-footer-text: rgb(255 255 255 / 70%); + --tcc-color-social-icon: #6c6d74; + --tcc-color-border: #e5e5e7; + + --tcc-font-body: "Raleway", "Helvetica Neue", Arial, sans-serif; + --tcc-font-display: "Montserrat", Arial, sans-serif; + + --tcc-container-max: 1200px; + --tcc-space-1: 0.5rem; + --tcc-space-2: 1rem; + --tcc-space-3: 1.5rem; + --tcc-space-4: 2rem; + --tcc-space-5: 3rem; + --tcc-space-6: 4rem; + --tcc-space-7: 4.375rem; + + --tcc-radius-small: 5px; + --tcc-radius-round: 999px; + --tcc-shadow-medium: 0 5px 15px rgb(0 0 0 / 8%); + --tcc-focus-ring: 0 0 0 4px rgb(0 202 219 / 35%); + + --tcc-breakpoint-small: 640px; + --tcc-breakpoint-medium: 960px; +} + +/* base.css */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + color: var(--tcc-color-ink); + font-family: var(--tcc-font-body); + line-height: 1.7; + text-rendering: optimizeLegibility; +} + +body { + margin: 0; + background: var(--tcc-color-white); + color: var(--tcc-color-ink); +} + +img { + display: block; + max-width: 100%; + height: auto; +} + +a { + color: inherit; + text-decoration-thickness: 0.08em; + text-underline-offset: 0.18em; +} + +a:focus-visible, +button:focus-visible { + outline: 2px solid var(--tcc-color-ink); + outline-offset: 4px; + box-shadow: var(--tcc-focus-ring); +} + +button, +input, +select, +textarea { + font: inherit; +} + +.tcc-container { + width: min(calc(100% - 3rem), var(--tcc-container-max)); + margin-inline: auto; +} + +@media (max-width: 639px) { + .tcc-container { + width: min(calc(100% - 2rem), var(--tcc-container-max)); + } +} + +/* typography.css */ +h1, +h2, +h3, +h4, +h5, +h6 { + margin-block: 0 0.75em; + color: var(--tcc-color-ink); + font-family: var(--tcc-font-display); + font-weight: 400; + line-height: 1.25; +} + +p { + margin-block: 0 1.25em; +} + +.tcc-eyebrow { + font-family: var(--tcc-font-display); + font-size: 0.8125rem; + letter-spacing: 0.22em; + text-transform: uppercase; +} + +/* header.css */ +.tcc-site-header, +.tm-header .uk-navbar-container, +.tm-header-mobile .uk-navbar-container { + border-top: 1px solid var(--tcc-color-cyan); + background: var(--tcc-color-white); +} + +.tcc-site-header { + position: sticky; + z-index: 1000; + top: 0; +} + +.tcc-header__container { + display: flex; + min-height: 80px; + align-items: center; + justify-content: space-between; + gap: var(--tcc-space-2); +} + +.tcc-header__brand { + display: inline-flex; + flex: 0 0 auto; + align-items: center; +} + +.tcc-header__brand img, +.tm-header .uk-logo img, +.tm-header-mobile .uk-logo img { + width: 288px; + height: 77px; + object-fit: contain; +} + +.tm-header .uk-navbar-container > .uk-container { + max-width: var(--tcc-container-max); +} + +.tcc-header__menu, +.tcc-header__menu ul { + margin: 0; + padding: 0; + list-style: none; +} + +.tcc-header__menu { + display: flex; + align-items: center; + gap: 0; +} + +.tcc-header__menu > li { + position: relative; +} + +.tcc-header__menu > li > a, +.tcc-header__menu > li > button, +.tm-header .uk-navbar-nav > li > a { + display: inline-flex; + min-height: 44px; + align-items: center; + border: 0; + padding-inline: 15px; + background: transparent; + color: var(--tcc-color-nav-muted); + cursor: pointer; + font-family: var(--tcc-font-display); + font-size: 0.6875rem; + font-weight: 400; + letter-spacing: 2px; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; +} + +.tcc-header__menu > li:last-child > a { + padding-right: 0; +} + +.tcc-header__menu > li > a:hover, +.tcc-header__menu > li > button:hover, +.tcc-header__menu > li > a:focus-visible, +.tcc-header__menu > li > button:focus-visible, +.tcc-header__menu > .is-active > a, +.tm-header .uk-navbar-nav > .uk-active > a, +.tm-header .uk-navbar-nav > li > a:hover { + color: var(--tcc-color-ink); +} + +.tcc-header__menu ul { + position: absolute; + top: 100%; + left: 0; + display: none; + min-width: 220px; + padding: var(--tcc-space-2); + border: 1px solid var(--tcc-color-border); + background: var(--tcc-color-white); + box-shadow: var(--tcc-shadow-medium); +} + +.tcc-header__menu ul a { + display: block; + padding: 0.35rem 0.5rem; + color: var(--tcc-color-ink); + font-size: 0.875rem; + text-decoration: none; +} + +.tcc-header__services > a { + gap: 0.7rem; +} + +.tcc-header__services small { + display: block; + margin-top: 0.15rem; + font-family: var(--tcc-font-display); + font-size: 0.75rem; + letter-spacing: 0.0125rem; + text-transform: none; +} + +.tcc-header__services-icon { + width: 20px; + height: 20px; + flex: 0 0 20px; + letter-spacing: 0; +} + +.tcc-icon { + display: block; + width: 20px; + height: 20px; + fill: currentcolor; +} + +.tcc-icon--stroke { + fill: none; + stroke: currentcolor; +} + +.tcc-header__toggle { + display: none; + min-width: 44px; + min-height: 44px; + border: 0; + background: transparent; + color: var(--tcc-color-charcoal); + font-size: 1.5rem; +} + +.tcc-header__close, +.tcc-header__scrim { + display: none; +} + +@media (max-width: 959px) { + html.tcc-menu-open { + overflow: hidden; + } + + .tcc-header__container { + min-height: 80px; + justify-content: center; + } + + .tcc-header__brand img { + width: min(240px, 66vw); + height: auto; + } + + .tcc-header__toggle { + position: absolute; + left: 1rem; + display: inline-grid; + place-items: center; + } + + .tcc-header__navigation { + position: fixed; + z-index: 1001; + inset: 0 auto 0 0; + width: min(88vw, 360px); + padding: 6rem 2rem 2rem; + overflow-y: auto; + background: var(--tcc-color-white); + box-shadow: var(--tcc-shadow-medium); + visibility: hidden; + pointer-events: none; + transform: translateX(-105%); + transition: transform 180ms ease, visibility 0s linear 180ms; + } + + .tcc-site-header[data-menu-open="true"] .tcc-header__navigation { + visibility: visible; + pointer-events: auto; + transform: translateX(0); + transition-delay: 0s; + } + + .tcc-header__close { + position: absolute; + top: 1rem; + right: 1rem; + display: inline-grid; + width: 44px; + height: 44px; + place-items: center; + border: 0; + background: transparent; + color: var(--tcc-color-ink); + font-size: 1.75rem; + line-height: 1; + } + + .tcc-header__scrim { + position: fixed; + z-index: 1000; + inset: 0; + display: block; + border: 0; + padding: 0; + background: rgb(45 46 51 / 45%); + opacity: 0; + visibility: hidden; + pointer-events: none; + transition: opacity 180ms ease, visibility 0s linear 180ms; + } + + .tcc-site-header[data-menu-open="true"] .tcc-header__scrim { + opacity: 1; + visibility: visible; + pointer-events: auto; + transition-delay: 0s; + } + + .tcc-header__menu { + display: grid; + align-items: stretch; + gap: 0.4rem; + } + + .tcc-header__menu > li > a, + .tcc-header__menu > li > button { + width: 100%; + justify-content: flex-start; + padding-inline: 0; + white-space: normal; + } + + .tcc-header__menu ul { + position: static; + display: none; + border: 0; + padding: 0 0 0 var(--tcc-space-2); + box-shadow: none; + } + + .tcc-header__menu li[data-submenu-open="true"] > ul { + display: grid; + gap: 0.4rem; + } +} + +@media (min-width: 960px) { + .tm-header .uk-navbar, + .tm-header .uk-navbar-item, + .tm-header .uk-navbar-nav > li > a { + min-height: 80px; + } + + .tcc-header__menu > li.has-submenu:hover > ul, + .tcc-header__menu > li[data-submenu-open="true"] > ul { + display: grid; + gap: 0.4rem; + } +} + +/* footer.css */ +.tcc-footer-directory, +.tm-bottom.uk-section-muted { + background: var(--tcc-color-directory); + color: var(--tcc-color-ink); +} + +.tcc-footer-directory, +.tm-bottom.uk-section-muted { + padding-block: var(--tcc-space-7); +} + +.tm-bottom > .uk-container, +.tm-page > footer .uk-container { + max-width: var(--tcc-container-max); +} + +.tcc-footer-directory__grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 40px; +} + +.tcc-footer-group h2, +.tm-bottom .uk-h5 { + margin: 0 0 20px; + color: var(--tcc-color-ink); + font-family: var(--tcc-font-display); + font-size: 1rem; + font-weight: 400; + letter-spacing: 2px; + line-height: 1.4; + text-transform: uppercase; +} + +.tcc-footer-group ul { + margin: 0; + padding: 0; + list-style: none; +} + +.tcc-footer-group li + li { + margin-top: 10px; +} + +.tcc-footer-group li a, +.tm-bottom .uk-h6, +.tm-bottom .uk-h6 a { + color: var(--tcc-color-ink); + font-family: var(--tcc-font-display); + font-size: 0.6875rem; + font-weight: 400; + letter-spacing: 2px; + line-height: 1.4; + text-decoration: none; + text-transform: uppercase; +} + +.tcc-footer-group li a:hover, +.tcc-footer-group li a:focus-visible, +.tm-bottom .uk-h6 a:hover { + text-decoration: underline; +} + +.tcc-footer-social__links { + display: flex; + flex-wrap: wrap; + gap: 20px; + margin-top: 20px; +} + +.tcc-footer-social__links a, +.tm-bottom .uk-icon-button { + display: inline-grid; + width: 36px; + height: 36px; + place-items: center; + border: 1px solid var(--tcc-color-border); + border-radius: var(--tcc-radius-round); + background: var(--tcc-color-white); + color: var(--tcc-color-social-icon); + font-family: var(--tcc-font-display); + font-size: 0.875rem; + font-weight: 600; + text-decoration: none; +} + +.tcc-footer-social img, +.tm-bottom .uk-border-rounded { + width: 270px; + height: 180px; + margin-top: 20px; + border-radius: var(--tcc-radius-small); + box-shadow: var(--tcc-shadow-medium); + object-fit: cover; +} + +.tcc-footer-copyright, +.tm-page > footer .uk-section-primary { + background: var(--tcc-color-charcoal); + color: var(--tcc-color-footer-text); +} + +.tcc-footer-copyright, +.tm-page > footer .uk-section-primary { + padding-block: var(--tcc-space-7); + font-family: var(--tcc-font-body); + font-size: 1rem; + font-weight: 400; + line-height: 1.6; +} + +.tcc-footer-copyright a, +.tm-page > footer #license a { + color: var(--tcc-color-white); + text-decoration: none; +} + +.tcc-footer-copyright a:hover, +.tcc-footer-copyright a:focus-visible, +.tm-page > footer #license a:hover { + color: var(--tcc-color-white); + text-decoration: underline; +} + +.tcc-footer-copyright a:focus-visible, +.tm-page > footer #license a:focus-visible { + outline-color: var(--tcc-color-white); +} + +@media (max-width: 959px) { + .tcc-footer-directory__grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .tcc-footer-copyright { + padding-block: var(--tcc-space-5); + font-size: 1rem; + } + + .tm-page > footer .uk-section-primary { + padding-block: var(--tcc-space-5); + } + + .tcc-footer-social img { + display: none; + } +} + +@media (max-width: 639px) { + .tcc-footer-directory, + .tm-bottom.uk-section-muted { + padding-block: var(--tcc-space-5); + } + + .tcc-footer-directory__grid { + grid-template-columns: 1fr; + gap: var(--tcc-space-4); + text-align: center; + } + + .tcc-footer-social__links { + justify-content: center; + } + + .tcc-footer-copyright { + text-align: center; + } +} + +/* utilities.css */ +.tcc-visually-hidden { + position: absolute !important; + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} + +@media print { + .tcc-site-header { + position: static; + } + + .tcc-header__toggle, + .tcc-footer-social { + display: none !important; + } + + .tcc-footer-directory, + .tcc-footer-copyright { + padding-block: 1.5rem; + background: #fff !important; + color: #000 !important; + } +} diff --git a/dist/truechristian-theme.js b/dist/truechristian-theme.js new file mode 100644 index 0000000..acc5fe8 --- /dev/null +++ b/dist/truechristian-theme.js @@ -0,0 +1,108 @@ +(function initializeTrueChristianHeaders() { + const mobileQuery = window.matchMedia("(max-width: 959px)"); + const focusableSelector = [ + "a[href]", + "button:not([disabled])", + "input:not([disabled])", + "select:not([disabled])", + "textarea:not([disabled])", + "[tabindex]:not([tabindex='-1'])" + ].join(","); + + document.querySelectorAll("[data-tcc-global-header]").forEach((header) => { + const toggle = header.querySelector(".tcc-header__toggle"); + const navigation = header.querySelector(".tcc-header__navigation"); + const closeButton = header.querySelector(".tcc-header__close"); + const scrim = header.querySelector(".tcc-header__scrim"); + const submenuButtons = header.querySelectorAll(".has-submenu > button"); + let returnFocusTo = null; + + if (!toggle || !navigation || !closeButton || !scrim) return; + + const collapseSubmenus = (except) => { + submenuButtons.forEach((button) => { + if (button === except) return; + button.setAttribute("aria-expanded", "false"); + button.closest(".has-submenu")?.removeAttribute("data-submenu-open"); + }); + }; + + const setMenuOpen = (open, restoreFocus = true) => { + const nextOpen = Boolean(open && mobileQuery.matches); + header.dataset.menuOpen = String(nextOpen); + toggle.setAttribute("aria-expanded", String(nextOpen)); + navigation.toggleAttribute("inert", mobileQuery.matches && !nextOpen); + if (mobileQuery.matches) navigation.setAttribute("aria-hidden", String(!nextOpen)); + else navigation.removeAttribute("aria-hidden"); + document.documentElement.classList.toggle("tcc-menu-open", nextOpen); + + if (nextOpen) { + returnFocusTo = document.activeElement; + closeButton.focus(); + } else { + collapseSubmenus(); + if (restoreFocus && returnFocusTo instanceof HTMLElement) returnFocusTo.focus(); + returnFocusTo = null; + } + }; + + toggle.addEventListener("click", () => setMenuOpen(header.dataset.menuOpen !== "true")); + closeButton.addEventListener("click", () => setMenuOpen(false)); + scrim.addEventListener("click", () => setMenuOpen(false)); + + submenuButtons.forEach((button) => { + button.addEventListener("click", () => { + const open = button.getAttribute("aria-expanded") !== "true"; + collapseSubmenus(button); + button.setAttribute("aria-expanded", String(open)); + button.closest(".has-submenu")?.toggleAttribute("data-submenu-open", open); + }); + }); + + navigation.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + event.preventDefault(); + if (mobileQuery.matches && header.dataset.menuOpen === "true") { + setMenuOpen(false); + } else { + const owningItem = document.activeElement?.closest?.(".has-submenu"); + const owningTrigger = owningItem?.querySelector(":scope > button"); + const expandedTrigger = [...submenuButtons].find( + (button) => button.getAttribute("aria-expanded") === "true" + ); + collapseSubmenus(); + (owningTrigger || expandedTrigger)?.focus(); + } + return; + } + if (event.key !== "Tab" || header.dataset.menuOpen !== "true") return; + + const focusable = [...navigation.querySelectorAll(focusableSelector)].filter( + (element) => !element.closest("[inert]") && element.getClientRects().length > 0 + ); + if (!focusable.length) return; + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (event.shiftKey && document.activeElement === first) { + event.preventDefault(); + last.focus(); + } else if (!event.shiftKey && document.activeElement === last) { + event.preventDefault(); + first.focus(); + } + }); + + document.addEventListener("keydown", (event) => { + if (event.key === "Escape" && header.dataset.menuOpen === "true") setMenuOpen(false); + }); + + document.addEventListener("click", (event) => { + if (!header.contains(event.target)) collapseSubmenus(); + }); + + const syncBreakpoint = () => setMenuOpen(false, false); + if (mobileQuery.addEventListener) mobileQuery.addEventListener("change", syncBreakpoint); + else mobileQuery.addListener?.(syncBreakpoint); + syncBreakpoint(); + }); +})(); diff --git a/dist/truechristian-theme.min.css b/dist/truechristian-theme.min.css new file mode 100644 index 0000000..4e5e57f --- /dev/null +++ b/dist/truechristian-theme.min.css @@ -0,0 +1 @@ +:root{--tcc-color-white:#ffffff;--tcc-color-cyan:#00cadb;--tcc-color-directory:#f7f7f7;--tcc-color-charcoal:#303033;--tcc-color-ink:#2d2e33;--tcc-color-nav-muted:#b4b5ba;--tcc-color-footer-text:rgb(255 255 255 / 70%);--tcc-color-social-icon:#6c6d74;--tcc-color-border:#e5e5e7;--tcc-font-body:"Raleway","Helvetica Neue",Arial,sans-serif;--tcc-font-display:"Montserrat",Arial,sans-serif;--tcc-container-max:1200px;--tcc-space-1:0.5rem;--tcc-space-2:1rem;--tcc-space-3:1.5rem;--tcc-space-4:2rem;--tcc-space-5:3rem;--tcc-space-6:4rem;--tcc-space-7:4.375rem;--tcc-radius-small:5px;--tcc-radius-round:999px;--tcc-shadow-medium:0 5px 15px rgb(0 0 0 / 8%);--tcc-focus-ring:0 0 0 4px rgb(0 202 219 / 35%);--tcc-breakpoint-small:640px;--tcc-breakpoint-medium:960px;} *,*::before,*::after{box-sizing:border-box;} html{color:var(--tcc-color-ink);font-family:var(--tcc-font-body);line-height:1.7;text-rendering:optimizeLegibility;} body{margin:0;background:var(--tcc-color-white);color:var(--tcc-color-ink);} img{display:block;max-width:100%;height:auto;} a{color:inherit;text-decoration-thickness:0.08em;text-underline-offset:0.18em;} a:focus-visible,button:focus-visible{outline:2px solid var(--tcc-color-ink);outline-offset:4px;box-shadow:var(--tcc-focus-ring);} button,input,select,textarea{font:inherit;} .tcc-container{width:min(calc(100% - 3rem),var(--tcc-container-max));margin-inline:auto;} @media (max-width:639px){.tcc-container{width:min(calc(100% - 2rem),var(--tcc-container-max));}} h1,h2,h3,h4,h5,h6{margin-block:0 0.75em;color:var(--tcc-color-ink);font-family:var(--tcc-font-display);font-weight:400;line-height:1.25;} p{margin-block:0 1.25em;} .tcc-eyebrow{font-family:var(--tcc-font-display);font-size:0.8125rem;letter-spacing:0.22em;text-transform:uppercase;} .tcc-site-header,.tm-header .uk-navbar-container,.tm-header-mobile .uk-navbar-container{border-top:1px solid var(--tcc-color-cyan);background:var(--tcc-color-white);} .tcc-site-header{position:sticky;z-index:1000;top:0;} .tcc-header__container{display:flex;min-height:80px;align-items:center;justify-content:space-between;gap:var(--tcc-space-2);} .tcc-header__brand{display:inline-flex;flex:0 0 auto;align-items:center;} .tcc-header__brand img,.tm-header .uk-logo img,.tm-header-mobile .uk-logo img{width:288px;height:77px;object-fit:contain;} .tm-header .uk-navbar-container>.uk-container{max-width:var(--tcc-container-max);} .tcc-header__menu,.tcc-header__menu ul{margin:0;padding:0;list-style:none;} .tcc-header__menu{display:flex;align-items:center;gap:0;} .tcc-header__menu>li{position:relative;} .tcc-header__menu>li>a,.tcc-header__menu>li>button,.tm-header .uk-navbar-nav>li>a{display:inline-flex;min-height:44px;align-items:center;border:0;padding-inline:15px;background:transparent;color:var(--tcc-color-nav-muted);cursor:pointer;font-family:var(--tcc-font-display);font-size:0.6875rem;font-weight:400;letter-spacing:2px;text-decoration:none;text-transform:uppercase;white-space:nowrap;} .tcc-header__menu>li:last-child>a{padding-right:0;} .tcc-header__menu>li>a:hover,.tcc-header__menu>li>button:hover,.tcc-header__menu>li>a:focus-visible,.tcc-header__menu>li>button:focus-visible,.tcc-header__menu>.is-active>a,.tm-header .uk-navbar-nav>.uk-active>a,.tm-header .uk-navbar-nav>li>a:hover{color:var(--tcc-color-ink);} .tcc-header__menu ul{position:absolute;top:100%;left:0;display:none;min-width:220px;padding:var(--tcc-space-2);border:1px solid var(--tcc-color-border);background:var(--tcc-color-white);box-shadow:var(--tcc-shadow-medium);} .tcc-header__menu ul a{display:block;padding:0.35rem 0.5rem;color:var(--tcc-color-ink);font-size:0.875rem;text-decoration:none;} .tcc-header__services>a{gap:0.7rem;} .tcc-header__services small{display:block;margin-top:0.15rem;font-family:var(--tcc-font-display);font-size:0.75rem;letter-spacing:0.0125rem;text-transform:none;} .tcc-header__services-icon{width:20px;height:20px;flex:0 0 20px;letter-spacing:0;} .tcc-icon{display:block;width:20px;height:20px;fill:currentcolor;} .tcc-icon--stroke{fill:none;stroke:currentcolor;} .tcc-header__toggle{display:none;min-width:44px;min-height:44px;border:0;background:transparent;color:var(--tcc-color-charcoal);font-size:1.5rem;} .tcc-header__close,.tcc-header__scrim{display:none;} @media (max-width:959px){html.tcc-menu-open{overflow:hidden;} .tcc-header__container{min-height:80px;justify-content:center;} .tcc-header__brand img{width:min(240px,66vw);height:auto;} .tcc-header__toggle{position:absolute;left:1rem;display:inline-grid;place-items:center;} .tcc-header__navigation{position:fixed;z-index:1001;inset:0 auto 0 0;width:min(88vw,360px);padding:6rem 2rem 2rem;overflow-y:auto;background:var(--tcc-color-white);box-shadow:var(--tcc-shadow-medium);visibility:hidden;pointer-events:none;transform:translateX(-105%);transition:transform 180ms ease,visibility 0s linear 180ms;} .tcc-site-header[data-menu-open="true"] .tcc-header__navigation{visibility:visible;pointer-events:auto;transform:translateX(0);transition-delay:0s;} .tcc-header__close{position:absolute;top:1rem;right:1rem;display:inline-grid;width:44px;height:44px;place-items:center;border:0;background:transparent;color:var(--tcc-color-ink);font-size:1.75rem;line-height:1;} .tcc-header__scrim{position:fixed;z-index:1000;inset:0;display:block;border:0;padding:0;background:rgb(45 46 51 / 45%);opacity:0;visibility:hidden;pointer-events:none;transition:opacity 180ms ease,visibility 0s linear 180ms;} .tcc-site-header[data-menu-open="true"] .tcc-header__scrim{opacity:1;visibility:visible;pointer-events:auto;transition-delay:0s;} .tcc-header__menu{display:grid;align-items:stretch;gap:0.4rem;} .tcc-header__menu>li>a,.tcc-header__menu>li>button{width:100%;justify-content:flex-start;padding-inline:0;white-space:normal;} .tcc-header__menu ul{position:static;display:none;border:0;padding:0 0 0 var(--tcc-space-2);box-shadow:none;} .tcc-header__menu li[data-submenu-open="true"]>ul{display:grid;gap:0.4rem;}} @media (min-width:960px){.tm-header .uk-navbar,.tm-header .uk-navbar-item,.tm-header .uk-navbar-nav>li>a{min-height:80px;} .tcc-header__menu>li.has-submenu:hover>ul,.tcc-header__menu>li[data-submenu-open="true"]>ul{display:grid;gap:0.4rem;}} .tcc-footer-directory,.tm-bottom.uk-section-muted{background:var(--tcc-color-directory);color:var(--tcc-color-ink);} .tcc-footer-directory,.tm-bottom.uk-section-muted{padding-block:var(--tcc-space-7);} .tm-bottom>.uk-container,.tm-page>footer .uk-container{max-width:var(--tcc-container-max);} .tcc-footer-directory__grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:40px;} .tcc-footer-group h2,.tm-bottom .uk-h5{margin:0 0 20px;color:var(--tcc-color-ink);font-family:var(--tcc-font-display);font-size:1rem;font-weight:400;letter-spacing:2px;line-height:1.4;text-transform:uppercase;} .tcc-footer-group ul{margin:0;padding:0;list-style:none;} .tcc-footer-group li + li{margin-top:10px;} .tcc-footer-group li a,.tm-bottom .uk-h6,.tm-bottom .uk-h6 a{color:var(--tcc-color-ink);font-family:var(--tcc-font-display);font-size:0.6875rem;font-weight:400;letter-spacing:2px;line-height:1.4;text-decoration:none;text-transform:uppercase;} .tcc-footer-group li a:hover,.tcc-footer-group li a:focus-visible,.tm-bottom .uk-h6 a:hover{text-decoration:underline;} .tcc-footer-social__links{display:flex;flex-wrap:wrap;gap:20px;margin-top:20px;} .tcc-footer-social__links a,.tm-bottom .uk-icon-button{display:inline-grid;width:36px;height:36px;place-items:center;border:1px solid var(--tcc-color-border);border-radius:var(--tcc-radius-round);background:var(--tcc-color-white);color:var(--tcc-color-social-icon);font-family:var(--tcc-font-display);font-size:0.875rem;font-weight:600;text-decoration:none;} .tcc-footer-social img,.tm-bottom .uk-border-rounded{width:270px;height:180px;margin-top:20px;border-radius:var(--tcc-radius-small);box-shadow:var(--tcc-shadow-medium);object-fit:cover;} .tcc-footer-copyright,.tm-page>footer .uk-section-primary{background:var(--tcc-color-charcoal);color:var(--tcc-color-footer-text);} .tcc-footer-copyright,.tm-page>footer .uk-section-primary{padding-block:var(--tcc-space-7);font-family:var(--tcc-font-body);font-size:1rem;font-weight:400;line-height:1.6;} .tcc-footer-copyright a,.tm-page>footer #license a{color:var(--tcc-color-white);text-decoration:none;} .tcc-footer-copyright a:hover,.tcc-footer-copyright a:focus-visible,.tm-page>footer #license a:hover{color:var(--tcc-color-white);text-decoration:underline;} .tcc-footer-copyright a:focus-visible,.tm-page>footer #license a:focus-visible{outline-color:var(--tcc-color-white);} @media (max-width:959px){.tcc-footer-directory__grid{grid-template-columns:repeat(2,minmax(0,1fr));} .tcc-footer-copyright{padding-block:var(--tcc-space-5);font-size:1rem;} .tm-page>footer .uk-section-primary{padding-block:var(--tcc-space-5);} .tcc-footer-social img{display:none;}} @media (max-width:639px){.tcc-footer-directory,.tm-bottom.uk-section-muted{padding-block:var(--tcc-space-5);} .tcc-footer-directory__grid{grid-template-columns:1fr;gap:var(--tcc-space-4);text-align:center;} .tcc-footer-social__links{justify-content:center;} .tcc-footer-copyright{text-align:center;}} .tcc-visually-hidden{position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0,0,0,0) !important;white-space:nowrap !important;border:0 !important;} @media (prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto !important;transition-duration:0.01ms !important;animation-duration:0.01ms !important;animation-iteration-count:1 !important;}} @media print{.tcc-site-header{position:static;} .tcc-header__toggle,.tcc-footer-social{display:none !important;} .tcc-footer-directory,.tcc-footer-copyright{padding-block:1.5rem;background:#fff !important;color:#000 !important;}} diff --git a/docs/brand-guide.md b/docs/brand-guide.md new file mode 100644 index 0000000..5074b5c --- /dev/null +++ b/docs/brand-guide.md @@ -0,0 +1,42 @@ +# Brand guide + +## Identity + +The required masthead is the supplied `A True Christian Church` logo: + +```text +assets/brand/logo.jpg +native size: 288 × 77 +alt text: A True Christian Church +``` + +Use it on white. Preserve its aspect ratio and built-in shadow. Do not recolor, crop, redraw, stretch, or place text over it. + +## Core palette + +| Token | Value | Use | Provenance | +| --- | --- | --- | --- | +| `--tcc-color-white` | `#ffffff` | Header and light surfaces | Measured | +| `--tcc-color-cyan` | `#00cadb` | Thin header top rule and restrained focus accent | Measured | +| `--tcc-color-directory` | `#f7f7f7` | Directory/link footer | Measured | +| `--tcc-color-charcoal` | `#303033` | Copyright footer | Measured | +| `--tcc-color-ink` | `#2d2e33` | Active navigation and directory text | Measured | +| `--tcc-color-nav-muted` | `#b4b5ba` | Inactive desktop navigation | Measured | +| `--tcc-color-social-icon` | `#6c6d74` | Social glyphs | Measured | +| `--tcc-color-footer-text` | `rgb(255 255 255 / 70%)` | Copyright prefix and separators | Measured composite | +The JPEG's dominant core samples are approximately red `#a40702`, gold `#ffdb3c`, and shadow grey `#a8a8a8`. JPEG compression and antialiasing produce many neighboring values, so these are documented observations rather than reusable CSS tokens. The logo remains the only authoritative rendering of its colors. + +### Contrast note + +The measured inactive navigation color `#b4b5ba` on white does not meet WCAG AA contrast for 11px text. It is retained because this repository records the supplied visual exactly. A deployment that must meet AA should override `--tcc-color-nav-muted` with at least `#6c6d74` and document that deliberate visual change. Keyboard focus uses a dark outline plus cyan halo rather than cyan alone. + +## Shape and effects + +- Global header and directory footer are square-edged full-width bands. +- Social buttons are circular. +- The directory footer's optional image uses modest rounding and a medium, soft shadow. +- Use borders and shadows sparingly. The brand depends on whitespace, typography, and restrained contrast rather than heavy decoration. + +## Spacing rhythm + +The screenshots were captured at an effective `1.25×` scale. Their `1500`-device-pixel content region corresponds to a `1200px` CSS container. Use `70px` vertical padding for both footer bands, a `40px` four-column gutter, and the spacing tokens elsewhere. diff --git a/docs/component-inventory.md b/docs/component-inventory.md new file mode 100644 index 0000000..4825059 --- /dev/null +++ b/docs/component-inventory.md @@ -0,0 +1,43 @@ +# Page-source CSS and component inventory + +This inventory records what the owner-supplied page source proves beyond the global header and footer. It prevents future agents from mistaking an absent stylesheet body for permission to invent a new visual system. + +## Referenced stylesheets + +The page source loads these files in this order: + +1. `/media/vendor/joomla-custom-elements/css/joomla-alert.min.css?0.4.1` +2. `/media/system/css/joomla-fontawesome.min.css?5.0.35` using stylesheet preload/onload +3. `/templates/yootheme/css/theme.13.css?1782988037` + +The compiled contents of `theme.13.css` were not included in the supplied HTML. This repository therefore does not claim to reproduce every page-body declaration from that file. It records the roles visible in the markup and provides an evidence-based church-specific global chrome layer. + +## Framework roles present in the supplied markup + +| Area | Source roles/classes | Meaning for implementations | +| --- | --- | --- | +| Page shell | `tm-page`, `tm-main`, `uk-container`, `uk-container-expand` | Full-width bands with centered content containers | +| Header | `tm-header`, `tm-header-mobile`, `uk-navbar*`, `uk-logo`, `uk-sticky` | Desktop sticky navbar plus mobile replacement | +| Navigation | `uk-nav*`, `uk-navbar-dropdown`, `uk-drop`, `uk-offcanvas`, `uk-nav-accordion` | Dropdown/mega-menu and accessible mobile drawer behavior | +| Sections | `uk-section-default`, `uk-section-primary`, `uk-section-muted`, `uk-section-large`, `uk-section-xlarge` | Semantic surface and spacing variants | +| Layout | `uk-grid*`, `uk-width-*`, `uk-flex*`, `uk-tile*` | Responsive grids, widths, alignment, and matched tiles | +| Type | `uk-heading-xlarge`, `uk-heading-small`, `uk-h4`, `uk-h5`, `uk-h6`, `uk-text-lead`, `uk-text-meta`, `uk-text-small` | Page heading, lead, metadata, and footer label roles | +| Links/buttons | `uk-button-primary`, `uk-button-secondary`, `uk-button-default`, `uk-button-small`, `uk-link-*` | Framework-provided action and link variants | +| Cards/media | `uk-card*`, `uk-background-*`, `uk-box-shadow-medium`, `uk-border-rounded`, `uk-img` | Cards, responsive imagery, cover positioning, shadow, and radius | +| Motion/content | `uk-parallax`, `uk-scrollspy`, `uk-slider`, `uk-height-viewport` | Optional YOOtheme/UIkit behavior rather than brand constants | +| Utilities | `uk-margin*`, `uk-padding*`, `uk-text-*`, `uk-visible@m`, `uk-hidden@m`, `uk-light` | Responsive visibility, spacing, alignment, and inverse text | + +## What is fixed here + +The following are supported by supplied assets/source/screenshots and are implemented in this repository: + +- brand logo and favicon; +- global header silhouette, navigation roles, responsive breakpoints, and portable behavior; +- global directory/link footer, social controls, image slot, and responsive columns; +- global copyright footer; +- measured colors, container geometry, spacing, font roles, and exact fixed links; +- Joomla/YOOtheme compatibility selectors for the global chrome. + +## What requires additional source + +Exact styling for page-body buttons, cards, tiles, sliders, hero sections, forms, tables, alerts, and content modules remains owned by the referenced compiled YOOtheme stylesheet. Do not fabricate exact values for those components from class names alone. If the owner later supplies `theme.13.css`, add an audited token/component layer and update this inventory, source provenance, examples, and validation together. diff --git a/docs/footer.md b/docs/footer.md new file mode 100644 index 0000000..d0c7a89 --- /dev/null +++ b/docs/footer.md @@ -0,0 +1,99 @@ +# Mandatory footer contract + +![Desktop footer reference](reference/footer-desktop.png) + +## Footer terminology + +The supplied source has two visible regions: + +1. `div.tm-bottom.uk-section-muted.uk-section` — the large directory/link footer. +2. `footer > .uk-section-primary.uk-section` — the dark copyright footer. + +Together they form the required bottom footer assembly. They must remain adjacent and must be applied to every site. + +## Exact visual specification + +| Part | Desktop contract | +| --- | --- | +| Directory surface | `#f7f7f7`, `70px` block padding | +| Directory container | `1200px` maximum width | +| Directory grid | Four equal `270px` tracks with `40px` gaps | +| Group heading | Montserrat 400, `16px/1.4`, uppercase, `2px` tracking, `20px` bottom gap | +| Group link | Montserrat 400, `11px/1.4`, uppercase, `2px` tracking, `10px` sibling gap, `#2d2e33` | +| Social controls | Four `36px` white circles, `20px` gaps, `#6c6d74` glyphs, `#e5e5e7` edge | +| Skyline slot | Desktop only, `270 × 180px`, `20px` top gap, `5px` radius, subdued shadow | +| Copyright surface | `#303033`, `70px` block padding | +| Copyright line | Raleway 400, `16px/1.6`; prefix and pipes 70%-white, links white | + +The `1920 × 767` screenshot is a 1.25× capture. Do not copy its device-pixel widths as CSS values. + +## Directory/link footer + +The directory footer uses a light `#f7f7f7` background, a wide centered container, generous vertical space, and four desktop columns. + +### Church Info + +- History +- Brotherhood +- Blog +- Testimonies +- Confession of Faith +- Storehouse +- Privacy Policy +- Copyright + +### Projects + +- GETBIBLE +- Loudvoice +- SHE Cares +- Trousseau Chest +- Why Bible +- we fear God +- Amana +- Mission (Namibia) + +This is a church-wide link directory. The presence of a project link does not make the shared theme specific to that project. + +### Welcome + +- Follow Jesus +- Live Services +- Locations +- Events +- Revival +- Telegram +- Contact Us + +### Social Outreach + +- X/Twitter +- Facebook +- GitHub +- Telegram + +The source also shows `/images/city-skyline-skyscrapers-top.jpg` beneath the social links on desktop. Its CSS slot is `270px × 180px`, with a `5px` radius, `20px` top margin, and subdued shadow; it is hidden below `960px`. The original image file was not supplied. `assets/footer/city-skyline-reference.png` is a correctly sized crop from the supplied footer screenshot, retained as an explicit reference/fallback rather than invented stock imagery. + +## Copyright footer + +The dark `#303033` footer appears immediately below the directory footer. Prefix text and separators are 70%-white; link text is white. Its exact sequence is: + +```text +Copyright © trueChristian.Church free distribution only | Privacy Policy | Mission | Revival | We Fear God | Why we believe the Bible | Trousseau Chest +``` + +See `src/data/site-chrome.json` for exact URLs. The line may wrap on smaller screens, but its order and content remain fixed. + +The source HTML records internal destinations as root-relative paths. The portable template resolves them to `https://truechristian.church` so the required global directory remains correct when this footer is installed on another domain. A main-site Joomla/YOOtheme implementation may retain the original root-relative form. + +## Responsive rules + +- Desktop: four columns, left-aligned. +- Small/tablet: two columns where space permits. +- Narrow mobile: one column, centered. +- Social buttons stay touch-friendly and retain visible focus. +- Copyright links wrap naturally; never force horizontal scrolling. + +## Reusable markup + +Use `src/html/site-footer.html`. It includes both portable `tcc-*` classes and relevant YOOtheme/UIkit compatibility classes. diff --git a/docs/header.md b/docs/header.md new file mode 100644 index 0000000..2678969 --- /dev/null +++ b/docs/header.md @@ -0,0 +1,60 @@ +# Global header contract + +![Desktop header reference](reference/header-desktop.png) + +## Desktop composition + +The supplied page source uses: + +```text +header.tm-header.uk-visible@m + sticky wrapper: media="@m", show-on-up, slide-top animation + .uk-navbar-container + .uk-container + nav.uk-navbar + .uk-navbar-left -> linked logo + .uk-navbar-right -> navigation +``` + +The white bar has a thin cyan top rule. The logo sits on the left, and navigation aligns right. `Home` is visibly active in the supplied screenshot; inactive entries are muted until hover/focus. + +The source logo markup declares `288 × 77`, links to `https://truechristian.church/`, and uses the alt text `A True Christian Church`. + +| Part | Desktop contract | +| --- | --- | +| Surface | White with `1px solid #00cadb` top rule | +| Container | `1200px` maximum width | +| Navbar | `80px` minimum height | +| Logo | `288 × 77px`, intrinsic aspect ratio | +| Navigation | Montserrat 400, `11px`, uppercase, `2px` tracking | +| Active text | `#2d2e33` | +| Inactive text | `#b4b5ba` as measured in the supplied source screenshot | +| Link spacing | Approximately `15px` inline hit-area padding on each side | +| Services icon/subtitle | `20px` world icon; `Join us` is Montserrat 400 at `12px` | + +The screenshot is a 1.25× capture; its device-pixel geometry is larger than the CSS contract. + +The supplied screenshot is the home page, so `Home` is active there. The reusable fragment intentionally has no hardcoded current item; the consuming page must add `aria-current="page"` and `.is-active` to its actual route. + +## Canonical main-site order + +1. Home +2. The Holy Scriptures +3. Sermons +4. Articles +5. Daily +6. Services — subtitle: Join us + +For another True Christian Church site, the logo and `Home` remain fixed. Navigation required by that site follows `Home` using the same spacing, typography, active state, and accessible dropdown behavior. + +The canonical main-site mega-menu is not a generic requirement for every satellite. Its full static labels, nesting, and URLs are preserved in `src/data/main-site-navigation.json`, and the supplied desktop markup is retained in `docs/source/header-desktop-source.html`. Date-sensitive cards inside the Articles and Daily mega-menus remain content, not theme constants. + +## Mobile composition + +The source uses `tm-header-mobile uk-hidden@m`, a left menu toggle, centered logo, and a slide-mode off-canvas menu. Nested entries become an accordion. Implementations on other frameworks must preserve this behavior even when class names differ. + +The portable template uses `src/js/site-header.js` for open/close state, submenu state, Escape, focus containment, inert off-screen navigation, and breakpoint synchronization. Load the built script with `defer`. + +## Reusable markup + +Use `src/html/site-header.html` as the generic portable shell. It intentionally demonstrates the top-level/dropdown pattern without freezing the content-heavy Articles mega-menu into every site. A canonical main-site implementation must render the complete `src/data/main-site-navigation.json`; a satellite replaces the example entries after `Home` with its own links. diff --git a/docs/reference/footer-desktop.png b/docs/reference/footer-desktop.png new file mode 100644 index 0000000..313470a Binary files /dev/null and b/docs/reference/footer-desktop.png differ diff --git a/docs/reference/header-desktop.png b/docs/reference/header-desktop.png new file mode 100644 index 0000000..b713bcf Binary files /dev/null and b/docs/reference/header-desktop.png differ diff --git a/docs/source-inventory.md b/docs/source-inventory.md new file mode 100644 index 0000000..68772e8 --- /dev/null +++ b/docs/source-inventory.md @@ -0,0 +1,85 @@ +# Source inventory and evidence + +This document distinguishes extracted facts from repository implementation decisions. + +## Supplied authoritative materials + +| Material | What it establishes | +| --- | --- | +| Full True Christian Church page source | Document metadata, favicon references, stylesheet references, Joomla/YOOtheme/UIkit integration, responsive header structure, `tm-bottom` link footer, and copyright footer markup | +| Supplied header HTML | Complete desktop navbar and mega-menu markup | +| `assets/brand/logo.jpg` | Source logo, `288 × 77`, with source alt text `A True Christian Church` | +| `assets/favicons/favicon.ico` | Supplied 16 × 16 browser icon | +| `docs/reference/header-desktop.png` | Desktop header silhouette, spacing, colors, navigation presentation, and services subtitle | +| `docs/reference/footer-desktop.png` | Directory footer and copyright footer composition | + +The supplied desktop header excerpt is retained in `docs/source/header-desktop-source.html` with trailing whitespace normalized for version control. Its stable static menu content is normalized in `src/data/main-site-navigation.json`. The global footer is retained as a whitespace-normalized semantic extract in `docs/source/footer-source-extract.html` and as canonical data in `src/data/site-chrome.json`. + +Protected source assets are checked byte-for-byte by `npm run check`; this prevents an automated edit from silently replacing the logo, favicon, or visual references. + +## Exact references found in the page source + +### Icons + +```text +/images/icon.png rel="icon" sizes="any" +/images/250x250.png rel="apple-touch-icon" +``` + +The supplied `favicon.ico` is retained in this repository. The separately referenced `icon.png` and `250x250.png` were not included as source files and are therefore not fabricated here. + +### Stylesheets + +```text +/media/vendor/joomla-custom-elements/css/joomla-alert.min.css?0.4.1 +/media/system/css/joomla-fontawesome.min.css?5.0.35 +/templates/yootheme/css/theme.13.css?1782988037 +``` + +### Theme scripts + +```text +/templates/yootheme/vendor/assets/uikit/dist/js/uikit.min.js?5.0.35 +/templates/yootheme/vendor/assets/uikit/dist/js/uikit-icons-fuse.min.js?5.0.35 +/templates/yootheme/assets/site/js/theme.js?5.0.35 +``` + +The GetBible loader and analytics scripts found in the page source are functional integrations, not theme dependencies. + +## Framework evidence + +- Joomla document structure and system assets. +- YOOtheme Pro theme assets, version query `5.0.35`. +- UIkit component and responsive classes. +- Desktop `tm-header` with a sticky, show-on-up navbar. +- Mobile `tm-header-mobile` with an off-canvas menu. +- `tm-bottom uk-section-muted` for the directory footer. +- `uk-section-primary` inside the final `footer` for the copyright band. + +## Measured screenshot values and capture scale + +The screenshots use an effective `1.25×` capture scale. This is proven by the native `288 × 77` logo appearing as `360 × 96` device pixels, `36px` UIkit social buttons appearing as `45` device pixels, and the `1200px` container occupying `1500` device pixels. Geometry below is reported in CSS pixels after de-scaling. + +| Role | Value | +| --- | --- | +| Header background | `#ffffff` | +| Header top rule | `#00cadb`, `1px` CSS | +| Directory footer background | `#f7f7f7` | +| Copyright footer background | `#303033` | +| Active/directory text | `#2d2e33` | +| Inactive navigation | `#b4b5ba` | +| Desktop container | `1200px` | +| Navbar minimum height | `80px` | +| Footer band padding | `70px` block | +| Footer grid gutter | `40px` | +| Social controls | `36px`, `20px` gap | +| Social image slot | `270 × 180` | +| Desktop header reference size | `1917 × 112` | +| Desktop footer reference size | `1920 × 767` | +| Directory/copyright transition | device y=560 in the footer reference | + +Montserrat and Raleway are metric-matched font identifications rather than declarations extracted from the supplied HTML. See `docs/typography.md`. + +## Implementation defaults + +The portable `tcc-*` CSS reproduces the supplied structure without requiring Joomla. Its typography stacks and spacing tokens are maintained as repository defaults. They are documented in `docs/typography.md` and `docs/brand-guide.md` and should be revised if the owner supplies the original compiled CSS or font files. diff --git a/docs/source/footer-source-extract.html b/docs/source/footer-source-extract.html new file mode 100644 index 0000000..7f148f7 --- /dev/null +++ b/docs/source/footer-source-extract.html @@ -0,0 +1,73 @@ + +
+
+
+ + + +
+

Social Outreach

+
+
+
+
+
+
+
+ +
+
+
+
+
+ + diff --git a/docs/source/header-desktop-source.html b/docs/source/header-desktop-source.html new file mode 100644 index 0000000..d4c4ecf --- /dev/null +++ b/docs/source/header-desktop-source.html @@ -0,0 +1,787 @@ +
+
+ +
+ +
\ No newline at end of file diff --git a/docs/typography.md b/docs/typography.md new file mode 100644 index 0000000..afb2010 --- /dev/null +++ b/docs/typography.md @@ -0,0 +1,25 @@ +# Typography + +The supplied page source exposes semantic YOOtheme roles (`uk-h4`, `uk-h5`, `uk-h6`, `uk-text-lead`, `uk-text-meta`, and heading classes), but it does not contain the compiled font declarations from `theme.13.css`. + +## Repository defaults + +- Display, navigation, and footer labels: `Montserrat`, falling back to `Arial` and sans-serif. +- Body and copyright copy: `Raleway`, falling back to `Helvetica Neue`, `Arial`, and sans-serif. + +These stacks are maintained implementation defaults chosen to reproduce the visual roles in the supplied screenshots. A consuming site must load the named fonts itself or accept the fallbacks. No font binaries are vendored here. + +## Required visual roles + +| Role | Default treatment | +| --- | --- | +| Desktop navigation | Montserrat 400, 11px, uppercase, 2px tracking | +| Directory headings | Montserrat 400, 16px/1.4, uppercase, 2px tracking | +| Directory links | Montserrat 400, 11px/1.4, uppercase, 2px tracking | +| Services subtitle | Montserrat 400, 12px, normal case, approximately 0.2px tracking | +| Copyright copy | Raleway 400, 16px/1.6, normal tracking | +| Body copy | 16px, 1.7 line-height | + +Do not simulate the masthead with a text font. Always use the supplied logo image. + +Montserrat and Raleway were identified by matching the screenshot geometry against font metrics; the supplied HTML does not contain the compiled `@font-face` rules. The portable head fragment loads the required 400 weights from Google Fonts. A privacy-restricted deployment may self-host licensed copies and retain the same family names. diff --git a/docs/yootheme-integration.md b/docs/yootheme-integration.md new file mode 100644 index 0000000..f074744 --- /dev/null +++ b/docs/yootheme-integration.md @@ -0,0 +1,40 @@ +# Joomla, YOOtheme, and UIkit integration + +The supplied source is a Joomla page rendered through YOOtheme Pro and UIkit. + +## Source load order + +1. Joomla alert CSS. +2. Joomla Font Awesome CSS, preloaded and promoted to a stylesheet. +3. Compiled YOOtheme site stylesheet `theme.13.css`. +4. Joomla core/messages scripts. +5. UIkit. +6. UIkit fused icons. +7. YOOtheme site script. + +The portable stylesheet in this repository belongs after the framework stylesheet when used as a church-specific override: + +```html + + +``` + +When used outside Joomla/YOOtheme, load the repository stylesheet and portable header script, then render the `tcc-*` classes from the supplied HTML templates: + +```html + + +``` + +When UIkit already owns the mobile off-canvas and dropdown behavior, do not initialize the portable script against that separate UIkit markup. The `data-tcc-global-header` hook belongs only on the portable header fragment. + +## Compatibility selectors + +The CSS intentionally recognizes these source regions: + +- `.tm-header` +- `.tm-header-mobile` +- `.tm-bottom` +- `#license` + +Do not rely solely on UIkit utility classes for the brand contract. The `tcc-*` classes make the required structure explicit and portable. diff --git a/examples/style-guide.html b/examples/style-guide.html new file mode 100644 index 0000000..46b1167 --- /dev/null +++ b/examples/style-guide.html @@ -0,0 +1,55 @@ + + + + + + True Christian Church theme style guide + + + + + + + + +
+
+ + A True Christian Church + + + + +
+
+ +
+

Unified theme reference

+

A True Christian Church

+

This page exercises the mandatory global header and footer assembly. Project pages belong between them.

+
+ + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..81322f9 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "@truechristian/theme", + "version": "0.1.0", + "private": true, + "description": "Unified theme and mandatory site chrome for True Christian Church websites", + "repository": "https://github.com/trueChristian/theme", + "style": "dist/truechristian-theme.css", + "type": "module", + "scripts": { + "build": "node scripts/build-css.mjs", + "validate": "node --check src/js/site-header.js && node --check dist/truechristian-theme.js && node scripts/validate.mjs", + "check": "npm run build && npm run validate" + } +} diff --git a/scripts/build-css.mjs b/scripts/build-css.mjs new file mode 100644 index 0000000..4a517c8 --- /dev/null +++ b/scripts/build-css.mjs @@ -0,0 +1,95 @@ +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const entry = await readFile(path.join(root, "src/css/theme.css"), "utf8"); +const modules = [...entry.matchAll(/@import\s+["']\.\/([^"']+)["'];?/g)].map((match) => match[1]); +const headerScript = "site-header.js"; + +if (!modules.length) throw new Error("src/css/theme.css contains no module imports."); + +const sections = []; +for (const file of modules) { + const css = await readFile(path.join(root, "src/css", file), "utf8"); + sections.push(`/* ${file} */\n${css.trim()}\n`); +} + +const banner = "/* True Christian Church unified theme. Generated from src/css; do not edit dist directly. */\n"; +const compiled = banner + sections.join("\n"); + +function minifyCss(input) { + let output = ""; + let quote = ""; + let inComment = false; + let pendingSpace = false; + + for (let index = 0; index < input.length; index += 1) { + const character = input[index]; + const next = input[index + 1]; + + if (inComment) { + if (character === "*" && next === "/") { + inComment = false; + pendingSpace = true; + index += 1; + } + continue; + } + + if (quote) { + output += character; + if (character === "\\" && next) { + output += next; + index += 1; + } else if (character === quote) { + quote = ""; + } + continue; + } + + if (character === "/" && next === "*") { + inComment = true; + index += 1; + continue; + } + + if (character === '"' || character === "'") { + if (pendingSpace && output && !"{:;,>".includes(output.at(-1))) output += " "; + pendingSpace = false; + quote = character; + output += character; + continue; + } + + if (/\s/.test(character)) { + pendingSpace = true; + continue; + } + + if ("{}:;,>".includes(character)) { + if (output.endsWith(" ")) output = output.slice(0, -1); + output += character; + pendingSpace = false; + continue; + } + + if (pendingSpace && output && !"{:;,>".includes(output.at(-1))) output += " "; + pendingSpace = false; + output += character; + } + + return output.trim(); +} + +const minified = minifyCss(compiled); + +await mkdir(path.join(root, "dist"), { recursive: true }); +await writeFile(path.join(root, "dist/truechristian-theme.css"), compiled); +await writeFile(path.join(root, "dist/truechristian-theme.min.css"), `${minified}\n`); +await writeFile( + path.join(root, "dist/truechristian-theme.js"), + await readFile(path.join(root, "src/js", headerScript), "utf8") +); + +console.log(`Built ${modules.length} CSS modules and ${headerScript}.`); diff --git a/scripts/validate.mjs b/scripts/validate.mjs new file mode 100644 index 0000000..c491331 --- /dev/null +++ b/scripts/validate.mjs @@ -0,0 +1,175 @@ +import { access, readFile, stat } from "node:fs/promises"; +import { createHash } from "node:crypto"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const requiredFiles = [ + "AGENTS.md", + "assets/brand/logo.jpg", + "assets/favicons/favicon.ico", + "assets/footer/city-skyline-reference.png", + "docs/header.md", + "docs/footer.md", + "docs/reference/header-desktop.png", + "docs/reference/footer-desktop.png", + "docs/source/header-desktop-source.html", + "docs/source/footer-source-extract.html", + "src/html/site-header.html", + "src/html/site-footer.html", + "src/html/document-head.html", + "src/data/site-chrome.json", + "src/data/main-site-navigation.json", + "src/js/site-header.js", + "dist/truechristian-theme.css", + "dist/truechristian-theme.min.css", + "dist/truechristian-theme.js", + "examples/style-guide.html" +]; + +for (const file of requiredFiles) await access(path.join(root, file)); + +const logo = await stat(path.join(root, "assets/brand/logo.jpg")); +const favicon = await stat(path.join(root, "assets/favicons/favicon.ico")); +if (logo.size !== 25467) throw new Error(`Unexpected logo byte size: ${logo.size}`); +if (favicon.size !== 1150) throw new Error(`Unexpected favicon byte size: ${favicon.size}`); + +const expectedHashes = { + "assets/brand/logo.jpg": "69b53d5ff26b260efbc859887112a1be8ffc851c335befa3c47a2d3ae9aa53bf", + "assets/favicons/favicon.ico": "2a28086f29f140828e2c1e03629a5d8306000c456453a9e6d25cf4ccfadb9a2d", + "assets/footer/city-skyline-reference.png": "e21c5357ee6583efc20eb2c6c3926dfd89554a327cd2ea00518c9b3b7ca4f12a", + "docs/reference/header-desktop.png": "23c862136e521fb69a46aca6f38b125ec58a7c340638c4b09dc62bc51ebec40f", + "docs/reference/footer-desktop.png": "392add221768d63d1055ade7d487808ba63236d83b717d60e3fc274df6a46fe2", + "docs/source/header-desktop-source.html": "1ec7e611834da96da84c35c2fd579e64752ee2687df3379d4da27edddbde99b9" +}; +for (const [file, expected] of Object.entries(expectedHashes)) { + const bytes = await readFile(path.join(root, file)); + const actual = createHash("sha256").update(bytes).digest("hex"); + if (actual !== expected) throw new Error(`Protected source asset changed: ${file}`); +} + +const chrome = JSON.parse(await readFile(path.join(root, "src/data/site-chrome.json"), "utf8")); +const mainNavigation = JSON.parse(await readFile(path.join(root, "src/data/main-site-navigation.json"), "utf8")); +const groupLabels = chrome.footer.directoryGroups.map((group) => group.label); +if (groupLabels.join("|") !== "Church Info|Projects|Welcome") throw new Error("Directory group order changed."); +if (chrome.footer.social.label !== "Social Outreach") throw new Error("Social footer group is missing."); + +const requiredCopyright = ["free distribution only", "Privacy Policy", "Mission", "Revival", "We Fear God", "Why we believe the Bible", "Trousseau Chest"]; +const copyrightLabels = chrome.footer.copyright.links.map((link) => link.label); +if (copyrightLabels.join("|") !== requiredCopyright.join("|")) throw new Error("Copyright link order changed."); + +const header = await readFile(path.join(root, "src/html/site-header.html"), "utf8"); +const footer = await readFile(path.join(root, "src/html/site-footer.html"), "utf8"); +const example = await readFile(path.join(root, "examples/style-guide.html"), "utf8"); +const css = await readFile(path.join(root, "dist/truechristian-theme.css"), "utf8"); +const sourceScript = await readFile(path.join(root, "src/js/site-header.js"), "utf8"); +const distScript = await readFile(path.join(root, "dist/truechristian-theme.js"), "utf8"); +const suppliedHeader = await readFile(path.join(root, "docs/source/header-desktop-source.html"), "utf8"); + +if (sourceScript !== distScript) throw new Error("Built header script is stale."); +if (chrome.header.siteSpecificLinks.position !== "after-home") throw new Error("Site-specific links must follow Home."); +const portableHref = (href) => href.startsWith("/") ? new URL(href, chrome.brand.canonicalOrigin).href : href; + +const mainLabels = mainNavigation.items.map((item) => item.label); +if (mainLabels.join("|") !== "Home|The Holy Scriptures|Sermons|Articles|Daily|Services") { + throw new Error("Canonical main-site navigation order changed."); +} + +for (const label of ["A True Christian Church", "Home", "The Holy Scriptures", "Sermons", "Articles", "Daily", "Services", "Join us"]) { + if (!header.includes(label)) throw new Error(`Header is missing: ${label}`); +} + +if (/aria-current="page"/.test(header)) throw new Error("Reusable header hardcodes an active page."); +for (const requirement of ["tcc-header__close", "tcc-header__scrim", "aria-haspopup", "data-tcc-global-header"]) { + if (!header.includes(requirement)) throw new Error(`Header behavior hook is missing: ${requirement}`); +} + +for (const item of mainNavigation.items) { + if (!suppliedHeader.includes(item.label)) throw new Error(`Supplied header fixture is missing: ${item.label}`); +} +for (const landmark of ["Remnant Topics", "Trousseau Chest", "Transcribed Sermons", "Download Remnant PDFs"]) { + if (!suppliedHeader.includes(landmark)) throw new Error(`Mega-menu source fixture is missing: ${landmark}`); + if (!JSON.stringify(mainNavigation).includes(landmark)) throw new Error(`Main navigation data is missing: ${landmark}`); +} + +for (const label of ["Church Info", "Projects", "Welcome", "Social Outreach", ...requiredCopyright]) { + if (!footer.includes(label)) throw new Error(`Footer is missing: ${label}`); + if (!example.includes(label)) throw new Error(`Style guide is missing: ${label}`); +} + +if (example.includes('href="#')) throw new Error("Style guide contains an unresolved fragment link."); +for (const behaviorHook of ["has-submenu", "aria-expanded", "tcc-header__close", "tcc-header__scrim"]) { + if (!example.includes(behaviorHook)) throw new Error(`Style guide does not exercise: ${behaviorHook}`); +} + +for (const group of chrome.footer.directoryGroups) { + for (const link of group.links) { + const marker = `href="${portableHref(link.href)}"`; + const index = example.indexOf(marker); + if (index < 0) throw new Error(`Style guide is missing footer URL: ${link.href}`); + const tagEnd = example.indexOf("", index); + const anchor = example.slice(index, tagEnd); + if (link.newTab === true && !anchor.includes('target="_blank"')) throw new Error(`Style guide target behavior changed: ${link.label}`); + if (link.newTab === false && anchor.includes('target="_blank"')) throw new Error(`Style guide target behavior changed: ${link.label}`); + } +} + +const exampleSocial = example.slice(example.indexOf("tcc-footer-social")); +for (const link of chrome.footer.social.links) { + const marker = `href="${portableHref(link.href)}"`; + const index = exampleSocial.indexOf(marker); + if (index < 0) throw new Error(`Style guide is missing social URL: ${link.href}`); + const tagEnd = exampleSocial.indexOf("", index); + if (link.newTab && !exampleSocial.slice(index, tagEnd).includes('target="_blank"')) throw new Error(`Style guide social target changed: ${link.label}`); +} + + +const directoryMarkup = footer.split("", index); + if (!directoryMarkup.slice(index, tagEnd).includes(link.label)) throw new Error(`Directory label/URL mismatch: ${link.label}`); + if (link.newTab === true && !directoryMarkup.slice(index, tagEnd).includes('target="_blank"')) { + throw new Error(`Directory target behavior changed: ${link.label}`); + } + if (link.newTab === false && directoryMarkup.slice(index, tagEnd).includes('target="_blank"')) { + throw new Error(`Directory target behavior changed: ${link.label}`); + } + previousDirectoryIndex = index; + } +} + +for (const link of chrome.footer.social.links) { + if (!directoryMarkup.includes(`href="${portableHref(link.href)}"`)) throw new Error(`Social footer URL is missing: ${link.href}`); +} +if (!footer.includes("city-skyline-reference.png")) throw new Error("Footer image reference is missing."); + +const copyrightMarkup = footer.slice(footer.indexOf("", index); + if (!copyrightMarkup.slice(index, tagEnd).includes(link.label)) throw new Error(`Copyright label/URL mismatch: ${link.label}`); + previousCopyrightIndex = index; +} + +for (const selector of [".tcc-site-header", ".tcc-footer-directory", ".tcc-footer-copyright", ".tm-bottom", "#license"]) { + if (!css.includes(selector)) throw new Error(`Built CSS is missing selector: ${selector}`); +} + +for (const value of ["--tcc-container-max: 1200px", "border-top: 1px solid", "min-height: 80px", "font-size: 0.6875rem", "gap: 40px", "width: 36px", "padding-block: var(--tcc-space-7)"]) { + if (!css.includes(value)) throw new Error(`Built CSS is missing measured contract: ${value}`); +} + +for (const behavior of ["Escape", "inert", "aria-expanded", "tcc-menu-open"]) { + if (!sourceScript.includes(behavior)) throw new Error(`Header script is missing behavior: ${behavior}`); +} + +console.log("Theme validation passed."); diff --git a/src/css/base.css b/src/css/base.css new file mode 100644 index 0000000..483d0b1 --- /dev/null +++ b/src/css/base.css @@ -0,0 +1,55 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + color: var(--tcc-color-ink); + font-family: var(--tcc-font-body); + line-height: 1.7; + text-rendering: optimizeLegibility; +} + +body { + margin: 0; + background: var(--tcc-color-white); + color: var(--tcc-color-ink); +} + +img { + display: block; + max-width: 100%; + height: auto; +} + +a { + color: inherit; + text-decoration-thickness: 0.08em; + text-underline-offset: 0.18em; +} + +a:focus-visible, +button:focus-visible { + outline: 2px solid var(--tcc-color-ink); + outline-offset: 4px; + box-shadow: var(--tcc-focus-ring); +} + +button, +input, +select, +textarea { + font: inherit; +} + +.tcc-container { + width: min(calc(100% - 3rem), var(--tcc-container-max)); + margin-inline: auto; +} + +@media (max-width: 639px) { + .tcc-container { + width: min(calc(100% - 2rem), var(--tcc-container-max)); + } +} diff --git a/src/css/footer.css b/src/css/footer.css new file mode 100644 index 0000000..9b112f9 --- /dev/null +++ b/src/css/footer.css @@ -0,0 +1,168 @@ +.tcc-footer-directory, +.tm-bottom.uk-section-muted { + background: var(--tcc-color-directory); + color: var(--tcc-color-ink); +} + +.tcc-footer-directory, +.tm-bottom.uk-section-muted { + padding-block: var(--tcc-space-7); +} + +.tm-bottom > .uk-container, +.tm-page > footer .uk-container { + max-width: var(--tcc-container-max); +} + +.tcc-footer-directory__grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 40px; +} + +.tcc-footer-group h2, +.tm-bottom .uk-h5 { + margin: 0 0 20px; + color: var(--tcc-color-ink); + font-family: var(--tcc-font-display); + font-size: 1rem; + font-weight: 400; + letter-spacing: 2px; + line-height: 1.4; + text-transform: uppercase; +} + +.tcc-footer-group ul { + margin: 0; + padding: 0; + list-style: none; +} + +.tcc-footer-group li + li { + margin-top: 10px; +} + +.tcc-footer-group li a, +.tm-bottom .uk-h6, +.tm-bottom .uk-h6 a { + color: var(--tcc-color-ink); + font-family: var(--tcc-font-display); + font-size: 0.6875rem; + font-weight: 400; + letter-spacing: 2px; + line-height: 1.4; + text-decoration: none; + text-transform: uppercase; +} + +.tcc-footer-group li a:hover, +.tcc-footer-group li a:focus-visible, +.tm-bottom .uk-h6 a:hover { + text-decoration: underline; +} + +.tcc-footer-social__links { + display: flex; + flex-wrap: wrap; + gap: 20px; + margin-top: 20px; +} + +.tcc-footer-social__links a, +.tm-bottom .uk-icon-button { + display: inline-grid; + width: 36px; + height: 36px; + place-items: center; + border: 1px solid var(--tcc-color-border); + border-radius: var(--tcc-radius-round); + background: var(--tcc-color-white); + color: var(--tcc-color-social-icon); + font-family: var(--tcc-font-display); + font-size: 0.875rem; + font-weight: 600; + text-decoration: none; +} + +.tcc-footer-social img, +.tm-bottom .uk-border-rounded { + width: 270px; + height: 180px; + margin-top: 20px; + border-radius: var(--tcc-radius-small); + box-shadow: var(--tcc-shadow-medium); + object-fit: cover; +} + +.tcc-footer-copyright, +.tm-page > footer .uk-section-primary { + background: var(--tcc-color-charcoal); + color: var(--tcc-color-footer-text); +} + +.tcc-footer-copyright, +.tm-page > footer .uk-section-primary { + padding-block: var(--tcc-space-7); + font-family: var(--tcc-font-body); + font-size: 1rem; + font-weight: 400; + line-height: 1.6; +} + +.tcc-footer-copyright a, +.tm-page > footer #license a { + color: var(--tcc-color-white); + text-decoration: none; +} + +.tcc-footer-copyright a:hover, +.tcc-footer-copyright a:focus-visible, +.tm-page > footer #license a:hover { + color: var(--tcc-color-white); + text-decoration: underline; +} + +.tcc-footer-copyright a:focus-visible, +.tm-page > footer #license a:focus-visible { + outline-color: var(--tcc-color-white); +} + +@media (max-width: 959px) { + .tcc-footer-directory__grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .tcc-footer-copyright { + padding-block: var(--tcc-space-5); + font-size: 1rem; + } + + .tm-page > footer .uk-section-primary { + padding-block: var(--tcc-space-5); + } + + .tcc-footer-social img { + display: none; + } +} + +@media (max-width: 639px) { + .tcc-footer-directory, + .tm-bottom.uk-section-muted { + padding-block: var(--tcc-space-5); + } + + .tcc-footer-directory__grid { + grid-template-columns: 1fr; + gap: var(--tcc-space-4); + text-align: center; + } + + .tcc-footer-social__links { + justify-content: center; + } + + .tcc-footer-copyright { + text-align: center; + } +} diff --git a/src/css/header.css b/src/css/header.css new file mode 100644 index 0000000..a988d9a --- /dev/null +++ b/src/css/header.css @@ -0,0 +1,278 @@ +.tcc-site-header, +.tm-header .uk-navbar-container, +.tm-header-mobile .uk-navbar-container { + border-top: 1px solid var(--tcc-color-cyan); + background: var(--tcc-color-white); +} + +.tcc-site-header { + position: sticky; + z-index: 1000; + top: 0; +} + +.tcc-header__container { + display: flex; + min-height: 80px; + align-items: center; + justify-content: space-between; + gap: var(--tcc-space-2); +} + +.tcc-header__brand { + display: inline-flex; + flex: 0 0 auto; + align-items: center; +} + +.tcc-header__brand img, +.tm-header .uk-logo img, +.tm-header-mobile .uk-logo img { + width: 288px; + height: 77px; + object-fit: contain; +} + +.tm-header .uk-navbar-container > .uk-container { + max-width: var(--tcc-container-max); +} + +.tcc-header__menu, +.tcc-header__menu ul { + margin: 0; + padding: 0; + list-style: none; +} + +.tcc-header__menu { + display: flex; + align-items: center; + gap: 0; +} + +.tcc-header__menu > li { + position: relative; +} + +.tcc-header__menu > li > a, +.tcc-header__menu > li > button, +.tm-header .uk-navbar-nav > li > a { + display: inline-flex; + min-height: 44px; + align-items: center; + border: 0; + padding-inline: 15px; + background: transparent; + color: var(--tcc-color-nav-muted); + cursor: pointer; + font-family: var(--tcc-font-display); + font-size: 0.6875rem; + font-weight: 400; + letter-spacing: 2px; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; +} + +.tcc-header__menu > li:last-child > a { + padding-right: 0; +} + +.tcc-header__menu > li > a:hover, +.tcc-header__menu > li > button:hover, +.tcc-header__menu > li > a:focus-visible, +.tcc-header__menu > li > button:focus-visible, +.tcc-header__menu > .is-active > a, +.tm-header .uk-navbar-nav > .uk-active > a, +.tm-header .uk-navbar-nav > li > a:hover { + color: var(--tcc-color-ink); +} + +.tcc-header__menu ul { + position: absolute; + top: 100%; + left: 0; + display: none; + min-width: 220px; + padding: var(--tcc-space-2); + border: 1px solid var(--tcc-color-border); + background: var(--tcc-color-white); + box-shadow: var(--tcc-shadow-medium); +} + +.tcc-header__menu ul a { + display: block; + padding: 0.35rem 0.5rem; + color: var(--tcc-color-ink); + font-size: 0.875rem; + text-decoration: none; +} + +.tcc-header__services > a { + gap: 0.7rem; +} + +.tcc-header__services small { + display: block; + margin-top: 0.15rem; + font-family: var(--tcc-font-display); + font-size: 0.75rem; + letter-spacing: 0.0125rem; + text-transform: none; +} + +.tcc-header__services-icon { + width: 20px; + height: 20px; + flex: 0 0 20px; + letter-spacing: 0; +} + +.tcc-icon { + display: block; + width: 20px; + height: 20px; + fill: currentcolor; +} + +.tcc-icon--stroke { + fill: none; + stroke: currentcolor; +} + +.tcc-header__toggle { + display: none; + min-width: 44px; + min-height: 44px; + border: 0; + background: transparent; + color: var(--tcc-color-charcoal); + font-size: 1.5rem; +} + +.tcc-header__close, +.tcc-header__scrim { + display: none; +} + +@media (max-width: 959px) { + html.tcc-menu-open { + overflow: hidden; + } + + .tcc-header__container { + min-height: 80px; + justify-content: center; + } + + .tcc-header__brand img { + width: min(240px, 66vw); + height: auto; + } + + .tcc-header__toggle { + position: absolute; + left: 1rem; + display: inline-grid; + place-items: center; + } + + .tcc-header__navigation { + position: fixed; + z-index: 1001; + inset: 0 auto 0 0; + width: min(88vw, 360px); + padding: 6rem 2rem 2rem; + overflow-y: auto; + background: var(--tcc-color-white); + box-shadow: var(--tcc-shadow-medium); + visibility: hidden; + pointer-events: none; + transform: translateX(-105%); + transition: transform 180ms ease, visibility 0s linear 180ms; + } + + .tcc-site-header[data-menu-open="true"] .tcc-header__navigation { + visibility: visible; + pointer-events: auto; + transform: translateX(0); + transition-delay: 0s; + } + + .tcc-header__close { + position: absolute; + top: 1rem; + right: 1rem; + display: inline-grid; + width: 44px; + height: 44px; + place-items: center; + border: 0; + background: transparent; + color: var(--tcc-color-ink); + font-size: 1.75rem; + line-height: 1; + } + + .tcc-header__scrim { + position: fixed; + z-index: 1000; + inset: 0; + display: block; + border: 0; + padding: 0; + background: rgb(45 46 51 / 45%); + opacity: 0; + visibility: hidden; + pointer-events: none; + transition: opacity 180ms ease, visibility 0s linear 180ms; + } + + .tcc-site-header[data-menu-open="true"] .tcc-header__scrim { + opacity: 1; + visibility: visible; + pointer-events: auto; + transition-delay: 0s; + } + + .tcc-header__menu { + display: grid; + align-items: stretch; + gap: 0.4rem; + } + + .tcc-header__menu > li > a, + .tcc-header__menu > li > button { + width: 100%; + justify-content: flex-start; + padding-inline: 0; + white-space: normal; + } + + .tcc-header__menu ul { + position: static; + display: none; + border: 0; + padding: 0 0 0 var(--tcc-space-2); + box-shadow: none; + } + + .tcc-header__menu li[data-submenu-open="true"] > ul { + display: grid; + gap: 0.4rem; + } +} + +@media (min-width: 960px) { + .tm-header .uk-navbar, + .tm-header .uk-navbar-item, + .tm-header .uk-navbar-nav > li > a { + min-height: 80px; + } + + .tcc-header__menu > li.has-submenu:hover > ul, + .tcc-header__menu > li[data-submenu-open="true"] > ul { + display: grid; + gap: 0.4rem; + } +} diff --git a/src/css/theme.css b/src/css/theme.css new file mode 100644 index 0000000..dee18db --- /dev/null +++ b/src/css/theme.css @@ -0,0 +1,6 @@ +@import "./tokens.css"; +@import "./base.css"; +@import "./typography.css"; +@import "./header.css"; +@import "./footer.css"; +@import "./utilities.css"; diff --git a/src/css/tokens.css b/src/css/tokens.css new file mode 100644 index 0000000..419fc55 --- /dev/null +++ b/src/css/tokens.css @@ -0,0 +1,31 @@ +:root { + --tcc-color-white: #ffffff; + --tcc-color-cyan: #00cadb; + --tcc-color-directory: #f7f7f7; + --tcc-color-charcoal: #303033; + --tcc-color-ink: #2d2e33; + --tcc-color-nav-muted: #b4b5ba; + --tcc-color-footer-text: rgb(255 255 255 / 70%); + --tcc-color-social-icon: #6c6d74; + --tcc-color-border: #e5e5e7; + + --tcc-font-body: "Raleway", "Helvetica Neue", Arial, sans-serif; + --tcc-font-display: "Montserrat", Arial, sans-serif; + + --tcc-container-max: 1200px; + --tcc-space-1: 0.5rem; + --tcc-space-2: 1rem; + --tcc-space-3: 1.5rem; + --tcc-space-4: 2rem; + --tcc-space-5: 3rem; + --tcc-space-6: 4rem; + --tcc-space-7: 4.375rem; + + --tcc-radius-small: 5px; + --tcc-radius-round: 999px; + --tcc-shadow-medium: 0 5px 15px rgb(0 0 0 / 8%); + --tcc-focus-ring: 0 0 0 4px rgb(0 202 219 / 35%); + + --tcc-breakpoint-small: 640px; + --tcc-breakpoint-medium: 960px; +} diff --git a/src/css/typography.css b/src/css/typography.css new file mode 100644 index 0000000..c0db795 --- /dev/null +++ b/src/css/typography.css @@ -0,0 +1,23 @@ +h1, +h2, +h3, +h4, +h5, +h6 { + margin-block: 0 0.75em; + color: var(--tcc-color-ink); + font-family: var(--tcc-font-display); + font-weight: 400; + line-height: 1.25; +} + +p { + margin-block: 0 1.25em; +} + +.tcc-eyebrow { + font-family: var(--tcc-font-display); + font-size: 0.8125rem; + letter-spacing: 0.22em; + text-transform: uppercase; +} diff --git a/src/css/utilities.css b/src/css/utilities.css new file mode 100644 index 0000000..343acca --- /dev/null +++ b/src/css/utilities.css @@ -0,0 +1,40 @@ +.tcc-visually-hidden { + position: absolute !important; + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} + +@media print { + .tcc-site-header { + position: static; + } + + .tcc-header__toggle, + .tcc-footer-social { + display: none !important; + } + + .tcc-footer-directory, + .tcc-footer-copyright { + padding-block: 1.5rem; + background: #fff !important; + color: #000 !important; + } +} diff --git a/src/data/main-site-navigation.json b/src/data/main-site-navigation.json new file mode 100644 index 0000000..2aa85e0 --- /dev/null +++ b/src/data/main-site-navigation.json @@ -0,0 +1,120 @@ +{ + "scope": "Canonical navigation content extracted from the supplied main-site desktop header HTML", + "source": "docs/source/header-desktop-source.html", + "notes": [ + "This file records main-site content; satellite sites replace these entries with links belonging to the site being built.", + "The Articles and Daily dropdowns also contained date-sensitive cards and featured content. Those dynamic records are not fixed theme content and are intentionally described rather than frozen." + ], + "items": [ + { "label": "Home", "href": "/" }, + { + "label": "The Holy Scriptures", + "children": [ + { "label": "Text", "href": "/scriptures/kjv/" } + ] + }, + { + "label": "Sermons", + "children": [ + { "label": "Videos", "href": "/videos" }, + { "label": "The Godly Home", "href": "/godlyhome" } + ] + }, + { + "label": "Articles", + "href": "/remnant-articles", + "presentation": "three-column-mega-menu", + "groups": [ + { + "label": "Remnant Topics", + "links": [ + { "label": "Blessing corner", "href": "/blessing-corner" }, + { "label": "Book reviews", "href": "/book-reviews" }, + { "label": "Christian Living", "href": "/christian-living" }, + { "label": "Church Matters", "href": "/church-matters" }, + { "label": "Deception", "href": "/deception" }, + { "label": "Divorce and Remarriage", "href": "/divorce-and-remarriage" }, + { "label": "Head covering", "href": "/head-covering" }, + { "label": "Home histories", "href": "/home-histories" }, + { "label": "Judgment", "href": "/judgment" }, + { "label": "Martyrdom", "href": "/martyrdom" }, + { "label": "Missions", "href": "/missions" }, + { "label": "Modesty Articles", "href": "/modesty-articles" }, + { "label": "Non-resistance", "href": "/non-resistance" }, + { "label": "Parenting", "href": "/parenting" }, + { "label": "Poetry", "href": "/poetry" }, + { "label": "Revival", "href": "/revival-article" }, + { "label": "Sharpened words", "href": "/sharpened-words" }, + { "label": "Spiritual exercise", "href": "/spiritual-exercise" }, + { "label": "Woman", "href": "/woman" } + ], + "dynamicContent": "Featured article cards and slider controls are content-managed, not fixed theme links." + }, + { + "label": "Trousseau Chest", + "href": "/trousseauchest", + "sourceSubtitle": "Spritual preparation should exceed the physical preparation that women make for life and marriage", + "sections": [ + { + "label": "The Adornment", + "href": "/trousseauchest/the-adornments", + "links": [ + { "label": "Jewels", "href": "/trousseauchest/the-adornments/jewels" }, + { "label": "Apparel", "href": "/trousseauchest/the-adornments/apparel" }, + { "label": "Chastity", "href": "/trousseauchest/the-adornments/chastity" }, + { "label": "Submission", "href": "/trousseauchest/the-adornments/submission" } + ] + }, + { + "label": "The Dowry", + "href": "/trousseauchest/the-dowry", + "links": [ + { "label": "To be a Helpmeet", "href": "/trousseauchest/the-dowry/to-be-a-helpmeet" }, + { "label": "To be a Mother", "href": "/trousseauchest/the-dowry/to-be-a-mother" }, + { "label": "Vibrant Christianity", "href": "/trousseauchest/the-dowry/vibrant-christianity" }, + { "label": "Fidelity", "href": "/trousseauchest/the-dowry/fidelity" } + ] + }, + { + "label": "Homemaking", + "href": "/trousseauchest/practical-homemaking", + "links": [ + { "label": "Homeschool", "href": "/trousseauchest/practical-homemaking/homeschool" }, + { "label": "Hospitality", "href": "/trousseauchest/practical-homemaking/hospitality" }, + { "label": "House Keeping", "href": "/trousseauchest/practical-homemaking/house-keeping" } + ] + }, + { + "label": "The Legacy", + "href": "/trousseauchest/the-legacy", + "links": [ + { "label": "Devout Women", "href": "/trousseauchest/the-legacy/devout-women" }, + { "label": "Testimonies", "href": "/trousseauchest/the-legacy/testimonies" }, + { "label": "Raising Missionaries", "href": "/trousseauchest/the-legacy/raising-missionaries" }, + { "label": "Prayer", "href": "/trousseauchest/the-legacy/prayer" } + ] + }, + { "label": "Recipe Repertoire", "href": "/trousseauchest/recipe-repertoire" } + ] + }, + { + "label": "Reading and downloads", + "links": [ + { "label": "Transcribed Sermons", "href": "/transcribed-sermons" }, + { "label": "Download Remnant PDFs", "href": "/remnant-articles/remnant-pdf" }, + { "label": "See All Remnant PDFs", "href": "/remnant-articles/remnant-pdf" } + ], + "dynamicContent": "PDF issue cards are content-managed and change independently of the theme." + } + ] + }, + { + "label": "Daily", + "children": [ + { "label": "Daily Light", "href": "/daily-light" } + ], + "dynamicContent": "The source mega-menu also renders the current date, image, description, and today's devotion call-to-action." + }, + { "label": "Services", "subtitle": "Join us", "href": "/services", "icon": "world" } + ] +} diff --git a/src/data/site-chrome.json b/src/data/site-chrome.json new file mode 100644 index 0000000..8fcb4d7 --- /dev/null +++ b/src/data/site-chrome.json @@ -0,0 +1,117 @@ +{ + "brand": { + "name": "A True Christian Church", + "canonicalOrigin": "https://truechristian.church", + "home": "https://truechristian.church/", + "logo": "assets/brand/logo.jpg", + "logoWidth": 288, + "logoHeight": 77, + "logoAlt": "A True Christian Church" + }, + "header": { + "home": { "label": "Home", "href": "/" }, + "items": [ + { + "label": "The Holy Scriptures", + "children": [ + { "label": "Text", "href": "/scriptures/kjv/" } + ] + }, + { + "label": "Sermons", + "children": [ + { "label": "Videos", "href": "/videos" }, + { "label": "The Godly Home", "href": "/godlyhome" } + ] + }, + { + "label": "Articles", + "href": "/remnant-articles", + "presentation": "mega-menu", + "sourceReference": "docs/source/header-desktop-source.html", + "navigationData": "src/data/main-site-navigation.json" + }, + { + "label": "Daily", + "children": [ + { "label": "Daily Light", "href": "/daily-light" } + ] + }, + { "label": "Services", "subtitle": "Join us", "href": "/services", "icon": "world" } + ], + "siteSpecificLinks": { + "position": "after-home", + "rule": "Render every navigation link required by the site being built using the canonical visual and accessibility pattern." + } + }, + "footer": { + "linkPolicy": { + "sourceForm": "Root-relative paths are preserved in this data exactly as supplied.", + "portableForm": "Resolve root-relative footer paths against brand.canonicalOrigin when the consuming site uses another origin." + }, + "directoryGroups": [ + { + "label": "Church Info", + "links": [ + { "label": "History", "href": "/history" }, + { "label": "Brotherhood", "href": "/brotherhood" }, + { "label": "Blog", "href": "/blog/" }, + { "label": "Testimonies", "href": "/testimonies/" }, + { "label": "Confession of Faith", "href": "/confession-of-faith" }, + { "label": "Storehouse", "href": "/storehouse" }, + { "label": "Privacy Policy", "href": "/privacy-policy" }, + { "label": "Copyright", "href": "/copyright" } + ] + }, + { + "label": "Projects", + "links": [ + { "label": "GETBIBLE", "href": "https://getBible.life/", "external": true, "newTab": true }, + { "label": "Loudvoice", "href": "https://www.loudvoice.org/", "external": true, "newTab": true }, + { "label": "SHE Cares", "href": "https://shecares.life/", "external": true, "newTab": false }, + { "label": "Trousseau Chest", "href": "/trousseauchest" }, + { "label": "Why Bible", "href": "/whybible/" }, + { "label": "we fear God", "href": "/wefeargod/" }, + { "label": "Amana", "href": "/amana" }, + { "label": "Mission (Namibia)", "href": "/mission" } + ] + }, + { + "label": "Welcome", + "links": [ + { "label": "Follow Jesus", "href": "/comeandfollowjesus" }, + { "label": "Live Services", "href": "/services" }, + { "label": "Locations", "href": "/fellowships" }, + { "label": "Events", "href": "/events" }, + { "label": "Revival", "href": "/revival" }, + { "label": "Telegram", "href": "/telegram" }, + { "label": "Contact Us", "href": "/contact-us" } + ] + } + ], + "social": { + "label": "Social Outreach", + "links": [ + { "label": "X", "href": "https://twitter.com/llewellynvdm", "icon": "twitter", "external": true, "newTab": true }, + { "label": "Facebook", "href": "https://www.facebook.com/aTrueChristianChurch", "icon": "facebook", "external": true, "newTab": true }, + { "label": "GitHub", "href": "https://github.com/trueChristian", "icon": "github", "external": true, "newTab": true }, + { "label": "Telegram", "href": "/telegram", "icon": "telegram", "external": false, "newTab": true } + ], + "desktopImage": "/images/city-skyline-skyscrapers-top.jpg", + "repositoryReferenceImage": "assets/footer/city-skyline-reference.png" + }, + "copyright": { + "prefix": "Copyright © trueChristian.Church", + "links": [ + { "label": "free distribution only", "href": "/copyright" }, + { "label": "Privacy Policy", "href": "/privacy-policy" }, + { "label": "Mission", "href": "/mission/" }, + { "label": "Revival", "href": "/revival/" }, + { "label": "We Fear God", "href": "/wefeargod/" }, + { "label": "Why we believe the Bible", "href": "/whybible/" }, + { "label": "Trousseau Chest", "href": "/trousseauchest/" } + ], + "separator": " | " + } + } +} diff --git a/src/html/document-head.html b/src/html/document-head.html new file mode 100644 index 0000000..1a256d6 --- /dev/null +++ b/src/html/document-head.html @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/src/html/site-footer.html b/src/html/site-footer.html new file mode 100644 index 0000000..6990b03 --- /dev/null +++ b/src/html/site-footer.html @@ -0,0 +1,77 @@ + + + + diff --git a/src/html/site-header.html b/src/html/site-header.html new file mode 100644 index 0000000..ceb906a --- /dev/null +++ b/src/html/site-header.html @@ -0,0 +1,51 @@ + +
+
+ + + + + + +
+
diff --git a/src/js/site-header.js b/src/js/site-header.js new file mode 100644 index 0000000..acc5fe8 --- /dev/null +++ b/src/js/site-header.js @@ -0,0 +1,108 @@ +(function initializeTrueChristianHeaders() { + const mobileQuery = window.matchMedia("(max-width: 959px)"); + const focusableSelector = [ + "a[href]", + "button:not([disabled])", + "input:not([disabled])", + "select:not([disabled])", + "textarea:not([disabled])", + "[tabindex]:not([tabindex='-1'])" + ].join(","); + + document.querySelectorAll("[data-tcc-global-header]").forEach((header) => { + const toggle = header.querySelector(".tcc-header__toggle"); + const navigation = header.querySelector(".tcc-header__navigation"); + const closeButton = header.querySelector(".tcc-header__close"); + const scrim = header.querySelector(".tcc-header__scrim"); + const submenuButtons = header.querySelectorAll(".has-submenu > button"); + let returnFocusTo = null; + + if (!toggle || !navigation || !closeButton || !scrim) return; + + const collapseSubmenus = (except) => { + submenuButtons.forEach((button) => { + if (button === except) return; + button.setAttribute("aria-expanded", "false"); + button.closest(".has-submenu")?.removeAttribute("data-submenu-open"); + }); + }; + + const setMenuOpen = (open, restoreFocus = true) => { + const nextOpen = Boolean(open && mobileQuery.matches); + header.dataset.menuOpen = String(nextOpen); + toggle.setAttribute("aria-expanded", String(nextOpen)); + navigation.toggleAttribute("inert", mobileQuery.matches && !nextOpen); + if (mobileQuery.matches) navigation.setAttribute("aria-hidden", String(!nextOpen)); + else navigation.removeAttribute("aria-hidden"); + document.documentElement.classList.toggle("tcc-menu-open", nextOpen); + + if (nextOpen) { + returnFocusTo = document.activeElement; + closeButton.focus(); + } else { + collapseSubmenus(); + if (restoreFocus && returnFocusTo instanceof HTMLElement) returnFocusTo.focus(); + returnFocusTo = null; + } + }; + + toggle.addEventListener("click", () => setMenuOpen(header.dataset.menuOpen !== "true")); + closeButton.addEventListener("click", () => setMenuOpen(false)); + scrim.addEventListener("click", () => setMenuOpen(false)); + + submenuButtons.forEach((button) => { + button.addEventListener("click", () => { + const open = button.getAttribute("aria-expanded") !== "true"; + collapseSubmenus(button); + button.setAttribute("aria-expanded", String(open)); + button.closest(".has-submenu")?.toggleAttribute("data-submenu-open", open); + }); + }); + + navigation.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + event.preventDefault(); + if (mobileQuery.matches && header.dataset.menuOpen === "true") { + setMenuOpen(false); + } else { + const owningItem = document.activeElement?.closest?.(".has-submenu"); + const owningTrigger = owningItem?.querySelector(":scope > button"); + const expandedTrigger = [...submenuButtons].find( + (button) => button.getAttribute("aria-expanded") === "true" + ); + collapseSubmenus(); + (owningTrigger || expandedTrigger)?.focus(); + } + return; + } + if (event.key !== "Tab" || header.dataset.menuOpen !== "true") return; + + const focusable = [...navigation.querySelectorAll(focusableSelector)].filter( + (element) => !element.closest("[inert]") && element.getClientRects().length > 0 + ); + if (!focusable.length) return; + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (event.shiftKey && document.activeElement === first) { + event.preventDefault(); + last.focus(); + } else if (!event.shiftKey && document.activeElement === last) { + event.preventDefault(); + first.focus(); + } + }); + + document.addEventListener("keydown", (event) => { + if (event.key === "Escape" && header.dataset.menuOpen === "true") setMenuOpen(false); + }); + + document.addEventListener("click", (event) => { + if (!header.contains(event.target)) collapseSubmenus(); + }); + + const syncBreakpoint = () => setMenuOpen(false, false); + if (mobileQuery.addEventListener) mobileQuery.addEventListener("change", syncBreakpoint); + else mobileQuery.addListener?.(syncBreakpoint); + syncBreakpoint(); + }); +})();