Redesigns themes page
parent
251cfa35f3
commit
969875460f
|
|
@ -284,6 +284,115 @@ EOT;
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get descriptive metadata for a theme.
|
||||
*
|
||||
* @param string $themeId
|
||||
* @return array{name:string,description:string,version:string,author:string,tags:array}
|
||||
*/
|
||||
public static function getThemeMetadata(string $themeId): array
|
||||
{
|
||||
static $cache = [];
|
||||
if (isset($cache[$themeId])) {
|
||||
return $cache[$themeId];
|
||||
}
|
||||
|
||||
$config = self::getConfig();
|
||||
$defaults = $config['default_config'] ?? [];
|
||||
$availableEntry = $config['available_themes'][$themeId] ?? null;
|
||||
|
||||
$metadata = [
|
||||
'name' => is_array($availableEntry) ? ($availableEntry['name'] ?? ucfirst($themeId)) : ($availableEntry ?? ucfirst($themeId)),
|
||||
'description' => $defaults['description'] ?? '',
|
||||
'version' => $defaults['version'] ?? '',
|
||||
'author' => $defaults['author'] ?? '',
|
||||
'tags' => [],
|
||||
'type' => $themeId === 'default' ? 'Core built-in' : 'Custom',
|
||||
'path' => $themeId === 'default' ? 'app/templates' : ('themes/' . $themeId),
|
||||
'last_modified' => null,
|
||||
'file_count' => null
|
||||
];
|
||||
|
||||
if (is_array($availableEntry)) {
|
||||
$metadata = array_merge($metadata, array_intersect_key($availableEntry, array_flip(['name', 'description', 'version', 'author', 'tags'])));
|
||||
}
|
||||
|
||||
if ($themeId !== 'default') {
|
||||
$themesDir = rtrim($config['paths']['themes'] ?? (__DIR__ . '/../../themes'), '/');
|
||||
$themeConfigPath = $themesDir . '/' . $themeId . '/config.php';
|
||||
if (file_exists($themeConfigPath)) {
|
||||
$themeConfig = require $themeConfigPath;
|
||||
if (is_array($themeConfig)) {
|
||||
$metadata = array_merge($metadata, array_intersect_key($themeConfig, array_flip(['name', 'description', 'version', 'author', 'tags'])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($metadata['description'])) {
|
||||
$metadata['description'] = $defaults['description'] ?? 'A Jilo Web theme';
|
||||
}
|
||||
if (empty($metadata['version'])) {
|
||||
$metadata['version'] = $defaults['version'] ?? '1.0.0';
|
||||
}
|
||||
if (empty($metadata['author'])) {
|
||||
$metadata['author'] = $defaults['author'] ?? 'Lindeas';
|
||||
}
|
||||
|
||||
if (empty($metadata['tags']) || !is_array($metadata['tags'])) {
|
||||
$metadata['tags'] = [];
|
||||
}
|
||||
|
||||
$paths = $config['paths'] ?? [];
|
||||
if ($themeId === 'default') {
|
||||
$absolutePath = realpath($paths['templates'] ?? (__DIR__ . '/../templates')) ?: null;
|
||||
} else {
|
||||
$absolutePath = self::getThemePath($themeId);
|
||||
}
|
||||
|
||||
if ($absolutePath && is_dir($absolutePath)) {
|
||||
[$lastModified, $fileCount] = self::getDirectoryStats($absolutePath);
|
||||
if ($lastModified !== null) {
|
||||
$metadata['last_modified'] = $lastModified;
|
||||
}
|
||||
if ($fileCount > 0) {
|
||||
$metadata['file_count'] = $fileCount;
|
||||
}
|
||||
}
|
||||
|
||||
return $cache[$themeId] = $metadata;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate directory statistics for a theme folder.
|
||||
*/
|
||||
private static function getDirectoryStats(string $path): array
|
||||
{
|
||||
$latest = null;
|
||||
$count = 0;
|
||||
|
||||
try {
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS)
|
||||
);
|
||||
foreach ($iterator as $fileInfo) {
|
||||
if (!$fileInfo->isFile()) {
|
||||
continue;
|
||||
}
|
||||
$count++;
|
||||
$mtime = $fileInfo->getMTime();
|
||||
if ($latest === null || $mtime > $latest) {
|
||||
$latest = $mtime;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return [null, 0];
|
||||
}
|
||||
|
||||
return [$latest, $count];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the URL for a theme asset
|
||||
*
|
||||
|
|
|
|||
|
|
@ -51,11 +51,20 @@ if (isset($_GET['switch_to'])) {
|
|||
$themes = \App\Helpers\Theme::getAvailableThemes();
|
||||
$currentTheme = \App\Helpers\Theme::getCurrentThemeName();
|
||||
|
||||
// Prepare theme data with screenshot URLs for the view
|
||||
// Prepare theme data with screenshot URLs and metadata for the view
|
||||
$themeData = [];
|
||||
foreach ($themes as $id => $name) {
|
||||
$meta = \App\Helpers\Theme::getThemeMetadata($id);
|
||||
$themeData[$id] = [
|
||||
'name' => $name,
|
||||
'name' => $meta['name'] ?? $name,
|
||||
'description' => $meta['description'] ?? '',
|
||||
'version' => $meta['version'] ?? '',
|
||||
'author' => $meta['author'] ?? '',
|
||||
'tags' => $meta['tags'] ?? [],
|
||||
'type' => $meta['type'] ?? '',
|
||||
'path' => $meta['path'] ?? '',
|
||||
'last_modified' => $meta['last_modified'] ?? null,
|
||||
'file_count' => $meta['file_count'] ?? null,
|
||||
'screenshotUrl' => \App\Helpers\Theme::getAssetUrl($id, 'screenshot.png'),
|
||||
'isActive' => $id === $currentTheme
|
||||
];
|
||||
|
|
|
|||
|
|
@ -10,35 +10,124 @@
|
|||
* - isActive: Whether this is the current theme
|
||||
*/
|
||||
?>
|
||||
<div class="container mt-4">
|
||||
<h2>Theme switcher</h2>
|
||||
<p class="text-muted">Select a theme to change the appearance of the application.</p>
|
||||
<div class="row mt-4">
|
||||
<?php foreach ($themes as $themeId => $theme): ?>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100 <?= $theme['isActive'] ? 'border-primary' : '' ?>">
|
||||
<!-- Theme screenshot -->
|
||||
<div class="theme-screenshot" style="height: 150px; background-size: cover; background-position: center; background-color: #f8f9fa; <?= $theme['screenshotUrl'] ? 'background-image: url(' . htmlspecialchars($theme['screenshotUrl']) . ')' : '' ?>">
|
||||
<?php if (!$theme['screenshotUrl']): ?>
|
||||
<div class="h-100 d-flex align-items-center justify-content-center text-muted">No preview available</div>
|
||||
<?php
|
||||
$activeThemeName = 'Default';
|
||||
foreach ($themes as $themeData) {
|
||||
if (!empty($themeData['isActive'])) {
|
||||
$activeThemeName = $themeData['name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
$totalThemes = count($themes);
|
||||
?>
|
||||
|
||||
<section class="tm-directory tm-theme-directory">
|
||||
<div class="tm-hero-card tm-hero-card--stacked">
|
||||
<div class="tm-hero-head">
|
||||
<div class="tm-hero-body">
|
||||
<div class="tm-hero-heading">
|
||||
<h1 class="tm-hero-title">Themes</h1>
|
||||
<p class="tm-hero-subtitle">Personalize <?= htmlspecialchars($config['site_name']); ?> with custom visual styles.</p>
|
||||
</div>
|
||||
<div class="tm-hero-meta">
|
||||
<span class="tm-hero-pill pill-neutral">
|
||||
<i class="fas fa-layer-group"></i>
|
||||
<?= $totalThemes ?> available
|
||||
</span>
|
||||
<span class="tm-hero-pill pill-primary">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
Active: <?= htmlspecialchars($activeThemeName) ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tm-theme-gallery">
|
||||
<div class="tm-theme-grid">
|
||||
<?php foreach ($themes as $themeId => $theme):
|
||||
$isActive = !empty($theme['isActive']);
|
||||
$screenshot = $theme['screenshotUrl'];
|
||||
?>
|
||||
<article class="tm-theme-card<?= $isActive ? ' is-active' : '' ?>">
|
||||
<div class="tm-theme-preview" style="<?= $screenshot ? 'background-image: url(' . htmlspecialchars($screenshot) . ')' : '' ?>">
|
||||
<?php if (!$screenshot): ?>
|
||||
<span>No preview available</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if ($theme['isActive']): ?>
|
||||
<div class="card-header bg-primary text-white">Current theme</div>
|
||||
<div class="tm-theme-body">
|
||||
<div class="tm-theme-heading">
|
||||
<p class="tm-theme-id">ID: <code><?= htmlspecialchars($themeId) ?></code></p>
|
||||
<h3 class="tm-theme-name"><?= htmlspecialchars($theme['name']) ?></h3>
|
||||
</div>
|
||||
<?php if (!empty($theme['description'])): ?>
|
||||
<p class="tm-theme-description">
|
||||
<?= htmlspecialchars($theme['description']) ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title"><?= htmlspecialchars($theme['name']) ?></h5>
|
||||
<p class="card-text text-muted">Theme ID: <code><?= htmlspecialchars($themeId) ?></code></p>
|
||||
<div class="mt-auto">
|
||||
<?php if (!$theme['isActive']): ?>
|
||||
<a href="?page=theme&switch_to=<?= urlencode($themeId) ?>&csrf_token=<?= $csrf_token ?>" class="btn btn-primary">Switch to this theme</a>
|
||||
<dl class="tm-theme-meta">
|
||||
<?php if (!empty($theme['version'])): ?>
|
||||
<div class="tm-theme-meta-item">
|
||||
<dt>Version</dt>
|
||||
<dd><?= htmlspecialchars($theme['version']) ?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($theme['author'])): ?>
|
||||
<div class="tm-theme-meta-item">
|
||||
<dt>Author</dt>
|
||||
<dd><?= htmlspecialchars($theme['author']) ?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</dl>
|
||||
<?php if (!empty($theme['tags'])): ?>
|
||||
<ul class="tm-theme-tags">
|
||||
<?php foreach ($theme['tags'] as $tag): $tagLabel = trim((string)$tag); if ($tagLabel === '') { continue; } ?>
|
||||
<li><?= htmlspecialchars($tagLabel) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
<dl class="tm-theme-stats">
|
||||
<?php if (!empty($theme['type'])): ?>
|
||||
<div class="tm-theme-stat">
|
||||
<dt>Type</dt>
|
||||
<dd><?= htmlspecialchars($theme['type']) ?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($theme['file_count'])): ?>
|
||||
<div class="tm-theme-stat">
|
||||
<dt>Files</dt>
|
||||
<dd><?= number_format((int)$theme['file_count']) ?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($theme['path'])): ?>
|
||||
<div class="tm-theme-stat">
|
||||
<dt>Location</dt>
|
||||
<dd><code><?= htmlspecialchars($theme['path']) ?></code></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($theme['last_modified'])):
|
||||
$lastEdited = is_numeric($theme['last_modified']) ? date('M j, Y', (int)$theme['last_modified']) : $theme['last_modified'];
|
||||
?>
|
||||
<div class="tm-theme-stat">
|
||||
<dt>Last edit</dt>
|
||||
<dd><?= htmlspecialchars($lastEdited) ?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</dl>
|
||||
<div class="tm-theme-actions">
|
||||
<?php if ($isActive): ?>
|
||||
<button class="btn btn-outline-secondary" disabled>
|
||||
<i class="fas fa-check"></i> Active
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<button class="btn btn-outline-secondary" disabled>Currently active</button>
|
||||
<a class="btn btn-primary" href="?page=theme&switch_to=<?= urlencode($themeId) ?>&csrf_token=<?= $csrf_token ?>">
|
||||
<i class="fas fa-paint-brush"></i> Apply
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,210 @@
|
|||
.tm-theme-gallery {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.tm-theme-header {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tm-theme-eyebrow {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.2em;
|
||||
font-size: 0.75rem;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.tm-theme-title {
|
||||
margin: 0;
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.tm-theme-subtitle {
|
||||
margin: 0.5rem 0 0;
|
||||
color: #475569;
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.tm-theme-current {
|
||||
background: rgba(15, 23, 42, 0.03);
|
||||
border: 1px solid rgba(148, 163, 184, 0.3);
|
||||
border-radius: 1rem;
|
||||
padding: 1rem 1.5rem;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.tm-theme-current-label {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.21em;
|
||||
font-size: 0.7rem;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.tm-theme-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.tm-theme-card {
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
box-shadow: 0 25px 45px rgba(15, 23, 42, 0.06);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tm-theme-card.is-active {
|
||||
border-color: rgba(37, 99, 235, 0.5);
|
||||
box-shadow: 0 35px 60px rgba(37, 99, 235, 0.15);
|
||||
}
|
||||
|
||||
.tm-theme-preview {
|
||||
position: relative;
|
||||
padding-top: 58%;
|
||||
background-color: rgba(148, 163, 184, 0.15);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.tm-theme-preview span {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #64748b;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.tm-theme-body {
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.tm-theme-heading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.tm-theme-id {
|
||||
margin: 0 0 0.25rem;
|
||||
font-size: 0.85rem;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.tm-theme-name {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.tm-theme-description {
|
||||
margin: 0;
|
||||
color: #475569;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.tm-theme-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tm-theme-meta-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.tm-theme-meta-item dt {
|
||||
margin: 0;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.2em;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.tm-theme-meta-item dd {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.tm-theme-tags {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: -0.25rem 0 0.25rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tm-theme-tags li {
|
||||
background: rgba(37, 99, 235, 0.08);
|
||||
color: #1d4ed8;
|
||||
font-size: 0.8rem;
|
||||
padding: 0.25rem 0.65rem;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.tm-theme-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
||||
gap: 0.25rem;
|
||||
/* margin: 0.25rem 0 0;*/
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tm-theme-stat {
|
||||
background: rgba(15, 23, 42, 0.03);
|
||||
border: 1px solid rgba(148, 163, 184, 0.25);
|
||||
border-radius: 0.5rem;
|
||||
/* padding: 0.75rem 1rem;*/
|
||||
padding: 0.3rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* gap: 0.2rem;*/
|
||||
}
|
||||
|
||||
.tm-theme-stat dt {
|
||||
margin: 0;
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.2em;
|
||||
text-transform: uppercase;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.tm-theme-stat dd {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.tm-theme-actions .btn {
|
||||
width: 100%;
|
||||
}
|
||||
html, body {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
|
|
@ -86,6 +293,46 @@ html, body {
|
|||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.tm-hero-controls {
|
||||
display: inline-flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.85rem;
|
||||
background: rgba(148, 163, 184, 0.15);
|
||||
padding: 0.25rem;
|
||||
border-radius: 999px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.tm-toggle-pill {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #475569;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
padding: 0.4rem 1.05rem;
|
||||
border-radius: 999px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.tm-toggle-pill i {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.tm-toggle-pill:is(:hover, :focus-visible) {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.tm-toggle-pill.is-active {
|
||||
background: #1d4ed8;
|
||||
color: #fff;
|
||||
box-shadow: 0 10px 25px rgba(30, 64, 175, 0.25);
|
||||
}
|
||||
|
||||
.tm-hero-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -504,18 +751,40 @@ html, body {
|
|||
/* Dashboard widgets */
|
||||
.tm-widget-card {
|
||||
background: rgba(255, 255, 255, 0.97);
|
||||
border-radius: 1.25rem;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 20px 45px rgba(15, 23, 42, 0.08);
|
||||
padding: 1.75rem;
|
||||
/* padding: 1.75rem;*/
|
||||
margin-bottom: 1.75rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tm-widget-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
align-items: center;
|
||||
/* gap: 0.85rem;*/
|
||||
background: linear-gradient(135deg, #eef2ff 0%, #e0f2fe 100%);
|
||||
/* border-radius: 1rem;*/
|
||||
padding: 0.25rem;
|
||||
/* margin: -0.25rem -0.25rem 1.25rem;*/
|
||||
margin: -0.25rem -0.25rem 0rem;
|
||||
}
|
||||
|
||||
.tm-widget-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* gap: 0.75rem;*/
|
||||
}
|
||||
|
||||
.tm-widget-title-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 16px;
|
||||
/* background: rgba(15, 23, 42, 0.08);*/
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #0f172a;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.tm-widget-eyebrow {
|
||||
|
|
@ -531,13 +800,18 @@ html, body {
|
|||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 1.35rem;
|
||||
/* font-size: 1.35rem;*/
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
/* gap: 0.4rem;*/
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.tm-widget-title a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tm-widget-title:focus {
|
||||
|
|
@ -583,7 +857,9 @@ html, body {
|
|||
|
||||
.tm-widget-body {
|
||||
border-top: 1px solid rgba(148, 163, 184, 0.2);
|
||||
padding-top: 1rem;
|
||||
/* padding-top: 1rem;*/
|
||||
padding: 1rem;
|
||||
/* margin-top: 0.75rem;*/
|
||||
}
|
||||
|
||||
.tm-widget-table thead {
|
||||
|
|
@ -848,6 +1124,7 @@ html, body {
|
|||
border: 1px solid rgba(148, 163, 184, 0.5);
|
||||
border-radius: 0.85rem;
|
||||
padding: 0.55rem 0.85rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tm-filter-input input {
|
||||
|
|
@ -872,9 +1149,51 @@ html, body {
|
|||
gap: 1rem;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
height: 2.25rem;
|
||||
/* margin-bottom: 1rem;*/
|
||||
}
|
||||
|
||||
/*.tm-call-filter-row--compact {*/
|
||||
/* align-items: center;*/
|
||||
/* gap: 0.65rem;*/
|
||||
/* height: auto;*/
|
||||
/*}*/
|
||||
|
||||
.tm-call-filter-row--compact .tm-filter-field {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.tm-call-filter-row--compact .tm-filter-input {
|
||||
width: auto;
|
||||
padding: 0.4rem 0.85rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tm-call-filter-row--compact .tm-filter-input select,
|
||||
.tm-call-filter-row--compact .tm-filter-input input[type="date"] {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.tm-call-filter-row--compact .tm-filter-input--tagged {
|
||||
padding: 0.35rem 0.6rem;
|
||||
}
|
||||
|
||||
.tm-call-filter-row--compact .tm-filter-input--tagged input[type="date"] {
|
||||
width: auto;
|
||||
text-transform: uppercase;
|
||||
font-weight: lighter;
|
||||
font-variant: all-small-caps;
|
||||
}
|
||||
|
||||
.tm-call-filter-actions--compact {
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
gap: 0.6rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tm-call-filter-row:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
@ -884,6 +1203,11 @@ html, body {
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
.tm-call-filter-row.tm-call-filter-row--compact .tm-filter-field {
|
||||
flex: 0 0 auto;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tm-call-filter-row {
|
||||
flex-wrap: wrap;
|
||||
|
|
@ -902,7 +1226,8 @@ html, body {
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.2rem 0.6rem;
|
||||
border-radius: 999px;
|
||||
/* border-radius: 999px;*/
|
||||
border-radius: 10px;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
|
|
@ -913,18 +1238,52 @@ html, body {
|
|||
.tm-directory-filter-form .form-select,
|
||||
.tm-directory-filter-form select {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.tm-directory-filter-form .select2-container {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.tm-directory-filter-form .form-select:focus,
|
||||
.tm-directory-filter-form select:focus,
|
||||
.tm-directory-filter-form .select2-selection:focus,
|
||||
.tm-directory-filter-form .select2-selection--single:focus,
|
||||
.tm-directory-filter-form .select2-selection--multiple:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tm-directory-filter-form .select2-selection,
|
||||
.tm-directory-filter-form .select2-container--default .select2-selection--single,
|
||||
.tm-directory-filter-form .select2-container--default .select2-selection--multiple {
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
min-height: auto;
|
||||
padding-left: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tm-directory-filter-form .select2-selection__rendered {
|
||||
padding-left: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.tm-directory-filter-form .select2-selection__choice {
|
||||
background: rgba(37, 99, 235, 0.08);
|
||||
border: none;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.tm-filter-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tm-directory-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -1036,6 +1395,7 @@ html, body {
|
|||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 0.3rem 0.65rem;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tm-directory-field {
|
||||
|
|
@ -1057,6 +1417,227 @@ html, body {
|
|||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.tm-directory-match-flag {
|
||||
position: absolute;
|
||||
top: 0.4rem;
|
||||
right: 0.4rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: rgba(37, 99, 235, 0.12);
|
||||
color: #1d4ed8;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.tm-notification-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
/* margin-top: 1.5rem;*/
|
||||
}
|
||||
|
||||
.tm-notification-card {
|
||||
background: #fff;
|
||||
border: 1px solid rgba(148, 163, 184, 0.25);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1.25rem;
|
||||
box-shadow: 0 20px 45px rgba(15, 23, 42, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.85rem;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tm-notification-card.is-unread {
|
||||
border-color: rgba(37, 99, 235, 0.35);
|
||||
}
|
||||
|
||||
.tm-notification-card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.tm-notification-card-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tm-notification-subject {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.tm-notification-status-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: rgba(148, 163, 184, 0.6);
|
||||
display: inline-block;
|
||||
margin-right: 0.45rem;
|
||||
}
|
||||
|
||||
.tm-notification-card.is-unread .tm-notification-status-dot {
|
||||
background: #1d4ed8;
|
||||
}
|
||||
|
||||
.tm-notification-badge {
|
||||
border-radius: 999px;
|
||||
padding: 0.4rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.tm-notification-badge.badge-unread {
|
||||
background: rgba(37, 99, 235, 0.12);
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.tm-notification-badge.badge-read {
|
||||
background: rgba(148, 163, 184, 0.15);
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.tm-notification-meta {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.tm-notification-meta i {
|
||||
margin-right: 0.3rem;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.tm-notification-card-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.tm-notification-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.15rem 0.7rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.tm-notification-pill.is-read {
|
||||
background: rgba(148, 163, 184, 0.25);
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.tm-notification-pill.is-unread {
|
||||
background: rgba(248, 113, 113, 0.25);
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
/*.tm-notification-view {*/
|
||||
/* margin-top: 1.5rem;*/
|
||||
/*}*/
|
||||
|
||||
.tm-notification-view-card {
|
||||
background: #fff;
|
||||
border: 1px solid rgba(148, 163, 184, 0.25);
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 30px 60px rgba(15, 23, 42, 0.12);
|
||||
padding: 1.75rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.tm-notification-view-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tm-notification-view-label {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.18em;
|
||||
font-size: 0.75rem;
|
||||
color: #94a3b8;
|
||||
margin: 0 0 0.35rem;
|
||||
}
|
||||
|
||||
.tm-notification-view-title {
|
||||
margin: 0;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.tm-notification-view-body {
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.7;
|
||||
color: #1e293b;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.tm-notification-view-body p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tm-empty-state,
|
||||
.tm-notification-empty {
|
||||
padding: 3rem 1.5rem;
|
||||
text-align: center;
|
||||
border: 2px dashed rgba(148, 163, 184, 0.45);
|
||||
border-radius: 1.25rem;
|
||||
background: rgba(248, 250, 252, 0.9);
|
||||
/* margin: 1.5rem auto 0;*/
|
||||
margin: 0 auto 0;
|
||||
max-width: 640px;
|
||||
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.tm-empty-state > i,
|
||||
.tm-notification-empty > i {
|
||||
font-size: 3rem;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 0.9rem;
|
||||
}
|
||||
|
||||
.tm-empty-state h3,
|
||||
.tm-notification-empty h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.5rem;
|
||||
color: #0f172a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tm-empty-state p,
|
||||
.tm-notification-empty p {
|
||||
margin: 0 auto;
|
||||
max-width: 460px;
|
||||
color: #475569;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tm-empty-state-actions {
|
||||
margin-top: 1.25rem;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.tm-directory-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
@ -1176,6 +1757,7 @@ html, body {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tm-call-card-actions .btn {
|
||||
|
|
@ -1183,6 +1765,17 @@ html, body {
|
|||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.tm-call-card {
|
||||
grid-template-columns: minmax(220px, 1fr) minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.tm-call-card-actions {
|
||||
grid-column: 1 / -1;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.tm-call-card {
|
||||
grid-template-columns: 1fr;
|
||||
|
|
@ -1656,7 +2249,7 @@ html, body {
|
|||
|
||||
.dashboard-stats-row {
|
||||
margin-bottom: 1.5rem;
|
||||
justify-content: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue