Отзывы и предложения к софту от AleXStam
  • Страница 1 из 1
  • 1
Поговорим о...
s
{{/* layouts/shortcodes/cardinfo.html */}}
{{ $link := .Get "link" }}
{{ $title := .Get "title" }}
{{ $desc := .Get "desc" | default "" }}
{{ $date := .Get "date" }}
{{ $audience := .Get "audience" }}
{{ $tag := .Get "tag" }}
{{ $tagtype := .Get "tagtype" | default "default" }}
{{ $icon := .Get "icon" }}
{{ $iconsize := .Get "iconsize" | default "24px" }}
{{ $visible := .Get "visible" | default "true" }}
{{ $style := .Get "style" | default "button" }}

{{ if ne $visible "false" }}
<div class="info-card info-card-style-{{ $style }}">
<a href="{{ $link }}" class="info-card-link" target="_blank" rel="noopener">
<div class="info-card-header">
<div class="info-card-left">
{{ if $icon }}
<span class="info-card-title-icon">
{{ if .Site.Data.icons }}
{{ $iconData := index .Site.Data.icons $icon }}
{{ if $iconData }}
{{ $iconSvg := replaceRE "<svg" (printf "<svg width='%s' height='%s'" $iconsize $iconsize) $iconData }}
<span class="custom-icon">{{ $iconSvg | safeHTML }}</span>
{{ else }}
<span class="custom-icon">
<svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
</svg>
</span>
{{ end }}
{{ else }}
<span class="custom-icon">
<svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
</svg>
</span>
{{ end }}
</span>
{{ end }}
<span class="info-card-title">{{ $title }}</span>
</div>
<div class="info-card-right-section">
{{ if $tag }}
<span class="info-card-tag info-card-tag-{{ $tagtype }}">
{{ $tag }}
</span>
{{ end }}

{{ if $desc }}
<div class="info-card-tooltip-wrapper">
<span class="info-card-icon">
<svg class="info-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 13A6 6 0 1 1 8 2a6 6 0 0 1 0 12z"/>
<path d="M7 11h2v2H7zm0-8h2v6H7z"/>
</svg>
</span>
<div class="info-card-tooltip">
<div class="tooltip-description">{{ $desc | safeHTML }}</div>
{{ if or $date $audience }}
<div class="tooltip-details">
{{ if $date }}
<div class="tooltip-row">
<svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"/>
</svg>
<span class="tooltip-row-text">Добавлено: {{ $date }}</span>
</div>
{{ end }}
{{ if $audience }}
<div class="tooltip-row">
<svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/>
</svg>
<span class="tooltip-row-text">Для кого: {{ $audience }}</span>
</div>
{{ end }}
</div>
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
</a>
</div>
{{ end }}

<style>
/* ===== ОСНОВНЫЕ СТИЛИ ===== */
.info-card {
transition: all 0.2s ease;
position: relative;
box-sizing: border-box;
display: inline-block;
vertical-align: top;
margin: 0.75rem 0 0 0;
padding: 0;
width: 100%;
}

.info-card-link {
display: block;
text-decoration: none;
color: #333333;
width: 100%;
height: 100%;
box-sizing: border-box;
}

.info-card-link:hover {
text-decoration: none;
}

.info-card-header {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 24px;
width: 100%;
gap: 12px; /* Увеличил gap для лучшего разделения */
}

.info-card-left {
display: flex;
align-items: center;
flex: 1 1 auto;
min-width: 0; /* Позволяет тексту сжиматься */
padding-right: 8px;
gap: 10px;
}

.info-card-title-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}

.info-card-title-icon .custom-icon {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 0;
}

.info-card-title-icon .custom-icon svg {
width: 100%;
height: 100%;
fill: currentColor;
color: #666666;
vertical-align: middle;
}

.info-card-title {
font-size: 1rem;
font-weight: 600;
color: #333333;
line-height: 1.4;
flex: 1 1 auto;
min-width: 30px;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}

.info-card-right-section {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
flex-wrap: nowrap; /* Запрещаем перенос внутри правой секции */
}

/* ===== КОНТЕЙНЕР ДЛЯ СЕТКИ ===== */
.cards-grid {
display: flex;
flex-wrap: wrap;
margin: 0;
gap: 20px;
}

.cards-grid .info-card {
flex: 0 0 auto;
padding: 0;
margin: 0.75rem 0 0 0;
box-sizing: border-box;
}

/* 1 колонка */
.cards-grid.cols-1 .info-card {
width: calc(100%);
min-width: 350px;
}

/* 2 колонки */
.cards-grid.cols-2 .info-card {
width: calc(50% - 10px);
min-width: 350px;
}

/* 3 колонки */
.cards-grid.cols-3 .info-card {
width: calc(33.333% - 13.333px);
min-width: 350px;
}

/* 4 колонки */
.cards-grid.cols-4 .info-card {
width: calc(25% - 15px);
min-width: 350px;
}

/* Когда карточка переносится на новую строку из-за min-width */
@media (max-width: 750px) {
.cards-grid.cols-2 .info-card,
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: 100%; /* На всю ширину при переносе */
min-width: auto;
}
}

/* Промежуточные состояния для 4 колонок */
@media (max-width: 1100px) and (min-width: 901px) {
.cards-grid.cols-4 .info-card {
width: calc(50% - 10px); /* 2 колонки */
}
}

/* Промежуточные состояния для 3 и 4 колонок */
@media (max-width: 900px) and (min-width: 751px) {
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: calc(50% - 10px); /* 2 колонки */
}
}

/* ===== СТИЛЬ: BUTTON (по умолчанию) ===== */
.info-card-style-button {
border: 1px solid #e0e0e0;
border-radius: 8px;
background: #ffffff;
}

.info-card-style-button:hover {
border-color: #4a90e2;
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.15);
}

.info-card-style-button .info-card-link {
padding: 1rem 1.25rem;
}

/* ===== СТИЛЬ: LINK (простая ссылка с отступами) ===== */
.info-card-style-link {
border: none;
background: transparent;
border-radius: 0;
}

.info-card-style-link .info-card-link {
padding: 0.5rem 0.25rem;
border-bottom: 1px solid transparent;
}

.info-card-style-link:hover .info-card-link {
border-bottom-color: #4a90e2;
}

.info-card-style-link .info-card-title {
font-weight: 500;
}

.info-card-style-link .info-card-title-icon .custom-icon svg {
color: #4a90e2;
opacity: 0.8;
}

/* ===== СТИЛЬ: MINIMAL (минималистичный) ===== */
.info-card-style-minimal {
border: none;
background: transparent;
}

.info-card-style-minimal .info-card-link {
padding: 0.3rem 0;
}

.info-card-style-minimal .info-card-title {
font-weight: 400;
font-size: 0.95rem;
}

.info-card-style-minimal .info-card-title-icon .custom-icon svg {
color: #888;
width: 18px;
height: 18px;
}

.info-card-style-minimal:hover .info-card-title {
color: #4a90e2;
}

/* ===== СТИЛЬ: CARD (карточка с тенью) ===== */
.info-card-style-card {
border: none;
border-radius: 12px;
background: #ffffff;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
transition: all 0.3s ease;
}

.info-card-style-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

.info-card-style-card .info-card-link {
padding: 1.25rem 1.5rem;
}

.info-card-style-card .info-card-title {
font-size: 1.1rem;
}

/* ===== СТИЛЬ: PILL (в виде пилюли) ===== */
.info-card-style-pill {
border: 1px solid #e0e0e0;
border-radius: 50px;
background: #f8f9fa;
}

.info-card-style-pill:hover {
border-color: #4a90e2;
background: #ffffff;
}

.info-card-style-pill .info-card-link {
padding: 0.6rem 1.25rem;
}

.info-card-style-pill .info-card-title {
font-weight: 500;
font-size: 0.95rem;
}

/* ===== ТЕГИ ===== */
.info-card-tag {
display: inline-block;
font-size: 0.75rem;
padding: 0.2rem 0.6rem;
border-radius: 12px;
font-weight: 500;
white-space: nowrap; /* Запрещаем перенос текста в теге */
border: 1px solid transparent;
line-height: 1.4;
flex-shrink: 0;
}

.info-card-tag-default { background: #f0f0f0; color: #666666; border-color: #ddd; }
.info-card-tag-info { background: #e3f2fd; color: #1565c0; border-color: #90caf9; }
.info-card-tag-warning { background: #fff3e0; color: #e65100; border-color: #ffcc80; }
.info-card-tag-success { background: #e8f5e9; color: #2e7d32; border-color: #a5d6a7; }
.info-card-tag-error { background: #ffebee; color: #c62828; border-color: #ef9a9a; }

/* ===== ТУЛТИПЫ ===== */
.info-card-tooltip-wrapper {
position: relative;
display: flex;
align-items: center;
flex-shrink: 0;
}

.info-card-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
cursor: help;
color: #666666;
border-radius: 50%;
transition: all 0.15s;
flex-shrink: 0;
}

.info-card-icon:hover {
background: #f0f0f0;
color: #4a90e2;
}

.info-icon {
width: 16px;
height: 16px;
}

.info-card-tooltip {
position: absolute;
bottom: calc(100% + 10px);
right: 50%;
transform: translateX(50%);
width: 420px;
max-width: 90vw;
padding: 18px;
background: white;
border-radius: 8px;
box-shadow: 0 5px 25px rgba(0,0,0,0.15);
border: 1px solid #e0e0e0;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: auto;
}

.info-card-tooltip.tooltip-bottom {
bottom: auto;
top: calc(100% + 10px);
}

.info-card-tooltip-wrapper:hover .info-card-tooltip {
opacity: 1;
visibility: visible;
transform: translateX(50%) translateY(-5px);
}

.info-card-tooltip.tooltip-bottom:hover {
transform: translateX(50%) translateY(5px);
}

.info-card-tooltip::before {
content: '';
position: absolute;
bottom: -7px;
left: 50%;
transform: translateX(-50%) rotate(45deg);
width: 14px;
height: 14px;
background: white;
border-right: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
z-index: -1;
}

.info-card-tooltip.tooltip-bottom::before {
bottom: auto;
top: -7px;
transform: translateX(-50%) rotate(-135deg);
}

.tooltip-description {
font-size: 0.92rem;
line-height: 1.6;
color: #444444;
margin-bottom: 14px;
max-height: 350px;
overflow-y: auto;
padding-right: 6px;
}

.tooltip-description::-webkit-scrollbar { width: 6px; }
.tooltip-description::-webkit-scrollbar-track { background: #f5f5f5; border-radius: 3px; }
.tooltip-description::-webkit-scrollbar-thumb { background: #b0b0b0; border-radius: 3px; }

.tooltip-details {
font-size: 0.85rem;
color: #666666;
border-top: 1px solid #f0f0f0;
padding-top: 12px;
}

.tooltip-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}

.tooltip-row:last-child {
margin-bottom: 0;
}

.tooltip-row-icon {
flex-shrink: 0;
width: 14px;
height: 14px;
}

.tooltip-row-text {
line-height: 1.4;
}

/* ===== ТЕМНАЯ ТЕМА ===== */
body.dark .info-card,
html.dark .info-card {
background: #2d3748;
border-color: #4a5568;
}

body.dark .info-card:hover,
html.dark .info-card:hover {
border-color: #63b3ed;
box-shadow: 0 2px 8px rgba(99, 179, 237, 0.2);
}

body.dark .info-card-link,
html.dark .info-card-link {
color: #e2e8f0;
}

body.dark .info-card-title,
html.dark .info-card-title {
color: #f7fafc;
}

body.dark .info-card-title-icon .custom-icon svg,
html.dark .info-card-title-icon .custom-icon svg {
color: #a0aec0;
}

body.dark .info-card-icon,
html.dark .info-card-icon {
color: #a0aec0;
}

body.dark .info-card-icon:hover,
html.dark .info-card-icon:hover {
background: #4a5568;
color: #63b3ed;
}

body.dark .info-card-tooltip,
html.dark .info-card-tooltip {
background: #1a202c;
border-color: #4a5568;
box-shadow: 0 5px 25px rgba(0,0,0,0.5);
}

body.dark .info-card-tooltip::before,
html.dark .info-card-tooltip::before {
background: #1a202c;
border-color: #4a5568;
}

body.dark .tooltip-description,
html.dark .tooltip-description {
color: #e2e8f0;
}

body.dark .tooltip-details,
html.dark .tooltip-details {
color: #a0aec0;
border-top-color: #4a5568;
}

body.dark .info-card-tag-default,
html.dark .info-card-tag-default {
background: #4a5568;
color: #cbd5e0;
border-color: #718096;
}

body.dark .info-card-tag-info,
html.dark .info-card-tag-info {
background: #2c5282;
color: #bee3f8;
border-color: #3182ce;
}

body.dark .info-card-tag-warning,
html.dark .info-card-tag-warning {
background: #975a16;
color: #feebc8;
border-color: #d69e2e;
}

body.dark .info-card-tag-success,
html.dark .info-card-tag-success {
background: #276749;
color: #c6f6d5;
border-color: #38a169;
}

body.dark .info-card-tag-error,
html.dark .info-card-tag-error {
background: #9b2c2c;
color: #fed7d7;
border-color: #e53e3e;
}

body.dark .info-card-style-link,
html.dark .info-card-style-link,
body.dark .info-card-style-minimal,
html.dark .info-card-style-minimal {
background: transparent;
}

body.dark .info-card-style-card,
html.dark .info-card-style-card {
background: #2d3748;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}

body.dark .info-card-style-pill,
html.dark .info-card-style-pill {
background: #2d3748;
border-color: #4a5568;
}

body.dark .info-card-style-pill:hover,
html.dark .info-card-style-pill:hover {
background: #374151;
}

/* ===== АДАПТИВНОСТЬ ===== */
@media (max-width: 768px) {
.info-card-header {
flex-direction: row; /* Оставляем в строку на мобильных */
flex-wrap: wrap; /* Разрешаем перенос если нужно */
gap: 8px;
}

.info-card-left {
width: auto; /* Автоматическая ширина */
flex: 1 1 auto;
padding-right: 0;
}

.info-card-right-section {
width: auto; /* Автоматическая ширина */
flex-shrink: 0;
}

/* Если на мобильных совсем мало места - переносим правую секцию */
@media (max-width: 480px) {
.info-card-header {
flex-wrap: wrap;
}

.info-card-right-section {
width: 100%;
justify-content: flex-start;
margin-top: 4px;
}
}

.info-card-tooltip {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) !important;
width: calc(100vw - 40px);
max-width: 500px;
max-height: 70vh;
overflow-y: auto;
bottom: auto !important;
right: auto !important;
}

.info-card-tooltip::before {
display: none;
}

.info-card {
margin: 0.5rem 0 0 0;
}

.info-card-link {
padding: 0.75rem 1rem;
}

.cards-grid {
gap: 15px;
}

.cards-grid .info-card {
margin: 0.5rem 0 0 0;
}
}

@media (max-width: 480px) {
.info-card-tooltip {
width: calc(100vw - 20px);
padding: 14px;
}

.info-card-left {
gap: 8px;
}

.info-card {
margin: 0.4rem 0 0 0;
}

.cards-grid {
gap: 10px;
}

.cards-grid .info-card {
margin: 0.4rem 0 0 0;
}
}

@media (min-width: 1200px) {
.info-card-tooltip {
width: 450px;
}
}

@media (min-width: 1400px) {
.info-card-tooltip {
width: 480px;
}
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
var wrappers = document.querySelectorAll('.info-card-tooltip-wrapper');

wrappers.forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
var icon = wrapper.querySelector('.info-card-icon');

if (!tooltip || !icon) return;

function adjustTooltipPosition() {
if (window.innerWidth <= 768) return;

var wrapperRect = wrapper.getBoundingClientRect();
var tooltipRect = tooltip.getBoundingClientRect();
var viewportHeight = window.innerHeight;

var spaceAbove = wrapperRect.top;
var spaceBelow = viewportHeight - wrapperRect.bottom;
var tooltipHeight = tooltipRect.height;

if (spaceAbove < tooltipHeight + 20 && spaceBelow >= tooltipHeight + 20) {
tooltip.classList.add('tooltip-bottom');
} else {
tooltip.classList.remove('tooltip-bottom');
}
}

wrapper.addEventListener('mouseenter', function() {
adjustTooltipPosition();
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
});

wrapper.addEventListener('mouseleave', function() {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});

wrapper.addEventListener('click', function(e) {
if (window.innerWidth <= 768) {
e.preventDefault();
e.stopPropagation();

if (tooltip.style.opacity === '1') {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
} else {
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
}
}
});
});

var scrollTimer;
window.addEventListener('scroll', function() {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(function() {
document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
if (tooltip && tooltip.style.opacity === '1') {
tooltip.classList.remove('tooltip-bottom');
setTimeout(function() {
var wrapperRect = wrapper.getBoundingClientRect();
var tooltipRect = tooltip.getBoundingClientRect();
var viewportHeight = window.innerHeight;
var spaceAbove = wrapperRect.top;
var spaceBelow = viewportHeight - wrapperRect.bottom;

if (spaceAbove < tooltipRect.height + 20 && spaceBelow >= tooltipRect.height + 20) {
tooltip.classList.add('tooltip-bottom');
}
}, 10);
}
});
}, 50);
});

window.addEventListener('resize', function() {
document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
if (tooltip && tooltip.style.opacity === '1') {
tooltip.style.opacity = '0';
setTimeout(function() {
tooltip.style.opacity = '1';
}, 10);
}
});
});

document.addEventListener('click', function(e) {
if (!e.target.closest('.info-card-tooltip-wrapper')) {
document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});
}
});

document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});
}
});
});
</script>
{{/* layouts/shortcodes/cardinfo.html */}}
{{ $link := .Get "link" }}
{{ $title := .Get "title" }}
{{ $desc := .Get "desc" | default "" }}
{{ $date := .Get "date" }}
{{ $audience := .Get "audience" }}
{{ $tag := .Get "tag" }}
{{ $tagtype := .Get "tagtype" | default "default" }}
{{ $icon := .Get "icon" }}
{{ $iconsize := .Get "iconsize" | default "24px" }}
{{ $visible := .Get "visible" | default "true" }}
{{ $style := .Get "style" | default "button" }}

{{ if ne $visible "false" }}
<div class="info-card info-card-style-{{ $style }}">
<a href="{{ $link }}" class="info-card-link" target="_blank" rel="noopener">
<div class="info-card-header">
<div class="info-card-left">
{{ if $icon }}
<span class="info-card-title-icon">
{{ if .Site.Data.icons }}
{{ $iconData := index .Site.Data.icons $icon }}
{{ if $iconData }}
{{ $iconSvg := replaceRE "<svg" (printf "<svg width='%s' height='%s'" $iconsize $iconsize) $iconData }}
<span class="custom-icon">{{ $iconSvg | safeHTML }}</span>
{{ else }}
<span class="custom-icon">
<svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
</svg>
</span>
{{ end }}
{{ else }}
<span class="custom-icon">
<svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
</svg>
</span>
{{ end }}
</span>
{{ end }}
<span class="info-card-title">{{ $title }}</span>
</div>
<div class="info-card-right-section">
{{ if $tag }}
<span class="info-card-tag info-card-tag-{{ $tagtype }}">
{{ $tag }}
</span>
{{ end }}

{{ if $desc }}
<div class="info-card-tooltip-wrapper">
<span class="info-card-icon">
<svg class="info-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 13A6 6 0 1 1 8 2a6 6 0 0 1 0 12z"/>
<path d="M7 11h2v2H7zm0-8h2v6H7z"/>
</svg>
</span>
<div class="info-card-tooltip">
<div class="tooltip-description">{{ $desc | safeHTML }}</div>
{{ if or $date $audience }}
<div class="tooltip-details">
{{ if $date }}
<div class="tooltip-row">
<svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"/>
</svg>
<span class="tooltip-row-text">Добавлено: {{ $date }}</span>
</div>
{{ end }}
{{ if $audience }}
<div class="tooltip-row">
<svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/>
</svg>
<span class="tooltip-row-text">Для кого: {{ $audience }}</span>
</div>
{{ end }}
</div>
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
</a>
</div>
{{ end }}

<style>
/* ===== ОСНОВНЫЕ СТИЛИ ===== */
.info-card {
transition: all 0.2s ease;
position: relative;
box-sizing: border-box;
display: inline-block;
vertical-align: top;
margin: 0.75rem 0 0 0;
padding: 0;
width: 100%;
}

.info-card-link {
display: block;
text-decoration: none;
color: #333333;
width: 100%;
height: 100%;
box-sizing: border-box;
}

.info-card-link:hover {
text-decoration: none;
}

.info-card-header {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 24px;
width: 100%;
gap: 12px;
}

.info-card-left {
display: flex;
align-items: center;
flex: 1 1 auto;
min-width: 0;
padding-right: 8px;
gap: 10px;
}

.info-card-title-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}

.info-card-title-icon .custom-icon {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 0;
}

.info-card-title-icon .custom-icon svg {
width: 100%;
height: 100%;
fill: currentColor;
color: #666666;
vertical-align: middle;
}

.info-card-title {
font-size: 1rem;
font-weight: 600;
color: #333333;
line-height: 1.4;
flex: 1 1 auto;
min-width: 30px;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}

.info-card-right-section {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
flex-wrap: nowrap;
}

/* ===== КОНТЕЙНЕР ДЛЯ СЕТКИ ===== */
.cards-grid {
display: flex;
flex-wrap: wrap;
margin: 0;
gap: 20px;
}

.cards-grid .info-card {
flex: 0 0 auto;
padding: 0;
margin: 0.75rem 0 0 0;
box-sizing: border-box;
}

/* 1 колонка - всегда на всю ширину */
.cards-grid.cols-1 .info-card {
width: 100%;
}

/* 2 колонки */
.cards-grid.cols-2 .info-card {
width: calc(50% - 10px);
}

/* 3 колонки */
.cards-grid.cols-3 .info-card {
width: calc(33.333% - 13.333px);
}

/* 4 колонки */
.cards-grid.cols-4 .info-card {
width: calc(25% - 15px);
}

/* Минимальная ширина карточки - принудительный перенос */
@media (max-width: 750px) {
.cards-grid.cols-2 .info-card,
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: 100%; /* На всю ширину на мобильных */
}
}

/* Промежуточные состояния */
@media (max-width: 1100px) and (min-width: 751px) {
/* 4 колонки превращаются в 2 */
.cards-grid.cols-4 .info-card {
width: calc(50% - 10px);
}
}

@media (max-width: 900px) and (min-width: 751px) {
/* 3 колонки превращаются в 2 */
.cards-grid.cols-3 .info-card {
width: calc(50% - 10px);
}
}

/* Отступы для первой карточки в ряду */
.cards-grid .info-card:nth-child(1),
.cards-grid .info-card:nth-child(2),
.cards-grid .info-card:nth-child(3),
.cards-grid .info-card:nth-child(4) {
margin-top: 0; /* Убираем отступ у первого ряда */
}

/* ===== СТИЛЬ: BUTTON (по умолчанию) ===== */
.info-card-style-button {
border: 1px solid #e0e0e0;
border-radius: 8px;
background: #ffffff;
}

.info-card-style-button:hover {
border-color: #4a90e2;
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.15);
}

.info-card-style-button .info-card-link {
padding: 1rem 1.25rem;
}

/* ===== СТИЛЬ: LINK (простая ссылка с отступами) ===== */
.info-card-style-link {
border: none;
background: transparent;
border-radius: 0;
}

.info-card-style-link .info-card-link {
padding: 0.5rem 0.25rem;
border-bottom: 1px solid transparent;
}

.info-card-style-link:hover .info-card-link {
border-bottom-color: #4a90e2;
}

.info-card-style-link .info-card-title {
font-weight: 500;
}

.info-card-style-link .info-card-title-icon .custom-icon svg {
color: #4a90e2;
opacity: 0.8;
}

/* ===== СТИЛЬ: MINIMAL (минималистичный) ===== */
.info-card-style-minimal {
border: none;
background: transparent;
}

.info-card-style-minimal .info-card-link {
padding: 0.3rem 0;
}

.info-card-style-minimal .info-card-title {
font-weight: 400;
font-size: 0.95rem;
}

.info-card-style-minimal .info-card-title-icon .custom-icon svg {
color: #888;
width: 18px;
height: 18px;
}

.info-card-style-minimal:hover .info-card-title {
color: #4a90e2;
}

/* ===== СТИЛЬ: CARD (карточка с тенью) ===== */
.info-card-style-card {
border: none;
border-radius: 12px;
background: #ffffff;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
transition: all 0.3s ease;
}

.info-card-style-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

.info-card-style-card .info-card-link {
padding: 1.25rem 1.5rem;
}

.info-card-style-card .info-card-title {
font-size: 1.1rem;
}

/* ===== СТИЛЬ: PILL (в виде пилюли) ===== */
.info-card-style-pill {
border: 1px solid #e0e0e0;
border-radius: 50px;
background: #f8f9fa;
}

.info-card-style-pill:hover {
border-color: #4a90e2;
background: #ffffff;
}

.info-card-style-pill .info-card-link {
padding: 0.6rem 1.25rem;
}

.info-card-style-pill .info-card-title {
font-weight: 500;
font-size: 0.95rem;
}

/* ===== ТЕГИ ===== */
.info-card-tag {
display: inline-block;
font-size: 0.75rem;
padding: 0.2rem 0.6rem;
border-radius: 12px;
font-weight: 500;
white-space: nowrap;
border: 1px solid transparent;
line-height: 1.4;
flex-shrink: 0;
}

.info-card-tag-default { background: #f0f0f0; color: #666666; border-color: #ddd; }
.info-card-tag-info { background: #e3f2fd; color: #1565c0; border-color: #90caf9; }
.info-card-tag-warning { background: #fff3e0; color: #e65100; border-color: #ffcc80; }
.info-card-tag-success { background: #e8f5e9; color: #2e7d32; border-color: #a5d6a7; }
.info-card-tag-error { background: #ffebee; color: #c62828; border-color: #ef9a9a; }

/* ===== ТУЛТИПЫ ===== */
.info-card-tooltip-wrapper {
position: relative;
display: flex;
align-items: center;
flex-shrink: 0;
}

.info-card-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
cursor: help;
color: #666666;
border-radius: 50%;
transition: all 0.15s;
flex-shrink: 0;
}

.info-card-icon:hover {
background: #f0f0f0;
color: #4a90e2;
}

.info-icon {
width: 16px;
height: 16px;
}

.info-card-tooltip {
position: absolute;
bottom: calc(100% + 10px);
right: 50%;
transform: translateX(50%);
width: 420px;
max-width: 90vw;
padding: 18px;
background: white;
border-radius: 8px;
box-shadow: 0 5px 25px rgba(0,0,0,0.15);
border: 1px solid #e0e0e0;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: auto;
}

.info-card-tooltip.tooltip-bottom {
bottom: auto;
top: calc(100% + 10px);
}

.info-card-tooltip-wrapper:hover .info-card-tooltip {
opacity: 1;
visibility: visible;
transform: translateX(50%) translateY(-5px);
}

.info-card-tooltip.tooltip-bottom:hover {
transform: translateX(50%) translateY(5px);
}

.info-card-tooltip::before {
content: '';
position: absolute;
bottom: -7px;
left: 50%;
transform: translateX(-50%) rotate(45deg);
width: 14px;
height: 14px;
background: white;
border-right: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
z-index: -1;
}

.info-card-tooltip.tooltip-bottom::before {
bottom: auto;
top: -7px;
transform: translateX(-50%) rotate(-135deg);
}

.tooltip-description {
font-size: 0.92rem;
line-height: 1.6;
color: #444444;
margin-bottom: 14px;
max-height: 350px;
overflow-y: auto;
padding-right: 6px;
}

.tooltip-description::-webkit-scrollbar { width: 6px; }
.tooltip-description::-webkit-scrollbar-track { background: #f5f5f5; border-radius: 3px; }
.tooltip-description::-webkit-scrollbar-thumb { background: #b0b0b0; border-radius: 3px; }

.tooltip-details {
font-size: 0.85rem;
color: #666666;
border-top: 1px solid #f0f0f0;
padding-top: 12px;
}

.tooltip-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}

.tooltip-row:last-child {
margin-bottom: 0;
}

.tooltip-row-icon {
flex-shrink: 0;
width: 14px;
height: 14px;
}

.tooltip-row-text {
line-height: 1.4;
}

/* ===== ТЕМНАЯ ТЕМА ===== */
body.dark .info-card,
html.dark .info-card {
background: #2d3748;
border-color: #4a5568;
}

body.dark .info-card:hover,
html.dark .info-card:hover {
border-color: #63b3ed;
box-shadow: 0 2px 8px rgba(99, 179, 237, 0.2);
}

body.dark .info-card-link,
html.dark .info-card-link {
color: #e2e8f0;
}

body.dark .info-card-title,
html.dark .info-card-title {
color: #f7fafc;
}

body.dark .info-card-title-icon .custom-icon svg,
html.dark .info-card-title-icon .custom-icon svg {
color: #a0aec0;
}

body.dark .info-card-icon,
html.dark .info-card-icon {
color: #a0aec0;
}

body.dark .info-card-icon:hover,
html.dark .info-card-icon:hover {
background: #4a5568;
color: #63b3ed;
}

body.dark .info-card-tooltip,
html.dark .info-card-tooltip {
background: #1a202c;
border-color: #4a5568;
box-shadow: 0 5px 25px rgba(0,0,0,0.5);
}

body.dark .info-card-tooltip::before,
html.dark .info-card-tooltip::before {
background: #1a202c;
border-color: #4a5568;
}

body.dark .tooltip-description,
html.dark .tooltip-description {
color: #e2e8f0;
}

body.dark .tooltip-details,
html.dark .tooltip-details {
color: #a0aec0;
border-top-color: #4a5568;
}

body.dark .info-card-tag-default,
html.dark .info-card-tag-default {
background: #4a5568;
color: #cbd5e0;
border-color: #718096;
}

body.dark .info-card-tag-info,
html.dark .info-card-tag-info {
background: #2c5282;
color: #bee3f8;
border-color: #3182ce;
}

body.dark .info-card-tag-warning,
html.dark .info-card-tag-warning {
background: #975a16;
color: #feebc8;
border-color: #d69e2e;
}

body.dark .info-card-tag-success,
html.dark .info-card-tag-success {
background: #276749;
color: #c6f6d5;
border-color: #38a169;
}

body.dark .info-card-tag-error,
html.dark .info-card-tag-error {
background: #9b2c2c;
color: #fed7d7;
border-color: #e53e3e;
}

body.dark .info-card-style-link,
html.dark .info-card-style-link,
body.dark .info-card-style-minimal,
html.dark .info-card-style-minimal {
background: transparent;
}

body.dark .info-card-style-card,
html.dark .info-card-style-card {
background: #2d3748;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}

body.dark .info-card-style-pill,
html.dark .info-card-style-pill {
background: #2d3748;
border-color: #4a5568;
}

body.dark .info-card-style-pill:hover,
html.dark .info-card-style-pill:hover {
background: #374151;
}

/* ===== АДАПТИВНОСТЬ ===== */
@media (max-width: 768px) {
.info-card-header {
flex-direction: row;
flex-wrap: wrap;
gap: 8px;
}

.info-card-left {
width: auto;
flex: 1 1 auto;
padding-right: 0;
}

.info-card-right-section {
width: auto;
flex-shrink: 0;
}

@media (max-width: 480px) {
.info-card-header {
flex-wrap: wrap;
}

.info-card-right-section {
width: 100%;
justify-content: flex-start;
margin-top: 4px;
}
}

.info-card-tooltip {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) !important;
width: calc(100vw - 40px);
max-width: 500px;
max-height: 70vh;
overflow-y: auto;
bottom: auto !important;
right: auto !important;
}

.info-card-tooltip::before {
display: none;
}

.info-card {
margin: 0.5rem 0 0 0;
}

.info-card-link {
padding: 0.75rem 1rem;
}

.cards-grid {
gap: 15px;
}

.cards-grid .info-card {
margin: 0.5rem 0 0 0;
}

/* Убираем отступ у первого ряда на мобильных */
.cards-grid .info-card:nth-child(1) {
margin-top: 0;
}
}

@media (max-width: 480px) {
.info-card-tooltip {
width: calc(100vw - 20px);
padding: 14px;
}

.info-card-left {
gap: 8px;
}

.info-card {
margin: 0.4rem 0 0 0;
}

.cards-grid {
gap: 10px;
}

.cards-grid .info-card {
margin: 0.4rem 0 0 0;
}

.cards-grid .info-card:nth-child(1) {
margin-top: 0;
}
}

@media (min-width: 1200px) {
.info-card-tooltip {
width: 450px;
}
}

@media (min-width: 1400px) {
.info-card-tooltip {
width: 480px;
}
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
var wrappers = document.querySelectorAll('.info-card-tooltip-wrapper');

wrappers.forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
var icon = wrapper.querySelector('.info-card-icon');

if (!tooltip || !icon) return;

function adjustTooltipPosition() {
if (window.innerWidth <= 768) return;

var wrapperRect = wrapper.getBoundingClientRect();
var tooltipRect = tooltip.getBoundingClientRect();
var viewportHeight = window.innerHeight;

var spaceAbove = wrapperRect.top;
var spaceBelow = viewportHeight - wrapperRect.bottom;
var tooltipHeight = tooltipRect.height;

if (spaceAbove < tooltipHeight + 20 && spaceBelow >= tooltipHeight + 20) {
tooltip.classList.add('tooltip-bottom');
} else {
tooltip.classList.remove('tooltip-bottom');
}
}

wrapper.addEventListener('mouseenter', function() {
adjustTooltipPosition();
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
});

wrapper.addEventListener('mouseleave', function() {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});

wrapper.addEventListener('click', function(e) {
if (window.innerWidth <= 768) {
e.preventDefault();
e.stopPropagation();

if (tooltip.style.opacity === '1') {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
} else {
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
}
}
});
});

var scrollTimer;
window.addEventListener('scroll', function() {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(function() {
document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
if (tooltip && tooltip.style.opacity === '1') {
tooltip.classList.remove('tooltip-bottom');
setTimeout(function() {
var wrapperRect = wrapper.getBoundingClientRect();
var tooltipRect = tooltip.getBoundingClientRect();
var viewportHeight = window.innerHeight;
var spaceAbove = wrapperRect.top;
var spaceBelow = viewportHeight - wrapperRect.bottom;

if (spaceAbove < tooltipRect.height + 20 && spaceBelow >= tooltipRect.height + 20) {
tooltip.classList.add('tooltip-bottom');
}
}, 10);
}
});
}, 50);
});

window.addEventListener('resize', function() {
document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
if (tooltip && tooltip.style.opacity === '1') {
tooltip.style.opacity = '0';
setTimeout(function() {
tooltip.style.opacity = '1';
}, 10);
}
});
});

document.addEventListener('click', function(e) {
if (!e.target.closest('.info-card-tooltip-wrapper')) {
document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});
}
});

document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});
}
});
});
</script>

Добавлено (2026-03-10, 15:53)
---------------------------------------------
{{/* layouts/shortcodes/cardinfo.html */}}
{{ $link := .Get "link" }}
{{ $title := .Get "title" }}
{{ $desc := .Get "desc" | default "" }}
{{ $date := .Get "date" }}
{{ $audience := .Get "audience" }}
{{ $tag := .Get "tag" }}
{{ $tagtype := .Get "tagtype" | default "default" }}
{{ $icon := .Get "icon" }}
{{ $iconsize := .Get "iconsize" | default "24px" }}
{{ $visible := .Get "visible" | default "true" }}
{{ $style := .Get "style" | default "button" }}

{{ if ne $visible "false" }}
<div class="info-card info-card-style-{{ $style }}">
<a href="{{ $link }}" class="info-card-link" target="_blank" rel="noopener">
<div class="info-card-header">
<div class="info-card-left">
{{ if $icon }}
<span class="info-card-title-icon">
{{ if .Site.Data.icons }}
{{ $iconData := index .Site.Data.icons $icon }}
{{ if $iconData }}
{{ $iconSvg := replaceRE "<svg" (printf "<svg width='%s' height='%s'" $iconsize $iconsize) $iconData }}
<span class="custom-icon">{{ $iconSvg | safeHTML }}</span>
{{ else }}
<span class="custom-icon">
<svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
</svg>
</span>
{{ end }}
{{ else }}
<span class="custom-icon">
<svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
</svg>
</span>
{{ end }}
</span>
{{ end }}
<span class="info-card-title">{{ $title }}</span>
</div>
<div class="info-card-right-section">
{{ if $tag }}
<span class="info-card-tag info-card-tag-{{ $tagtype }}">
{{ $tag }}
</span>
{{ end }}

{{ if $desc }}
<div class="info-card-tooltip-wrapper">
<span class="info-card-icon">
<svg class="info-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 13A6 6 0 1 1 8 2a6 6 0 0 1 0 12z"/>
<path d="M7 11h2v2H7zm0-8h2v6H7z"/>
</svg>
</span>
<div class="info-card-tooltip">
<div class="tooltip-description">{{ $desc | safeHTML }}</div>
{{ if or $date $audience }}
<div class="tooltip-details">
{{ if $date }}
<div class="tooltip-row">
<svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"/>
</svg>
<span class="tooltip-row-text">Добавлено: {{ $date }}</span>
</div>
{{ end }}
{{ if $audience }}
<div class="tooltip-row">
<svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/>
</svg>
<span class="tooltip-row-text">Для кого: {{ $audience }}</span>
</div>
{{ end }}
</div>
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
</a>
</div>
{{ end }}

<style>
/* ===== ОСНОВНЫЕ СТИЛИ ===== */
.info-card {
transition: all 0.2s ease;
position: relative;
box-sizing: border-box;
display: inline-block;
vertical-align: top;
margin: 0;
padding: 0;
width: 100%;
}

.info-card-link {
display: block;
text-decoration: none;
color: #333333;
width: 100%;
height: 100%;
box-sizing: border-box;
}

.info-card-link:hover {
text-decoration: none;
}

.info-card-header {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 24px;
width: 100%;
gap: 12px;
}

.info-card-left {
display: flex;
align-items: center;
flex: 1 1 auto;
min-width: 0;
padding-right: 8px;
gap: 10px;
}

.info-card-title-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}

.info-card-title-icon .custom-icon {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 0;
}

.info-card-title-icon .custom-icon svg {
width: 100%;
height: 100%;
fill: currentColor;
color: #666666;
vertical-align: middle;
}

.info-card-title {
font-size: 1rem;
font-weight: 600;
color: #333333;
line-height: 1.4;
flex: 1 1 auto;
min-width: 30px;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}

.info-card-right-section {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
flex-wrap: nowrap;
}

/* ===== КОНТЕЙНЕР ДЛЯ СЕТКИ ===== */
.cards-grid {
display: flex;
flex-wrap: wrap;
margin: 0;
gap: 20px;
}

.cards-grid .info-card {
flex: 0 0 auto;
padding: 0;
margin: 20px 0 0 0; /* Отступ сверху у каждой карточки */
box-sizing: border-box;
}

/* Убираем отступ у карточек первого ряда */
.cards-grid .info-card:nth-child(-n+4) { /* До 4-х карточек в первом ряду */
margin-top: 0;
}

/* Для 1 колонки */
.cards-grid.cols-1 .info-card:nth-child(1) {
margin-top: 0;
}

/* Для 2 колонок - первый ряд это 2 карточки */
.cards-grid.cols-2 .info-card:nth-child(-n+2) {
margin-top: 0;
}

/* Для 3 колонок - первый ряд это 3 карточки */
.cards-grid.cols-3 .info-card:nth-child(-n+3) {
margin-top: 0;
}

/* Для 4 колонок - первый ряд это 4 карточки */
.cards-grid.cols-4 .info-card:nth-child(-n+4) {
margin-top: 0;
}

/* 1 колонка - всегда на всю ширину */
.cards-grid.cols-1 .info-card {
width: 100%;
}

/* 2 колонки */
.cards-grid.cols-2 .info-card {
width: calc(50% - 10px);
}

/* 3 колонки */
.cards-grid.cols-3 .info-card {
width: calc(33.333% - 13.333px);
}

/* 4 колонки */
.cards-grid.cols-4 .info-card {
width: calc(25% - 15px);
}

/* Промежуточные состояния */
@media (max-width: 1100px) and (min-width: 901px) {
/* 4 колонки превращаются в 2 */
.cards-grid.cols-4 .info-card {
width: calc(50% - 10px);
}
/* Корректируем первый ряд для нового количества колонок */
.cards-grid.cols-4 .info-card:nth-child(-n+2) {
margin-top: 0;
}
.cards-grid.cols-4 .info-card:nth-child(n+3) {
margin-top: 20px;
}
}

@media (max-width: 900px) and (min-width: 751px) {
/* 3 и 4 колонки превращаются в 2 */
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: calc(50% - 10px);
}
/* Корректируем первый ряд */
.cards-grid.cols-3 .info-card:nth-child(-n+2),
.cards-grid.cols-4 .info-card:nth-child(-n+2) {
margin-top: 0;
}
.cards-grid.cols-3 .info-card:nth-child(n+3),
.cards-grid.cols-4 .info-card:nth-child(n+3) {
margin-top: 20px;
}
}

@media (max-width: 750px) {
/* Все колонки становятся в 1 ряд */
.cards-grid.cols-2 .info-card,
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: 100%;
}
/* Только первая карточка без отступа */
.cards-grid.cols-2 .info-card:nth-child(1),
.cards-grid.cols-3 .info-card:nth-child(1),
.cards-grid.cols-4 .info-card:nth-child(1) {
margin-top: 0;
}
.cards-grid.cols-2 .info-card:nth-child(n+2),
.cards-grid.cols-3 .info-card:nth-child(n+2),
.cards-grid.cols-4 .info-card:nth-child(n+2) {
margin-top: 20px;
}
}

/* ===== СТИЛЬ: BUTTON (по умолчанию) ===== */
.info-card-style-button {
border: 1px solid #e0e0e0;
border-radius: 8px;
background: #ffffff;
}

.info-card-style-button:hover {
border-color: #4a90e2;
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.15);
}

.info-card-style-button .info-card-link {
padding: 1rem 1.25rem;
}

/* ===== СТИЛЬ: LINK (простая ссылка с отступами) ===== */
.info-card-style-link {
border: none;
background: transparent;
border-radius: 0;
}

.info-card-style-link .info-card-link {
padding: 0.5rem 0.25rem;
border-bottom: 1px solid transparent;
}

.info-card-style-link:hover .info-card-link {
border-bottom-color: #4a90e2;
}

.info-card-style-link .info-card-title {
font-weight: 500;
}

.info-card-style-link .info-card-title-icon .custom-icon svg {
color: #4a90e2;
opacity: 0.8;
}

/* ===== СТИЛЬ: MINIMAL (минималистичный) ===== */
.info-card-style-minimal {
border: none;
background: transparent;
}

.info-card-style-minimal .info-card-link {
padding: 0.3rem 0;
}

.info-card-style-minimal .info-card-title {
font-weight: 400;
font-size: 0.95rem;
}

.info-card-style-minimal .info-card-title-icon .custom-icon svg {
color: #888;
width: 18px;
height: 18px;
}

.info-card-style-minimal:hover .info-card-title {
color: #4a90e2;
}

/* ===== СТИЛЬ: CARD (карточка с тенью) ===== */
.info-card-style-card {
border: none;
border-radius: 12px;
background: #ffffff;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
transition: all 0.3s ease;
}

.info-card-style-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

.info-card-style-card .info-card-link {
padding: 1.25rem 1.5rem;
}

.info-card-style-card .info-card-title {
font-size: 1.1rem;
}

/* ===== СТИЛЬ: PILL (в виде пилюли) ===== */
.info-card-style-pill {
border: 1px solid #e0e0e0;
border-radius: 50px;
background: #f8f9fa;
}

.info-card-style-pill:hover {
border-color: #4a90e2;
background: #ffffff;
}

.info-card-style-pill .info-card-link {
padding: 0.6rem 1.25rem;
}

.info-card-style-pill .info-card-title {
font-weight: 500;
font-size: 0.95rem;
}

/* ===== ТЕГИ ===== */
.info-card-tag {
display: inline-block;
font-size: 0.75rem;
padding: 0.2rem 0.6rem;
border-radius: 12px;
font-weight: 500;
white-space: nowrap;
border: 1px solid transparent;
line-height: 1.4;
flex-shrink: 0;
}

.info-card-tag-default { background: #f0f0f0; color: #666666; border-color: #ddd; }
.info-card-tag-info { background: #e3f2fd; color: #1565c0; border-color: #90caf9; }
.info-card-tag-warning { background: #fff3e0; color: #e65100; border-color: #ffcc80; }
.info-card-tag-success { background: #e8f5e9; color: #2e7d32; border-color: #a5d6a7; }
.info-card-tag-error { background: #ffebee; color: #c62828; border-color: #ef9a9a; }

/* ===== ТУЛТИПЫ ===== */
.info-card-tooltip-wrapper {
position: relative;
display: flex;
align-items: center;
flex-shrink: 0;
}

.info-card-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
cursor: help;
color: #666666;
border-radius: 50%;
transition: all 0.15s;
flex-shrink: 0;
}

.info-card-icon:hover {
background: #f0f0f0;
color: #4a90e2;
}

.info-icon {
width: 16px;
height: 16px;
}

.info-card-tooltip {
position: absolute;
bottom: calc(100% + 10px);
right: 50%;
transform: translateX(50%);
width: 420px;
max-width: 90vw;
padding: 18px;
background: white;
border-radius: 8px;
box-shadow: 0 5px 25px rgba(0,0,0,0.15);
border: 1px solid #e0e0e0;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: auto;
}

.info-card-tooltip.tooltip-bottom {
bottom: auto;
top: calc(100% + 10px);
}

.info-card-tooltip-wrapper:hover .info-card-tooltip {
opacity: 1;
visibility: visible;
transform: translateX(50%) translateY(-5px);
}

.info-card-tooltip.tooltip-bottom:hover {
transform: translateX(50%) translateY(5px);
}

.info-card-tooltip::before {
content: '';
position: absolute;
bottom: -7px;
left: 50%;
transform: translateX(-50%) rotate(45deg);
width: 14px;
height: 14px;
background: white;
border-right: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
z-index: -1;
}

.info-card-tooltip.tooltip-bottom::before {
bottom: auto;
top: -7px;
transform: translateX(-50%) rotate(-135deg);
}

.tooltip-description {
font-size: 0.92rem;
line-height: 1.6;
color: #444444;
margin-bottom: 14px;
max-height: 350px;
overflow-y: auto;
padding-right: 6px;
}

.tooltip-description::-webkit-scrollbar { width: 6px; }
.tooltip-description::-webkit-scrollbar-track { background: #f5f5f5; border-radius: 3px; }
.tooltip-description::-webkit-scrollbar-thumb { background: #b0b0b0; border-radius: 3px; }

.tooltip-details {
font-size: 0.85rem;
color: #666666;
border-top: 1px solid #f0f0f0;
padding-top: 12px;
}

.tooltip-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}

.tooltip-row:last-child {
margin-bottom: 0;
}

.tooltip-row-icon {
flex-shrink: 0;
width: 14px;
height: 14px;
}

.tooltip-row-text {
line-height: 1.4;
}

/* ===== ТЕМНАЯ ТЕМА ===== */
body.dark .info-card,
html.dark .info-card {
background: #2d3748;
border-color: #4a5568;
}

body.dark .info-card:hover,
html.dark .info-card:hover {
border-color: #63b3ed;
box-shadow: 0 2px 8px rgba(99, 179, 237, 0.2);
}

body.dark .info-card-link,
html.dark .info-card-link {
color: #e2e8f0;
}

body.dark .info-card-title,
html.dark .info-card-title {
color: #f7fafc;
}

body.dark .info-card-title-icon .custom-icon svg,
html.dark .info-card-title-icon .custom-icon svg {
color: #a0aec0;
}

body.dark .info-card-icon,
html.dark .info-card-icon {
color: #a0aec0;
}

body.dark .info-card-icon:hover,
html.dark .info-card-icon:hover {
background: #4a5568;
color: #63b3ed;
}

body.dark .info-card-tooltip,
html.dark .info-card-tooltip {
background: #1a202c;
border-color: #4a5568;
box-shadow: 0 5px 25px rgba(0,0,0,0.5);
}

body.dark .info-card-tooltip::before,
html.dark .info-card-tooltip::before {
background: #1a202c;
border-color: #4a5568;
}

body.dark .tooltip-description,
html.dark .tooltip-description {
color: #e2e8f0;
}

body.dark .tooltip-details,
html.dark .tooltip-details {
color: #a0aec0;
border-top-color: #4a5568;
}

body.dark .info-card-tag-default,
html.dark .info-card-tag-default {
background: #4a5568;
color: #cbd5e0;
border-color: #718096;
}

body.dark .info-card-tag-info,
html.dark .info-card-tag-info {
background: #2c5282;
color: #bee3f8;
border-color: #3182ce;
}

body.dark .info-card-tag-warning,
html.dark .info-card-tag-warning {
background: #975a16;
color: #feebc8;
border-color: #d69e2e;
}

body.dark .info-card-tag-success,
html.dark .info-card-tag-success {
background: #276749;
color: #c6f6d5;
border-color: #38a169;
}

body.dark .info-card-tag-error,
html.dark .info-card-tag-error {
background: #9b2c2c;
color: #fed7d7;
border-color: #e53e3e;
}

body.dark .info-card-style-link,
html.dark .info-card-style-link,
body.dark .info-card-style-minimal,
html.dark .info-card-style-minimal {
background: transparent;
}

body.dark .info-card-style-card,
html.dark .info-card-style-card {
background: #2d3748;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}

body.dark .info-card-style-pill,
html.dark .info-card-style-pill {
background: #2d3748;
border-color: #4a5568;
}

body.dark .info-card-style-pill:hover,
html.dark .info-card-style-pill:hover {
background: #374151;
}

/* ===== АДАПТИВНОСТЬ ===== */
@media (max-width: 768px) {
.info-card-header {
flex-direction: row;
flex-wrap: wrap;
gap: 8px;
}

.info-card-left {
width: auto;
flex: 1 1 auto;
padding-right: 0;
}

.info-card-right-section {
width: auto;
flex-shrink: 0;
}

@media (max-width: 480px) {
.info-card-header {
flex-wrap: wrap;
}

.info-card-right-section {
width: 100%;
justify-content: flex-start;
margin-top: 4px;
}
}

.info-card-tooltip {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) !important;
width: calc(100vw - 40px);
max-width: 500px;
max-height: 70vh;
overflow-y: auto;
bottom: auto !important;
right: auto !important;
}

.info-card-tooltip::before {
display: none;
}

.info-card {
margin: 0;
}

.info-card-link {
padding: 0.75rem 1rem;
}

.cards-grid {
gap: 15px;
}

.cards-grid .info-card {
margin: 15px 0 0 0;
}

/* Корректируем отступы на мобильных */
.cards-grid .info-card:nth-child(1) {
margin-top: 0;
}
}

@media (max-width: 480px) {
.info-card-tooltip {
width: calc(100vw - 20px);
padding: 14px;
}

.info-card-left {
gap: 8px;
}

.info-card {
margin: 0;
}

.cards-grid {
gap: 10px;
}

.cards-grid .info-card {
margin: 10px 0 0 0;
}

.cards-grid .info-card:nth-child(1) {
margin-top: 0;
}
}

@media (min-width: 1200px) {
.info-card-tooltip {
width: 450px;
}
}

@media (min-width: 1400px) {
.info-card-tooltip {
width: 480px;
}
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
var wrappers = document.querySelectorAll('.info-card-tooltip-wrapper');

wrappers.forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
var icon = wrapper.querySelector('.info-card-icon');

if (!tooltip || !icon) return;

function adjustTooltipPosition() {
if (window.innerWidth <= 768) return;

var wrapperRect = wrapper.getBoundingClientRect();
var tooltipRect = tooltip.getBoundingClientRect();
var viewportHeight = window.innerHeight;

var spaceAbove = wrapperRect.top;
var spaceBelow = viewportHeight - wrapperRect.bottom;
var tooltipHeight = tooltipRect.height;

if (spaceAbove < tooltipHeight + 20 && spaceBelow >= tooltipHeight + 20) {
tooltip.classList.add('tooltip-bottom');
} else {
tooltip.classList.remove('tooltip-bottom');
}
}

wrapper.addEventListener('mouseenter', function() {
adjustTooltipPosition();
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
});

wrapper.addEventListener('mouseleave', function() {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});

wrapper.addEventListener('click', function(e) {
if (window.innerWidth <= 768) {
e.preventDefault();
e.stopPropagation();

if (tooltip.style.opacity === '1') {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
} else {
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
}
}
});
});

var scrollTimer;
window.addEventListener('scroll', function() {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(function() {
document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
if (tooltip && tooltip.style.opacity === '1') {
tooltip.classList.remove('tooltip-bottom');
setTimeout(function() {
var wrapperRect = wrapper.getBoundingClientRect();
var tooltipRect = tooltip.getBoundingClientRect();
var viewportHeight = window.innerHeight;
var spaceAbove = wrapperRect.top;
var spaceBelow = viewportHeight - wrapperRect.bottom;

if (spaceAbove < tooltipRect.height + 20 && spaceBelow >= tooltipRect.height + 20) {
tooltip.classList.add('tooltip-bottom');
}
}, 10);
}
});
}, 50);
});

window.addEventListener('resize', function() {
document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
if (tooltip && tooltip.style.opacity === '1') {
tooltip.style.opacity = '0';
setTimeout(function() {
tooltip.style.opacity = '1';
}, 10);
}
});
});

document.addEventListener('click', function(e) {
if (!e.target.closest('.info-card-tooltip-wrapper')) {
document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});
}
});

document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});
}
});
});
</script>

Добавлено (2026-03-10, 16:03)
---------------------------------------------
{{/* layouts/shortcodes/cardinfo.html */}}
{{ $link := .Get "link" }}
{{ $title := .Get "title" }}
{{ $desc := .Get "desc" | default "" }}
{{ $date := .Get "date" }}
{{ $audience := .Get "audience" }}
{{ $tag := .Get "tag" }}
{{ $tagtype := .Get "tagtype" | default "default" }}
{{ $icon := .Get "icon" }}
{{ $iconsize := .Get "iconsize" | default "24px" }}
{{ $visible := .Get "visible" | default "true" }}
{{ $style := .Get "style" | default "button" }}

{{ if ne $visible "false" }}
<div class="info-card info-card-style-{{ $style }}">
<a href="{{ $link }}" class="info-card-link" target="_blank" rel="noopener">
<div class="info-card-header">
<div class="info-card-left">
{{ if $icon }}
<span class="info-card-title-icon">
{{ if .Site.Data.icons }}
{{ $iconData := index .Site.Data.icons $icon }}
{{ if $iconData }}
{{ $iconSvg := replaceRE "<svg" (printf "<svg width='%s' height='%s'" $iconsize $iconsize) $iconData }}
<span class="custom-icon">{{ $iconSvg | safeHTML }}</span>
{{ else }}
<span class="custom-icon">
<svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
</svg>
</span>
{{ end }}
{{ else }}
<span class="custom-icon">
<svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
</svg>
</span>
{{ end }}
</span>
{{ end }}
<span class="info-card-title">{{ $title }}</span>
</div>
<div class="info-card-right-section">
{{ if $tag }}
<span class="info-card-tag info-card-tag-{{ $tagtype }}">
{{ $tag }}
</span>
{{ end }}

{{ if $desc }}
<div class="info-card-tooltip-wrapper">
<span class="info-card-icon">
<svg class="info-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 13A6 6 0 1 1 8 2a6 6 0 0 1 0 12z"/>
<path d="M7 11h2v2H7zm0-8h2v6H7z"/>
</svg>
</span>
<div class="info-card-tooltip">
<div class="tooltip-description">{{ $desc | safeHTML }}</div>
{{ if or $date $audience }}
<div class="tooltip-details">
{{ if $date }}
<div class="tooltip-row">
<svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"/>
</svg>
<span class="tooltip-row-text">Добавлено: {{ $date }}</span>
</div>
{{ end }}
{{ if $audience }}
<div class="tooltip-row">
<svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/>
</svg>
<span class="tooltip-row-text">Для кого: {{ $audience }}</span>
</div>
{{ end }}
</div>
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
</a>
</div>
{{ end }}

<style>
/* ===== ОСНОВНЫЕ СТИЛИ ===== */
.info-card {
transition: all 0.2s ease;
position: relative;
box-sizing: border-box;
display: inline-block;
vertical-align: top;
margin: 0.75rem 0 0 0; /* Отступ сверху для всех карточек */
padding: 0;
width: 100%;
}

.info-card:first-of-type {
margin-top: 0; /* Убираем отступ у самой первой карточки */
}

.info-card-link {
display: block;
text-decoration: none;
color: #333333;
width: 100%;
height: 100%;
box-sizing: border-box;
}

.info-card-link:hover {
text-decoration: none;
}

.info-card-header {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 24px;
width: 100%;
gap: 12px;
}

.info-card-left {
display: flex;
align-items: center;
flex: 1 1 auto;
min-width: 0;
padding-right: 8px;
gap: 10px;
}

.info-card-title-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}

.info-card-title-icon .custom-icon {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 0;
}

.info-card-title-icon .custom-icon svg {
width: 100%;
height: 100%;
fill: currentColor;
color: #666666;
vertical-align: middle;
}

.info-card-title {
font-size: 1rem;
font-weight: 600;
color: #333333;
line-height: 1.4;
flex: 1 1 auto;
min-width: 30px;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}

.info-card-right-section {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
flex-wrap: nowrap;
}

/* ===== КОНТЕЙНЕР ДЛЯ СЕТКИ ===== */
.cards-grid {
display: flex;
flex-wrap: wrap;
margin: 0;
gap: 20px;
}

.cards-grid .info-card {
flex: 0 0 auto;
padding: 0;
margin: 0; /* Убираем margin у карточек в сетке */
box-sizing: border-box;
}

/* 1 колонка */
.cards-grid.cols-1 .info-card {
width: 100%;
}

/* 2 колонки */
.cards-grid.cols-2 .info-card {
width: calc(50% - 10px);
}

/* 3 колонки */
.cards-grid.cols-3 .info-card {
width: calc(33.333% - 13.333px);
}

/* 4 колонки */
.cards-grid.cols-4 .info-card {
width: calc(25% - 15px);
}

/* Промежуточные состояния */
@media (max-width: 1100px) and (min-width: 901px) {
.cards-grid.cols-4 .info-card {
width: calc(50% - 10px);
}
}

@media (max-width: 900px) and (min-width: 751px) {
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: calc(50% - 10px);
}
}

@media (max-width: 750px) {
.cards-grid.cols-2 .info-card,
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: 100%;
}
}

/* ===== СТИЛЬ: BUTTON (по умолчанию) ===== */
.info-card-style-button {
border: 1px solid #e0e0e0;
border-radius: 8px;
background: #ffffff;
}

.info-card-style-button:hover {
border-color: #4a90e2;
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.15);
}

.info-card-style-button .info-card-link {
padding: 1rem 1.25rem;
}

/* ===== СТИЛЬ: LINK (простая ссылка с отступами) ===== */
.info-card-style-link {
border: none;
background: transparent;
border-radius: 0;
}

.info-card-style-link .info-card-link {
padding: 0.5rem 0.25rem;
border-bottom: 1px solid transparent;
}

.info-card-style-link:hover .info-card-link {
border-bottom-color: #4a90e2;
}

.info-card-style-link .info-card-title {
font-weight: 500;
}

.info-card-style-link .info-card-title-icon .custom-icon svg {
color: #4a90e2;
opacity: 0.8;
}

/* ===== СТИЛЬ: MINIMAL (минималистичный) ===== */
.info-card-style-minimal {
border: none;
background: transparent;
}

.info-card-style-minimal .info-card-link {
padding: 0.3rem 0;
}

.info-card-style-minimal .info-card-title {
font-weight: 400;
font-size: 0.95rem;
}

.info-card-style-minimal .info-card-title-icon .custom-icon svg {
color: #888;
width: 18px;
height: 18px;
}

.info-card-style-minimal:hover .info-card-title {
color: #4a90e2;
}

/* ===== СТИЛЬ: CARD (карточка с тенью) ===== */
.info-card-style-card {
border: none;
border-radius: 12px;
background: #ffffff;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
transition: all 0.3s ease;
}

.info-card-style-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

.info-card-style-card .info-card-link {
padding: 1.25rem 1.5rem;
}

.info-card-style-card .info-card-title {
font-size: 1.1rem;
}

/* ===== СТИЛЬ: PILL (в виде пилюли) ===== */
.info-card-style-pill {
border: 1px solid #e0e0e0;
border-radius: 50px;
background: #f8f9fa;
}

.info-card-style-pill:hover {
border-color: #4a90e2;
background: #ffffff;
}

.info-card-style-pill .info-card-link {
padding: 0.6rem 1.25rem;
}

.info-card-style-pill .info-card-title {
font-weight: 500;
font-size: 0.95rem;
}

/* ===== ТЕГИ ===== */
.info-card-tag {
display: inline-block;
font-size: 0.75rem;
padding: 0.2rem 0.6rem;
border-radius: 12px;
font-weight: 500;
white-space: nowrap;
border: 1px solid transparent;
line-height: 1.4;
flex-shrink: 0;
}

.info-card-tag-default { background: #f0f0f0; color: #666666; border-color: #ddd; }
.info-card-tag-info { background: #e3f2fd; color: #1565c0; border-color: #90caf9; }
.info-card-tag-warning { background: #fff3e0; color: #e65100; border-color: #ffcc80; }
.info-card-tag-success { background: #e8f5e9; color: #2e7d32; border-color: #a5d6a7; }
.info-card-tag-error { background: #ffebee; color: #c62828; border-color: #ef9a9a; }

/* ===== ТУЛТИПЫ ===== */
.info-card-tooltip-wrapper {
position: relative;
display: flex;
align-items: center;
flex-shrink: 0;
}

.info-card-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
cursor: help;
color: #666666;
border-radius: 50%;
transition: all 0.15s;
flex-shrink: 0;
}

.info-card-icon:hover {
background: #f0f0f0;
color: #4a90e2;
}

.info-icon {
width: 16px;
height: 16px;
}

.info-card-tooltip {
position: absolute;
bottom: calc(100% + 10px);
right: 50%;
transform: translateX(50%);
width: 420px;
max-width: 90vw;
padding: 18px;
background: white;
border-radius: 8px;
box-shadow: 0 5px 25px rgba(0,0,0,0.15);
border: 1px solid #e0e0e0;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: auto;
}

.info-card-tooltip.tooltip-bottom {
bottom: auto;
top: calc(100% + 10px);
}

.info-card-tooltip-wrapper:hover .info-card-tooltip {
opacity: 1;
visibility: visible;
transform: translateX(50%) translateY(-5px);
}

.info-card-tooltip.tooltip-bottom:hover {
transform: translateX(50%) translateY(5px);
}

.info-card-tooltip::before {
content: '';
position: absolute;
bottom: -7px;
left: 50%;
transform: translateX(-50%) rotate(45deg);
width: 14px;
height: 14px;
background: white;
border-right: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
z-index: -1;
}

.info-card-tooltip.tooltip-bottom::before {
bottom: auto;
top: -7px;
transform: translateX(-50%) rotate(-135deg);
}

.tooltip-description {
font-size: 0.92rem;
line-height: 1.6;
color: #444444;
margin-bottom: 14px;
max-height: 350px;
overflow-y: auto;
padding-right: 6px;
}

.tooltip-description::-webkit-scrollbar { width: 6px; }
.tooltip-description::-webkit-scrollbar-track { background: #f5f5f5; border-radius: 3px; }
.tooltip-description::-webkit-scrollbar-thumb { background: #b0b0b0; border-radius: 3px; }

.tooltip-details {
font-size: 0.85rem;
color: #666666;
border-top: 1px solid #f0f0f0;
padding-top: 12px;
}

.tooltip-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}

.tooltip-row:last-child {
margin-bottom: 0;
}

.tooltip-row-icon {
flex-shrink: 0;
width: 14px;
height: 14px;
}

.tooltip-row-text {
line-height: 1.4;
}

/* ===== ТЕМНАЯ ТЕМА ===== */
body.dark .info-card,
html.dark .info-card {
background: #2d3748;
border-color: #4a5568;
}

body.dark .info-card:hover,
html.dark .info-card:hover {
border-color: #63b3ed;
box-shadow: 0 2px 8px rgba(99, 179, 237, 0.2);
}

body.dark .info-card-link,
html.dark .info-card-link {
color: #e2e8f0;
}

body.dark .info-card-title,
html.dark .info-card-title {
color: #f7fafc;
}

body.dark .info-card-title-icon .custom-icon svg,
html.dark .info-card-title-icon .custom-icon svg {
color: #a0aec0;
}

body.dark .info-card-icon,
html.dark .info-card-icon {
color: #a0aec0;
}

body.dark .info-card-icon:hover,
html.dark .info-card-icon:hover {
background: #4a5568;
color: #63b3ed;
}

body.dark .info-card-tooltip,
html.dark .info-card-tooltip {
background: #1a202c;
border-color: #4a5568;
box-shadow: 0 5px 25px rgba(0,0,0,0.5);
}

body.dark .info-card-tooltip::before,
html.dark .info-card-tooltip::before {
background: #1a202c;
border-color: #4a5568;
}

body.dark .tooltip-description,
html.dark .tooltip-description {
color: #e2e8f0;
}

body.dark .tooltip-details,
html.dark .tooltip-details {
color: #a0aec0;
border-top-color: #4a5568;
}

body.dark .info-card-tag-default,
html.dark .info-card-tag-default {
background: #4a5568;
color: #cbd5e0;
border-color: #718096;
}

body.dark .info-card-tag-info,
html.dark .info-card-tag-info {
background: #2c5282;
color: #bee3f8;
border-color: #3182ce;
}

body.dark .info-card-tag-warning,
html.dark .info-card-tag-warning {
background: #975a16;
color: #feebc8;
border-color: #d69e2e;
}

body.dark .info-card-tag-success,
html.dark .info-card-tag-success {
background: #276749;
color: #c6f6d5;
border-color: #38a169;
}

body.dark .info-card-tag-error,
html.dark .info-card-tag-error {
background: #9b2c2c;
color: #fed7d7;
border-color: #e53e3e;
}

body.dark .info-card-style-link,
html.dark .info-card-style-link,
body.dark .info-card-style-minimal,
html.dark .info-card-style-minimal {
background: transparent;
}

body.dark .info-card-style-card,
html.dark .info-card-style-card {
background: #2d3748;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}

body.dark .info-card-style-pill,
html.dark .info-card-style-pill {
background: #2d3748;
border-color: #4a5568;
}

body.dark .info-card-style-pill:hover,
html.dark .info-card-style-pill:hover {
background: #374151;
}

/* ===== АДАПТИВНОСТЬ ===== */
@media (max-width: 768px) {
.info-card-header {
flex-direction: row;
flex-wrap: wrap;
gap: 8px;
}

.info-card-left {
width: auto;
flex: 1 1 auto;
padding-right: 0;
}

.info-card-right-section {
width: auto;
flex-shrink: 0;
}

@media (max-width: 480px) {
.info-card-header {
flex-wrap: wrap;
}

.info-card-right-section {
width: 100%;
justify-content: flex-start;
margin-top: 4px;
}
}

.info-card-tooltip {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) !important;
width: calc(100vw - 40px);
max-width: 500px;
max-height: 70vh;
overflow-y: auto;
bottom: auto !important;
right: auto !important;
}

.info-card-tooltip::before {
display: none;
}

.info-card {
margin: 0.5rem 0 0 0;
}

.info-card:first-of-type {
margin-top: 0;
}

.info-card-link {
padding: 0.75rem 1rem;
}

.cards-grid {
gap: 15px;
}

.cards-grid .info-card {
margin: 0;
}
}

@media (max-width: 480px) {
.info-card-tooltip {
width: calc(100vw - 20px);
padding: 14px;
}

.info-card-left {
gap: 8px;
}

.info-card {
margin: 0.4rem 0 0 0;
}

.info-card:first-of-type {
margin-top: 0;
}

.cards-grid {
gap: 10px;
}

.cards-grid .info-card {
margin: 0;
}
}

@media (min-width: 1200px) {
.info-card-tooltip {
width: 450px;
}
}

@media (min-width: 1400px) {
.info-card-tooltip {
width: 480px;
}
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
var wrappers = document.querySelectorAll('.info-card-tooltip-wrapper');

wrappers.forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
var icon = wrapper.querySelector('.info-card-icon');

if (!tooltip || !icon) return;

function adjustTooltipPosition() {
if (window.innerWidth <= 768) return;

var wrapperRect = wrapper.getBoundingClientRect();
var tooltipRect = tooltip.getBoundingClientRect();
var viewportHeight = window.innerHeight;

var spaceAbove = wrapperRect.top;
var spaceBelow = viewportHeight - wrapperRect.bottom;
var tooltipHeight = tooltipRect.height;

if (spaceAbove < tooltipHeight + 20 && spaceBelow >= tooltipHeight + 20) {
tooltip.classList.add('tooltip-bottom');
} else {
tooltip.classList.remove('tooltip-bottom');
}
}

wrapper.addEventListener('mouseenter', function() {
adjustTooltipPosition();
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
});

wrapper.addEventListener('mouseleave', function() {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});

wrapper.addEventListener('click', function(e) {
if (window.innerWidth <= 768) {
e.preventDefault();
e.stopPropagation();

if (tooltip.style.opacity === '1') {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
} else {
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
}
}
});
});

var scrollTimer;
window.addEventListener('scroll', function() {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(function() {
document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
if (tooltip && tooltip.style.opacity === '1') {
tooltip.classList.remove('tooltip-bottom');
setTimeout(function() {
var wrapperRect = wrapper.getBoundingClientRect();
var tooltipRect = tooltip.getBoundingClientRect();
var viewportHeight = window.innerHeight;
var spaceAbove = wrapperRect.top;
var spaceBelow = viewportHeight - wrapperRect.bottom;

if (spaceAbove < tooltipRect.height + 20 && spaceBelow >= tooltipRect.height + 20) {
tooltip.classList.add('tooltip-bottom');
}
}, 10);
}
});
}, 50);
});

window.addEventListener('resize', function() {
document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
if (tooltip && tooltip.style.opacity === '1') {
tooltip.style.opacity = '0';
setTimeout(function() {
tooltip.style.opacity = '1';
}, 10);
}
});
});

document.addEventListener('click', function(e) {
if (!e.target.closest('.info-card-tooltip-wrapper')) {
document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});
}
});

document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});
}
});
});
</script>

Добавлено (2026-03-10, 16:11)
---------------------------------------------
{{/* layouts/shortcodes/cardinfo.html */}}
{{ $link := .Get "link" }}
{{ $title := .Get "title" }}
{{ $desc := .Get "desc" | default "" }}
{{ $date := .Get "date" }}
{{ $audience := .Get "audience" }}
{{ $tag := .Get "tag" }}
{{ $tagtype := .Get "tagtype" | default "default" }}
{{ $icon := .Get "icon" }}
{{ $iconsize := .Get "iconsize" | default "24px" }}
{{ $visible := .Get "visible" | default "true" }}
{{ $style := .Get "style" | default "button" }}

{{ if ne $visible "false" }}
<div class="info-card info-card-style-{{ $style }}">
<a href="{{ $link }}" class="info-card-link" target="_blank" rel="noopener">
<div class="info-card-header">
<div class="info-card-left">
{{ if $icon }}
<span class="info-card-title-icon">
{{ if .Site.Data.icons }}
{{ $iconData := index .Site.Data.icons $icon }}
{{ if $iconData }}
{{ $iconSvg := replaceRE "<svg" (printf "<svg width='%s' height='%s'" $iconsize $iconsize) $iconData }}
<span class="custom-icon">{{ $iconSvg | safeHTML }}</span>
{{ else }}
<span class="custom-icon">
<svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
</svg>
</span>
{{ end }}
{{ else }}
<span class="custom-icon">
<svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
</svg>
</span>
{{ end }}
</span>
{{ end }}
<span class="info-card-title">{{ $title }}</span>
</div>
<div class="info-card-right-section">
{{ if $tag }}
<span class="info-card-tag info-card-tag-{{ $tagtype }}">
{{ $tag }}
</span>
{{ end }}

{{ if $desc }}
<div class="info-card-tooltip-wrapper">
<span class="info-card-icon">
<svg class="info-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 13A6 6 0 1 1 8 2a6 6 0 0 1 0 12z"/>
<path d="M7 11h2v2H7zm0-8h2v6H7z"/>
</svg>
</span>
<div class="info-card-tooltip">
<div class="tooltip-description">{{ $desc | safeHTML }}</div>
{{ if or $date $audience }}
<div class="tooltip-details">
{{ if $date }}
<div class="tooltip-row">
<svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"/>
</svg>
<span class="tooltip-row-text">Добавлено: {{ $date }}</span>
</div>
{{ end }}
{{ if $audience }}
<div class="tooltip-row">
<svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/>
</svg>
<span class="tooltip-row-text">Для кого: {{ $audience }}</span>
</div>
{{ end }}
</div>
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
</a>
</div>
{{ end }}

<style>
/* ===== ОСНОВНЫЕ СТИЛИ ===== */
.info-card {
transition: all 0.2s ease;
position: relative;
box-sizing: border-box;
display: inline-block;
vertical-align: top;
margin: 0 0 0.75rem 0; /* Отступ снизу для всех карточек */
padding: 0;
width: 100%;
}

.info-card:last-of-type {
margin-bottom: 0; /* Убираем отступ у самой последней карточки */
}

.info-card-link {
display: block;
text-decoration: none;
color: #333333;
width: 100%;
height: 100%;
box-sizing: border-box;
}

.info-card-link:hover {
text-decoration: none;
}

.info-card-header {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 24px;
width: 100%;
gap: 12px;
}

.info-card-left {
display: flex;
align-items: center;
flex: 1 1 auto;
min-width: 0;
padding-right: 8px;
gap: 10px;
}

.info-card-title-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}

.info-card-title-icon .custom-icon {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 0;
}

.info-card-title-icon .custom-icon svg {
width: 100%;
height: 100%;
fill: currentColor;
color: #666666;
vertical-align: middle;
}

.info-card-title {
font-size: 1rem;
font-weight: 600;
color: #333333;
line-height: 1.4;
flex: 1 1 auto;
min-width: 30px;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}

.info-card-right-section {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
flex-wrap: nowrap;
}

/* ===== КОНТЕЙНЕР ДЛЯ СЕТКИ ===== */
.cards-grid {
display: flex;
flex-wrap: wrap;
margin: 0;
gap: 20px;
}

.cards-grid .info-card {
flex: 0 0 auto;
padding: 0;
margin: 0; /* Убираем margin у карточек в сетке */
box-sizing: border-box;
}

/* 1 колонка */
.cards-grid.cols-1 .info-card {
width: 100%;
}

/* 2 колонки */
.cards-grid.cols-2 .info-card {
width: calc(50% - 10px);
}

/* 3 колонки */
.cards-grid.cols-3 .info-card {
width: calc(33.333% - 13.333px);
}

/* 4 колонки */
.cards-grid.cols-4 .info-card {
width: calc(25% - 15px);
}

/* Промежуточные состояния */
@media (max-width: 1100px) and (min-width: 901px) {
.cards-grid.cols-4 .info-card {
width: calc(50% - 10px);
}
}

@media (max-width: 900px) and (min-width: 751px) {
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: calc(50% - 10px);
}
}

@media (max-width: 750px) {
.cards-grid.cols-2 .info-card,
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: 100%;
}
}

/* ===== СТИЛЬ: BUTTON (по умолчанию) ===== */
.info-card-style-button {
border: 1px solid #e0e0e0;
border-radius: 8px;
background: #ffffff;
}

.info-card-style-button:hover {
border-color: #4a90e2;
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.15);
}

.info-card-style-button .info-card-link {
padding: 1rem 1.25rem;
}

/* ===== СТИЛЬ: LINK (простая ссылка с отступами) ===== */
.info-card-style-link {
border: none;
background: transparent;
border-radius: 0;
}

.info-card-style-link .info-card-link {
padding: 0.5rem 0.25rem;
border-bottom: 1px solid transparent;
}

.info-card-style-link:hover .info-card-link {
border-bottom-color: #4a90e2;
}

.info-card-style-link .info-card-title {
font-weight: 500;
}

.info-card-style-link .info-card-title-icon .custom-icon svg {
color: #4a90e2;
opacity: 0.8;
}

/* ===== СТИЛЬ: MINIMAL (минималистичный) ===== */
.info-card-style-minimal {
border: none;
background: transparent;
}

.info-card-style-minimal .info-card-link {
padding: 0.3rem 0;
}

.info-card-style-minimal .info-card-title {
font-weight: 400;
font-size: 0.95rem;
}

.info-card-style-minimal .info-card-title-icon .custom-icon svg {
color: #888;
width: 18px;
height: 18px;
}

.info-card-style-minimal:hover .info-card-title {
color: #4a90e2;
}

/* ===== СТИЛЬ: CARD (карточка с тенью) ===== */
.info-card-style-card {
border: none;
border-radius: 12px;
background: #ffffff;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
transition: all 0.3s ease;
}

.info-card-style-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

.info-card-style-card .info-card-link {
padding: 1.25rem 1.5rem;
}

.info-card-style-card .info-card-title {
font-size: 1.1rem;
}

/* ===== СТИЛЬ: PILL (в виде пилюли) ===== */
.info-card-style-pill {
border: 1px solid #e0e0e0;
border-radius: 50px;
background: #f8f9fa;
}

.info-card-style-pill:hover {
border-color: #4a90e2;
background: #ffffff;
}

.info-card-style-pill .info-card-link {
padding: 0.6rem 1.25rem;
}

.info-card-style-pill .info-card-title {
font-weight: 500;
font-size: 0.95rem;
}

/* ===== ТЕГИ ===== */
.info-card-tag {
display: inline-block;
font-size: 0.75rem;
padding: 0.2rem 0.6rem;
border-radius: 12px;
font-weight: 500;
white-space: nowrap;
border: 1px solid transparent;
line-height: 1.4;
flex-shrink: 0;
}

.info-card-tag-default { background: #f0f0f0; color: #666666; border-color: #ddd; }
.info-card-tag-info { background: #e3f2fd; color: #1565c0; border-color: #90caf9; }
.info-card-tag-warning { background: #fff3e0; color: #e65100; border-color: #ffcc80; }
.info-card-tag-success { background: #e8f5e9; color: #2e7d32; border-color: #a5d6a7; }
.info-card-tag-error { background: #ffebee; color: #c62828; border-color: #ef9a9a; }

/* ===== ТУЛТИПЫ ===== */
.info-card-tooltip-wrapper {
position: relative;
display: flex;
align-items: center;
flex-shrink: 0;
}

.info-card-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
cursor: help;
color: #666666;
border-radius: 50%;
transition: all 0.15s;
flex-shrink: 0;
}

.info-card-icon:hover {
background: #f0f0f0;
color: #4a90e2;
}

.info-icon {
width: 16px;
height: 16px;
}

.info-card-tooltip {
position: absolute;
bottom: calc(100% + 10px);
right: 50%;
transform: translateX(50%);
width: 420px;
max-width: 90vw;
padding: 18px;
background: white;
border-radius: 8px;
box-shadow: 0 5px 25px rgba(0,0,0,0.15);
border: 1px solid #e0e0e0;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: auto;
}

.info-card-tooltip.tooltip-bottom {
bottom: auto;
top: calc(100% + 10px);
}

.info-card-tooltip-wrapper:hover .info-card-tooltip {
opacity: 1;
visibility: visible;
transform: translateX(50%) translateY(-5px);
}

.info-card-tooltip.tooltip-bottom:hover {
transform: translateX(50%) translateY(5px);
}

.info-card-tooltip::before {
content: '';
position: absolute;
bottom: -7px;
left: 50%;
transform: translateX(-50%) rotate(45deg);
width: 14px;
height: 14px;
background: white;
border-right: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
z-index: -1;
}

.info-card-tooltip.tooltip-bottom::before {
bottom: auto;
top: -7px;
transform: translateX(-50%) rotate(-135deg);
}

.tooltip-description {
font-size: 0.92rem;
line-height: 1.6;
color: #444444;
margin-bottom: 14px;
max-height: 350px;
overflow-y: auto;
padding-right: 6px;
}

.tooltip-description::-webkit-scrollbar { width: 6px; }
.tooltip-description::-webkit-scrollbar-track { background: #f5f5f5; border-radius: 3px; }
.tooltip-description::-webkit-scrollbar-thumb { background: #b0b0b0; border-radius: 3px; }

.tooltip-details {
font-size: 0.85rem;
color: #666666;
border-top: 1px solid #f0f0f0;
padding-top: 12px;
}

.tooltip-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}

.tooltip-row:last-child {
margin-bottom: 0;
}

.tooltip-row-icon {
flex-shrink: 0;
width: 14px;
height: 14px;
}

.tooltip-row-text {
line-height: 1.4;
}

/* ===== ТЕМНАЯ ТЕМА ===== */
body.dark .info-card,
html.dark .info-card {
background: #2d3748;
border-color: #4a5568;
}

body.dark .info-card:hover,
html.dark .info-card:hover {
border-color: #63b3ed;
box-shadow: 0 2px 8px rgba(99, 179, 237, 0.2);
}

body.dark .info-card-link,
html.dark .info-card-link {
color: #e2e8f0;
}

body.dark .info-card-title,
html.dark .info-card-title {
color: #f7fafc;
}

body.dark .info-card-title-icon .custom-icon svg,
html.dark .info-card-title-icon .custom-icon svg {
color: #a0aec0;
}

body.dark .info-card-icon,
html.dark .info-card-icon {
color: #a0aec0;
}

body.dark .info-card-icon:hover,
html.dark .info-card-icon:hover {
background: #4a5568;
color: #63b3ed;
}

body.dark .info-card-tooltip,
html.dark .info-card-tooltip {
background: #1a202c;
border-color: #4a5568;
box-shadow: 0 5px 25px rgba(0,0,0,0.5);
}

body.dark .info-card-tooltip::before,
html.dark .info-card-tooltip::before {
background: #1a202c;
border-color: #4a5568;
}

body.dark .tooltip-description,
html.dark .tooltip-description {
color: #e2e8f0;
}

body.dark .tooltip-details,
html.dark .tooltip-details {
color: #a0aec0;
border-top-color: #4a5568;
}

body.dark .info-card-tag-default,
html.dark .info-card-tag-default {
background: #4a5568;
color: #cbd5e0;
border-color: #718096;
}

body.dark .info-card-tag-info,
html.dark .info-card-tag-info {
background: #2c5282;
color: #bee3f8;
border-color: #3182ce;
}

body.dark .info-card-tag-warning,
html.dark .info-card-tag-warning {
background: #975a16;
color: #feebc8;
border-color: #d69e2e;
}

body.dark .info-card-tag-success,
html.dark .info-card-tag-success {
background: #276749;
color: #c6f6d5;
border-color: #38a169;
}

body.dark .info-card-tag-error,
html.dark .info-card-tag-error {
background: #9b2c2c;
color: #fed7d7;
border-color: #e53e3e;
}

body.dark .info-card-style-link,
html.dark .info-card-style-link,
body.dark .info-card-style-minimal,
html.dark .info-card-style-minimal {
background: transparent;
}

body.dark .info-card-style-card,
html.dark .info-card-style-card {
background: #2d3748;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}

body.dark .info-card-style-pill,
html.dark .info-card-style-pill {
background: #2d3748;
border-color: #4a5568;
}

body.dark .info-card-style-pill:hover,
html.dark .info-card-style-pill:hover {
background: #374151;
}

/* ===== АДАПТИВНОСТЬ ===== */
@media (max-width: 768px) {
.info-card-header {
flex-direction: row;
flex-wrap: wrap;
gap: 8px;
}

.info-card-left {
width: auto;
flex: 1 1 auto;
padding-right: 0;
}

.info-card-right-section {
width: auto;
flex-shrink: 0;
}

@media (max-width: 480px) {
.info-card-header {
flex-wrap: wrap;
}

.info-card-right-section {
width: 100%;
justify-content: flex-start;
margin-top: 4px;
}
}

.info-card-tooltip {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) !important;
width: calc(100vw - 40px);
max-width: 500px;
max-height: 70vh;
overflow-y: auto;
bottom: auto !important;
right: auto !important;
}

.info-card-tooltip::before {
display: none;
}

.info-card {
margin: 0 0 0.5rem 0; /* Отступ снизу на мобильных */
}

.info-card:last-of-type {
margin-bottom: 0;
}

.info-card-link {
padding: 0.75rem 1rem;
}

.cards-grid {
gap: 15px;
}

.cards-grid .info-card {
margin: 0;
}
}

@media (max-width: 480px) {
.info-card-tooltip {
width: calc(100vw - 20px);
padding: 14px;
}

.info-card-left {
gap: 8px;
}

.info-card {
margin: 0 0 0.4rem 0; /* Отступ снизу на маленьких экранах */
}

.info-card:last-of-type {
margin-bottom: 0;
}

.cards-grid {
gap: 10px;
}

.cards-grid .info-card {
margin: 0;
}
}

@media (min-width: 1200px) {
.info-card-tooltip {
width: 450px;
}
}

@media (min-width: 1400px) {
.info-card-tooltip {
width: 480px;
}
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
var wrappers = document.querySelectorAll('.info-card-tooltip-wrapper');

wrappers.forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
var icon = wrapper.querySelector('.info-card-icon');

if (!tooltip || !icon) return;

function adjustTooltipPosition() {
if (window.innerWidth <= 768) return;

var wrapperRect = wrapper.getBoundingClientRect();
var tooltipRect = tooltip.getBoundingClientRect();
var viewportHeight = window.innerHeight;

var spaceAbove = wrapperRect.top;
var spaceBelow = viewportHeight - wrapperRect.bottom;
var tooltipHeight = tooltipRect.height;

if (spaceAbove < tooltipHeight + 20 && spaceBelow >= tooltipHeight + 20) {
tooltip.classList.add('tooltip-bottom');
} else {
tooltip.classList.remove('tooltip-bottom');
}
}

wrapper.addEventListener('mouseenter', function() {
adjustTooltipPosition();
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
});

wrapper.addEventListener('mouseleave', function() {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});

wrapper.addEventListener('click', function(e) {
if (window.innerWidth <= 768) {
e.preventDefault();
e.stopPropagation();

if (tooltip.style.opacity === '1') {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
} else {
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
}
}
});
});

var scrollTimer;
window.addEventListener('scroll', function() {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(function() {
document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
if (tooltip && tooltip.style.opacity === '1') {
tooltip.classList.remove('tooltip-bottom');
setTimeout(function() {
var wrapperRect = wrapper.getBoundingClientRect();
var tooltipRect = tooltip.getBoundingClientRect();
var viewportHeight = window.innerHeight;
var spaceAbove = wrapperRect.top;
var spaceBelow = viewportHeight - wrapperRect.bottom;

if (spaceAbove < tooltipRect.height + 20 && spaceBelow >= tooltipRect.height + 20) {
tooltip.classList.add('tooltip-bottom');
}
}, 10);
}
});
}, 50);
});

window.addEventListener('resize', function() {
document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
if (tooltip && tooltip.style.opacity === '1') {
tooltip.style.opacity = '0';
setTimeout(function() {
tooltip.style.opacity = '1';
}, 10);
}
});
});

document.addEventListener('click', function(e) {
if (!e.target.closest('.info-card-tooltip-wrapper')) {
document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});
}
});

document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});
}
});
});
</script>

Добавлено (2026-03-10, 16:32)
---------------------------------------------
{{/* layouts/shortcodes/cardinfo.html */}}
{{ $link := .Get "link" }}
{{ $title := .Get "title" }}
{{ $desc := .Get "desc" | default "" }}
{{ $date := .Get "date" }}
{{ $audience := .Get "audience" }}
{{ $tag := .Get "tag" }}
{{ $tagtype := .Get "tagtype" | default "default" }}
{{ $icon := .Get "icon" }}
{{ $iconsize := .Get "iconsize" | default "24px" }}
{{ $visible := .Get "visible" | default "true" }}
{{ $style := .Get "style" | default "button" }}

{{ if ne $visible "false" }}
<div class="info-card info-card-style-{{ $style }}">
<a href="{{ $link }}" class="info-card-link" target="_blank" rel="noopener">
<div class="info-card-header">
<div class="info-card-left">
{{ if $icon }}
<span class="info-card-title-icon">
{{ if .Site.Data.icons }}
{{ $iconData := index .Site.Data.icons $icon }}
{{ if $iconData }}
{{ $iconSvg := replaceRE "<svg" (printf "<svg width='%s' height='%s'" $iconsize $iconsize) $iconData }}
<span class="custom-icon">{{ $iconSvg | safeHTML }}</span>
{{ else }}
<span class="custom-icon">
<svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
</svg>
</span>
{{ end }}
{{ else }}
<span class="custom-icon">
<svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
</svg>
</span>
{{ end }}
</span>
{{ end }}
<span class="info-card-title">{{ $title }}</span>
</div>
<div class="info-card-right-section">
{{ if $tag }}
<span class="info-card-tag info-card-tag-{{ $tagtype }}">
{{ $tag }}
</span>
{{ end }}

{{ if $desc }}
<div class="info-card-tooltip-wrapper">
<span class="info-card-icon">
<svg class="info-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 13A6 6 0 1 1 8 2a6 6 0 0 1 0 12z"/>
<path d="M7 11h2v2H7zm0-8h2v6H7z"/>
</svg>
</span>
<div class="info-card-tooltip">
<div class="tooltip-description">{{ $desc | safeHTML }}</div>
{{ if or $date $audience }}
<div class="tooltip-details">
{{ if $date }}
<div class="tooltip-row">
<svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"/>
</svg>
<span class="tooltip-row-text">Добавлено: {{ $date }}</span>
</div>
{{ end }}
{{ if $audience }}
<div class="tooltip-row">
<svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/>
</svg>
<span class="tooltip-row-text">Для кого: {{ $audience }}</span>
</div>
{{ end }}
</div>
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
</a>
</div>
{{ end }}

<style>
/* ===== ОСНОВНЫЕ СТИЛИ ===== */
.info-card {
transition: all 0.2s ease;
position: relative;
box-sizing: border-box;
display: inline-block;
vertical-align: top;
margin: 0.75rem 0 0 0; /* Отступ сверху для всех карточек */
padding: 0;
width: 100%;
}

.info-card:first-of-type {
margin-top: 0; /* Убираем отступ у самой первой карточки */
}

.info-card-link {
display: block;
text-decoration: none;
color: #333333;
width: 100%;
height: 100%;
box-sizing: border-box;
}

.info-card-link:hover {
text-decoration: none;
}

.info-card-header {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 24px;
width: 100%;
gap: 12px;
}

.info-card-left {
display: flex;
align-items: center;
flex: 1 1 auto;
min-width: 0;
padding-right: 8px;
gap: 10px;
}

.info-card-title-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}

.info-card-title-icon .custom-icon {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 0;
}

.info-card-title-icon .custom-icon svg {
width: 100%;
height: 100%;
fill: currentColor;
color: #666666;
vertical-align: middle;
}

.info-card-title {
font-size: 1rem;
font-weight: 600;
color: #333333;
line-height: 1.4;
flex: 1 1 auto;
min-width: 30px;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}

.info-card-right-section {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
flex-wrap: nowrap;
}

/* ===== КОНТЕЙНЕР ДЛЯ СЕТКИ ===== */
.cards-grid {
display: flex;
flex-wrap: wrap;
margin: 0;
gap: 10px; /* Уменьшил отступ в 2 раза (было 20px) */
}

.cards-grid .info-card {
flex: 0 0 auto;
padding: 0;
margin: 0; /* Убираем margin у карточек в сетке */
box-sizing: border-box;
}

/* 1 колонка */
.cards-grid.cols-1 .info-card {
width: 100%;
}

/* 2 колонки */
.cards-grid.cols-2 .info-card {
width: calc(50% - 5px); /* 10px gap / 2 = 5px компенсация */
}

/* 3 колонки */
.cards-grid.cols-3 .info-card {
width: calc(33.333% - 6.667px); /* Компенсация для 3 колонок */
}

/* 4 колонки */
.cards-grid.cols-4 .info-card {
width: calc(25% - 7.5px); /* Компенсация для 4 колонок */
}

/* Промежуточные состояния */
@media (max-width: 1100px) and (min-width: 901px) {
.cards-grid.cols-4 .info-card {
width: calc(50% - 5px);
}
}

@media (max-width: 900px) and (min-width: 751px) {
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: calc(50% - 5px);
}
}

@media (max-width: 750px) {
.cards-grid.cols-2 .info-card,
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: 100%;
}
}

/* ===== СТИЛЬ: BUTTON (по умолчанию) ===== */
.info-card-style-button {
border: 1px solid #e0e0e0;
border-radius: 8px;
background: #ffffff;
}

.info-card-style-button:hover {
border-color: #4a90e2;
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.15);
}

.info-card-style-button .info-card-link {
padding: 1rem 1.25rem;
}

/* ===== СТИЛЬ: LINK (простая ссылка с отступами) ===== */
.info-card-style-link {
border: none;
background: transparent;
border-radius: 0;
}

.info-card-style-link .info-card-link {
padding: 0.5rem 0.25rem;
border-bottom: 1px solid transparent;
}

.info-card-style-link:hover .info-card-link {
border-bottom-color: #4a90e2;
}

.info-card-style-link .info-card-title {
font-weight: 500;
}

.info-card-style-link .info-card-title-icon .custom-icon svg {
color: #4a90e2;
opacity: 0.8;
}

/* ===== СТИЛЬ: MINIMAL (минималистичный) ===== */
.info-card-style-minimal {
border: none;
background: transparent;
}

.info-card-style-minimal .info-card-link {
padding: 0.3rem 0;
}

.info-card-style-minimal .info-card-title {
font-weight: 400;
font-size: 0.95rem;
}

.info-card-style-minimal .info-card-title-icon .custom-icon svg {
color: #888;
width: 18px;
height: 18px;
}

.info-card-style-minimal:hover .info-card-title {
color: #4a90e2;
}

/* ===== СТИЛЬ: CARD (карточка с тенью) ===== */
.info-card-style-card {
border: none;
border-radius: 12px;
background: #ffffff;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
transition: all 0.3s ease;
}

.info-card-style-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

.info-card-style-card .info-card-link {
padding: 1.25rem 1.5rem;
}

.info-card-style-card .info-card-title {
font-size: 1.1rem;
}

/* ===== СТИЛЬ: PILL (в виде пилюли) ===== */
.info-card-style-pill {
border: 1px solid #e0e0e0;
border-radius: 50px;
background: #f8f9fa;
}

.info-card-style-pill:hover {
border-color: #4a90e2;
background: #ffffff;
}

.info-card-style-pill .info-card-link {
padding: 0.6rem 1.25rem;
}

.info-card-style-pill .info-card-title {
font-weight: 500;
font-size: 0.95rem;
}

/* ===== ТЕГИ ===== */
.info-card-tag {
display: inline-block;
font-size: 0.75rem;
padding: 0.2rem 0.6rem;
border-radius: 12px;
font-weight: 500;
white-space: nowrap;
border: 1px solid transparent;
line-height: 1.4;
flex-shrink: 0;
}

.info-card-tag-default { background: #f0f0f0; color: #666666; border-color: #ddd; }
.info-card-tag-info { background: #e3f2fd; color: #1565c0; border-color: #90caf9; }
.info-card-tag-warning { background: #fff3e0; color: #e65100; border-color: #ffcc80; }
.info-card-tag-success { background: #e8f5e9; color: #2e7d32; border-color: #a5d6a7; }
.info-card-tag-error { background: #ffebee; color: #c62828; border-color: #ef9a9a; }

/* ===== ТУЛТИПЫ ===== */
.info-card-tooltip-wrapper {
position: relative;
display: flex;
align-items: center;
flex-shrink: 0;
}

.info-card-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
cursor: help;
color: #666666;
border-radius: 50%;
transition: all 0.15s;
flex-shrink: 0;
}

.info-card-icon:hover {
background: #f0f0f0;
color: #4a90e2;
}

.info-icon {
width: 16px;
height: 16px;
}

.info-card-tooltip {
position: absolute;
bottom: calc(100% + 10px);
right: 50%;
transform: translateX(50%);
width: 420px;
max-width: 90vw;
padding: 18px;
background: white;
border-radius: 8px;
box-shadow: 0 5px 25px rgba(0,0,0,0.15);
border: 1px solid #e0e0e0;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: auto;
}

.info-card-tooltip.tooltip-bottom {
bottom: auto;
top: calc(100% + 10px);
}

.info-card-tooltip-wrapper:hover .info-card-tooltip {
opacity: 1;
visibility: visible;
transform: translateX(50%) translateY(-5px);
}

.info-card-tooltip.tooltip-bottom:hover {
transform: translateX(50%) translateY(5px);
}

.info-card-tooltip::before {
content: '';
position: absolute;
bottom: -7px;
left: 50%;
transform: translateX(-50%) rotate(45deg);
width: 14px;
height: 14px;
background: white;
border-right: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
z-index: -1;
}

.info-card-tooltip.tooltip-bottom::before {
bottom: auto;
top: -7px;
transform: translateX(-50%) rotate(-135deg);
}

.tooltip-description {
font-size: 0.92rem;
line-height: 1.6;
color: #444444;
margin-bottom: 14px;
max-height: 350px;
overflow-y: auto;
padding-right: 6px;
}

.tooltip-description::-webkit-scrollbar { width: 6px; }
.tooltip-description::-webkit-scrollbar-track { background: #f5f5f5; border-radius: 3px; }
.tooltip-description::-webkit-scrollbar-thumb { background: #b0b0b0; border-radius: 3px; }

.tooltip-details {
font-size: 0.85rem;
color: #666666;
border-top: 1px solid #f0f0f0;
padding-top: 12px;
}

.tooltip-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}

.tooltip-row:last-child {
margin-bottom: 0;
}

.tooltip-row-icon {
flex-shrink: 0;
width: 14px;
height: 14px;
}

.tooltip-row-text {
line-height: 1.4;
}

/* ===== ТЕМНАЯ ТЕМА ===== */
body.dark .info-card,
html.dark .info-card {
background: #2d3748;
border-color: #4a5568;
}

body.dark .info-card:hover,
html.dark .info-card:hover {
border-color: #63b3ed;
box-shadow: 0 2px 8px rgba(99, 179, 237, 0.2);
}

body.dark .info-card-link,
html.dark .info-card-link {
color: #e2e8f0;
}

body.dark .info-card-title,
html.dark .info-card-title {
color: #f7fafc;
}

body.dark .info-card-title-icon .custom-icon svg,
html.dark .info-card-title-icon .custom-icon svg {
color: #a0aec0;
}

body.dark .info-card-icon,
html.dark .info-card-icon {
color: #a0aec0;
}

body.dark .info-card-icon:hover,
html.dark .info-card-icon:hover {
background: #4a5568;
color: #63b3ed;
}

body.dark .info-card-tooltip,
html.dark .info-card-tooltip {
background: #1a202c;
border-color: #4a5568;
box-shadow: 0 5px 25px rgba(0,0,0,0.5);
}

body.dark .info-card-tooltip::before,
html.dark .info-card-tooltip::before {
background: #1a202c;
border-color: #4a5568;
}

body.dark .tooltip-description,
html.dark .tooltip-description {
color: #e2e8f0;
}

body.dark .tooltip-details,
html.dark .tooltip-details {
color: #a0aec0;
border-top-color: #4a5568;
}

body.dark .info-card-tag-default,
html.dark .info-card-tag-default {
background: #4a5568;
color: #cbd5e0;
border-color: #718096;
}

body.dark .info-card-tag-info,
html.dark .info-card-tag-info {
background: #2c5282;
color: #bee3f8;
border-color: #3182ce;
}

body.dark .info-card-tag-warning,
html.dark .info-card-tag-warning {
background: #975a16;
color: #feebc8;
border-color: #d69e2e;
}

body.dark .info-card-tag-success,
html.dark .info-card-tag-success {
background: #276749;
color: #c6f6d5;
border-color: #38a169;
}

body.dark .info-card-tag-error,
html.dark .info-card-tag-error {
background: #9b2c2c;
color: #fed7d7;
border-color: #e53e3e;
}

body.dark .info-card-style-link,
html.dark .info-card-style-link,
body.dark .info-card-style-minimal,
html.dark .info-card-style-minimal {
background: transparent;
}

body.dark .info-card-style-card,
html.dark .info-card-style-card {
background: #2d3748;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}

body.dark .info-card-style-pill,
html.dark .info-card-style-pill {
background: #2d3748;
border-color: #4a5568;
}

body.dark .info-card-style-pill:hover,
html.dark .info-card-style-pill:hover {
background: #374151;
}

/* ===== АДАПТИВНОСТЬ ===== */
@media (max-width: 768px) {
.info-card-header {
flex-direction: row;
flex-wrap: wrap;
gap: 8px;
}

.info-card-left {
width: auto;
flex: 1 1 auto;
padding-right: 0;
}

.info-card-right-section {
width: auto;
flex-shrink: 0;
}

@media (max-width: 480px) {
.info-card-header {
flex-wrap: wrap;
}

.info-card-right-section {
width: 100%;
justify-content: flex-start;
margin-top: 4px;
}
}

.info-card-tooltip {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) !important;
width: calc(100vw - 40px);
max-width: 500px;
max-height: 70vh;
overflow-y: auto;
bottom: auto !important;
right: auto !important;
}

.info-card-tooltip::before {
display: none;
}

.info-card {
margin: 0.5rem 0 0 0; /* Отступ сверху на мобильных */
}

.info-card:first-of-type {
margin-top: 0;
}

.info-card-link {
padding: 0.75rem 1rem;
}

.cards-grid {
gap: 8px; /* Уменьшенный отступ на мобильных */
}

.cards-grid .info-card {
margin: 0;
}
}

@media (max-width: 480px) {
.info-card-tooltip {
width: calc(100vw - 20px);
padding: 14px;
}

.info-card-left {
gap: 8px;
}

.info-card {
margin: 0.4rem 0 0 0; /* Отступ сверху на маленьких экранах */
}

.info-card:first-of-type {
margin-top: 0;
}

.cards-grid {
gap: 6px; /* Еще меньше на очень маленьких экранах */
}

.cards-grid .info-card {
margin: 0;
}
}

@media (min-width: 1200px) {
.info-card-tooltip {
width: 450px;
}
}

@media (min-width: 1400px) {
.info-card-tooltip {
width: 480px;
}
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
var wrappers = document.querySelectorAll('.info-card-tooltip-wrapper');

wrappers.forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
var icon = wrapper.querySelector('.info-card-icon');

if (!tooltip || !icon) return;

function adjustTooltipPosition() {
if (window.innerWidth <= 768) return;

var wrapperRect = wrapper.getBoundingClientRect();
var tooltipRect = tooltip.getBoundingClientRect();
var viewportHeight = window.innerHeight;

var spaceAbove = wrapperRect.top;
var spaceBelow = viewportHeight - wrapperRect.bottom;
var tooltipHeight = tooltipRect.height;

if (spaceAbove < tooltipHeight + 20 && spaceBelow >= tooltipHeight + 20) {
tooltip.classList.add('tooltip-bottom');
} else {
tooltip.classList.remove('tooltip-bottom');
}
}

wrapper.addEventListener('mouseenter', function() {
adjustTooltipPosition();
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
});

wrapper.addEventListener('mouseleave', function() {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});

wrapper.addEventListener('click', function(e) {
if (window.innerWidth <= 768) {
e.preventDefault();
e.stopPropagation();

if (tooltip.style.opacity === '1') {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
} else {
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
}
}
});
});

var scrollTimer;
window.addEventListener('scroll', function() {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(function() {
document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
if (tooltip && tooltip.style.opacity === '1') {
tooltip.classList.remove('tooltip-bottom');
setTimeout(function() {
var wrapperRect = wrapper.getBoundingClientRect();
var tooltipRect = tooltip.getBoundingClientRect();
var viewportHeight = window.innerHeight;
var spaceAbove = wrapperRect.top;
var spaceBelow = viewportHeight - wrapperRect.bottom;

if (spaceAbove < tooltipRect.height + 20 && spaceBelow >= tooltipRect.height + 20) {
tooltip.classList.add('tooltip-bottom');
}
}, 10);
}
});
}, 50);
});

window.addEventListener('resize', function() {
document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) {
var tooltip = wrapper.querySelector('.info-card-tooltip');
if (tooltip && tooltip.style.opacity === '1') {
tooltip.style.opacity = '0';
setTimeout(function() {
tooltip.style.opacity = '1';
}, 10);
}
});
});

document.addEventListener('click', function(e) {
if (!e.target.closest('.info-card-tooltip-wrapper')) {
document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});
}
});

document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) {
tooltip.style.opacity = '0';
tooltip.style.visibility = 'hidden';
});
}
});
});
</script>

/* ===== КОНТЕЙНЕР ДЛЯ СЕТКИ ===== */
.cards-grid {
display: flex;
flex-wrap: wrap;
margin: 0;
gap: 10px;
}

.cards-grid .info-card {
flex: 0 0 auto;
padding: 0;
margin: 10px 0 0 0; /* Отступ сверху 10px для всех карточек в сетке */
box-sizing: border-box;
}

/* Убираем отступ у карточек первого ряда */
.cards-grid .info-card:nth-child(-n+4) {
margin-top: 0;
}

/* Для 1 колонки */
.cards-grid.cols-1 .info-card:nth-child(1) {
margin-top: 0;
}

/* Для 2 колонок - первый ряд это 2 карточки */
.cards-grid.cols-2 .info-card:nth-child(-n+2) {
margin-top: 0;
}

/* Для 3 колонок - первый ряд это 3 карточки */
.cards-grid.cols-3 .info-card:nth-child(-n+3) {
margin-top: 0;
}

/* Для 4 колонок - первый ряд это 4 карточки */
.cards-grid.cols-4 .info-card:nth-child(-n+4) {
margin-top: 0;
}

Добавлено (2026-03-11, 07:39)
---------------------------------------------
/* ===== КОНТЕЙНЕР ДЛЯ СЕТКИ ===== */
.cards-grid {
display: flex;
flex-wrap: wrap;
margin: 0;
gap: 10px;
}

.cards-grid .info-card {
flex: 0 0 auto;
padding: 0;
margin: 10px 0 0 0; /* Отступ сверху 10px для всех карточек в сетке */
box-sizing: border-box;
}

/* Убираем отступ у карточек ПЕРВОГО РЯДА в зависимости от количества колонок */
/* Для 1 колонки - первый ряд это 1 карточка */
.cards-grid.cols-1 .info-card:nth-child(1) {
margin-top: 0;
}
/* Для 1 колонки - все остальные карточки имеют отступ */
.cards-grid.cols-1 .info-card:nth-child(n+2) {
margin-top: 10px;
}

/* Для 2 колонок - первый ряд это карточки 1 и 2 */
.cards-grid.cols-2 .info-card:nth-child(1),
.cards-grid.cols-2 .info-card:nth-child(2) {
margin-top: 0;
}
/* Для 2 колонок - карточки с 3 и далее имеют отступ */
.cards-grid.cols-2 .info-card:nth-child(n+3) {
margin-top: 10px;
}

/* Для 3 колонок - первый ряд это карточки 1, 2, 3 */
.cards-grid.cols-3 .info-card:nth-child(1),
.cards-grid.cols-3 .info-card:nth-child(2),
.cards-grid.cols-3 .info-card:nth-child(3) {
margin-top: 0;
}
/* Для 3 колонок - карточки с 4 и далее имеют отступ */
.cards-grid.cols-3 .info-card:nth-child(n+4) {
margin-top: 10px;
}

/* Для 4 колонок - первый ряд это карточки 1, 2, 3, 4 */
.cards-grid.cols-4 .info-card:nth-child(1),
.cards-grid.cols-4 .info-card:nth-child(2),
.cards-grid.cols-4 .info-card:nth-child(3),
.cards-grid.cols-4 .info-card:nth-child(4) {
margin-top: 0;
}
/* Для 4 колонок - карточки с 5 и далее имеют отступ */
.cards-grid.cols-4 .info-card:nth-child(n+5) {
margin-top: 10px;
}

/* Промежуточные состояния для адаптивности */
@media (max-width: 1100px) and (min-width: 901px) {
/* При 4→2 колонках, первый ряд теперь 2 карточки */
.cards-grid.cols-4 .info-card:nth-child(1),
.cards-grid.cols-4 .info-card:nth-child(2) {
margin-top: 0;
}
.cards-grid.cols-4 .info-card:nth-child(n+3) {
margin-top: 10px;
}
}

@media (max-width: 900px) and (min-width: 751px) {
/* При 3→2 и 4→2 колонках, первый ряд теперь 2 карточки */
.cards-grid.cols-3 .info-card:nth-child(1),
.cards-grid.cols-3 .info-card:nth-child(2),
.cards-grid.cols-4 .info-card:nth-child(1),
.cards-grid.cols-4 .info-card:nth-child(2) {
margin-top: 0;
}
.cards-grid.cols-3 .info-card:nth-child(n+3),
.cards-grid.cols-4 .info-card:nth-child(n+3) {
margin-top: 10px;
}
}

@media (max-width: 750px) {
/* При 1 колонке, первый ряд только первая карточка */
.cards-grid.cols-2 .info-card:nth-child(1),
.cards-grid.cols-3 .info-card:nth-child(1),
.cards-grid.cols-4 .info-card:nth-child(1) {
margin-top: 0;
}
.cards-grid.cols-2 .info-card:nth-child(n+2),
.cards-grid.cols-3 .info-card:nth-child(n+2),
.cards-grid.cols-4 .info-card:nth-child(n+2) {
margin-top: 10px;
}
}

Добавлено (2026-03-11, 07:44)
---------------------------------------------
/* ===== КОНТЕЙНЕР ДЛЯ СЕТКИ ===== */
.cards-grid {
display: flex;
flex-wrap: wrap;
margin: 0;
gap: 10px; /* Отступы между карточками */
}

.cards-grid .info-card {
flex: 0 0 auto;
padding: 0;
margin: 10px 0 0 0; /* Отступ сверху 10px для всех карточек в сетке */
box-sizing: border-box;
}

/* Убираем отступ у карточек ПЕРВОГО РЯДА в зависимости от количества колонок */

/* Для 1 колонки - первый ряд это 1 карточка */
.cards-grid.cols-1 .info-card:nth-child(1) {
margin-top: 0;
}

/* Для 2 колонок - первый ряд это первые 2 карточки */
.cards-grid.cols-2 .info-card:nth-child(1),
.cards-grid.cols-2 .info-card:nth-child(2) {
margin-top: 0;
}

/* Для 3 колонок - первый ряд это первые 3 карточки */
.cards-grid.cols-3 .info-card:nth-child(1),
.cards-grid.cols-3 .info-card:nth-child(2),
.cards-grid.cols-3 .info-card:nth-child(3) {
margin-top: 0;
}

/* Для 4 колонок - первый ряд это первые 4 карточки */
.cards-grid.cols-4 .info-card:nth-child(1),
.cards-grid.cols-4 .info-card:nth-child(2),
.cards-grid.cols-4 .info-card:nth-child(3),
.cards-grid.cols-4 .info-card:nth-child(4) {
margin-top: 0;
}

/* При адаптации, когда колонки меняются */

/* Когда 4 колонки превращаются в 2 */
@media (max-width: 1100px) and (min-width: 901px) {
.cards-grid.cols-4 .info-card {
width: calc(50% - 5px);
}
/* Первый ряд теперь 2 карточки */
.cards-grid.cols-4 .info-card:nth-child(1),
.cards-grid.cols-4 .info-card:nth-child(2) {
margin-top: 0;
}
/* Убираем отступ у следующих карточек в новом ряду */
.cards-grid.cols-4 .info-card:nth-child(3) {
margin-top: 10px;
}
}

/* Когда 3 и 4 колонки превращаются в 2 */
@media (max-width: 900px) and (min-width: 751px) {
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: calc(50% - 5px);
}
/* Первый ряд теперь 2 карточки */
.cards-grid.cols-3 .info-card:nth-child(1),
.cards-grid.cols-3 .info-card:nth-child(2),
.cards-grid.cols-4 .info-card:nth-child(1),
.cards-grid.cols-4 .info-card:nth-child(2) {
margin-top: 0;
}
/* Третья карточка начинает новый ряд */
.cards-grid.cols-3 .info-card:nth-child(3),
.cards-grid.cols-4 .info-card:nth-child(3) {
margin-top: 10px;
}
}

/* Когда все становятся в 1 колонку */
@media (max-width: 750px) {
.cards-grid.cols-2 .info-card,
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: 100%;
}
/* Первый ряд - только первая карточка */
.cards-grid.cols-2 .info-card:nth-child(1),
.cards-grid.cols-3 .info-card:nth-child(1),
.cards-grid.cols-4 .info-card:nth-child(1) {
margin-top: 0;
}
/* Все остальные карточки имеют отступ сверху */
.cards-grid.cols-2 .info-card:nth-child(n+2),
.cards-grid.cols-3 .info-card:nth-child(n+2),
.cards-grid.cols-4 .info-card:nth-child(n+2) {
margin-top: 10px;
}
}

Добавлено (2026-03-11, 07:48)
---------------------------------------------
Теперь понял! Только в cols-1 нужно уменьшить отступы. Вот простое решение:

```css
/* ===== КОНТЕЙНЕР ДЛЯ СЕТКИ ===== */
.cards-grid {
display: flex;
flex-wrap: wrap;
margin: 0;
gap: 10px; /* Стандартный отступ для всех сеток */
}

.cards-grid .info-card {
flex: 0 0 auto;
padding: 0;
margin: 0; /* Убираем margin у карточек в сетке */
box-sizing: border-box;
}

/* 1 колонка - уменьшенные отступы */
.cards-grid.cols-1 {
gap: 5px; /* Уменьшаем отступ для cols-1 */
}

/* 2 колонки */
.cards-grid.cols-2 .info-card {
width: calc(50% - 5px);
}

/* 3 колонки */
.cards-grid.cols-3 .info-card {
width: calc(33.333% - 6.667px);
}

/* 4 колонки */
.cards-grid.cols-4 .info-card {
width: calc(25% - 7.5px);
}

/* Промежуточные состояния */
@media (max-width: 1100px) and (min-width: 901px) {
.cards-grid.cols-4 .info-card {
width: calc(50% - 5px);
}
}

@media (max-width: 900px) and (min-width: 751px) {
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: calc(50% - 5px);
}
}

@media (max-width: 750px) {
.cards-grid.cols-2 .info-card,
.cards-grid.cols-3 .info-card,
.cards-grid.cols-4 .info-card {
width: 100%;
}
/* На мобильных тоже уменьшаем отступы */
.cards-grid {
gap: 8px;
}
.cards-grid.cols-1 {
gap: 4px;
}
}

@media (max-width: 480px) {
.cards-grid {
gap: 6px;
}
.cards-grid.cols-1 {
gap: 3px;
}
}
```

Что изменил:

1. Для cols-1 поставил отдельный gap: 5px (вместо общих 10px)
2. На мобильных тоже пропорционально уменьшил
3. Всё остальное работает как прежде

Теперь в cols-1 отступы между карточками будут меньше, а в остальных сетках как обычно.

Добавлено (2026-03-11, 09:22)
---------------------------------------------
Отлично! Вот финальные правки:

```css
/* ===== ТЕГИ - уменьшенные ===== */
.info-card-tag {
display: inline-block;
font-size: 0.7rem; /* Уменьшил с 0.75rem до 0.7rem */
padding: 0.15rem 0.5rem; /* Уменьшил отступы */
border-radius: 10px; /* Чуть уменьшил скругление */
font-weight: 500;
white-space: nowrap;
border: 1px solid transparent;
line-height: 1.3; /* Чуть уменьшил высоту строки */
flex-shrink: 0;
}

/* ===== ИКОНКИ - прозрачный фон ===== */
.info-card-title-icon .custom-icon {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 0;
background: transparent; /* Прозрачный фон */
}

.info-card-title-icon .custom-icon svg {
width: 100%;
height: 100%;
fill: currentColor;
color: #666666;
vertical-align: middle;
background: transparent; /* Прозрачный фон для SVG */
}

/* Для темной темы тоже прозрачный фон */
body.dark .info-card-title-icon .custom-icon,
html.dark .info-card-title-icon .custom-icon,
body.dark .info-card-title-icon .custom-icon svg,
html.dark .info-card-title-icon .custom-icon svg {
background: transparent;
}
```

Если у каких-то конкретных SVG иконок есть проблемы с фоном, можно добавить принудительно:

```css
/* Принудительно убираем фон у всех SVG в карточках */
.info-card svg {
background: transparent !important;
}

.info-card svg * {
background: transparent !important;
}
```

Но лучше сначала попробовать первый вариант - он должен решить проблему с фоном.

Добавлено (2026-03-11, 09:29)
---------------------------------------------
Понял проблему! Нужно добавить стили для иконок, чтобы они сохраняли свои оригинальные пропорции и толщину линий. Вот исправление:

```css
/* ===== ИКОНКИ - сохраняем оригинальный вид ===== */
.info-card-title-icon .custom-icon {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 0;
background: transparent;
}

.info-card-title-icon .custom-icon svg {
width: auto; /* Автоматическая ширина */
height: auto; /* Автоматическая высота */
max-width: 100%; /* Но не больше контейнера */
max-height: 100%; /* Но не больше контейнера */
fill: currentColor;
color: #666666;
vertical-align: middle;
background: transparent;
stroke: currentColor; /* Сохраняем обводку если есть */
stroke-width: inherit; /* Сохраняем оригинальную толщину */
}

/* Для иконок с фиксированным размером через iconsize */
.info-card-title-icon .custom-icon svg[width],
.info-card-title-icon .custom-icon svg[height] {
width: attr(width);
height: attr(height);
}

/* Если иконка использует stroke (обводку) */
.info-card-title-icon .custom-icon svg path,
.info-card-title-icon .custom-icon svg circle,
.info-card-title-icon .custom-icon svg rect,
.info-card-title-icon .custom-icon svg line,
.info-card-title-icon .custom-icon svg polyline {
stroke: currentColor;
stroke-width: inherit;
}

/* Убираем лишние заливки которые могут делать иконку жирной */
.info-card-title-icon .custom-icon svg [fill="none"] {
fill: none;
}

.info-card-title-icon .custom-icon svg [fill="currentColor"] {
fill: currentColor;
}

/* Для темной темы */
body.dark .info-card-title-icon .custom-icon svg,
html.dark .info-card-title-icon .custom-icon svg {
color: #a0aec0;
}
```

Также в самом шаблоне нужно убедиться, что SVG вставляется правильно. Найдите эту часть:

```html
{{ $iconSvg := replaceRE "<svg" (printf "<svg width='%s' height='%s'" $iconsize $iconsize) $iconData }}
<span class="custom-icon">{{ $iconSvg | safeHTML }}</span>
```

Замените на:

```html
{{ $iconSvg := replaceRE "<svg" (printf "<svg width='%s' height='%s' preserveAspectRatio='xMidYMid meet'" $iconsize $iconsize) $iconData }}
<span class="custom-icon">{{ $iconSvg | safeHTML }}</span>
```

Добавил preserveAspectRatio='xMidYMid meet' чтобы SVG сохранял пропорции.

Если какая-то конкретная иконка все еще отображается слишком жирно, проверьте оригинальный SVG файл - возможно у него слишком толстая обводка (stroke-width).

Добавлено (2026-03-11, 09:37)
---------------------------------------------
Конечно! Вот стили для эффекта при наведении:

```css
/* ===== ЭФФЕКТ ПРИ НАВЕДЕНИИ ===== */
.info-card-link {
display: block;
text-decoration: none;
color: #333333;
width: 100%;
height: 100%;
box-sizing: border-box;
transition: color 0.2s ease; /* Плавный переход для цвета текста */
}

.info-card-link:hover {
text-decoration: none;
color: #000000; /* Текст становится темнее при наведении */
}

.info-card-link:hover .info-card-title {
color: #000000; /* Заголовок тоже темнеет */
}

.info-card-link:hover .info-card-title-icon .custom-icon svg {
color: #000000; /* Иконка тоже темнеет */
}

/* Для иконок с вашим новым цветом #9c9c9c */
.info-card-title-icon .custom-icon svg {
width: 100%;
height: 100%;
color: #9c9c9c; /* Ваш новый цвет */
vertical-align: middle;
background: transparent;
transition: color 0.2s ease; /* Плавный переход для иконки */
}

/* Для темной темы */
body.dark .info-card-link,
html.dark .info-card-link {
color: #e2e8f0;
}

body.dark .info-card-link:hover,
html.dark .info-card-link:hover {
color: #ffffff; /* На темной теме еще светлее */
}

body.dark .info-card-link:hover .info-card-title,
html.dark .info-card-link:hover .info-card-title {
color: #ffffff;
}

body.dark .info-card-link:hover .info-card-title-icon .custom-icon svg,
html.dark .info-card-link:hover .info-card-title-icon .custom-icon svg {
color: #ffffff;
}
```

Теперь при наведении на всю карточку:

· Текст заголовка темнеет до #000000
· Иконка тоже темнеет до #000000
· На темной теме наоборот - становится светлее до #ffffff
· Все изменения плавные благодаря transition

Добавлено (2026-03-11, 09:44)
---------------------------------------------
Вот с более плавными переходами:

```css
/* ===== ОСНОВНЫЕ СТИЛИ С ПЛАВНЫМИ ПЕРЕХОДАМИ ===== */
.info-card {
transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1); /* Плавный переход для всей карточки */
position: relative;
box-sizing: border-box;
display: inline-block;
vertical-align: top;
margin: 0.75rem 0 0 0;
padding: 0;
width: 100%;
}

.info-card-link {
display: block;
text-decoration: none;
color: #333333;
width: 100%;
height: 100%;
box-sizing: border-box;
transition: color 0.4s ease; /* Более плавный переход для цвета ссылки */
}

.info-card-link:hover {
text-decoration: none;
color: #000000;
}

.info-card-title {
font-size: 1rem;
font-weight: 600;
color: #333333;
line-height: 1.4;
flex: 1 1 auto;
min-width: 30px;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
transition: color 0.4s ease; /* Плавный переход для заголовка */
}

.info-card-link:hover .info-card-title {
color: #000000;
}

/* Иконки с очень плавным переходом */
.info-card-title-icon .custom-icon svg {
width: 100%;
height: 100%;
color: #9c9c9c;
vertical-align: middle;
background: transparent;
transition: color 0.5s ease; /* Еще более плавный для иконки */
}

.info-card-link:hover .info-card-title-icon .custom-icon svg {
color: #000000;
}

/* Плавные переходы для границ и теней карточки */
.info-card-style-button {
border: 1px solid #e0e0e0;
border-radius: 8px;
background: #ffffff;
transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1),
border-color 0.4s ease,
box-shadow 0.5s ease; /* Разные скорости для разных свойств */
}

.info-card-style-button:hover {
border-color: #4a90e2;
box-shadow: 0 4px 12px rgba(74, 144, 226, 0.2);
}

.info-card-style-card {
border: none;
border-radius: 12px;
background: #ffffff;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
transition: all 0.4s cubic-bezier(0.2, 0, 0, 1), /* Кастомный easing */
transform 0.3s ease,
box-shadow 0.5s ease;
}

.info-card-style-card:hover {
transform: translateY(-3px);
box-shadow: 0 12px 28px rgba(0,0,0,0.15);
}

/* Для темной темы */
body.dark .info-card-link,
html.dark .info-card-link {
color: #e2e8f0;
transition: color 0.4s ease;
}

body.dark .info-card-link:hover,
html.dark .info-card-link:hover {
color: #ffffff;
}

body.dark .info-card-title,
html.dark .info-card-title {
color: #f7fafc;
transition: color 0.4s ease;
}

body.dark .info-card-link:hover .info-card-title,
html.dark .info-card-link:hover .info-card-title {
color: #ffffff;
}

body.dark .info-card-title-icon .custom-icon svg,
html.dark .info-card-title-icon .custom-icon svg {
color: #a0aec0;
transition: color 0.5s ease;
}

body.dark .info-card-link:hover .info-card-title-icon .custom-icon svg,
html.dark .info-card-link:hover .info-card-title-icon .custom-icon svg {
color: #ffffff;
}
```

Что улучшил:

1. Разные скорости для разных свойств:
· Текст и заголовок: 0.4s
· Иконки: 0.5s (самые плавные)
· Границы и тени: комбинированные переходы
2. Кастомные easing-функции:
· cubic-bezier(0.25, 0.1, 0.25, 1) - стандартный плавный
· cubic-bezier(0.2, 0, 0, 1) - более динамичный для карточек
3. Плавность при наведении и снятии теперь одинаковая благодаря transition на основном элементе, а не только на :hover

Теперь все изменения происходят очень плавно как при наведении, так и при убирании курсора.

654654564654
Прикрепления:
2933699.zip (432.7 Kb)
https://presoft.ru/download/4.zip
Мпии
Прикрепления:
ddddd_1.noext (33.2 Kb)
let database = {
tables: {},
tableStats: {},
descriptions: {}, // пользовательские описания
searchResults: {}, // результаты поиска по данным {tableName: [{recordIndex, field, value}]}
knownTables: {
"MR_DBA.ASSAY_METHOD_ASMT": "Метод опробования",
"MR_DBA.ASSAY_PLACE_ASPL": "Место отбора пробы",
"MR_DBA.ASSAY_TYPE_PO_ASPO": "Тип пробы на ТН",
"MR_DBA.ASSAY_TYPE_PROP_ASTR": "Тип пробы",
"MR_DBA.COLOR_CLRA": "Возраст (полное название с цветом но без индексов)",
"MR_DBA.COLOR_COM_CLRC": "цвет общий",
"MR_DBA.CONDITION_DRILL_CNDR": "условие бурения",
"MR_DBA.COORDINATE_DT_STDT": "названия эллипсоида системы координат",
"MR_DBA.COORDINATE_GR_STGR": "таблица с названиями типов координат",
"MR_DBA.COORDINATE_STMC": "таблица с названиями систем координат",
"MR_DBA.COORDINATE_ZN_ORZN": "таблица координат с зонами",
"MR_DBA.DIAM_BORE_DMBR": "Диаметр бурения/Коронки",
"MR_DBA.DRIFTING_METHOD_DRMT": "вид бурения",
"MR_DBA.DRIFTING_MODE_DRMD": "Способ проходки",
"MR_DBA.DRIFTING_PROC_DRPR": "способ бурения",
"MR_DBA.DRIFTING_WAY_DRWY": "способ выноса шлама",
"MR_DBA.EMPLOYEE_FIO_EMFI": "также имена сотрудников (видимо для связей)",
"MR_DBA.EMPLOYEE_LOGIN_EMLG": "логины",
"MR_DBA.HOLE_DESTIN_HLAA": "назначение скважины",
"MR_DBA.HOLE_INCLINE_HLIN": "вид скважины",
"MR_DBA.METHOD_GWS_GWMT": "метод ГИС (геофизических исследований)",
"MR_DBA.METHOD_TIE_ALTIT_MTTA": "метод высотной привязки (для координат)",
"MR_DBA.METHOD_TIE_PLANE_MTTP": "метод плановой привязки (для координат)",
"MR_DBA.MIN_WEALTH_MNWL": "полезное ископаемое",
"MR_DBA.OBJECT_DESTIN_OBDA": "стадия работ на объекте (участке)",
"MR_DBA.OUR_DIVISION": "Подразделения (бригады)",
"MR_DBA.OUR_EMPLOYEE": "Сотрудники (авторы)",
"MR_DBA.POINT_OBSERV_TYPE_PNOT": "Тип точки наблюдения",
"MR_DBA.R_INSTALL_END_COL_INEC": "способ оборудования низа буровой колонны",
"MR_DBA.R_PERFORATION_PRFR": "способ перфорации фильтра",
"MR_DBA.R_TYPE_BORE_TPBR": "тип буровой коронки",
"MR_DBA.R_TYPE_FILTER_TPFL": "тип фильтра",
"MR_DBA.SPECK_SPCK": "Литология (геологическая порода)",
"MR_DBA.SYSTEM_HEIGHT_STMH": "система высот для координат",
"MR_DBA.S_EI_EIEI": "единица измерения для координат",
"MR_DBA.TYPE_DOCUM_TNG_DCTT": "Тип документирования",
"MR_DBA.PO_GWSURVEY_DEV_POGD": "Тип средства измерения",
"MR_DBA.TYPE_ROCK_BORE_RBTP": "Категория пород по буримости",
"MR_DBA.CLASSIFIER_LITHOLOGY": "Литология (породы) из классификатора",
"MR_DBA.CLASSIFIER_AGE": "Возраст/Стратиграфия из классификатора",
"MR_DBA.CLASSIFIER_GEOLOGY_BIND": "Геологическая привязка из классификатора",
"MR_DBA.CLASSIFIER_PROPERTIES": "Геологические свойства пород из классификатора",
"MR_DBA.PO_GWSURVEY_USE_POGU": "Назначение средств измерений",
"MR_DBA.CLASSIFIER_OTHER": "Прочие данные классификатора"
}
};
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Геологическая БД - Документирование скважин</title>
<style>
* { box-sizing: border-box; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; }
body { background: #e9ecef; margin: 0; padding: 20px; }
.app-container { max-width: 1400px; margin: 0 auto; }

.json-panel {
background: white; border-radius: 16px; padding: 15px 20px; margin-bottom: 20px;
display: flex; flex-wrap: wrap; align-items: center; gap: 15px; box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.json-panel label {
font-weight: 600; background: #2c3e66; color: white; padding: 8px 16px;
border-radius: 40px; cursor: pointer; font-size: 14px;
}
.json-status { font-size: 13px; padding: 5px 12px; border-radius: 40px; background: #e6f4ea; color: #2b7a3e; }
.json-status.error { background: #fee2e2; color: #c62828; }

.tabs { display: flex; gap: 8px; margin-bottom: 20px; flex-wrap: wrap; }
.tab-btn {
background: #dee2e6; border: none; padding: 10px 24px; border-radius: 40px;
font-weight: 600; cursor: pointer; transition: 0.2s;
}
.tab-btn.active { background: #1e466e; color: white; }
.tab-content { display: none; background: white; border-radius: 24px; padding: 24px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin-bottom: 20px; }
.tab-content.active { display: block; }

.form-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; }
.field-group { display: flex; flex-direction: column; gap: 6px; }
.field-group label { font-weight: 600; font-size: 13px; color: #1e466e; }
input, select { padding: 10px 12px; border: 1px solid #ced4da; border-radius: 12px; font-size: 14px; }
input:focus, select:focus { outline: none; border-color: #1e466e; }

.combobox-container { position: relative; }
.combobox-dropdown {
position: absolute; top: 100%; left: 0; right: 0; background: white;
border: 1px solid #ccc; border-radius: 12px; max-height: 220px; overflow-y: auto;
z-index: 100; display: none; box-shadow: 0 6px 12px rgba(0,0,0,0.15);
}
.combobox-dropdown.show { display: block; }
.combobox-item { padding: 8px 12px; cursor: pointer; font-size: 13px; border-bottom: 1px solid #f0f0f0; }
.combobox-item:hover { background: #e9ecef; }

.btn-primary { background: #1e466e; color: white; border: none; padding: 8px 18px; border-radius: 40px; font-weight: 600; cursor: pointer; }
.card-list { display: flex; flex-direction: column; gap: 12px; margin-top: 12px; }
.interval-item, .sample-item {
background: #f8f9fc; border-radius: 20px; padding: 14px; border-left: 5px solid #2c7da0;
cursor: pointer; transition: 0.1s;
}
.interval-header, .sample-header { display: flex; justify-content: space-between; font-weight: 700; margin-bottom: 8px; }
.compact-data { display: flex; flex-wrap: wrap; gap: 16px; font-size: 13px; color: #2c3e50; }
.well-card {
background: white; border-radius: 20px; padding: 16px; margin-bottom: 12px;
box-shadow: 0 2px 5px rgba(0,0,0,0.08); cursor: pointer;
}
.progress-bar-container { background: #e9ecef; border-radius: 20px; height: 30px; position: relative; margin: 20px 0 10px; overflow: hidden; }
.progress-fill { background: #2c7da0; width: 0%; height: 100%; border-radius: 20px; transition: width 0.2s; }
.section-header { display: flex; justify-content: space-between; align-items: center; margin: 24px 0 16px 0; }
</style>
</head>
<body>
<div class="app-container">
<div class="json-panel">
<label for="jsonFile">📂 Загрузить JSON базу данных</label>
<input type="file" id="jsonFile" accept=".json" style="display:none">
<div class="json-status" id="jsonStatus">Ожидание загрузки JSON...</div>
</div>

<div class="tabs">
<button class="tab-btn active" data-tab="main">🏠 Мои скважины</button>
<button class="tab-btn" data-tab="general">📋 Общая информация</button>
<button class="tab-btn" data-tab="intervals">📊 Интервалы</button>
<button class="tab-btn" data-tab="sampling">🧪 Опробование</button>
</div>

<div id="tab-main" class="tab-content active">
<div style="display: flex; justify-content: space-between; margin-bottom: 20px;">
<h2>Список скважин</h2>
<button class="btn-primary" id="newWellBtn">➕ Новая скважина</button>
</div>
<div id="wellsList">Загрузка...</div>
</div>

<div id="tab-general" class="tab-content">
<h3>Общие сведения о скважине</h3>
<div class="form-grid">
<div class="field-group"><label>Объект</label><input type="text" id="objectName"></div>
<div class="field-group"><label>Участок</label><input type="text" id="areaName"></div>
<div class="field-group"><label>Буровая линия</label><input type="text" id="lineNumber"></div>
<div class="field-group"><label>Номер скважины</label><input type="text" id="holeNumber"></div>
<div class="field-group"><label>Дата начала бурения</label><input type="date" id="drillStartDate"></div>
<div class="field-group"><label>Дата окончания бурения</label><input type="date" id="drillEndDate"></div>
<div class="field-group"><label>Буровая бригада</label><div class="combobox-container"><input type="text" id="crewInput" placeholder="Выберите бригаду"><div id="crewDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Фактическая глубина (м)</label><input type="number" step="0.1" id="actualDepth"></div>
<div class="field-group"><label>Дата начала документирования</label><input type="date" id="docStartDate"></div>
<div class="field-group"><label>Дата окончания документирования</label><input type="date" id="docEndDate"></div>
<div class="field-group"><label>Документатор</label><div class="combobox-container"><input type="text" id="documentatorInput" placeholder="ФИО"><div id="documentatorDropdown" class="combobox-dropdown"></div></div></div>
</div>
<div class="progress-bar-container" id="progressContainer" style="display:none;"><div class="progress-fill" id="progressFill"></div></div>
<div style="margin-top:20px; text-align:right"><button class="btn-primary" id="saveWellBtn">💾 Сохранить скважину</button></div>
</div>

<div id="tab-intervals" class="tab-content">
<h3>Геологические интервалы</h3>
<div class="form-grid" style="margin-bottom: 16px;">
<div class="field-group"><label>От (м)</label><input type="number" step="0.1" id="intervalFrom"></div>
<div class="field-group"><label>До (м)</label><input type="number" step="0.1" id="intervalTo"></div>
<div class="field-group"><label>Возраст</label><div class="combobox-container"><input type="text" id="ageInput" placeholder="Возраст"><div id="ageDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Литология</label><div class="combobox-container"><input type="text" id="lithologyInput" placeholder="Литология"><div id="lithologyDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Категория пород</label><select id="catCategory"><option value="">Категория</option></select></div>
<div class="field-group"><label>Описание</label><input type="text" id="intervalDesc"></div>
</div>
<button class="btn-primary" id="addIntervalBtn">➕ Добавить интервал</button>
<div class="section-header"><h4>Список интервалов</h4></div>
<div id="intervalsList" class="card-list"></div>
</div>

<div id="tab-sampling" class="tab-content">
<h3>Пробы (опробование)</h3>
<div class="form-grid" style="margin-bottom:16px;">
<div class="field-group"><label>От (м)</label><input type="number" step="0.1" id="sampleFrom"></div>
<div class="field-group"><label>До (м)</label><input type="number" step="0.1" id="sampleTo"></div>
<div class="field-group"><label>Дата отбора</label><input type="date" id="sampleDate"></div>
<div class="field-group"><label>Автор отбора</label><div class="combobox-container"><input type="text" id="sampleAuthorInput" placeholder="Автор"><div id="sampleAuthorDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Место отбора</label><div class="combobox-container"><input type="text" id="sampleLocationInput" placeholder="Место"><div id="sampleLocationDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Тип пробы</label><div class="combobox-container"><input type="text" id="assayTypeInput" placeholder="Тип пробы"><div id="assayTypeDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Документ</label><select id="sampleDocType"><option value="primary">Первичное</option><option value="final">Итоговое</option></select></div>
<div class="field-group"><label>Объем</label><input type="text" id="sampleVolume"></div>
<div class="field-group"><label>Ед. изм. объема</label><div class="combobox-container"><input type="text" id="volumeUnitInput" placeholder="Ед.изм"><div id="volumeUnitDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Вес (кг)</label><input type="number" step="0.01" id="sampleWeight"></div>
</div>
<button class="btn-primary" id="addSampleBtn">➕ Добавить пробу</button>
<div class="section-header"><h4>Список проб</h4></div>
<div id="samplesList" class="card-list"></div>
</div>
</div>

<script>
// ========== ГЛОБАЛЬНАЯ БАЗА ДАННЫХ ==========
let db = {
tables: {},
knownTables: {
"MR_DBA.OUR_DIVISION": "Буровые бригады",
"MR_DBA.OUR_EMPLOYEE": "Сотрудники",
"MR_DBA.EMPLOYEE_FIO_EMFI": "ФИО сотрудников",
"MR_DBA.CLASSIFIER_AGE": "Возраст (стратиграфия)",
"MR_DBA.CLASSIFIER_LITHOLOGY": "Литология",
"MR_DBA.SPECK_SPCK": "Литология (породы)",
"MR_DBA.TYPE_ROCK_BORE_RBTP": "Категория пород",
"MR_DBA.ASSAY_TYPE_PROP_ASTR": "Тип пробы",
"MR_DBA.ASSAY_PLACE_ASPL": "Место отбора пробы",
"MR_DBA.S_EI_EIEI": "Единицы измерения",
"MR_DBA.COLOR_CLRA": "Возраст (цветной классификатор)"
}
};

let wells = [];
let currentWellId = null;
let intervals = [];
let samples = [];

// ========== ЗАГРУЗКА JSON И ПАРСИНГ ТАБЛИЦ ==========
document.getElementById('jsonFile').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;

const reader = new FileReader();
reader.onload = function(event) {
try {
const jsonData = JSON.parse(event.target.result);
db.tables = jsonData; // Сохраняем все таблицы

// Выводим информацию о загруженных таблицах
const tableNames = Object.keys(jsonData);
document.getElementById('jsonStatus').innerHTML = `✅ Загружено ${tableNames.length} таблиц: ${tableNames.slice(0, 5).join(', ')}${tableNames.length > 5 ? '...' : ''}`;
document.getElementById('jsonStatus').classList.remove('error');

// Обновляем все выпадающие списки из загруженных данных
loadDictionariesFromDB();

} catch (error) {
document.getElementById('jsonStatus').innerHTML = `❌ Ошибка парсинга JSON: ${error.message}`;
document.getElementById('jsonStatus').classList.add('error');
console.error(error);
}
};
reader.readAsText(file);
});

// ========== ЗАГРУЗКА СПРАВОЧНИКОВ ИЗ БАЗЫ ==========
function loadDictionariesFromDB() {
// 1. Буровые бригады из MR_DBA.OUR_DIVISION (поле NAME_SHORT или NAME)
if (db.tables["MR_DBA.OUR_DIVISION"]) {
const crews = db.tables["MR_DBA.OUR_DIVISION"]
.map(item => item.NAME_SHORT || item.NAME || item.NAME_FULL)
.filter(v => v);
setupCombobox('crew', crews);
console.log(`✅ Загружено бригад: ${crews.length}`);
} else {
console.warn("⚠️ Таблица MR_DBA.OUR_DIVISION не найдена");
setupCombobox('crew', []);
}

// 2. Сотрудники (документаторы и авторы) из MR_DBA.OUR_EMPLOYEE или MR_DBA.EMPLOYEE_FIO_EMFI
let employees = [];
if (db.tables["MR_DBA.OUR_EMPLOYEE"]) {
employees = db.tables["MR_DBA.OUR_EMPLOYEE"]
.map(item => item.FAMILY_EMFI || item.FAMILY ? `${item.FAMILY_EMFI || item.FAMILY} ${item.NAME_EMFI || item.NAME} ${item.PATR_EMFI || ''}`.trim() : item.NAME_SHORT)
.filter(v => v && v.length > 0);
}
if (db.tables["MR_DBA.EMPLOYEE_FIO_EMFI"] && employees.length === 0) {
employees = db.tables["MR_DBA.EMPLOYEE_FIO_EMFI"]
.map(item => `${item.FAMILY_EMFI || ''} ${item.NAME_EMFI || ''} ${item.PATR_EMFI || ''}`.trim())
.filter(v => v);
}
setupCombobox('documentator', employees);
setupCombobox('sampleAuthor', employees);
console.log(`✅ Загружено сотрудников: ${employees.length}`);

// 3. Возраст из CLASSIFIER_AGE или COLOR_CLRA
let ages = [];
if (db.tables["MR_DBA.CLASSIFIER_AGE"]) {
ages = db.tables["MR_DBA.CLASSIFIER_AGE"]
.map(item => item.NODE_NAME || item.NAME || item.VALUE)
.filter(v => v);
} else if (db.tables["MR_DBA.COLOR_CLRA"]) {
ages = db.tables["MR_DBA.COLOR_CLRA"]
.map(item => item.NODE_NAME || item.NAME_FULL || item.NAME)
.filter(v => v);
}
setupCombobox('age', ages);
console.log(`✅ Загружено возрастов: ${ages.length}`);

// 4. Литология из CLASSIFIER_LITHOLOGY или SPECK_SPCK
let lithologies = [];
if (db.tables["MR_DBA.CLASSIFIER_LITHOLOGY"]) {
lithologies = db.tables["MR_DBA.CLASSIFIER_LITHOLOGY"]
.map(item => item.NODE_NAME || item.NAME_SPCK || item.NAME)
.filter(v => v);
} else if (db.tables["MR_DBA.SPECK_SPCK"]) {
lithologies = db.tables["MR_DBA.SPECK_SPCK"]
.map(item => item.NAME_SPCK || item.NAME_FULL || item.NODE_NAME)
.filter(v => v);
}
setupCombobox('lithology', lithologies);
console.log(`✅ Загружено литологий: ${lithologies.length}`);

// 5. Категория пород из TYPE_ROCK_BORE_RBTP
if (db.tables["MR_DBA.TYPE_ROCK_BORE_RBTP"]) {
const categories = db.tables["MR_DBA.TYPE_ROCK_BORE_RBTP"]
.map(item => item.NAME_FULL_RBTP || item.NAME_SHORT || item.NAME)
.filter(v => v);
const catSelect = document.getElementById('catCategory');
catSelect.innerHTML = '<option value="">Категория пород</option>';
categories.forEach(cat => {
catSelect.innerHTML += `<option value="${cat.replace(/"/g, '"')}">${cat}</option>`;
});
console.log(`✅ Загружено категорий пород: ${categories.length}`);
}

// 6. Тип пробы из ASSAY_TYPE_PROP_ASTR
let assayTypes = [];
if (db.tables["MR_DBA.ASSAY_TYPE_PROP_ASTR"]) {
assayTypes = db.tables["MR_DBA.ASSAY_TYPE_PROP_ASTR"]
.map(item => {
let code = item.NUMBER_PP_ASTR ? `[${item.NUMBER_PP_ASTR}] ` : '';
let name = item.NAME_FULL_ASTR || item.NAME_SHORT || item.NAME;
return `${code}${name}`;
})
.filter(v => v);
}
setupCombobox('assayType', assayTypes);
console.log(`✅ Загружено типов проб: ${assayTypes.length}`);

// 7. Место отбора из ASSAY_PLACE_ASPL
let samplePlaces = [];
if (db.tables["MR_DBA.ASSAY_PLACE_ASPL"]) {
samplePlaces = db.tables["MR_DBA.ASSAY_PLACE_ASPL"]
.map(item => item.NAME_SHORT_ASPL || item.NAME_FULL || item.NAME)
.filter(v => v);
}
setupCombobox('sampleLocation', samplePlaces);
console.log(`✅ Загружено мест отбора: ${samplePlaces.length}`);

// 8. Единицы измерения из S_EI_EIEI
let units = [];
if (db.tables["MR_DBA.S_EI_EIEI"]) {
units = db.tables["MR_DBA.S_EI_EIEI"]
.map(item => item.EINAMES_EIEI || item.NAME_SHORT || item.NAME)
.filter(v => v);
}
setupCombobox('volumeUnit', units);
console.log(`✅ Загружено единиц измерения: ${units.length}`);
}

// ========== УНИВЕРСАЛЬНЫЙ КОМБОБОКС ==========
function setupCombobox(fieldId, dataArray) {
const input = document.getElementById(fieldId + 'Input');
const dropdown = document.getElementById(fieldId + 'Dropdown');
if (!input || !dropdown) return;

// Удаляем старые обработчики, чтобы не дублировать
const newInput = input.cloneNode(true);
input.parentNode.replaceChild(newInput, input);
const newDropdown = dropdown.cloneNode(true);
dropdown.parentNode.replaceChild(newDropdown, dropdown);

const finalInput = document.getElementById(fieldId + 'Input');
const finalDropdown = document.getElementById(fieldId + 'Dropdown');

function filterAndShow(searchText) {
const filtered = dataArray.filter(item =>
item.toLowerCase().includes(searchText.toLowerCase())
);
finalDropdown.innerHTML = filtered.slice(0, 200).map(item =>
`<div class="combobox-item" onclick="selectComboboxItem('${fieldId}', '${item.replace(/'/g, "\\'").replace(/"/g, '"')}')">${escapeHtml(item)}</div>`
).join('');
if (filtered.length === 0) {
finalDropdown.innerHTML = '<div class="combobox-item">Ничего не найдено</div>';
}
}

finalInput.addEventListener('focus', () => {
filterAndShow(finalInput.value);
finalDropdown.classList.add('show');
});

finalInput.addEventListener('input', (e) => {
filterAndShow(e.target.value);
finalDropdown.classList.add('show');
});

document.addEventListener('click', (e) => {
if (!finalInput.contains(e.target) && !finalDropdown.contains(e.target)) {
finalDropdown.classList.remove('show');
}
});
}

function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}

window.selectComboboxItem = function(fieldId, value) {
document.getElementById(fieldId + 'Input').value = value;
document.getElementById(fieldId + 'Dropdown').classList.remove('show');
};

// ========== РАБОТА СО СКВАЖИНАМИ ==========
function loadWells() {
const saved = localStorage.getItem('geology_wells_full');
if (saved) {
wells = JSON.parse(saved);
} else {
wells = [];
}
displayWellsList();
}

function saveWellsToLocal() {
localStorage.setItem('geology_wells_full', JSON.stringify(wells));
}

function displayWellsList() {
const container = document.getElementById('wellsList');
if (wells.length === 0) {
container.innerHTML = '<div style="padding:20px; text-align:center;">Нет сохранённых скважин. Создайте новую.</div>';
return;
}

const sorted = [...wells].sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified));
container.innerHTML = sorted.map(well => {
const line = well.info?.line || '';
const hole = well.info?.hole || '';
const object = well.info?.object || '';
const area = well.info?.area || '';
const depth = well.info?.actualDepth || '—';
const intervalsCount = well.intervals?.length || 0;
const samplesCount = well.samples?.length || 0;

return `
<div class="well-card" onclick="editWellById('${well.id}')">
<div class="interval-header">
<span><strong>${line ? line + ' / ' : ''}${hole || 'Без номера'}</strong></span>
<span>${new Date(well.lastModified).toLocaleDateString()}</span>
</div>
<div style="font-size:13px; color:#666;">${object}${area ? ' / ' + area : ''}</div>
<div class="compact-data" style="margin-top:8px;">
<span>📊 ${intervalsCount} интервалов</span>
<span>🧪 ${samplesCount} проб</span>
<span>📏 ${depth} м</span>
</div>
<div style="margin-top:10px;">
<button class="btn-outline" onclick="event.stopPropagation();editWellById('${well.id}')">✏️ Редактировать</button>
<button class="btn-outline" onclick="event.stopPropagation();deleteWell('${well.id}')">🗑️ Удалить</button>
</div>
</div>
`;
}).join('');
}

window.deleteWell = function(id) {
if (confirm('Удалить скважину?')) {
wells = wells.filter(w => w.id !== id);
saveWellsToLocal();
if (currentWellId === id) clearForm();
displayWellsList();
}
};

window.editWellById = function(id) {
const well = wells.find(w => w.id === id);
if (well) loadWellToForm(well);
document.querySelector('.tab-btn[data-tab="general"]').click();
};

function clearForm() {
currentWellId = null;
intervals = [];
samples = [];
document.getElementById('objectName').value = '';
document.getElementById('areaName').value = '';
document.getElementById('lineNumber').value = '';
document.getElementById('holeNumber').value = '';
document.getElementById('crewInput').value = '';
document.getElementById('documentatorInput').value = '';
document.getElementById('actualDepth').value = '';
updateIntervalsList();
updateSamplesList();
}

function loadWellToForm(well) {
currentWellId = well.id;
document.getElementById('objectName').value = well.info?.object || '';
document.getElementById('areaName').value = well.info?.area || '';
document.getElementById('lineNumber').value = well.info?.line || '';
document.getElementById('holeNumber').value = well.info?.hole || '';
document.getElementById('drillStartDate').value = well.info?.drillStart || '';
document.getElementById('drillEndDate').value = well.info?.drillEnd || '';
document.getElementById('crewInput').value = well.info?.crew || '';
document.getElementById('actualDepth').value = well.info?.actualDepth || '';
document.getElementById('docStartDate').value = well.info?.docStart || '';
document.getElementById('docEndDate').value = well.info?.docEnd || '';
document.getElementById('documentatorInput').value = well.info?.documentator || '';

intervals = well.intervals ? JSON.parse(JSON.stringify(well.intervals)) : [];
samples = well.samples ? JSON.parse(JSON.stringify(well.samples)) : [];

updateIntervalsList();
updateSamplesList();
updateProgressBar();
}

function getCurrentWellInfo() {
return {
object: document.getElementById('objectName').value,
area: document.getElementById('areaName').value,
line: document.getElementById('lineNumber').value,
hole: document.getElementById('holeNumber').value,
drillStart: document.getElementById('drillStartDate').value,
drillEnd: document.getElementById('drillEndDate').value,
crew: document.getElementById('crewInput').value,
actualDepth: parseFloat(document.getElementById('actualDepth').value) || 0,
docStart: document.getElementById('docStartDate').value,
docEnd: document.getElementById('docEndDate').value,
documentator: document.getElementById('documentatorInput').value
};
}

function saveCurrentWell() {
const info = getCurrentWellInfo();
if (!info.hole && !info.line) {
alert('Укажите хотя бы номер скважины или буровую линию');
return;
}

const wellData = {
id: currentWellId || Date.now().toString(),
info: info,
intervals: intervals,
samples: samples,
lastModified: new Date().toISOString()
};

if (currentWellId) {
const index = wells.findIndex(w => w.id === currentWellId);
if (index !== -1) wells[index] = wellData;
else wells.push(wellData);
} else {
wells.push(wellData);
}

saveWellsToLocal();
currentWellId = wellData.id;
displayWellsList();
alert('Скважина сохранена');
updateProgressBar();
}

// ========== ИНТЕРВАЛЫ ==========
function addInterval() {
const from = parseFloat(document.getElementById('intervalFrom').value);
const to = parseFloat(document.getElementById('intervalTo').value);
if (isNaN(from) || isNaN(to) || from >= to) {
alert('Введите корректные значения от и до (от < до)');
return;
}

intervals.push({
from: from,
to: to,
age: document.getElementById('ageInput').value,
lithology: document.getElementById('lithologyInput').value,
category: document.getElementById('catCategory').value,
desc: document.getElementById('intervalDesc').value
});

intervals.sort((a, b) => a.from - b.from);
updateIntervalsList();
document.getElementById('intervalFrom').value = to;
document.getElementById('intervalTo').value = '';
document.getElementById('intervalDesc').value = '';
updateProgressBar();
}

function updateIntervalsList() {
const container = document.getElementById('intervalsList');
if (intervals.length === 0) {
container.innerHTML = '<div style="padding:20px; text-align:center;">Нет добавленных интервалов</div>';
return;
}

container.innerHTML = intervals.map((int, idx) => `
<div class="interval-item" onclick="editInterval(${idx})">
<div class="interval-header">
<span>${int.from} - ${int.to} м (${(int.to - int.from).toFixed(1)} м)</span>
<span>
<span onclick="event.stopPropagation();editInterval(${idx})">✏️</span>
<span onclick="event.stopPropagation();deleteInterval(${idx})">✕</span>
</span>
</div>
<div class="compact-data">
<span>📅 ${int.age || '—'}</span>
<span>⛰️ ${int.lithology || '—'}</span>
<span>📊 ${int.category || '—'}</span>
</div>
${int.desc ? `<div>📝 ${int.desc}</div>` : ''}
</div>
`).join('');
}

window.deleteInterval = function(idx) {
intervals.splice(idx, 1);
updateIntervalsList();
updateProgressBar();
};

window.editInterval = function(idx) {
const int = intervals[idx];
document.getElementById('intervalFrom').value = int.from;
document.getElementById('intervalTo').value = int.to;
document.getElementById('ageInput').value = int.age || '';
document.getElementById('lithologyInput').value = int.lithology || '';
document.getElementById('catCategory').value = int.category || '';
document.getElementById('intervalDesc').value = int.desc || '';
deleteInterval(idx);
};

// ========== ПРОБЫ ==========
function addSample() {
const from = parseFloat(document.getElementById('sampleFrom').value);
const to = parseFloat(document.getElementById('sampleTo').value);
if (isNaN(from)) {
alert('Введите глубину отбора');
return;
}

samples.push({
from: from,
to: isNaN(to) ? from : to,
date: document.getElementById('sampleDate').value,
author: document.getElementById('sampleAuthorInput').value,
location: document.getElementById('sampleLocationInput').value,
type: document.getElementById('assayTypeInput').value,
docType: document.getElementById('sampleDocType').value,
volume: document.getElementById('sampleVolume').value,
volumeUnit: document.getElementById('volumeUnitInput').value,
weight: parseFloat(document.getElementById('sampleWeight').value) || null
});

updateSamplesList();
document.getElementById('sampleFrom').value = '';
document.getElementById('sampleTo').value = '';
document.getElementById('sampleDate').value = '';
document.getElementById('sampleAuthorInput').value = '';
document.getElementById('sampleLocationInput').value = '';
document.getElementById('assayTypeInput').value = '';
document.getElementById('sampleVolume').value = '';
document.getElementById('volumeUnitInput').value = '';
document.getElementById('sampleWeight').value = '';
}

function updateSamplesList() {
const container = document.getElementById('samplesList');
if (samples.length === 0) {
container.innerHTML = '<div style="padding:20px; text-align:center;">Нет добавленных проб</div>';
return;
}

container.innerHTML = samples.map((s, idx) => `
<div class="interval-item" onclick="editSample(${idx})">
<div class="interval-header">
<span>${s.from}${s.from !== s.to ? ' - ' + s.to + ' м' : ' м'}</span>
<span>
<span onclick="event.stopPropagation();editSample(${idx})">✏️</span>
<span onclick="event.stopPropagation();deleteSample(${idx})">✕</span>
</span>
</div>
<div class="compact-data">
<span>📅 ${s.date || '—'}</span>
<span>👤 ${s.author || '—'}</span>
<span>📍 ${s.location || '—'}</span>
</div>
<div>${s.type || '—'} (${s.docType === 'final' ? 'Итоговое' : 'Первичное'})</div>
${s.volume ? `<div>📊 Объем: ${s.volume} ${s.volumeUnit || ''}</div>` : ''}
${s.weight ? `<div>⚖️ Вес: ${s.weight} кг</div>` : ''}
</div>
`).join('');
}

window.deleteSample = function(idx) {
samples.splice(idx, 1);
updateSamplesList();
};

window.editSample = function(idx) {
const s = samples[idx];
document.getElementById('sampleFrom').value = s.from;
document.getElementById('sampleTo').value = s.to;
document.getElementById('sampleDate').value = s.date || '';
document.getElementById('sampleAuthorInput').value = s.author || '';
document.getElementById('sampleLocationInput').value = s.location || '';
document.getElementById('assayTypeInput').value = s.type || '';
document.getElementById('sampleDocType').value = s.docType || 'primary';
document.getElementById('sampleVolume').value = s.volume || '';
document.getElementById('volumeUnitInput').value = s.volumeUnit || '';
document.getElementById('sampleWeight').value = s.weight || '';
deleteSample(idx);
};

function updateProgressBar() {
const depth = parseFloat(document.getElementById('actualDepth').value);
if (!depth || depth <= 0 || intervals.length === 0) {
document.getElementById('progressContainer').style.display = 'none';
return;
}

const documentedDepth = intervals.reduce((sum, i) => sum + (i.to - i.from), 0);
const percent = Math.min(100, Math.round((documentedDepth / depth) * 100));
document.getElementById('progressFill').style.width = percent + '%';
document.getElementById('progressContainer').style.display = 'block';
}

// ========== ИНИЦИАЛИЗАЦИЯ ==========
document.getElementById('saveWellBtn').addEventListener('click', saveCurrentWell);
document.getElementById('addIntervalBtn').addEventListener('click', addInterval);
document.getElementById('addSampleBtn').addEventListener('click', addSample);
document.getElementById('newWellBtn').addEventListener('click', () => {
currentWellId = null;
intervals = [];
samples = [];
clearForm();
document.querySelector('.tab-btn[data-tab="general"]').click();
});

// Табы
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
document.getElementById(`tab-${btn.dataset.tab}`).classList.add('active');
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});

// Загружаем сохранённые скважины
loadWells();

// Показываем подсказку о необходимости загрузить JSON
document.getElementById('jsonStatus').innerHTML = '📁 Загрузите JSON файл с таблицами MR_DBA.*';
</script>
</body>
</html>
Пппрр
Прикрепления:
zzz.noext (70.4 Kb)
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Геологическая БД - Документирование скважин</title>
<style>
* { box-sizing: border-box; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; }
body { background: #e9ecef; margin: 0; padding: 20px; }
.app-container { max-width: 1400px; margin: 0 auto; }

.json-panel {
background: white; border-radius: 16px; padding: 15px 20px; margin-bottom: 20px;
display: flex; flex-wrap: wrap; align-items: center; gap: 15px; box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.json-panel label {
font-weight: 600; background: #2c3e66; color: white; padding: 8px 16px;
border-radius: 40px; cursor: pointer; font-size: 14px;
}
.json-status { font-size: 13px; padding: 5px 12px; border-radius: 40px; background: #e6f4ea; color: #2b7a3e; }
.json-status.error { background: #fee2e2; color: #c62828; }

.tabs { display: flex; gap: 8px; margin-bottom: 20px; flex-wrap: wrap; }
.tab-btn {
background: #dee2e6; border: none; padding: 10px 24px; border-radius: 40px;
font-weight: 600; cursor: pointer; transition: 0.2s;
}
.tab-btn.active { background: #1e466e; color: white; }
.tab-content { display: none; background: white; border-radius: 24px; padding: 24px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin-bottom: 20px; }
.tab-content.active { display: block; }

.form-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; }
.field-group { display: flex; flex-direction: column; gap: 6px; }
.field-group label { font-weight: 600; font-size: 13px; color: #1e466e; }
input, select { padding: 10px 12px; border: 1px solid #ced4da; border-radius: 12px; font-size: 14px; }
input:focus, select:focus { outline: none; border-color: #1e466e; }

.combobox-container { position: relative; }
.combobox-dropdown {
position: absolute; top: 100%; left: 0; right: 0; background: white;
border: 1px solid #ccc; border-radius: 12px; max-height: 220px; overflow-y: auto;
z-index: 100; display: none; box-shadow: 0 6px 12px rgba(0,0,0,0.15);
}
.combobox-dropdown.show { display: block; }
.combobox-item { padding: 8px 12px; cursor: pointer; font-size: 13px; border-bottom: 1px solid #f0f0f0; }
.combobox-item:hover { background: #e9ecef; }

.btn-primary { background: #1e466e; color: white; border: none; padding: 8px 18px; border-radius: 40px; font-weight: 600; cursor: pointer; }
.card-list { display: flex; flex-direction: column; gap: 12px; margin-top: 12px; }
.interval-item {
background: #f8f9fc; border-radius: 20px; padding: 14px; border-left: 5px solid #2c7da0;
cursor: pointer; transition: 0.1s;
}
.well-card {
background: white; border-radius: 20px; padding: 16px; margin-bottom: 12px;
box-shadow: 0 2px 5px rgba(0,0,0,0.08); cursor: pointer;
}
.progress-bar-container { background: #e9ecef; border-radius: 20px; height: 30px; position: relative; margin: 20px 0 10px; overflow: hidden; }
.progress-fill { background: #2c7da0; width: 0%; height: 100%; border-radius: 20px; transition: width 0.2s; }
.section-header { display: flex; justify-content: space-between; align-items: center; margin: 24px 0 16px 0; }
.compact-data { display: flex; flex-wrap: wrap; gap: 16px; font-size: 13px; color: #2c3e50; }
</style>
</head>
<body>
<div class="app-container">
<div class="json-panel">
<label for="jsonFile">📂 Загрузить JSON базу данных</label>
<input type="file" id="jsonFile" accept=".json" style="display:none">
<div class="json-status" id="jsonStatus">Ожидание загрузки JSON...</div>
</div>

<div class="tabs">
<button class="tab-btn active" data-tab="main">🏠 Мои скважины</button>
<button class="tab-btn" data-tab="general">📋 Общая информация</button>
<button class="tab-btn" data-tab="intervals">📊 Интервалы</button>
<button class="tab-btn" data-tab="sampling">🧪 Опробование</button>
</div>

<div id="tab-main" class="tab-content active">
<div style="display: flex; justify-content: space-between; margin-bottom: 20px;">
<h2>Список скважин</h2>
<button class="btn-primary" id="newWellBtn">➕ Новая скважина</button>
</div>
<div id="wellsList">Загрузка...</div>
</div>

<div id="tab-general" class="tab-content">
<h3>Общие сведения о скважине</h3>
<div class="form-grid">
<div class="field-group"><label>Объект</label><input type="text" id="objectName"></div>
<div class="field-group"><label>Участок</label><input type="text" id="areaName"></div>
<div class="field-group"><label>Буровая линия</label><input type="text" id="lineNumber"></div>
<div class="field-group"><label>Номер скважины</label><input type="text" id="holeNumber"></div>
<div class="field-group"><label>Дата начала бурения</label><input type="date" id="drillStartDate"></div>
<div class="field-group"><label>Дата окончания бурения</label><input type="date" id="drillEndDate"></div>
<div class="field-group"><label>Буровая бригада</label><div class="combobox-container"><input type="text" id="crewInput" placeholder="Выберите бригаду"><div id="crewDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Фактическая глубина (м)</label><input type="number" step="0.1" id="actualDepth"></div>
<div class="field-group"><label>Дата начала документирования</label><input type="date" id="docStartDate"></div>
<div class="field-group"><label>Дата окончания документирования</label><input type="date" id="docEndDate"></div>
<div class="field-group"><label>Документатор</label><div class="combobox-container"><input type="text" id="documentatorInput" placeholder="ФИО"><div id="documentatorDropdown" class="combobox-dropdown"></div></div></div>
</div>
<div class="progress-bar-container" id="progressContainer" style="display:none;"><div class="progress-fill" id="progressFill"></div></div>
<div style="margin-top:20px; text-align:right"><button class="btn-primary" id="saveWellBtn">💾 Сохранить скважину</button></div>
</div>

<div id="tab-intervals" class="tab-content">
<h3>Геологические интервалы</h3>
<div class="form-grid" style="margin-bottom: 16px;">
<div class="field-group"><label>От (м)</label><input type="number" step="0.1" id="intervalFrom"></div>
<div class="field-group"><label>До (м)</label><input type="number" step="0.1" id="intervalTo"></div>
<div class="field-group"><label>Возраст</label><div class="combobox-container"><input type="text" id="ageInput" placeholder="Возраст"><div id="ageDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Литология</label><div class="combobox-container"><input type="text" id="lithologyInput" placeholder="Литология"><div id="lithologyDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Категория пород</label><select id="catCategory"><option value="">Категория</option></select></div>
<div class="field-group"><label>Описание</label><input type="text" id="intervalDesc"></div>
</div>
<button class="btn-primary" id="addIntervalBtn">➕ Добавить интервал</button>
<div class="section-header"><h4>Список интервалов</h4></div>
<div id="intervalsList" class="card-list"></div>
</div>

<div id="tab-sampling" class="tab-content">
<h3>Пробы (опробование)</h3>
<div class="form-grid" style="margin-bottom:16px;">
<div class="field-group"><label>От (м)</label><input type="number" step="0.1" id="sampleFrom"></div>
<div class="field-group"><label>До (м)</label><input type="number" step="0.1" id="sampleTo"></div>
<div class="field-group"><label>Дата отбора</label><input type="date" id="sampleDate"></div>
<div class="field-group"><label>Автор отбора</label><div class="combobox-container"><input type="text" id="sampleAuthorInput" placeholder="Автор"><div id="sampleAuthorDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Место отбора</label><div class="combobox-container"><input type="text" id="sampleLocationInput" placeholder="Место"><div id="sampleLocationDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Тип пробы</label><div class="combobox-container"><input type="text" id="assayTypeInput" placeholder="Тип пробы"><div id="assayTypeDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Документ</label><select id="sampleDocType"><option value="primary">Первичное</option><option value="final">Итоговое</option></select></div>
<div class="field-group"><label>Объем</label><input type="text" id="sampleVolume"></div>
<div class="field-group"><label>Ед. изм. объема</label><div class="combobox-container"><input type="text" id="volumeUnitInput" placeholder="Ед.изм"><div id="volumeUnitDropdown" class="combobox-dropdown"></div></div></div>
<div class="field-group"><label>Вес (кг)</label><input type="number" step="0.01" id="sampleWeight"></div>
</div>
<button class="btn-primary" id="addSampleBtn">➕ Добавить пробу</button>
<div class="section-header"><h4>Список проб</h4></div>
<div id="samplesList" class="card-list"></div>
</div>
</div>

<script>
// ========== БАЗА ДАННЫХ ==========
let db = { tables: {} };

// Справочные данные (заполняются из JSON)
let dictionaries = {
crews: [], // Буровые бригады
documentators: [], // Документаторы
ages: [], // Возраста
lithologies: [], // Литологии
assayTypes: [], // Типы проб
samplePlaces: [], // Места отбора
rockCategories: [], // Категории пород
units: [] // Единицы измерения
};

// Данные приложения
let wells = [];
let currentWellId = null;
let intervals = [];
let samples = [];

// ========== ЗАГРУЗКА JSON ==========
document.getElementById('jsonFile').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;

const reader = new FileReader();
reader.onload = function(event) {
try {
const jsonData = JSON.parse(event.target.result);
db.tables = jsonData;

// Загружаем справочники из JSON
loadDictionariesFromJSON(jsonData);

document.getElementById('jsonStatus').innerHTML = `✅ Загружено ${Object.keys(jsonData).length} таблиц`;
document.getElementById('jsonStatus').classList.remove('error');

} catch (error) {
document.getElementById('jsonStatus').innerHTML = `❌ Ошибка: ${error.message}`;
document.getElementById('jsonStatus').classList.add('error');
}
};
reader.readAsText(file);
});

// ========== ЗАГРУЗКА СПРАВОЧНИКОВ ИЗ JSON ==========
function loadDictionariesFromJSON(jsonData) {

// 1. БУРОВЫЕ БРИГАДЫ → поле "Буровая бригада"
// Таблица: MR_DBA.OUR_DIVISION, поле: NAME_SHORT
if (jsonData['MR_DBA.OUR_DIVISION']) {
dictionaries.crews = jsonData['MR_DBA.OUR_DIVISION']
.map(item => item.NAME_SHORT || item.NAME)
.filter(v => v);
console.log(`✅ Буровых бригад: ${dictionaries.crews.length}`);
}

// 2. ДОКУМЕНТАТОРЫ и АВТОРЫ → поля "Документатор" и "Автор отбора"
// Таблица: MR_DBA.EMPLOYEE_FIO_EMFI, поля: FAMILY_EMFI, NAME_EMFI, PATR_EMFI
if (jsonData['MR_DBA.EMPLOYEE_FIO_EMFI']) {
dictionaries.documentators = jsonData['MR_DBA.EMPLOYEE_FIO_EMFI']
.map(item => {
const fam = item.FAMILY_EMFI || '';
const name = item.NAME_EMFI || '';
const patr = item.PATR_EMFI || '';
return `${fam} ${name} ${patr}`.trim();
})
.filter(v => v);
console.log(`✅ Сотрудников: ${dictionaries.documentators.length}`);
}
// Альтернативная таблица: MR_DBA.OUR_EMPLOYEE, поле: NAME_FULL
else if (jsonData['MR_DBA.OUR_EMPLOYEE']) {
dictionaries.documentators = jsonData['MR_DBA.OUR_EMPLOYEE']
.map(item => item.NAME_FULL || item.NAME_SHORT)
.filter(v => v);
console.log(`✅ Сотрудников (OUR_EMPLOYEE): ${dictionaries.documentators.length}`);
}

// 3. ВОЗРАСТ → поле "Возраст"
// Таблица: MR_DBA.CLASSIFIER, фильтр: NODE_ID начинается с 'A01010', поле: NODE_NAME
if (jsonData['MR_DBA.CLASSIFIER']) {
dictionaries.ages = jsonData['MR_DBA.CLASSIFIER']
.filter(item => item.NODE_ID && item.NODE_ID.startsWith('A01010'))
.map(item => item.NODE_NAME)
.filter(v => v);
console.log(`✅ Возрастов: ${dictionaries.ages.length}`);
}
// Альтернатива: MR_DBA.COLOR_CLRA
else if (jsonData['MR_DBA.COLOR_CLRA']) {
dictionaries.ages = jsonData['MR_DBA.COLOR_CLRA']
.map(item => item.NODE_NAME || item.NAME_FULL)
.filter(v => v);
console.log(`✅ Возрастов (COLOR_CLRA): ${dictionaries.ages.length}`);
}

// 4. ЛИТОЛОГИЯ → поле "Литология"
// Таблица: MR_DBA.SPECK_SPCK, поле: NAME_SPCK
if (jsonData['MR_DBA.SPECK_SPCK']) {
dictionaries.lithologies = jsonData['MR_DBA.SPECK_SPCK']
.map(item => item.NAME_SPCK)
.filter(v => v);
console.log(`✅ Литологий: ${dictionaries.lithologies.length}`);
}
// Альтернатива: MR_DBA.CLASSIFIER_LITHOLOGY
else if (jsonData['MR_DBA.CLASSIFIER_LITHOLOGY']) {
dictionaries.lithologies = jsonData['MR_DBA.CLASSIFIER_LITHOLOGY']
.map(item => item.NODE_NAME)
.filter(v => v);
console.log(`✅ Литологий (CLASSIFIER): ${dictionaries.lithologies.length}`);
}

// 5. ТИПЫ ПРОБ → поле "Тип пробы"
// Таблица: MR_DBA.ASSAY_TYPE_PROP_ASTR, поля: NUMBER_PP_ASTR, NAME_FULL_ASTR, ASSAY_TYPE_PROP_ASTR
if (jsonData['MR_DBA.ASSAY_TYPE_PROP_ASTR']) {
dictionaries.assayTypes = jsonData['MR_DBA.ASSAY_TYPE_PROP_ASTR']
.map(item => {
const number = item.NUMBER_PP_ASTR ? `[${item.NUMBER_PP_ASTR}] ` : '';
const name = item.NAME_FULL_ASTR || '';
const type = item.ASSAY_TYPE_PROP_ASTR ? ` (${item.ASSAY_TYPE_PROP_ASTR})` : '';
return number + name + type;
})
.filter(v => v);
console.log(`✅ Типов проб: ${dictionaries.assayTypes.length}`);
}

// 6. МЕСТО ОТБОРА → поле "Место отбора"
// Таблица: MR_DBA.ASSAY_PLACE_ASPL, поле: NAME_SHORT_ASPL
if (jsonData['MR_DBA.ASSAY_PLACE_ASPL']) {
dictionaries.samplePlaces = jsonData['MR_DBA.ASSAY_PLACE_ASPL']
.map(item => item.NAME_SHORT_ASPL || item.NAME_FULL)
.filter(v => v);
console.log(`✅ Мест отбора: ${dictionaries.samplePlaces.length}`);
}

// 7. КАТЕГОРИЯ ПОРОД → select "Категория"
// Таблица: MR_DBA.TYPE_ROCK_BORE_RBTP, поле: NAME_FULL_RBTP
if (jsonData['MR_DBA.TYPE_ROCK_BORE_RBTP']) {
dictionaries.rockCategories = jsonData['MR_DBA.TYPE_ROCK_BORE_RBTP']
.map(item => item.NAME_FULL_RBTP || item.NAME_SHORT)
.filter(v => v);

// Обновляем select
const catSelect = document.getElementById('catCategory');
catSelect.innerHTML = '<option value="">Категория пород</option>';
dictionaries.rockCategories.forEach(cat => {
catSelect.innerHTML += `<option value="${cat.replace(/"/g, '"')}">${cat}</option>`;
});
console.log(`✅ Категорий пород: ${dictionaries.rockCategories.length}`);
}

// 8. ЕДИНИЦЫ ИЗМЕРЕНИЯ → поля "Ед. изм."
// Таблица: MR_DBA.S_EI_EIEI, поле: EINAMES_EIEI
if (jsonData['MR_DBA.S_EI_EIEI']) {
dictionaries.units = jsonData['MR_DBA.S_EI_EIEI']
.map(item => item.EINAMES_EIEI)
.filter(v => v);
console.log(`✅ Единиц измерения: ${dictionaries.units.length}`);
}

// Обновляем все выпадающие списки
updateAllDropdowns();
}

// ========== НАСТРОЙКА КОМБОБОКСОВ ==========
function setupCombobox(fieldId, dataArray) {
const input = document.getElementById(fieldId + 'Input');
const dropdown = document.getElementById(fieldId + 'Dropdown');
if (!input || !dropdown) return;

// Клонируем чтобы убрать старые обработчики
const newInput = input.cloneNode(true);
input.parentNode.replaceChild(newInput, input);
const finalInput = document.getElementById(fieldId + 'Input');
const finalDropdown = document.getElementById(fieldId + 'Dropdown');

function filterAndShow() {
const searchText = finalInput.value.toLowerCase();
const filtered = dataArray.filter(item =>
item && item.toLowerCase().includes(searchText)
).slice(0, 200);

finalDropdown.innerHTML = filtered.map(item =>
`<div class="combobox-item" onclick="selectComboboxItem('${fieldId}', '${item.replace(/'/g, "\\'")}')">${escapeHtml(item)}</div>`
).join('');

if (filtered.length === 0) {
finalDropdown.innerHTML = '<div class="combobox-item">Ничего не найдено</div>';
}
}

finalInput.addEventListener('focus', () => {
filterAndShow();
finalDropdown.classList.add('show');
});

finalInput.addEventListener('input', filterAndShow);

document.addEventListener('click', (e) => {
if (!finalInput.contains(e.target) && !finalDropdown.contains(e.target)) {
finalDropdown.classList.remove('show');
}
});
}

function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}

window.selectComboboxItem = function(fieldId, value) {
document.getElementById(fieldId + 'Input').value = value;
document.getElementById(fieldId + 'Dropdown').classList.remove('show');
markUnsavedChanges();
};

function updateAllDropdowns() {
setupCombobox('crew', dictionaries.crews);
setupCombobox('documentator', dictionaries.documentators);
setupCombobox('sampleAuthor', dictionaries.documentators);
setupCombobox('age', dictionaries.ages);
setupCombobox('lithology', dictionaries.lithologies);
setupCombobox('assayType', dictionaries.assayTypes);
setupCombobox('sampleLocation', dictionaries.samplePlaces);
setupCombobox('volumeUnit', dictionaries.units);
}

// ========== РАБОТА СО СКВАЖИНАМИ ==========
function loadWells() {
const saved = localStorage.getItem('geology_wells');
if (saved) {
wells = JSON.parse(saved);
} else {
wells = [];
}
displayWellsList();
}

function saveWellsToLocal() {
localStorage.setItem('geology_wells', JSON.stringify(wells));
}

function displayWellsList() {
const container = document.getElementById('wellsList');
if (wells.length === 0) {
container.innerHTML = '<div style="padding:20px; text-align:center;">Нет сохранённых скважин. Создайте новую.</div>';
return;
}

const sorted = [...wells].sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified));
container.innerHTML = sorted.map(well => {
const line = well.info?.line || '';
const hole = well.info?.hole || '';
const object = well.info?.object || '';
const area = well.info?.area || '';
const depth = well.info?.actualDepth || '—';
const intervalsCount = well.intervals?.length || 0;
const samplesCount = well.samples?.length || 0;

return `
<div class="well-card" onclick="editWellById('${well.id}')">
<div style="display: flex; justify-content: space-between;">
<strong>${line ? line + ' / ' : ''}${hole || 'Без номера'}</strong>
<span style="font-size:12px; color:#666;">${new Date(well.lastModified).toLocaleDateString()}</span>
</div>
<div style="font-size:13px; color:#666;">${object}${area ? ' / ' + area : ''}</div>
<div class="compact-data" style="margin-top:8px;">
<span>📊 ${intervalsCount} интервалов</span>
<span>🧪 ${samplesCount} проб</span>
<span>📏 ${depth} м</span>
</div>
<div style="margin-top:10px;">
<button class="btn-primary" style="padding:4px 12px; margin-right:8px;" onclick="event.stopPropagation();editWellById('${well.id}')">✏️ Редактировать</button>
<button class="btn-primary" style="padding:4px 12px; background:#c62828;" onclick="event.stopPropagation();deleteWell('${well.id}')">🗑️ Удалить</button>
</div>
</div>
`;
}).join('');
}

window.deleteWell = function(id) {
if (confirm('Удалить скважину?')) {
wells = wells.filter(w => w.id !== id);
saveWellsToLocal();
if (currentWellId === id) clearForm();
displayWellsList();
}
};

window.editWellById = function(id) {
const well = wells.find(w => w.id === id);
if (well) loadWellToForm(well);
document.querySelector('.tab-btn[data-tab="general"]').click();
};

function clearForm() {
currentWellId = null;
intervals = [];
samples = [];
document.getElementById('objectName').value = '';
document.getElementById('areaName').value = '';
document.getElementById('lineNumber').value = '';
document.getElementById('holeNumber').value = '';
document.getElementById('crewInput').value = '';
document.getElementById('documentatorInput').value = '';
document.getElementById('actualDepth').value = '';
updateIntervalsList();
updateSamplesList();
}

function loadWellToForm(well) {
currentWellId = well.id;
document.getElementById('objectName').value = well.info?.object || '';
document.getElementById('areaName').value = well.info?.area || '';
document.getElementById('lineNumber').value = well.info?.line || '';
document.getElementById('holeNumber').value = well.info?.hole || '';
document.getElementById('drillStartDate').value = well.info?.drillStart || '';
document.getElementById('drillEndDate').value = well.info?.drillEnd || '';
document.getElementById('crewInput').value = well.info?.crew || '';
document.getElementById('actualDepth').value = well.info?.actualDepth || '';
document.getElementById('docStartDate').value = well.info?.docStart || '';
document.getElementById('docEndDate').value = well.info?.docEnd || '';
document.getElementById('documentatorInput').value = well.info?.documentator || '';

intervals = well.intervals ? JSON.parse(JSON.stringify(well.intervals)) : [];
samples = well.samples ? JSON.parse(JSON.stringify(well.samples)) : [];

updateIntervalsList();
updateSamplesList();
updateProgressBar();
}

function getCurrentWellInfo() {
return {
object: document.getElementById('objectName').value,
area: document.getElementById('areaName').value,
line: document.getElementById('lineNumber').value,
hole: document.getElementById('holeNumber').value,
drillStart: document.getElementById('drillStartDate').value,
drillEnd: document.getElementById('drillEndDate').value,
crew: document.getElementById('crewInput').value,
actualDepth: parseFloat(document.getElementById('actualDepth').value) || 0,
docStart: document.getElementById('docStartDate').value,
docEnd: document.getElementById('docEndDate').value,
documentator: document.getElementById('documentatorInput').value
};
}

function saveCurrentWell() {
const info = getCurrentWellInfo();
if (!info.hole && !info.line) {
alert('Укажите хотя бы номер скважины или буровую линию');
return;
}

const wellData = {
id: currentWellId || Date.now().toString(),
info: info,
intervals: intervals,
samples: samples,
lastModified: new Date().toISOString()
};

if (currentWellId) {
const index = wells.findIndex(w => w.id === currentWellId);
if (index !== -1) wells[index] = wellData;
else wells.push(wellData);
} else {
wells.push(wellData);
}

saveWellsToLocal();
currentWellId = wellData.id;
displayWellsList();
alert('Скважина сохранена');
updateProgressBar();
}

// ========== ИНТЕРВАЛЫ ==========
function addInterval() {
const from = parseFloat(document.getElementById('intervalFrom').value);
const to = parseFloat(document.getElementById('intervalTo').value);
if (isNaN(from) || isNaN(to) || from >= to) {
alert('Введите корректные значения от и до (от < до)');
return;
}

intervals.push({
from: from,
to: to,
age: document.getElementById('ageInput').value,
lithology: document.getElementById('lithologyInput').value,
category: document.getElementById('catCategory').value,
desc: document.getElementById('intervalDesc').value
});

intervals.sort((a, b) => a.from - b.from);
updateIntervalsList();
document.getElementById('intervalFrom').value = to;
document.getElementById('intervalTo').value = '';
document.getElementById('intervalDesc').value = '';
updateProgressBar();
}

function updateIntervalsList() {
const container = document.getElementById('intervalsList');
if (intervals.length === 0) {
container.innerHTML = '<div style="padding:20px; text-align:center;">Нет добавленных интервалов</div>';
return;
}

container.innerHTML = intervals.map((int, idx) => `
<div class="interval-item" onclick="editInterval(${idx})">
<div style="display: flex; justify-content: space-between;">
<span><strong>${int.from} - ${int.to} м</strong> (${(int.to - int.from).toFixed(1)} м)</span>
<span>
<span style="color:#1e466e; cursor:pointer;" onclick="event.stopPropagation();editInterval(${idx})">✏️</span>
<span style="color:#c62828; cursor:pointer;" onclick="event.stopPropagation();deleteInterval(${idx})">✕</span>
</span>
</div>
<div class="compact-data">
<span>📅 ${int.age || '—'}</span>
<span>⛰️ ${int.lithology || '—'}</span>
<span>📊 ${int.category || '—'}</span>
</div>
${int.desc ? `<div>📝 ${int.desc}</div>` : ''}
</div>
`).join('');
}

window.deleteInterval = function(idx) {
intervals.splice(idx, 1);
updateIntervalsList();
updateProgressBar();
};

window.editInterval = function(idx) {
const int = intervals[idx];
document.getElementById('intervalFrom').value = int.from;
document.getElementById('intervalTo').value = int.to;
document.getElementById('ageInput').value = int.age || '';
document.getElementById('lithologyInput').value = int.lithology || '';
document.getElementById('catCategory').value = int.category || '';
document.getElementById('intervalDesc').value = int.desc || '';
deleteInterval(idx);
};

// ========== ПРОБЫ ==========
function addSample() {
const from = parseFloat(document.getElementById('sampleFrom').value);
const to = parseFloat(document.getElementById('sampleTo').value);
if (isNaN(from)) {
alert('Введите глубину отбора');
return;
}

samples.push({
from: from,
to: isNaN(to) ? from : to,
date: document.getElementById('sampleDate').value,
author: document.getElementById('sampleAuthorInput').value,
location: document.getElementById('sampleLocationInput').value,
type: document.getElementById('assayTypeInput').value,
docType: document.getElementById('sampleDocType').value,
volume: document.getElementById('sampleVolume').value,
volumeUnit: document.getElementById('volumeUnitInput').value,
weight: parseFloat(document.getElementById('sampleWeight').value) || null
});

updateSamplesList();
document.getElementById('sampleFrom').value = '';
document.getElementById('sampleTo').value = '';
document.getElementById('sampleDate').value = '';
document.getElementById('sampleAuthorInput').value = '';
document.getElementById('sampleLocationInput').value = '';
document.getElementById('assayTypeInput').value = '';
document.getElementById('sampleVolume').value = '';
document.getElementById('volumeUnitInput').value = '';
document.getElementById('sampleWeight').value = '';
}

function updateSamplesList() {
const container = document.getElementById('samplesList');
if (samples.length === 0) {
container.innerHTML = '<div style="padding:20px; text-align:center;">Нет добавленных проб</div>';
return;
}

container.innerHTML = samples.map((s, idx) => `
<div class="interval-item" onclick="editSample(${idx})" style="border-left-color: ${s.docType === 'final' ? '#c62828' : '#2c7da0'}">
<div style="display: flex; justify-content: space-between;">
<span><strong>${s.from}${s.from !== s.to ? ' - ' + s.to + ' м' : ' м'}</strong></span>
<span>
<span style="color:#1e466e; cursor:pointer;" onclick="event.stopPropagation();editSample(${idx})">✏️</span>
<span style="color:#c62828; cursor:pointer;" onclick="event.stopPropagation();deleteSample(${idx})">✕</span>
</span>
</div>
<div class="compact-data">
<span>📅 ${s.date || '—'}</span>
<span>👤 ${s.author || '—'}</span>
<span>📍 ${s.location || '—'}</span>
</div>
<div>${s.type || '—'} (${s.docType === 'final' ? 'Итоговое' : 'Первичное'})</div>
${s.volume ? `<div>📊 Объем: ${s.volume} ${s.volumeUnit || ''}</div>` : ''}
${s.weight ? `<div>⚖️ Вес: ${s.weight} кг</div>` : ''}
</div>
`).join('');
}

window.deleteSample = function(idx) {
samples.splice(idx, 1);
updateSamplesList();
};

window.editSample = function(idx) {
const s = samples[idx];
document.getElementById('sampleFrom').value = s.from;
document.getElementById('sampleTo').value = s.to;
document.getElementById('sampleDate').value = s.date || '';
document.getElementById('sampleAuthorInput').value = s.author || '';
document.getElementById('sampleLocationInput').value = s.location || '';
document.getElementById('assayTypeInput').value = s.type || '';
document.getElementById('sampleDocType').value = s.docType || 'primary';
document.getElementById('sampleVolume').value = s.volume || '';
document.getElementById('volumeUnitInput').value = s.volumeUnit || '';
document.getElementById('sampleWeight').value = s.weight || '';
deleteSample(idx);
};

function updateProgressBar() {
const depth = parseFloat(document.getElementById('actualDepth').value);
if (!depth || depth <= 0 || intervals.length === 0) {
document.getElementById('progressContainer').style.display = 'none';
return;
}

const documentedDepth = intervals.reduce((sum, i) => sum + (i.to - i.from), 0);
const percent = Math.min(100, Math.round((documentedDepth / depth) * 100));
document.getElementById('progressFill').style.width = percent + '%';
document.getElementById('progressContainer').style.display = 'block';
}

function markUnsavedChanges() {}

// ========== ИНИЦИАЛИЗАЦИЯ ==========
document.getElementById('saveWellBtn').addEventListener('click', saveCurrentWell);
document.getElementById('addIntervalBtn').addEventListener('click', addInterval);
document.getElementById('addSampleBtn').addEventListener('click', addSample);
document.getElementById('newWellBtn').addEventListener('click', () => {
currentWellId = null;
intervals = [];
samples = [];
clearForm();
document.querySelector('.tab-btn[data-tab="general"]').click();
});

// Табы
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
document.getElementById(`tab-${btn.dataset.tab}`).classList.add('active');
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});

loadWells();
</script>
</body>
</html>
Ррмитт
Прикрепления:
ddddd_2.noext (41.6 Kb)
Ррпм
Прикрепления:
rrmmito.noext (41.6 Kb)
  • Страница 1 из 1
  • 1
Поиск:
Новый ответ
Имя:
Текст сообщения: