Initial commit: Bocken theme for webtrees

Nord-themed dark/light mode family tree theme with:
- Floating glass-morphism header bar
- Auto/light/dark theme toggle with Lucide icons
- Smart SVG logo with theme-aware fill colors
- Active page highlighting with per-menu Nord icon colors
- Language button showing 2-letter abbreviation
- Start page search form
- Mobile responsive icon-only nav
- Custom views inherited from ArgonLight
This commit is contained in:
2026-03-14 09:53:43 +01:00
commit ce11b25da4
54 changed files with 5426 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules/

351
BockenTheme.php Normal file
View File

@@ -0,0 +1,351 @@
<?php
declare(strict_types=1);
namespace Bocken\Themes;
use Fisharebest\Webtrees\Module\AbstractModule;
use Fisharebest\Webtrees\Module\ModuleCustomInterface;
use Fisharebest\Webtrees\Module\ModuleCustomTrait;
use Fisharebest\Webtrees\Module\ModuleGlobalInterface;
use Fisharebest\Webtrees\Module\ModuleGlobalTrait;
use Fisharebest\Webtrees\Module\ModuleThemeInterface;
use Fisharebest\Webtrees\Module\ModuleThemeTrait;
use Fisharebest\Webtrees\View;
class BockenTheme extends AbstractModule implements ModuleCustomInterface, ModuleGlobalInterface, ModuleThemeInterface
{
use ModuleCustomTrait, ModuleGlobalTrait, ModuleThemeTrait;
public const string MODULE_TITLE = "Bocken";
public const string MODULE_DESCRIPTION = "Nord-themed dark/light mode family tree theme";
public const string MODULE_AUTHOR = 'Alexander Bocken';
public const string MODULE_VERSION = '1.0.0';
public const string MODULE_RESOURCE_PATH = __DIR__ . '/resources/';
public function title(): string
{
return self::MODULE_TITLE;
}
public function description(): string
{
return self::MODULE_DESCRIPTION;
}
public function customModuleAuthorName(): string
{
return self::MODULE_AUTHOR;
}
public function customModuleVersion(): string
{
return self::MODULE_VERSION;
}
public function resourcesFolder(): string
{
return self::MODULE_RESOURCE_PATH;
}
public function stylesheets(): array
{
// imports.css must be built from within the webtrees installation
// (it references vendor/fisharebest/webtrees/resources/css/_base.css)
$sheets = [];
$importsPath = self::MODULE_RESOURCE_PATH . 'css/imports.css';
if (file_exists($importsPath)) {
$sheets[] = $this->assetUrl('css/imports.css');
}
$sheets[] = $this->assetUrl('css/fonts.css');
$sheets[] = $this->assetUrl('css/theme.css');
return $sheets;
}
public function headContent(): string
{
$faviconUrl = $this->assetUrl('img/favicon.svg');
return <<<HTML
<link rel="icon" href="{$faviconUrl}" type="image/svg+xml">
<script>
(function() {
var saved = localStorage.getItem('bocken-theme');
if (saved === 'dark' || saved === 'light') {
document.documentElement.setAttribute('data-theme', saved);
}
})();
</script>
HTML;
}
public function bodyContent(): string
{
$logoUrl = $this->assetUrl('img/logo.svg');
return <<<HTML
<script src="https://unpkg.com/lucide@latest"></script>
<script>
(function() {
'use strict';
// --- Theme toggle ---
var THEMES = ['auto', 'light', 'dark'];
var ICONS = {
auto: 'sun-moon',
light: 'sun',
dark: 'moon'
};
function getTheme() {
return localStorage.getItem('bocken-theme') || 'auto';
}
function applyTheme(theme) {
localStorage.setItem('bocken-theme', theme);
if (theme === 'auto') {
document.documentElement.removeAttribute('data-theme');
} else {
document.documentElement.setAttribute('data-theme', theme);
}
updateToggleButton();
updateLogoFill();
}
function cycleTheme() {
var current = getTheme();
var idx = THEMES.indexOf(current);
var next = THEMES[(idx + 1) % THEMES.length];
applyTheme(next);
}
function updateToggleButton() {
var btn = document.getElementById('bocken-theme-toggle');
if (!btn) return;
var theme = getTheme();
btn.setAttribute('title', 'Theme: ' + theme);
btn.setAttribute('aria-label', 'Toggle theme (' + theme + ')');
btn.innerHTML = '<i data-lucide="' + ICONS[theme] + '"></i>';
if (typeof lucide !== 'undefined') {
lucide.createIcons({ nodes: [btn] });
}
}
// --- Logo injection ---
function updateLogoFill() {
var svg = document.querySelector('.bocken-logo svg');
if (!svg) return;
var theme = getTheme();
var isDark;
if (theme === 'dark') {
isDark = true;
} else if (theme === 'light') {
isDark = false;
} else {
// auto — let the SVG's own prefers-color-scheme handle it
svg.style.removeProperty('--fill');
return;
}
svg.style.setProperty('--fill', isDark ? '#D8DEE9' : '#2E3440');
}
function injectLogo() {
var titleEl = document.querySelector('.wt-site-title');
if (!titleEl) return;
var logoContainer = document.createElement('div');
logoContainer.className = 'bocken-logo-container';
logoContainer.innerHTML = '<a href="https://bocken.org" class="bocken-logo-link" aria-label="Bocken.org"></a>';
// Fetch and inline the SVG so parent CSS variables work
fetch('{$logoUrl}')
.then(function(r) { return r.text(); })
.then(function(svgText) {
var wrapper = document.createElement('div');
wrapper.className = 'bocken-logo';
wrapper.innerHTML = svgText;
logoContainer.querySelector('a').appendChild(wrapper);
updateLogoFill();
});
titleEl.parentNode.insertBefore(logoContainer, titleEl);
}
// --- Theme toggle button injection ---
function injectToggle() {
var nav = document.querySelector('.wt-secondary-navigation .nav');
if (!nav) return;
var li = document.createElement('li');
li.className = 'nav-item bocken-theme-toggle-item';
var btn = document.createElement('button');
btn.id = 'bocken-theme-toggle';
btn.className = 'bocken-theme-btn';
btn.type = 'button';
btn.addEventListener('click', cycleTheme);
li.appendChild(btn);
nav.insertBefore(li, nav.firstChild);
updateToggleButton();
}
// --- Language abbreviation ---
function shortenLanguage() {
var langItem = document.querySelector('.menu-language > .nav-link');
if (!langItem) return;
// Find the active language from dropdown items
var activeLang = document.querySelector('.menu-language .dropdown-item.active');
var code = 'EN';
if (activeLang) {
// Extract from class like "menu-language-de" or "menu-language-en-US"
var cls = Array.from(activeLang.classList).find(function(c) {
return c.startsWith('menu-language-');
});
if (cls) {
code = cls.replace('menu-language-', '').split('-')[0].toUpperCase();
}
}
// Replace link content with just the abbreviation
var caret = langItem.querySelector('.caret');
langItem.textContent = '';
langItem.appendChild(document.createTextNode(code));
if (caret) langItem.appendChild(caret);
}
// --- Search on start page ---
function injectPageSearch() {
// Only on the tree page (start page)
if (!document.body.classList.contains('wt-route-TreePage')) return;
var mainContent = document.querySelector('.wt-main-container .flash-messages');
if (!mainContent) return;
var csrf = document.querySelector('meta[name="csrf"]');
var csrfValue = csrf ? csrf.getAttribute('content') : '';
// Derive the search action URL from the current tree path
var path = window.location.pathname;
var searchAction = path.replace(/\/?$/, '/search-quick');
var searchDiv = document.createElement('div');
searchDiv.className = 'bocken-page-search';
searchDiv.innerHTML =
'<form method="post" action="' + searchAction + '" class="bocken-search-form" role="search">' +
'<input type="search" class="bocken-search-input" name="query" placeholder="Search names, places, sources..." autocomplete="off">' +
'<button type="submit" class="bocken-search-btn" aria-label="Search">' +
'<i data-lucide="search"></i>' +
'</button>' +
'<input type="hidden" name="_csrf" value="' + csrfValue + '">' +
'</form>';
mainContent.parentNode.insertBefore(searchDiv, mainContent.nextSibling);
}
// --- Separator between primary nav and secondary nav ---
function injectSeparator() {
var headerContent = document.querySelector('.wt-header-content');
var secondaryNav = document.querySelector('.wt-secondary-navigation');
if (!headerContent || !secondaryNav) return;
// Walk up to find the direct child of headerContent that contains secondaryNav
var target = secondaryNav;
while (target.parentNode && target.parentNode !== headerContent) {
target = target.parentNode;
}
// If we couldn't walk up to a direct child, bail
if (target.parentNode !== headerContent) return;
var spacer = document.createElement('div');
spacer.className = 'bocken-nav-spacer';
headerContent.insertBefore(spacer, target);
}
// --- Highlight active nav item ---
function highlightActiveNav() {
// Map body route classes to nav menu classes
var routeMap = {
'TreePage': 'menu-tree',
'Chart': 'menu-chart',
'Individual': 'menu-tree',
'Family': 'menu-tree',
'Branches': 'menu-list',
'FamilyList': 'menu-list',
'IndividualList': 'menu-list',
'MediaList': 'menu-list',
'NoteList': 'menu-list',
'RepositoryList': 'menu-list',
'SourceList': 'menu-list',
'PlaceList': 'menu-list',
'Calendar': 'menu-calendar',
'Report': 'menu-report',
'Search': 'menu-search',
'Story': 'menu-story',
'Faq': 'menu-faq',
'Clippings': 'menu-clippings'
};
var bodyClasses = document.body.className;
var activeMenu = null;
// Find matching route
var keys = Object.keys(routeMap);
for (var i = 0; i < keys.length; i++) {
if (bodyClasses.indexOf('wt-route-' + keys[i]) !== -1) {
activeMenu = routeMap[keys[i]];
break;
}
}
if (!activeMenu) return;
var navItem = document.querySelector('.wt-primary-navigation .' + activeMenu);
if (navItem) {
navItem.classList.add('bocken-nav-active');
}
}
// --- Init ---
function init() {
injectLogo();
injectSeparator();
injectToggle();
shortenLanguage();
injectPageSearch();
highlightActiveNav();
// Init all Lucide icons
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>
HTML;
}
public function boot(): void
{
View::registerNamespace($this->name(), $this->resourcesFolder() . 'views/');
// Views inherited from ArgonLight
View::registerCustomView('::modules/block-template', $this->name() . '::modules/block-template');
View::registerCustomView('::modules/recent_changes/changes-list', $this->name() . '::modules/recent_changes/changes-list');
View::registerCustomView('::modules/lightbox/tab', $this->name() . '::modules/lightbox/tab');
View::registerCustomView('::modules/descendancy/sidebar', $this->name() . '::modules/descendancy/sidebar');
View::registerCustomView('::modules/lifespans-chart/chart', $this->name() . '::modules/lifespans-chart/chart');
View::registerCustomView('::modules/faq/show', $this->name() . '::modules/faq/show');
View::registerCustomView('::modules/place-hierarchy/list', $this->name() . '::modules/place-hierarchy/list');
View::registerCustomView('::lists/individuals-table', $this->name() . '::lists/individuals-table');
View::registerCustomView('::lists/families-table', $this->name() . '::lists/families-table');
View::registerCustomView('::individual-page-menu', $this->name() . '::individual-page-menu');
}
}

6
module.php Normal file
View File

@@ -0,0 +1,6 @@
<?php
namespace Bocken\Themes;
include "BockenTheme.php";
return new BockenTheme();

1905
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

19
package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "bocken-theme",
"description": "Nord-themed dark/light mode theme for webtrees",
"author": "Alexander Bocken",
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"autoprefixer": "^10.4.24",
"bootstrap": "^5.3.0",
"postcss": "^8.5.6",
"postcss-url": "^10.1.3",
"sass": "^1.97.3",
"vite": "^7.3.1"
},
"scripts": {
"dev": "vite build --watch --mode development",
"build": "vite build --mode production"
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
'autoprefixer': {},
'postcss-url': { url: 'inline' }
}
}

1
resources/css/fonts.css Normal file

File diff suppressed because one or more lines are too long

1
resources/css/theme.css Normal file

File diff suppressed because one or more lines are too long

221
resources/img/favicon.svg Normal file
View File

@@ -0,0 +1,221 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="44.976448mm"
height="47.592033mm"
viewBox="0 0 44.976448 47.592033"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1167">
<g
id="g1171"
transform="translate(-839.26546,-1493.2693)">
<path
d="M -77.063638,2283.1277 H 1922.9364 V 283.12772 H -77.063638 Z"
id="path1169" />
</g>
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1161">
<g
id="g1165"
transform="translate(-870.22246,-1471.759)">
<path
d="M -77.063638,2283.1277 H 1922.9364 V 283.12772 H -77.063638 Z"
id="path1163" />
</g>
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1155">
<g
id="g1159"
transform="translate(-879.11756,-1418.6585)">
<path
d="M -77.063638,2283.1277 H 1922.9364 V 283.12772 H -77.063638 Z"
id="path1157" />
</g>
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1125">
<g
id="g1129"
transform="translate(-985.61606,-1491.5374)">
<path
d="M -77.063638,2283.1277 H 1922.9364 V 283.12772 H -77.063638 Z"
id="path1127" />
</g>
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1119">
<g
id="g1123"
transform="translate(-984.96026,-1455.9641)">
<path
d="M -77.063638,2283.1277 H 1922.9364 V 283.12772 H -77.063638 Z"
id="path1121" />
</g>
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1113">
<g
id="g1117"
transform="translate(-945.76396,-1416.9275)">
<path
d="M -77.063638,2283.1277 H 1922.9364 V 283.12772 H -77.063638 Z"
id="path1115" />
</g>
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1107">
<g
id="g1111"
transform="translate(-898.03256,-1543.4085)">
<path
d="M -77.063638,2283.1277 H 1922.9364 V 283.12772 H -77.063638 Z"
id="path1109" />
</g>
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1101">
<g
id="g1105"
transform="translate(-916.79086,-1510.9075)">
<path
d="M -77.063638,2283.1277 H 1922.9364 V 283.12772 H -77.063638 Z"
id="path1103" />
</g>
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1083">
<g
id="g1087"
transform="translate(-941.26306,-1493.7234)">
<path
d="M -77.063638,2283.1277 H 1922.9364 V 283.12772 H -77.063638 Z"
id="path1085" />
</g>
</clipPath>
</defs>
<g
id="layer1"
transform="translate(-96.294406,-113.9353)">
<g
id="g70"
transform="matrix(0.35277777,0,0,-0.35277777,100.41097,127.47916)"
clip-path="url(#clipPath1167)"
style="fill:#2e3440;fill-opacity:1">
<path
d="M 0,0 C 6.633,-3.91 14.348,-4.302 20.992,-1.732 20.009,5.333 15.93,11.893 9.31,15.795 2.69,19.697 -5.025,20.088 -11.669,17.519 -10.7,10.462 -6.62,3.901 0,0"
style="fill:#2e3440;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path72" />
</g>
<g
id="g74"
transform="matrix(0.35277777,0,0,-0.35277777,106.63384,133.21363)"
clip-path="url(#clipPath1161)"
style="fill:#2e3440;fill-opacity:1">
<path
d="m 0,0 c -6.62,3.901 -14.335,4.293 -20.979,1.724 0.97,-7.058 5.049,-13.618 11.669,-17.519 6.633,-3.91 14.348,-4.301 20.992,-1.732 C 10.699,-10.462 6.62,-3.902 0,0"
style="fill:#2e3440;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path76" />
</g>
<g
id="g78"
transform="matrix(0.35277777,0,0,-0.35277777,107.19786,150.50755)"
clip-path="url(#clipPath1155)"
style="fill:#2e3440;fill-opacity:1">
<path
d="M 0,0 C 6.633,-3.909 14.348,-4.301 20.992,-1.731 20.009,5.333 15.93,11.894 9.31,15.795 2.69,19.697 -5.026,20.088 -11.669,17.52 -10.7,10.461 -6.62,3.902 0,0"
style="fill:#2e3440;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path80" />
</g>
<g
id="g98"
transform="matrix(0.35277777,0,0,-0.35277777,129.16573,138.05504)"
clip-path="url(#clipPath1125)"
style="fill:#2e3440;fill-opacity:1">
<path
d="M 0,0 C 6.644,-2.57 14.358,-2.178 20.992,1.732 27.612,5.633 31.691,12.194 32.661,19.25 26.017,21.82 18.302,21.429 11.682,17.527 5.062,13.625 0.982,7.065 0,0"
style="fill:#2e3440;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path100" />
</g>
<g
id="g102"
transform="matrix(0.35277777,0,0,-0.35277777,132.036,149.80546)"
clip-path="url(#clipPath1119)"
style="fill:#2e3440;fill-opacity:1">
<path
d="M 0,0 C 6.62,3.901 10.699,10.461 11.669,17.519 5.025,20.088 -2.689,19.696 -9.31,15.795 -15.93,11.893 -20.009,5.333 -20.992,-1.732 -14.348,-4.301 -6.633,-3.91 0,0"
style="fill:#2e3440;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path104" />
</g>
<g
id="g106"
transform="matrix(0.35277777,0,0,-0.35277777,139.41553,131.41004)"
clip-path="url(#clipPath1113)"
style="fill:#2e3440;fill-opacity:1">
<path
d="m -27.40181,13.441787 c 6.644,-2.57 14.359,-2.178 20.9920004,1.731 6.62000002,3.902 10.699,10.461 11.669,17.519 -6.644,2.569 -14.359,2.178 -20.9790004,-1.724 -6.62,-3.901 -10.7,-10.462 -11.682,-17.526"
style="fill:#2e3440;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path108" />
</g>
<g
id="g110"
transform="matrix(0.35277777,0,0,-0.35277777,116.37715,121.06317)"
clip-path="url(#clipPath1107)"
style="fill:#2e3440;fill-opacity:1">
<path
d="m 0,0 c 1.271,7.579 -1.125,14.922 -5.904,20.205 -6.242,-3.433 -10.906,-9.591 -12.178,-17.169 -1.275,-7.594 1.123,-14.937 5.902,-20.22 C -5.936,-13.736 -1.273,-7.578 0,0"
style="fill:#2e3440;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path112" />
</g>
<g
id="g114"
transform="matrix(0.35277777,0,0,-0.35277777,123.25775,134.69217)"
clip-path="url(#clipPath1101)"
style="fill:#2e3440;fill-opacity:1">
<path
d="m 0,0 c 1.271,7.579 -1.125,14.922 -5.904,20.206 -6.242,-3.434 -10.906,-9.592 -12.178,-17.17 -1.275,-7.593 1.123,-14.937 5.902,-20.22 C -5.937,-13.736 -1.273,-7.578 0,0"
style="fill:#2e3440;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path116" />
</g>
<g
id="g126"
transform="matrix(0.35277777,0,0,-0.35277777,125.36425,127.9867)"
clip-path="url(#clipPath1083)"
style="fill:#2e3440;fill-opacity:1">
<path
d="M 0,0 C 4.779,5.283 7.176,12.627 5.901,20.22 4.629,27.798 -0.035,33.956 -6.277,37.39 -11.055,32.106 -13.453,24.763 -12.18,17.184 -10.908,9.606 -6.244,3.448 0,0"
style="fill:#2e3440;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path128" />
</g>
<g
id="g10"
transform="translate(54.26113,76.790102)"
style="fill:#2e3440;fill-opacity:1;stroke:#2e3440;stroke-opacity:1">
<path
style="fill:#2e3440;fill-opacity:1;stroke:#2e3440;stroke-width:3;stroke-linecap:butt;stroke-dasharray:none;stroke-opacity:1"
d="m 65.113709,84.638921 c -0.346049,-9.794303 8.85917,-32.693347 8.85917,-32.693347"
id="path9" />
<path
style="fill:#2e3440;fill-opacity:1;stroke:#2e3440;stroke-width:3;stroke-linecap:butt;stroke-dasharray:none;stroke-opacity:1"
d="m 65.108044,84.684262 c 0.346049,-9.794303 -8.85917,-32.693347 -8.85917,-32.693347"
id="path9-7" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

89
resources/img/logo.svg Normal file
View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 45.742326 80.310542"
version="1.1"
id="svg12"
width="45.742325"
height="80.310539"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs12" />
<style id="style1">
svg {
--fill: #2E3440;
}
@media (prefers-color-scheme: dark) {
svg {
--fill: #D8DEE9;
}
}
</style>
<g
class="stroke"
id="branches"
transform="translate(-42.033271,-37.145192)">
<path
d="m 65.113709,84.638921 c -0.346049,-9.794303 8.85917,-32.693347 8.85917,-32.693347"
id="path1"
style="fill:none;stroke:var(--fill);stroke-width:3;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 65.108044,84.684262 c 0.346049,-9.794303 -8.85917,-32.693347 -8.85917,-32.693347"
id="path2"
style="fill:none;stroke:var(--fill);stroke-width:3;stroke-dasharray:none;stroke-opacity:1" />
</g>
<g
class="leaf"
id="g1"
style="fill:var(--fill);fill-opacity:1">
<path
d="M 0,0 C 6.633,-3.91 14.348,-4.302 20.992,-1.732 20.009,5.333 15.93,11.893 9.31,15.795 2.69,19.697 -5.025,20.088 -11.669,17.519 -10.7,10.462 -6.62,3.901 0,0"
transform="matrix(0.35277777,0,0,-0.35277777,4.116564,13.543871)"
id="path3"
style="fill:var(--fill);fill-opacity:1" />
<path
d="m 0,0 c -6.62,3.901 -14.335,4.293 -20.979,1.724 0.97,-7.058 5.049,-13.618 11.669,-17.519 6.633,-3.91 14.348,-4.301 20.992,-1.732 C 10.699,-10.462 6.62,-3.902 0,0"
transform="matrix(0.35277777,0,0,-0.35277777,10.339434,19.278333)"
id="path4"
style="fill:var(--fill);fill-opacity:1" />
<path
d="M 0,0 C 6.633,-3.909 14.348,-4.301 20.992,-1.731 20.009,5.333 15.93,11.894 9.31,15.795 2.69,19.697 -5.026,20.088 -11.669,17.52 -10.7,10.461 -6.62,3.902 0,0"
transform="matrix(0.35277777,0,0,-0.35277777,10.903454,36.572256)"
id="path5"
style="fill:var(--fill);fill-opacity:1" />
<path
d="M 0,0 C 6.644,-2.57 14.358,-2.178 20.992,1.732 27.612,5.633 31.691,12.194 32.661,19.25 26.017,21.82 18.302,21.429 11.682,17.527 5.062,13.625 0.982,7.065 0,0"
transform="matrix(0.35277777,0,0,-0.35277777,32.871328,24.119748)"
id="path6"
style="fill:var(--fill);fill-opacity:1" />
<path
d="M 0,0 C 6.62,3.901 10.699,10.461 11.669,17.519 5.025,20.088 -2.689,19.696 -9.31,15.795 -15.93,11.893 -20.009,5.333 -20.992,-1.732 -14.348,-4.301 -6.633,-3.91 0,0"
transform="matrix(0.35277777,0,0,-0.35277777,35.741597,35.870171)"
id="path7"
style="fill:var(--fill);fill-opacity:1" />
<path
d="m -27.40181,13.441787 c 6.644,-2.57 14.359,-2.178 20.9920004,1.731 6.62000002,3.902 10.699,10.461 11.669,17.519 -6.644,2.569 -14.359,2.178 -20.9790004,-1.724 -6.62,-3.901 -10.7,-10.462 -11.682,-17.526"
transform="matrix(0.35277777,0,0,-0.35277777,43.12113,17.474745)"
id="path8"
style="fill:var(--fill);fill-opacity:1" />
<path
d="m 0,0 c 1.271,7.579 -1.125,14.922 -5.904,20.205 -6.242,-3.433 -10.906,-9.591 -12.178,-17.169 -1.275,-7.594 1.123,-14.937 5.902,-20.22 C -5.936,-13.736 -1.273,-7.578 0,0"
transform="matrix(0.35277777,0,0,-0.35277777,20.082753,7.127875)"
id="path9"
style="fill:var(--fill);fill-opacity:1" />
<path
d="m 0,0 c 1.271,7.579 -1.125,14.922 -5.904,20.206 -6.242,-3.434 -10.906,-9.592 -12.178,-17.17 -1.275,-7.593 1.123,-14.937 5.902,-20.22 C -5.937,-13.736 -1.273,-7.578 0,0"
transform="matrix(0.35277777,0,0,-0.35277777,26.963346,20.756878)"
id="path10"
style="fill:var(--fill);fill-opacity:1" />
<path
d="M 0,0 C 4.779,5.283 7.176,12.627 5.901,20.22 4.629,27.798 -0.035,33.956 -6.277,37.39 -11.055,32.106 -13.453,24.763 -12.18,17.184 -10.908,9.606 -6.244,3.448 0,0"
transform="matrix(0.35277777,0,0,-0.35277777,29.06985,14.051408)"
id="path11"
style="fill:var(--fill);fill-opacity:1" />
</g>
<path
class="fill"
d="m 23.308833,63.179301 -3.288947,-3.831872 2.16535,-2.433461 1.123597,-1.262592 1.119364,1.257653 2.169936,2.4384 z M 37.853155,39.714993 c -0.02117,0.08396 -0.9652,3.0988 -3.220508,5.991225 -1.128536,1.453444 -2.574573,2.872317 -4.37515,3.93065 -1.617486,0.947914 -3.517195,1.617839 -5.820481,1.786467 l -1.128183,-1.267531 -1.127125,1.266825 C 19.838911,51.249415 17.912391,50.557971 16.276561,49.58254 13.551705,47.957293 11.640003,45.483263 10.434208,43.388115 9.8309581,42.34354 9.4048026,41.401624 9.134222,40.732051 8.9991081,40.397265 8.902447,40.131271 8.8414165,39.954529 8.8107248,39.865982 8.7892053,39.800013 8.7761526,39.75909 l -0.013053,-0.04233 -0.00212,-0.006 L 8.374688,38.405835 H 0.87287218 v 3.653366 H 5.7302693 c 0.5323417,1.327503 1.5515166,3.495323 3.2441444,5.720645 1.3409083,1.757539 3.1143223,3.553177 5.4257223,4.937477 1.423105,0.854428 3.055761,1.541992 4.884914,1.960034 l -1.365956,1.534936 -2.751314,3.091744 4.069998,4.741686 c -1.8415,0.426861 -3.481212,1.128536 -4.909256,1.995664 -3.439936,2.087739 -5.6744305,5.06095 -7.0735472,7.485944 -0.7094361,1.234017 -1.2043833,2.33292 -1.5250583,3.129845 H 0.87287218 v 3.653366 H 8.3746915 l 0.3869972,-1.306689 c 0.017992,-0.07479 0.9574388,-3.071988 3.1996943,-5.959122 1.120775,-1.448505 2.556934,-2.865966 4.343753,-3.928886 1.594908,-0.94615 3.464983,-1.623483 5.726994,-1.814336 l 1.276703,1.486959 1.276703,-1.486959 c 2.304697,0.193675 4.202641,0.89147 5.8166,1.865136 2.703336,1.631245 4.598811,4.097514 5.794022,6.182431 0.597605,1.039283 1.01988,1.975908 1.288344,2.640894 0.134056,0.33267 0.229658,0.597253 0.289983,0.772583 0.02999,0.08784 0.05151,0.153459 0.06456,0.194028 l 0.01305,0.04198 0.0014,0.0056 0.385939,1.306336 h 7.502877 V 76.657176 H 40.885985 C 40.357172,75.336729 39.347522,73.18549 37.673944,70.973573 36.344677,69.222384 34.586433,67.430273 32.294788,66.041387 30.865333,65.173554 29.224563,64.47082 27.3813,64.044312 L 31.450238,59.304037 28.698572,56.21194 27.33438,54.679121 c 1.829153,-0.417336 3.461456,-1.104195 4.884208,-1.957917 3.46957,-2.081036 5.72135,-5.0673 7.129639,-7.505347 0.716845,-1.244953 1.216025,-2.353733 1.538111,-3.156656 h 4.855986 v -3.653366 h -7.502877 z"
id="path12"
style="fill:var(--fill);fill-opacity:1;stroke-width:0.352778" />
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1,155 @@
<?php
declare(strict_types=1);
use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Fact;
use Fisharebest\Webtrees\Http\RequestHandlers\AddNewFact;
use Fisharebest\Webtrees\Http\RequestHandlers\DeleteRecord;
use Fisharebest\Webtrees\Http\RequestHandlers\EditFactPage;
use Fisharebest\Webtrees\Http\RequestHandlers\EditRawRecordPage;
use Fisharebest\Webtrees\Http\RequestHandlers\ReorderFamiliesPage;
use Fisharebest\Webtrees\Http\RequestHandlers\ReorderMediaPage;
use Fisharebest\Webtrees\Http\RequestHandlers\ReorderNamesPage;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Illuminate\Support\Collection;
// Added by me
use Fisharebest\Webtrees\Http\RequestHandlers\AddSpouseToIndividualPage;
use Fisharebest\Webtrees\Http\RequestHandlers\LinkSpouseToIndividualPage;
use Fisharebest\Webtrees\Http\RequestHandlers\AddChildToIndividualPage;
// End added
/**
* @var bool $can_upload_media
* @var Collection<int,Fact> $clipboard_facts
* @var Individual $record
* @var Collection<int,string> $shares
*/
?>
<div class="dropdown wt-page-menu">
<button class="btn btn-primary dropdown-toggle wt-page-menu-button" type="button" id="page-menu" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<?= view('icons/menu') ?>
<?= I18N::translate('edit') ?>
</button>
<div class="dropdown-menu dropdown-menu-end wt-page-menu-items" aria-labelledby="page-menu">
<?php if ($shares->isNotEmpty()) : ?>
<button class="dropdown-item" data-bs-toggle="modal" data-bs-target="#wt-shares-modal">
<?= view('icons/share') ?>
<?= I18N::translate('Share') ?>
</button>
<hr>
<?php endif ?>
<a class="dropdown-item" href="<?= e(route(AddNewFact::class, ['tree' => $record->tree()->name(), 'xref' => $record->xref(), 'fact' => 'NAME'])) ?>">
<?= view('icons/add') ?>
<?= I18N::translate('Add a name') ?>
</a>
<?php if ($record->facts(['NAME'], false, null, true)->count() > 1) : ?>
<a class="dropdown-item" href="<?= e(route(ReorderNamesPage::class, ['tree' => $record->tree()->name(), 'xref' => $record->xref()])) ?>">
<?= view('icons/reorder') ?>
<?= I18N::translate('Re-order names') ?>
</a>
<?php else : ?>
<div class="dropdown-item disabled">
<?= view('icons/spacer') ?>
<?= I18N::translate('Re-order names') ?>
</div>
<?php endif ?>
<div class="dropdown-divider"></div>
<?php if ($record->facts(['SEX'], false, null, true)->isEmpty()) : ?>
<a class="dropdown-item" href="<?= e(route(AddNewFact::class, ['fact' => 'SEX', 'tree' => $record->tree()->name(), 'xref' => $record->xref()])) ?>">
<?= view('icons/edit') ?>
<?= I18N::translate('Edit the sex') ?>
</a>
<?php endif ?>
<?php foreach ($record->facts(['SEX'], false, null, true) as $fact) : ?>
<?php if ($fact->canEdit()) : ?>
<a class="dropdown-item" href="<?= e(route(EditFactPage::class, ['xref' => $record->xref(), 'fact_id' => $fact->id(), 'tree' => $record->tree()->name()])) ?>">
<?= view('icons/edit') ?>
<?= I18N::translate('Edit the sex') ?>
</a>
<?php endif ?>
<?php endforeach ?>
<div class="dropdown-divider"></div>
<?php if ($record->spouseFamilies()->count() > 1 || $record->childFamilies()->count() > 1) : ?>
<a class="dropdown-item" href="<?= e(route(ReorderFamiliesPage::class, ['tree' => $record->tree()->name(), 'xref' => $record->xref()])) ?>">
<?= view('icons/reorder') ?>
<?= I18N::translate('Re-order families') ?>
</a>
<?php else : ?>
<div class="dropdown-item disabled">
<?= view('icons/spacer') ?>
<?= I18N::translate('Re-order families') ?>
</div>
<?php endif ?>
<!-- ADDED BY ME -->
<a class="dropdown-item" href="<?= e(route(AddSpouseToIndividualPage::class, ['tree' => $record->tree()->name(), 'xref' => $record->xref()])) ?>">
<?= view('icons/add') ?>
<?= I18N::translate($record->sex() === 'F' ? 'Add a husband' : 'Add a wife') ?>
</a>
<a class="dropdown-item" href="<?= e(route(LinkSpouseToIndividualPage::class, ['tree' => $record->tree()->name(), 'xref' => $record->xref()])) ?>">
<?= view('icons/link') ?>
<?= I18N::translate($record->sex() === 'F' ? 'Add a husband using an existing individual' : 'Add a wife using an existing individual') ?>
</a>
<a class="dropdown-item" href="<?= e(route(AddChildToIndividualPage::class, ['tree' => $record->tree()->name(), 'xref' => $record->xref()])) ?>">
<?= view('icons/individual') ?>
<?= I18N::translate('Add a child') ?>
</a>
<!-- END ADDED -->
<div class="dropdown-divider"></div>
<?php if ($can_upload_media) : ?>
<a class="dropdown-item" href="<?= e(route(AddNewFact::class, ['tree' => $record->tree()->name(), 'xref' => $record->xref(), 'fact' => 'OBJE'])) ?>">
<?= view('icons/add') ?>
<?= I18N::translate('Add a media object') ?>
</a>
<?php endif ?>
<?php if ($record->facts(['OBJE'], false, null, true)->count() > 1) : ?>
<a class="dropdown-item" href="<?= e(route(ReorderMediaPage::class, ['tree' => $record->tree()->name(), 'xref' => $record->xref()])) ?>">
<?= view('icons/reorder') ?>
<?= I18N::translate('Re-order media') ?>
</a>
<?php else : ?>
<div class="dropdown-item disabled">
<?= view('icons/spacer') ?>
<?= I18N::translate('Re-order media') ?>
</div>
<?php endif ?>
<?php if ($clipboard_facts->isNotEmpty()) : ?>
<div class="dropdown-divider"></div>
<?= view('record-page-menu-clipboard', ['clipboard_facts' => $clipboard_facts, 'record' => $record]) ?>
<?php endif ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" data-wt-confirm="<?= I18N::translate('Are you sure you want to delete “%s”?', strip_tags($record->fullName())) ?>" data-wt-post-url="<?= e(route(DeleteRecord::class, ['tree' => $record->tree()->name(), 'xref' => $record->xref()])) ?>">
<?= view('icons/delete') ?>
<?= I18N::translate('Delete') ?>
</a>
<?php if (Auth::isAdmin() || $record->tree()->getPreference('SHOW_GEDCOM_RECORD') === '1') : ?>
<a class="dropdown-item" href="<?= e(route(EditRawRecordPage::class, ['tree' => $record->tree()->name(), 'xref' => $record->xref()])) ?>">
<?= view('icons/edit') ?>
<?= I18N::translate('Edit the raw GEDCOM') ?>
</a>
<?php endif ?>
</div>
</div>

View File

@@ -0,0 +1,25 @@
<input type="number" id="family-page" />
<?= view('::lists/families-table', get_defined_vars()); ?>
<script>
window.onload = function() {
var table = $(".wt-table-family").DataTable();
var pageInput = $("#family-page");
var queries = new URLSearchParams(window.location.search);
pageInput.on('keyup', function () {
setPage(this.value);
});
table.on('page.dt', function () {
pageInput.val(table.page() + 1);
queries.set("page", table.page() + 1);
window.history.pushState( {} , '', '?' + queries.toString() );
});
setPage(queries.get("page"));
function setPage(page){
table.page(page - 1).draw('page');
}
};
</script>

View File

@@ -0,0 +1,25 @@
<input type="number" id="individual-page" />
<?= view('::lists/individuals-table', get_defined_vars()); ?>
<script>
window.onload = function() {
var table = $(".wt-table-individual").DataTable();
var pageInput = $("#individual-page");
var queries = new URLSearchParams(window.location.search);
pageInput.on('keyup', function () {
setPage(this.value);
});
table.on('page.dt', function () {
pageInput.val(table.page() + 1);
queries.set("page", table.page() + 1);
window.history.pushState( {} , '', '?' + queries.toString() );
});
setPage(queries.get("page"));
function setPage(page){
table.page(page - 1).draw('page');
}
};
</script>

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
use Fisharebest\Webtrees\I18N;
/**
* @var string $block
* @var string $content
* @var string $config_url
* @var int $id
* @var string $title
*/
?>
<div class="mb-4 wt-block wt-block-<?= e($block) ?>" id="block-<?= e($id) ?>">
<h3 class="wt-block-header wt-block-header-<?= e($block) ?>">
<?php if ($config_url !== '') : ?>
<a class="btn btn-link" href="<?= e($config_url) ?>" title="<?= I18N::translate('Preferences') ?>">
<?= view('icons/preferences') ?>
<span class="visually-hidden"><?= I18N::translate('Preferences') ?></span>
</a>
<?php endif ?>
<?= $title ?>
</h3>
<div class="wt-block-content wt-block-content-<?= e($block) ?>">
<?= $content ?>
</div>
</div>

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Tree;
use Fisharebest\Webtrees\View;
/**
* @var string $individual_list
* @var Tree $tree
*/
?>
<form method="post" action="<?= e(route('module', ['module' => 'descendancy', 'action' => 'Descendants', 'tree' => $tree->name()])) ?>" onsubmit="return false;">
<div class="form-group">
<input type="search" name="sb_desc_name" id="sb_desc_name" class="form-control" placeholder="<?= I18N::translate('Search') ?>">
<?= csrf_field() ?>
</div>
</form>
<div id="sb_desc_content">
<ul>
<?= $individual_list ?>
</ul>
</div>
<?php View::push('javascript') ?>
<script>
function dsearchQ() {
var query = $("#sb_desc_name").val();
if (query.length>1) {
$("#sb_desc_content").load(<?= json_encode(route('module', ['module' => 'descendancy', 'action' => 'Search', 'tree' => $tree->name(), 'search' => '']), JSON_THROW_ON_ERROR) ?> + encodeURIComponent(query));
}
}
$("#sb_desc_name").focus(function(){this.select();});
$("#sb_desc_name").blur(function(){if (this.value === "") this.value="<?= I18N::translate('Search') ?>";});
var dtimerid = null;
$("#sb_desc_name").keyup(function(e) {
if (dtimerid) window.clearTimeout(dtimerid);
dtimerid = window.setTimeout("dsearchQ()", 500);
});
$("#sb_desc_content").on("click", ".sb_desc_indi", function() {
var self = $(this),
state = self.children(".plusminus"),
target = self.siblings("div");
if(state.hasClass("icon-plus")) {
if (jQuery.trim(target.html())) {
target.show("fast"); // already got content so just show it
} else if (this.dataset.wtHref !== "#") {
target
.hide()
.load(this.dataset.wtHref, function(response, status, xhr) {
if(status === "success" && response !== "") {
target.show("fast");
}
})
}
} else {
target.hide("fast");
}
state.toggleClass("icon-minus icon-plus");
return false;
});
</script>
<?php View::endpush() ?>

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
use Fisharebest\Webtrees\I18N;
/**
* @var array<int,object{
* block_id: int,
* block_order: int,
* gedcom_id: int,
* header: string,
* faqbody: string,
* languages: string
* }> $faqs
* @var string $title
*/
?>
<h2 class="wt-page-title"><?= $title ?></h2>
<ul class="faq">
<?php foreach ($faqs as $id => $faq) : ?>
<li>
<a href="#faq<?= e($id) ?>"><?= e($faq->header) ?></a>
</li>
<?php endforeach ?>
</ul>
<?php foreach ($faqs as $id => $faq) : ?>
<article class="faq_article">
<header class="faq_title" id="faq<?= $id ?>">
<h3><?= e($faq->header) ?></h3>
<span class="faq_top faq_italic">
<a href="#content"><?= I18N::translate('back to top') ?></a>
</span>
</header>
<main class="faq_body">
<?= str_starts_with($faq->faqbody, '<') ? $faq->faqbody : nl2br(e($faq->faqbody), false) ?>
</main>
</article>
<?php endforeach ?>

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
use Fisharebest\Webtrees\Gedcom;
use Fisharebest\Webtrees\Individual;
/**
* @var string $dir
* @var int $end_year
* @var array<object{
* background: string,
* birth_year: int,
* death_year: int,
* id: string,
* individual: Individual,
* row: int
* }> $lifespans
* @var int $max_rows
* @var int $start_year
* @var string $subtitle
*/
?>
<p class="wt-lifespans-subtitle">
<?= $subtitle ?>
</p>
<div class="wt-lifespans-scale">
<?php for ($year = $start_year; $year < $end_year; $year += 10) :
?><div class="wt-lifespans-decade"><?= $year ?></div><?php
endfor ?>
</div>
<div class="wt-lifespans-individuals position-relative" style="height: <?= (5 + $max_rows) * 1.5 ?>rem; width: <?= ($end_year - $start_year) * 7 ?>px;">
<?php foreach ($lifespans as $lifespan) : ?>
<a href="#" data-bs-toggle="collapse" data-bs-target="#<?= e($lifespan->id) ?>" aria-expanded="false" aria-controls="<?= e($lifespan->id) ?>">
<div class="wt-lifespans-individual position-absolute text-nowrap text-truncate <?= 'wt-sex-'.strtolower($lifespan->individual->sex() ?? 'u') ?>" dir="auto" style="<?= $dir === 'ltr' ? 'left' : 'right' ?>:<?= ($lifespan->birth_year - $start_year) * 7 ?>px; top:<?= $lifespan->row * 2.5 ?>rem; width:<?= ($lifespan->death_year - $lifespan->birth_year) * 7 + 5 ?>px;">
<?= $lifespan->individual->fullName() ?>
<?= strip_tags($lifespan->individual->lifespan()) ?>
</div>
</a>
<div class="wt-lifespans-summary collapse position-absolute" id="<?= e($lifespan->id) ?>" style="<?= $dir === 'ltr' ? 'left' : 'right' ?>:<?= (min($lifespan->birth_year, $end_year - 50) - $start_year) * 7 ?>px; top:<?= ($lifespan->row + 1.2) * 2.5 ?>rem; width:350px;">
<a class="wt-lifespans-summary-link" href="<?= e($lifespan->individual->url()) ?>">
<?= $lifespan->individual->fullName() ?>
</a>
<?php foreach ($lifespan->individual->facts(array_merge(Gedcom::BIRTH_EVENTS, Gedcom::DEATH_EVENTS), true) as $fact) : ?>
<?= $fact->summary() ?>
<?php endforeach ?>
</div>
<?php endforeach ?>
</div>

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
use Fisharebest\Webtrees\Media;
use Illuminate\Support\Collection;
/**
* @var Collection<int,Media> $media_list
*/
?>
<div class="wt-tab-album container px-0 py-4">
<div class="row">
<?php foreach ($media_list as $media) : ?>
<figure class="figure text-center col-sm-6 col-md-4 col-lg-3 col-xl-3 wt-album-tab-figure">
<?php foreach ($media->mediaFiles() as $media_file) : ?>
<?= $media_file->displayImage(100, 100, 'contain', ['class' => 'img-thumbnail wt-album-tab-image']) ?>
<?php endforeach ?>
<figcaption class="figure-caption wt-album-tab-caption">
<a href="<?= e($media->url()) ?>">
<?= $media->fullName() ?>
</a>
</figcaption>
</figure>
<?php endforeach ?>
</div>
</div>

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Place;
/**
* @var array<array<Place>> $columns
*/
?>
<div class="row">
<h5 class="col list_label text-center">
<?= I18N::translate('Place list') ?>
</h5>
</div>
<div class="row">
<?php foreach ($columns as $column) : ?>
<ul class="col wt-page-options-value me-1 list-unstyled">
<?php foreach ($column as $place) : ?>
<li><?= $place->fullName(true) ?></li>
<?php endforeach ?>
</ul>
<?php endforeach ?>
</div>

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
use Fisharebest\Webtrees\Contracts\TimestampInterface;
use Fisharebest\Webtrees\Contracts\UserInterface;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\View;
use Illuminate\Support\Collection;
/**
* @var int $id
* @var int $limit_low
* @var int $limit_high
* @var Collection<int,object{record:GedcomRecord,time:TimestampInterface,user:UserInterface}> $rows
* @var bool $show_date
* @var bool $show_user
*/
?>
<div class="list-group">
<?php foreach ($rows as $n => $row): ?>
<?php if ($n === $limit_low && $rows->count() > $limit_high): ?>
<div>
<button class="btn btn-sm btn-secondary my-3" id="show-more-<?= e($id) ?>">
<?= view('icons/add') ?>
<?= /* I18N: button label */ I18N::translate('show more') ?>
</button>
</div>
<?php endif ?>
<a href="<?= e($row->record->url()) ?>" class="<?= $n >= $limit_low && $rows->count() > $limit_high ? 'd-none' : '' ?> list-group-item list-group-item-action">
<span class="d-block"><?= $row->record->fullName() ?></span>
<small class="d-block">
<?php if ($show_user && $show_date): ?>
<?= /* I18N: [a record was] Changed on <date/time> by <user> */ I18N::translate('Changed on %1$s by %2$s', view('components/datetime', ['timestamp' => $row->time]), e($row->record->lastChangeUser())) ?>
<?php elseif ($show_date): ?>
<?= /* I18N: [a record was] Changed on <date/time> */ I18N::translate('Changed on %1$s', view('components/datetime', ['timestamp' => $row->time])) ?>
<?php elseif ($show_user): ?>
<?= /* I18N: [a record was] Changed by <user> */ I18N::translate('Changed by %1$s', e($row->user->userName())) ?>
<?php endif ?>
</small>
</a>
<?php endforeach ?>
</div>
<?php View::push('javascript') ?>
<script>
document.getElementById("show-more-<?= e($id) ?>").addEventListener("click", function (ev) {
document.querySelectorAll("#block-<?= e($id) ?> .d-none").forEach(function (el) {
el.classList.remove("d-none");
});
ev.target.parentNode.removeChild(ev.target);
});
</script>
<?php View::endpush() ?>

7
src/css/imports.css Normal file
View File

@@ -0,0 +1,7 @@
/*! ********************************************
Bocken Theme for webtrees
************************************************
CSS imports
***********************************************/
@import "../../vendor/fisharebest/webtrees/resources/css/_base.css";

Binary file not shown.

Binary file not shown.

1
src/img/bars-solid.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 434 B

1
src/img/book-solid.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M448 360V24c0-13.3-10.7-24-24-24H96C43 0 0 43 0 96v320c0 53 43 96 96 96h328c13.3 0 24-10.7 24-24v-16c0-7.5-3.5-14.3-8.9-18.7-4.2-15.4-4.2-59.3 0-74.7 5.4-4.3 8.9-11.1 8.9-18.6zM128 134c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm0 64c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm253.4 250H96c-17.7 0-32-14.3-32-32 0-17.6 14.4-32 32-32h285.4c-1.9 17.1-1.9 46.9 0 64z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 668 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M0 464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V192H0v272zm320-196c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-40zm0 128c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-40zM192 268c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-40zm0 128c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-40zM64 268c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12H76c-6.6 0-12-5.4-12-12v-40zm0 128c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12H76c-6.6 0-12-5.4-12-12v-40zM400 64h-48V16c0-8.8-7.2-16-16-16h-32c-8.8 0-16 7.2-16 16v48H160V16c0-8.8-7.2-16-16-16h-32c-8.8 0-16 7.2-16 16v48H48C21.5 64 0 85.5 0 112v48h448v-48c0-26.5-21.5-48-48-48z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M416 192c0-88.4-93.1-160-208-160S0 103.6 0 192c0 34.3 14.1 65.9 38 92-13.4 30.2-35.5 54.2-35.8 54.5-2.2 2.3-2.8 5.7-1.5 8.7S4.8 352 8 352c36.6 0 66.9-12.3 88.7-25 32.2 15.7 70.3 25 111.3 25 114.9 0 208-71.6 208-160zm122 220c23.9-26 38-57.7 38-92 0-66.9-53.5-124.2-129.3-148.1.9 6.6 1.3 13.3 1.3 20.1 0 105.9-107.7 192-240 192-10.8 0-21.3-.8-31.7-1.9C207.8 439.6 281.8 480 368 480c41 0 79.1-9.2 111.3-25 21.8 12.7 52.1 25 88.7 25 3.2 0 6.1-1.9 7.3-4.8 1.3-2.9.7-6.3-1.5-8.7-.3-.3-22.4-24.2-35.8-54.5z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 729 B

1
src/img/home-solid.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M280.37 148.26L96 300.11V464a16 16 0 0 0 16 16l112.06-.29a16 16 0 0 0 15.92-16V368a16 16 0 0 1 16-16h64a16 16 0 0 1 16 16v95.64a16 16 0 0 0 16 16.05L464 480a16 16 0 0 0 16-16V300L295.67 148.26a12.19 12.19 0 0 0-15.3 0zM571.6 251.47L488 182.56V44.05a12 12 0 0 0-12-12h-56a12 12 0 0 0-12 12v72.61L318.47 43a48 48 0 0 0-61 0L4.34 251.47a12 12 0 0 0-1.6 16.9l25.5 31A12 12 0 0 0 45.15 301l235.22-193.74a12.19 12.19 0 0 1 15.3 0L530.9 301a12 12 0 0 0 16.9-1.6l25.5-31a12 12 0 0 0-1.7-16.93z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 715 B

56
src/img/icon-pedigree.svg Normal file
View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
data-fa-i2svg=""
viewBox="0 0 33.75 512"
height="27"
version="1.1"
id="svg4"
sodipodi:docname="icon-pedigree.svg"
width="33.75"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview6"
showgrid="false"
inkscape:zoom="8.925"
inkscape:cx="10.661658"
inkscape:cy="-7.5902336"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="m -175.125,352 h -96 c -17.67,0 -32,14.33 -32,32 v 96 c 0,17.67 14.33,32 32,32 h 96 c 17.67,0 32,-14.33 32,-32 v -96 c 0,-17.67 -14.33,-32 -32,-32 z m -24,-80 h 192 v 48 h 48 v -48 h 192 v 48 h 48 v -57.59 c 0,-21.17 -17.23,-38.41 -38.41,-38.41 H 40.875 v -64 h 40 c 17.67,0 32,-14.33 32,-32 V 32 c 0,-17.67 -14.33,-32 -32,-32 h -128 c -17.67,0 -32,14.33 -32,32 v 96 c 0,17.67 14.33,32 32,32 h 40 v 64 h -201.59 c -21.18,0 -38.41,17.23 -38.41,38.41 V 320 h 48 z m 264,80 h -96 c -17.67,0 -32,14.33 -32,32 v 96 c 0,17.67 14.33,32 32,32 h 96 c 17.67,0 32,-14.33 32,-32 v -96 c 0,-17.67 -14.33,-32 -32,-32 z m 240,0 h -96 c -17.67,0 -32,14.33 -32,32 v 96 c 0,17.67 14.33,32 32,32 h 96 c 17.67,0 32,-14.33 32,-32 v -96 c 0,-17.67 -14.33,-32 -32,-32 z"
id="path2"
inkscape:connector-curvature="0"
style="fill:#5e72e4;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M152.1 236.2c-3.5-12.1-7.8-33.2-7.8-33.2h-.5s-4.3 21.1-7.8 33.2l-11.1 37.5H163zM616 96H336v320h280c13.3 0 24-10.7 24-24V120c0-13.3-10.7-24-24-24zm-24 120c0 6.6-5.4 12-12 12h-11.4c-6.9 23.6-21.7 47.4-42.7 69.9 8.4 6.4 17.1 12.5 26.1 18 5.5 3.4 7.3 10.5 4.1 16.2l-7.9 13.9c-3.4 5.9-10.9 7.8-16.7 4.3-12.6-7.8-24.5-16.1-35.4-24.9-10.9 8.7-22.7 17.1-35.4 24.9-5.8 3.5-13.3 1.6-16.7-4.3l-7.9-13.9c-3.2-5.6-1.4-12.8 4.2-16.2 9.3-5.7 18-11.7 26.1-18-7.9-8.4-14.9-17-21-25.7-4-5.7-2.2-13.6 3.7-17.1l6.5-3.9 7.3-4.3c5.4-3.2 12.4-1.7 16 3.4 5 7 10.8 14 17.4 20.9 13.5-14.2 23.8-28.9 30-43.2H412c-6.6 0-12-5.4-12-12v-16c0-6.6 5.4-12 12-12h64v-16c0-6.6 5.4-12 12-12h16c6.6 0 12 5.4 12 12v16h64c6.6 0 12 5.4 12 12zM0 120v272c0 13.3 10.7 24 24 24h280V96H24c-13.3 0-24 10.7-24 24zm58.9 216.1L116.4 167c1.7-4.9 6.2-8.1 11.4-8.1h32.5c5.1 0 9.7 3.3 11.4 8.1l57.5 169.1c2.6 7.8-3.1 15.9-11.4 15.9h-22.9a12 12 0 0 1-11.5-8.6l-9.4-31.9h-60.2l-9.1 31.8c-1.5 5.1-6.2 8.7-11.5 8.7H70.3c-8.2 0-14-8.1-11.4-15.9z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M464 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V80c0-26.51-21.49-48-48-48zm-6 400H54a6 6 0 0 1-6-6V86a6 6 0 0 1 6-6h404a6 6 0 0 1 6 6v340a6 6 0 0 1-6 6zm-42-92v24c0 6.627-5.373 12-12 12H204c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h200c6.627 0 12 5.373 12 12zm0-96v24c0 6.627-5.373 12-12 12H204c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h200c6.627 0 12 5.373 12 12zm0-96v24c0 6.627-5.373 12-12 12H204c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h200c6.627 0 12 5.373 12 12zm-252 12c0 19.882-16.118 36-36 36s-36-16.118-36-36 16.118-36 36-36 36 16.118 36 36zm0 96c0 19.882-16.118 36-36 36s-36-16.118-36-36 16.118-36 36-36 36 16.118 36 36zm0 96c0 19.882-16.118 36-36 36s-36-16.118-36-36 16.118-36 36-36 36 16.118 36 36z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 1008 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 711 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M384 320H256c-17.67 0-32 14.33-32 32v128c0 17.67 14.33 32 32 32h128c17.67 0 32-14.33 32-32V352c0-17.67-14.33-32-32-32zM192 32c0-17.67-14.33-32-32-32H32C14.33 0 0 14.33 0 32v128c0 17.67 14.33 32 32 32h95.72l73.16 128.04C211.98 300.98 232.4 288 256 288h.28L192 175.51V128h224V64H192V32zM608 0H480c-17.67 0-32 14.33-32 32v128c0 17.67 14.33 32 32 32h128c17.67 0 32-14.33 32-32V32c0-17.67-14.33-32-32-32z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 629 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zM262.655 90c-54.497 0-89.255 22.957-116.549 63.758-3.536 5.286-2.353 12.415 2.715 16.258l34.699 26.31c5.205 3.947 12.621 3.008 16.665-2.122 17.864-22.658 30.113-35.797 57.303-35.797 20.429 0 45.698 13.148 45.698 32.958 0 14.976-12.363 22.667-32.534 33.976C247.128 238.528 216 254.941 216 296v4c0 6.627 5.373 12 12 12h56c6.627 0 12-5.373 12-12v-1.333c0-28.462 83.186-29.647 83.186-106.667 0-58.002-60.165-102-116.531-102zM256 338c-25.365 0-46 20.635-46 46 0 25.364 20.635 46 46 46s46-20.636 46-46c0-25.365-20.635-46-46-46z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 852 B

1
src/img/scroll-solid.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M48 0C21.53 0 0 21.53 0 48v64c0 8.84 7.16 16 16 16h80V48C96 21.53 74.47 0 48 0zm208 412.57V352h288V96c0-52.94-43.06-96-96-96H111.59C121.74 13.41 128 29.92 128 48v368c0 38.87 34.65 69.65 74.75 63.12C234.22 474 256 444.46 256 412.57zM288 384v32c0 52.93-43.06 96-96 96h336c61.86 0 112-50.14 112-112 0-8.84-7.16-16-16-16H288z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 551 B

1
src/img/search-solid.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 575 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M466.5 83.7l-192-80a48.15 48.15 0 0 0-36.9 0l-192 80C27.7 91.1 16 108.6 16 128c0 198.5 114.5 335.7 221.5 380.3 11.8 4.9 25.1 4.9 36.9 0C360.1 472.6 496 349.3 496 128c0-19.4-11.7-36.9-29.5-44.3zM256.1 446.3l-.1-381 175.9 73.3c-3.3 151.4-82.1 261.1-175.8 307.7z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 505 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M528.12 301.319l47.273-208C578.806 78.301 567.391 64 551.99 64H159.208l-9.166-44.81C147.758 8.021 137.93 0 126.529 0H24C10.745 0 0 10.745 0 24v16c0 13.255 10.745 24 24 24h69.883l70.248 343.435C147.325 417.1 136 435.222 136 456c0 30.928 25.072 56 56 56s56-25.072 56-56c0-15.674-6.447-29.835-16.824-40h209.647C430.447 426.165 424 440.326 424 456c0 30.928 25.072 56 56 56s56-25.072 56-56c0-22.172-12.888-41.332-31.579-50.405l5.517-24.276c3.413-15.018-8.002-29.319-23.403-29.319H218.117l-6.545-32h293.145c11.206 0 20.92-7.754 23.403-18.681z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 782 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M416 448h-84c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h84c17.7 0 32-14.3 32-32V160c0-17.7-14.3-32-32-32h-84c-6.6 0-12-5.4-12-12V76c0-6.6 5.4-12 12-12h84c53 0 96 43 96 96v192c0 53-43 96-96 96zm-47-201L201 79c-15-15-41-4.5-41 17v96H24c-13.3 0-24 10.7-24 24v96c0 13.3 10.7 24 24 24h136v96c0 21.5 26 32 41 17l168-168c9.3-9.4 9.3-24.6 0-34z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 566 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M497 273L329 441c-15 15-41 4.5-41-17v-96H152c-13.3 0-24-10.7-24-24v-96c0-13.3 10.7-24 24-24h136V88c0-21.4 25.9-32 41-17l168 168c9.3 9.4 9.3 24.6 0 34zM192 436v-40c0-6.6-5.4-12-12-12H96c-17.7 0-32-14.3-32-32V160c0-17.7 14.3-32 32-32h84c6.6 0 12-5.4 12-12V76c0-6.6-5.4-12-12-12H96c-53 0-96 43-96 96v192c0 53 43 96 96 96h84c6.6 0 12-5.4 12-12z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 570 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 800 1000"><path fill="#fff" fill-rule="evenodd" stroke-width="2.3" d="M358 29c16 4 32-15 50-16 25 0 56 14 102-7 10-5 29-11 41 1 8 9 17 13 26 10 22-10 44 20 57 24 7 2 14 9 21 14 10 7 21 23 30 32l26 21c13 14-1 18-11 35-10 10-20 23-32 46l15 100 23 76-16 26 80 113c2 16-1 28-13 35l-51 14c-5 39 4 50 13 64l-20 14 20 26-23 15 16 55c3 20-8 32-15 42-17 21-24 20-49 21-52 3-84-6-139-26-18 27-41 42-41 105-44 46-35 70-41 128-24 4-22 24-182-138-79-80-171-130-214-88 28-80 74-111 77-198l-21-33s-19-31-20-37l-16-51-22-169c5-106 75-164 125-202 38-32 86-45 137-54 22-4 44 2 67 2z"/></svg>

After

Width:  |  Height:  |  Size: 640 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 1000"><path fill="#fff" d="M119 956c57-142 35-248-89-448C-35 404-1 159 198 81c148-58 328-54 454 43 48 37 64 95 63 156 0 9 2 19 5 29 7 22 13 45 4 65-10 24-15 43 0 65l73 111c7 10 1 23-11 25-33 4-52 20-33 65 4 9 4 18-5 22-9 5-10 10-6 20 4 8 0 16-6 23-14 20-17 42-11 71 7 34-4 61-31 60-49-3-90-16-129 2-42 18-60 59-56 119l-390-1z"/></svg>

After

Width:  |  Height:  |  Size: 391 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 1000"><path fill="#fff" d="M444 31c-93-3-152 26-211 93-69-24-158 8-197 71-59 96-49 261 55 324-5 30-1 69 34 78 1 8 3 15 6 22 24 52 105 42 119 1 14 11 29 14 46 12l-1 1c-8 24-30 19-45 5 15 28 35 30 64 21-3 41-26 120-52 154-6 7-13 11-20 17-13 13-20 30-29 46 126-51 179 50 284 81 23 6 45 8 68 12-4-9-12-18-17-27-27-41-47-81-30-130 5-16 15-30 21-46 7-19 11-39 19-57 19-39 52-23 86-17 21 4 69 14 86 0 21-17 4-47 9-65 6-20 33-21 12-45 37-16 1-23 7-45 5-19 37-16 42-38 3-14-12-22-20-31-21-20-51-64-36-93 16-32 15-27 8-66-8-38-14-70-36-103 24-16 3-56-11-71-27-28-50-25-77-44-59-41-112-58-184-60zM199 146l17 3c-83 3-147 61-159 145l-2 16c-3-86 51-171 144-164zm-82 109c-13 38-6 80 25 108 3 2 9 5 10 7h-1a79 79 0 0 1-39-108l5-7zM88 418c18 13 41 20 63 17l-5 2c-17 6-51-1-58-19z"/></svg>

After

Width:  |  Height:  |  Size: 828 B

1
src/img/user-solid.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm89.6 32h-16.7c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16h-16.7C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4z" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 486 B

1
src/img/webtrees.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>Webtrees SVG Icon</title><path fill="currentColor" d="M2.957 4.34q.647 0 1.269.243q.634.243 1.093.7q.459.448.662 1l1.592 4.59l1.31-3.82Q9.84 4.26 11.92 4.26q.459 0 1.106.203q.729.23 1.228.809q.5.58.905 1.782l1.296 3.82l1.606-4.59q.189-.54.649-.998q.472-.459 1.079-.703q.608-.243 1.283-.243q.62.04 1.241.338q.783.378 1.228 1.106q.459.73.459 1.66q0 .81-.364 1.54l-4.225 8.652q-1.025 2.106-3.037 2.106q-.905-.04-1.634-.567q-.728-.54-1.133-1.498L12 13.72l-1.606 3.955q-.243.634-.647 1.093q-.406.447-.945.702q-.54.257-1.134.27q-1.013 0-1.755-.486q-.742-.5-1.297-1.62L.392 8.983Q0 8.16 0 7.443q0-.89.46-1.632q.459-.756 1.254-1.134q.622-.297 1.243-.337"/></svg>

After

Width:  |  Height:  |  Size: 744 B

View File

@@ -0,0 +1,262 @@
/*! ********************************************
Bocken Theme for webtrees
************************************************/
$prefix: "bs-";
// ---------------------------------------------------------------------------
// Color System
// ---------------------------------------------------------------------------
$primary: $nord10;
$secondary: $gray-200;
$tertiary: $gray-300;
$success: $nord14;
$info: $nord8;
$warning: $nord13;
$danger: $nord11;
$light: $gray-100;
$dark: $nord0;
$theme-colors: (
"primary": $primary,
"secondary": $secondary,
"success": $success,
"info": $info,
"warning": $warning,
"danger": $danger,
"light": $light,
"dark": $dark,
"white": $white
);
$min-contrast-ratio: 3;
// ---------------------------------------------------------------------------
// Options
// ---------------------------------------------------------------------------
$enable-negative-margins: true;
$enable-shadows: true;
// ---------------------------------------------------------------------------
// Spacing
// ---------------------------------------------------------------------------
$spacer: 1rem !default;
$spacers: (
0: 0,
1: $spacer * 0.25,
2: $spacer * 0.5,
3: $spacer,
4: $spacer * 1.5,
5: $spacer * 3,
6: $spacer * 4,
7: $spacer * 6,
8: $spacer * 8,
9: $spacer * 10,
10: $spacer * 12,
11: $spacer * 14,
12: $spacer * 16,
);
// ---------------------------------------------------------------------------
// Body
// ---------------------------------------------------------------------------
$body-bg: $gray-100;
$body-color: $font-color;
// ---------------------------------------------------------------------------
// Links
// ---------------------------------------------------------------------------
$link-color: $nord10;
$link-hover-color: $nord9;
$link-decoration: none;
$link-hover-decoration: none;
// ---------------------------------------------------------------------------
// Border & Shadows
// ---------------------------------------------------------------------------
$box-shadow-sm: 0 .3125rem .625rem 0 rgba(0, 0, 0, 0.08);
$box-shadow: 0 .25rem .375rem -.0625rem rgba(0, 0, 0, .08), 0 .125rem .25rem -.0625rem rgba(0, 0, 0, .04);
$box-shadow-lg: 0 8px 26px -4px rgba(0, 0, 0, 0.1), 0 8px 9px -5px rgba(0, 0, 0, 0.04);
// ---------------------------------------------------------------------------
// Typography
// ---------------------------------------------------------------------------
$font-family-sans-serif: "Open Sans", Helvetica, Arial, "Noto Sans", sans-serif;
$font-weight-bold: 600;
$font-weight-bolder: 700;
$small-font-size: .8em;
$h1-font-size: 3rem;
$h2-font-size: 2.25rem;
$h3-font-size: 1.875rem;
$headings-font-weight: 400;
$headings-color: $h-color;
// ---------------------------------------------------------------------------
// Tables
// ---------------------------------------------------------------------------
$table-cell-padding-y: 1rem;
$table-cell-padding-x: 1rem;
$table-cell-padding-y-sm: .75rem;
$table-cell-padding-x-sm: .75rem;
$table-border-color: $gray-200;
// ---------------------------------------------------------------------------
// Buttons
// ---------------------------------------------------------------------------
$btn-padding-y: .625rem;
$btn-padding-x: 1.25rem;
$btn-font-size: .875rem;
$btn-font-weight: 700;
$btn-padding-y-sm: .5rem;
$btn-padding-x-sm: 2rem;
$btn-font-size-sm: .75rem;
$btn-padding-y-lg: .875rem;
$btn-padding-x-lg: 4rem;
$btn-font-size-lg: .875rem;
$btn-box-shadow: 0 4px 6px rgba(50, 50, 93, .06), 0 1px 3px rgba(0, 0, 0, .05);
$btn-focus-box-shadow: 0 7px 14px rgba(50, 50, 93, .06), 0 3px 6px rgba(0, 0, 0, .05);
$btn-active-box-shadow: none;
$btn-border-radius: $border-radius-md;
$btn-border-radius-sm: .25rem;
$btn-border-radius-lg: .75rem;
$btn-transition: all .15s ease-in;
$btn-close-color: $gray-700;
// ---------------------------------------------------------------------------
// Forms
// ---------------------------------------------------------------------------
$form-label-font-size: .75rem;
$form-label-font-weight: 700;
$form-label-color: $dark;
$input-padding-y: .5rem;
$input-padding-x: .75rem;
$input-font-size: .875rem;
$input-line-height: 1.4rem;
$input-color: $gray-700;
$input-border-color: $gray-300;
$input-box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03);
$input-border-radius: $border-radius-md;
$input-border-radius-sm: $border-radius-md;
$input-border-radius-lg: $border-radius-md;
$input-focus-border-color: $primary;
$input-focus-width: 2px;
$input-focus-box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 129, 172, .10);
$input-placeholder-color: $gray-500;
$form-switch-color: rgba(0, 0, 0, 1);
$form-switch-bg-image: none;
$form-switch-focus-bg-image: none;
$form-switch-checked-bg-image: none;
$form-feedback-valid-color: $nord14;
$form-feedback-invalid-color: $nord11;
$input-group-addon-color: $dark;
$input-group-addon-bg: #fff;
$form-select-border-radius: $border-radius-md;
// ---------------------------------------------------------------------------
// Navs & Navbar
// ---------------------------------------------------------------------------
$nav-tabs-border-radius: $border-radius-md;
$nav-pills-border-radius: 0.75rem;
$nav-pills-link-active-color: $white;
$nav-pills-link-active-bg: $dark;
$navbar-dark-color: rgba(#fff, .85);
$navbar-dark-hover-color: rgba(#fff, .75);
$navbar-light-color: $white;
$navbar-light-hover-color: rgba($white, .7);
$navbar-light-active-color: rgba($white, .9);
$navbar-light-disabled-color: rgba($white, .3);
// ---------------------------------------------------------------------------
// Tooltips, Popovers, Toasts
// ---------------------------------------------------------------------------
$tooltip-border-radius: $border-radius-md;
$popover-font-size: $font-size-xs;
$popover-border-width: 0;
$popover-border-radius: .75rem;
$toast-border-width: 0;
$toast-border-color: transparent;
$toast-border-radius: $border-radius-md;
$toast-header-color: $h-color;
// ---------------------------------------------------------------------------
// Modals
// ---------------------------------------------------------------------------
$modal-content-border-radius: .75rem;
// ---------------------------------------------------------------------------
// Alerts
// ---------------------------------------------------------------------------
$alert-border-radius: $border-radius-md;
// ---------------------------------------------------------------------------
// Progress
// ---------------------------------------------------------------------------
$progress-height: 8px;
$progress-border-radius: $border-radius-md;
// ---------------------------------------------------------------------------
// List Group
// ---------------------------------------------------------------------------
$list-group-border-radius: $border-radius-md;
// ---------------------------------------------------------------------------
// Breadcrumbs
// ---------------------------------------------------------------------------
$breadcrumb-border-radius: $border-radius-md;
// ---------------------------------------------------------------------------
// Pagination
// ---------------------------------------------------------------------------
$pagination-border-radius: 50%;
$pagination-padding-x: 3px;
$pagination-padding-y: 3px;
$pagination-font-size: $btn-font-size;
// ---------------------------------------------------------------------------
// Card
// ---------------------------------------------------------------------------
$card-box-shadow: var(--#{$prefix}box-shadow-lg);
$card-inner-border-radius: var(--#{$prefix}card-border-radius);
$card-border-color: var(--#{$prefix}gray-200);

View File

@@ -0,0 +1,100 @@
/*! ********************************************
Bocken Theme for webtrees
Nord color palette with dark/light mode
************************************************/
@use "sass:color";
// ---------------------------------------------------------------------------
// Nord Color Palette
// ---------------------------------------------------------------------------
$nord0: #2E3440;
$nord1: #3B4252;
$nord2: #434C5E;
$nord3: #4C566A;
$nord4: #D8DEE9;
$nord5: #E5E9F0;
$nord6: #ECEFF4;
$nord7: #8FBCBB;
$nord8: #88C0D0;
$nord9: #81A1C1;
$nord10: #5E81AC;
$nord11: #BF616A;
$nord12: #D08770;
$nord13: #EBCB8B;
$nord14: #A3BE8C;
$nord15: #B48EAD;
// ---------------------------------------------------------------------------
// Light Mode Base Colors (cream background)
// ---------------------------------------------------------------------------
$font-color: #555 !default;
$h-color: #2a2a2a !default;
$white: #fff;
$gray-100: #f8f6f1; // cream base
$gray-200: #efecea;
$gray-300: #e8e5e1;
$gray-400: #dfdcd8;
$gray-500: #aaa;
$gray-600: #888;
$gray-700: #555;
$gray-800: #333;
$gray-900: #2a2a2a;
$black: #000;
$blue: $nord10;
$indigo: $nord10;
$purple: $nord15;
$pink: #f3a4b5;
$red: $nord11;
$orange: $nord12;
$yellow: $nord13;
$green: $nord14;
$teal: $nord8;
$cyan: $nord7;
// ---------------------------------------------------------------------------
// Genealogy Sex Colors (Nord-adapted)
// ---------------------------------------------------------------------------
$f-color: $nord11 !default;
$m-color: $nord8 !default;
$u-color: $nord3 !default;
$x-color: $nord13 !default;
$f-background: color.scale($f-color, $lightness: 90%) !default;
$m-background: color.scale($m-color, $lightness: 90%) !default;
$u-background: $gray-100 !default;
$x-background: color.scale($x-color, $lightness: 90%) !default;
// ---------------------------------------------------------------------------
// Layout Options
// ---------------------------------------------------------------------------
$compact-header: false !default;
$header-breakpoint: xxl;
// ---------------------------------------------------------------------------
// Extended Font Sizes
// ---------------------------------------------------------------------------
$font-size-xxs: .65rem !default;
$font-size-xs: .75rem !default;
// ---------------------------------------------------------------------------
// Extended Box Shadows
// ---------------------------------------------------------------------------
$box-shadow-xs: 0 1px 3px rgba(0,0,0,0.08), 0 1px rgba(0,0,0,0.02) !default;
$box-shadow-xl: 0 23px 45px -11px rgba(0, 0, 0, .15) !default;
// ---------------------------------------------------------------------------
// Extended Border Radii
// ---------------------------------------------------------------------------
$border-radius-xs: .125rem !default;
$border-radius-md: .5rem !default;
$border-radius-section: 10rem !default;

21
src/scss/fonts.scss Normal file
View File

@@ -0,0 +1,21 @@
/*! ********************************************
Bocken Theme for webtrees
************************************************/
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300 800;
font-stretch: 75% 100%;
font-display: swap;
src: url('../fonts/OpenSans[wdth\,wght].woff2') format('woff2-variations');
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300 800;
font-stretch: 75% 100%;
font-display: swap;
src: url('../fonts/OpenSans-Italic[wdth\,wght].woff2') format('woff2-variations');
}

View File

@@ -0,0 +1,28 @@
/*! ********************************************
Argon Light Theme
Originally made by jchue
Under the Internet Systems Consortium License
https://github.com/jchue/argon-webtrees-theme
Forked and maintained by Evan Galli
Under the same licence
https://github.com/06Games/Webtrees-ArgonLight
************************************************/
$collapse_mode: true !default;
@if $collapse_mode {
#sidebar-header-_sosa20_{
display: unset !important;
}
#sidebar-content-_sosa20_.collapse:not(.show) {
display: none;
}
}
#sidebar-content-_sosa20_{
#wt-sidebar-sosa1{
border: none !important;
}
}

35
src/scss/magicsunday.scss Normal file
View File

@@ -0,0 +1,35 @@
/*! ********************************************
Argon Light Theme
Originally made by jchue
Under the Internet Systems Consortium License
https://github.com/jchue/argon-webtrees-theme
Forked and maintained by Evan Galli
Under the same licence
https://github.com/06Games/Webtrees-ArgonLight
************************************************/
.wt-route-webtrees {
&-pedigree-chart,
&-descendants-chart,
&-fan-chart {
.icon {
width: auto;
height: auto;
svg {
font-size: 1rem;
}
}
}
&-fan-chart {
& > div.tooltip table {
border-collapse: initial;
th {
background: none;
}
}
}
}

7
src/scss/main.scss Normal file
View File

@@ -0,0 +1,7 @@
/*! ********************************************
Bocken Theme for webtrees
************************************************/
@use "theme.scss";
@use "gustine-sosa.scss";
@use "magicsunday.scss";

1731
src/scss/theme.scss Normal file

File diff suppressed because it is too large Load Diff

1
version.txt Normal file
View File

@@ -0,0 +1 @@
1.0.0

41
vite.config.js Normal file
View File

@@ -0,0 +1,41 @@
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig(({ command, mode }) => {
const isProd = mode === 'production';
return {
base: './',
css: {
preprocessorOptions: {
scss: {
quietDeps: true,
},
}
},
build: {
outDir: 'resources',
emptyOutDir: false,
sourcemap: 'inline',
minify: isProd,
esbuild: {
legalComments: 'inline',
},
rollupOptions: {
input: {
'theme': path.resolve(__dirname, 'src/scss/main.scss'),
'fonts': path.resolve(__dirname, 'src/scss/fonts.scss'),
// imports.css references webtrees vendor path — build separately when deployed
// 'imports': path.resolve(__dirname, 'src/css/imports.css'),
},
output: {
entryFileNames: `js/[name].js`,
chunkFileNames: `js/[name].js`,
assetFileNames: '[ext]/[name].[ext]'
}
}
}
};
});