1147 lines
56 KiB
HTML
1147 lines
56 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru" data-theme="dark" data-radius="medium" data-font="merriweather">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||
<meta
|
||
name="description"
|
||
content="Design System портфолио: цвета, типографика, сетка, компоненты интерфейса."
|
||
/>
|
||
<title>Design System — портфолио</title>
|
||
<script>
|
||
(function () {
|
||
var FONT_CSS = {
|
||
merriweather: "/assets/fonts/merriweather.css",
|
||
geologica: "/assets/fonts/geologica.css",
|
||
mono: "/assets/fonts/mono.css",
|
||
};
|
||
try {
|
||
var t = JSON.parse(localStorage.getItem("siteThemeV1") || "{}");
|
||
var d = document.documentElement;
|
||
var m = t.mode;
|
||
if (m !== "light" && m !== "dark" && m !== "system") m = "system";
|
||
var eff =
|
||
m === "system"
|
||
? window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
|
||
? "dark"
|
||
: "light"
|
||
: m;
|
||
d.dataset.theme = eff;
|
||
var h = typeof t.h === "number" ? t.h : typeof t.hue === "number" ? t.hue : null;
|
||
var s = typeof t.s === "number" ? t.s : typeof t.sat === "number" ? t.sat : null;
|
||
var l = typeof t.l === "number" ? t.l : typeof t.light === "number" ? t.light : null;
|
||
if (h !== null) d.style.setProperty("--theme-h", String(h));
|
||
if (s !== null) d.style.setProperty("--theme-s", String(s));
|
||
if (l !== null) {
|
||
var L = Number(l);
|
||
if (Number.isFinite(L)) d.style.setProperty("--theme-l", String(Math.min(70, Math.max(20, L))));
|
||
}
|
||
var hVal = h !== null ? h : 210;
|
||
var sVal = s !== null ? s : 90;
|
||
var lVal = l !== null ? Math.min(70, Math.max(20, Number(l))) : 40;
|
||
var H = (((hVal % 360) + 360) % 360) / 360;
|
||
var S = Math.min(100, Math.max(0, sVal)) / 100;
|
||
var Lv = Math.min(100, Math.max(0, lVal)) / 100;
|
||
var r, g, b;
|
||
if (S === 0) { r = g = b = Lv; }
|
||
else {
|
||
var q = Lv < 0.5 ? Lv * (1 + S) : Lv + S - Lv * S;
|
||
var p = 2 * Lv - q;
|
||
var hue2rgb = function(p, q, t) {
|
||
if (t < 0) t += 1; if (t > 1) t -= 1;
|
||
if (t < 1/6) return p + (q - p) * 6 * t;
|
||
if (t < 1/2) return q;
|
||
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||
return p;
|
||
};
|
||
r = hue2rgb(p, q, H + 1/3);
|
||
g = hue2rgb(p, q, H);
|
||
b = hue2rgb(p, q, H - 1/3);
|
||
}
|
||
var lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||
d.style.setProperty("--on-accent", lum > 0.58 ? "hsl(220 14% 12%)" : "hsl(0 0% 98%)");
|
||
var rad = t.radius;
|
||
if (rad === "soft" || rad === "medium" || rad === "strong") {
|
||
d.dataset.radius = rad;
|
||
} else {
|
||
d.dataset.radius = "medium";
|
||
}
|
||
var f = t.font;
|
||
if (f === "onest") f = "geologica";
|
||
if (f === "anonymous") f = "mono";
|
||
if (f !== "merriweather" && f !== "mono" && f !== "geologica") f = "merriweather";
|
||
d.dataset.font = f;
|
||
var mb = t.meshBg;
|
||
if (mb === "off") {
|
||
d.dataset.meshBg = "off";
|
||
} else {
|
||
d.dataset.meshBg = "on";
|
||
}
|
||
var href = FONT_CSS[f];
|
||
if (href && !document.querySelector('link[data-theme-font="' + f + '"]')) {
|
||
var link = document.createElement("link");
|
||
link.rel = "stylesheet";
|
||
link.href = href;
|
||
link.setAttribute("data-theme-font", f);
|
||
document.head.appendChild(link);
|
||
}
|
||
} catch (e) {}
|
||
})();
|
||
</script>
|
||
|
||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon/favicon-32x32.png" />
|
||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon/favicon-16x16.png" />
|
||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/favicon/apple-touch-icon.png" />
|
||
<link rel="manifest" href="/assets/favicon/site.webmanifest" />
|
||
<link rel="preload" href="/assets/fonts/merriweather.css" as="style" />
|
||
<link rel="preload" href="/css/main.css" as="style" />
|
||
<link rel="preload" href="/js/skill-connectors.js" as="script" />
|
||
<link rel="modulepreload" href="/js/main.js" />
|
||
<link rel="stylesheet" href="/css/main.css" />
|
||
<script src="/js/skill-connectors.js" defer></script>
|
||
<script type="module" src="/js/main.js"></script>
|
||
|
||
|
||
<style>
|
||
#main {
|
||
padding-bottom: clamp(3.5rem, 8vw, 5.5rem);
|
||
}
|
||
|
||
.case-article.ds-page {
|
||
max-width: 52rem;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.case-article.ds-page .case-page__h1 {
|
||
margin-bottom: 0.65rem;
|
||
}
|
||
|
||
.case-article.ds-page .case-page__sub {
|
||
margin-bottom: clamp(1.75rem, 4vw, 2.5rem);
|
||
}
|
||
|
||
.case-article.ds-page .section__prose {
|
||
margin-bottom: 1.15rem;
|
||
line-height: 1.65;
|
||
color: var(--text);
|
||
}
|
||
|
||
.case-article.ds-page > h2 {
|
||
margin: 0 0 1.15rem;
|
||
padding-top: clamp(2.75rem, 6vw, 4rem);
|
||
font-family: var(--font);
|
||
font-size: clamp(1.7rem, 3vw, 2rem);
|
||
font-weight: 700;
|
||
color: var(--text);
|
||
scroll-margin-top: 1.5rem;
|
||
border-top: 1px solid color-mix(in srgb, var(--line) 90%, transparent);
|
||
}
|
||
|
||
.case-article.ds-page > h2#colors {
|
||
padding-top: clamp(1.5rem, 4vw, 2.25rem);
|
||
border-top: none;
|
||
}
|
||
|
||
.case-article.ds-page > h3 {
|
||
margin: clamp(2rem, 4vw, 2.75rem) 0 0.85rem;
|
||
font-family: var(--font);
|
||
font-size: 1.55rem;
|
||
font-weight: 600;
|
||
color: var(--text);
|
||
}
|
||
|
||
.case-article.ds-page > h2 + .section__prose,
|
||
.case-article.ds-page > h2 + h3 {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.case-article.ds-page > h2 + h3 {
|
||
margin-top: 0.25rem;
|
||
}
|
||
|
||
.case-article.ds-page #components h3:first-of-type {
|
||
margin-top: 1rem;
|
||
}
|
||
.case-article.ds-page a:not(.btn):not(.fab) {
|
||
color: var(--accent);
|
||
}
|
||
.sg-toc {
|
||
margin: clamp(1.5rem, 3vw, 2rem) 0 clamp(2.25rem, 5vw, 3.25rem);
|
||
padding: 1.15rem 1.35rem;
|
||
border-radius: var(--radius-md);
|
||
border: 1px solid var(--line);
|
||
background: color-mix(in srgb, var(--surface) 72%, transparent);
|
||
}
|
||
.sg-toc ol {
|
||
margin: 0;
|
||
padding-left: 1.25rem;
|
||
color: var(--text-muted);
|
||
}
|
||
.sg-toc li + li {
|
||
margin-top: 0.35rem;
|
||
}
|
||
.sg-toc a {
|
||
color: var(--accent-text);
|
||
text-decoration: none;
|
||
}
|
||
.sg-toc a:hover,
|
||
.sg-toc a:focus-visible {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.sg-blocks-card {
|
||
margin: 0 0 2.5rem;
|
||
padding: 1.15rem 1.35rem;
|
||
border-radius: var(--radius-md);
|
||
border: 1px solid var(--line);
|
||
background: color-mix(in srgb, var(--surface) 72%, transparent);
|
||
}
|
||
|
||
.sg-blocks-card p {
|
||
margin: 0 0 0.85rem;
|
||
line-height: 1.65;
|
||
color: var(--text);
|
||
}
|
||
|
||
.sg-blocks-card p:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
#blocks,
|
||
#mini-projects {
|
||
scroll-margin-top: calc(var(--header-offset, 4.25rem) + 0.75rem);
|
||
}
|
||
.sg-swatch-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
|
||
gap: 0.75rem;
|
||
margin: 1rem 0 1.75rem;
|
||
}
|
||
.sg-swatch {
|
||
border-radius: var(--radius-sm);
|
||
border: 1px solid var(--line);
|
||
overflow: hidden;
|
||
}
|
||
.sg-swatch__chip {
|
||
height: 3.25rem;
|
||
}
|
||
.sg-swatch__name {
|
||
display: block;
|
||
padding: 0.35rem 0.45rem;
|
||
font-size: 0.68rem;
|
||
line-height: 1.25;
|
||
font-family: var(--font);
|
||
color: var(--text-muted);
|
||
word-break: break-all;
|
||
}
|
||
.sg-stack {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 0.75rem;
|
||
margin: 1rem 0 2rem;
|
||
}
|
||
.sg-stack--row {
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
}
|
||
.sg-label {
|
||
margin: 0.35rem 0 0;
|
||
font-size: 0.78rem;
|
||
color: var(--text-muted);
|
||
font-family: var(--font);
|
||
}
|
||
|
||
.case-article.ds-page .sg-label {
|
||
display: block;
|
||
margin-bottom: 1.75rem;
|
||
}
|
||
|
||
.sg-table-wrap {
|
||
overflow-x: auto;
|
||
margin: 1rem 0 2rem;
|
||
}
|
||
.sg-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 0.88rem;
|
||
}
|
||
.sg-table th,
|
||
.sg-table td {
|
||
padding: 0.65rem 0.85rem;
|
||
border: 1px solid var(--line);
|
||
text-align: left;
|
||
vertical-align: top;
|
||
}
|
||
.sg-table th {
|
||
background: color-mix(in srgb, var(--surface) 80%, transparent);
|
||
color: var(--text);
|
||
font-weight: 600;
|
||
}
|
||
.sg-table td {
|
||
color: var(--text-muted);
|
||
}
|
||
.sg-table code {
|
||
font-size: 0.82em;
|
||
}
|
||
.sg-table--type .sg-type-sample {
|
||
color: var(--text);
|
||
min-width: 11rem;
|
||
}
|
||
.sg-table--type .sg-type-sample > * {
|
||
margin: 0;
|
||
}
|
||
.sg-table--type td:first-child {
|
||
white-space: nowrap;
|
||
vertical-align: top;
|
||
}
|
||
.sg-table--type tbody td:last-child {
|
||
font-size: 0.88rem;
|
||
}
|
||
.sg-layout-demo {
|
||
margin: 1rem 0 2rem;
|
||
padding: 1.1rem;
|
||
border-radius: var(--radius-md);
|
||
border: 1px dashed var(--line);
|
||
background: color-mix(in srgb, var(--bg-elevated) 55%, transparent);
|
||
}
|
||
.sg-layout-demo__hero {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 0.65rem;
|
||
}
|
||
@media (min-width: 860px) {
|
||
.sg-layout-demo__hero {
|
||
grid-template-columns: 1.15fr 0.85fr;
|
||
}
|
||
}
|
||
.sg-layout-demo__block {
|
||
min-height: 2.75rem;
|
||
border-radius: var(--radius-sm);
|
||
background: color-mix(in srgb, var(--accent) 18%, var(--surface));
|
||
border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 0.75rem;
|
||
color: var(--text-muted);
|
||
}
|
||
.sg-contact-demo {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
.sg-contact-demo .contact-card {
|
||
pointer-events: none;
|
||
cursor: default;
|
||
}
|
||
/* В компонентах только 2 карточки — без 4-колоночной сетки главной */
|
||
.sg-contact-components.contact-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
@media (min-width: 560px) {
|
||
.sg-contact-components.contact-grid {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
}
|
||
.sg-portfolio-wrap {
|
||
max-width: 100%;
|
||
}
|
||
.sg-portfolio-wrap .portfolio-card {
|
||
pointer-events: none;
|
||
}
|
||
.sg-section-demo.section {
|
||
margin: 0.75rem calc(-1 * max(var(--space), env(safe-area-inset-left, 0px))) 2rem;
|
||
padding: 1.35rem max(var(--space), env(safe-area-inset-right, 0px)) 1.35rem
|
||
max(var(--space), env(safe-area-inset-left, 0px));
|
||
border-radius: var(--radius-md);
|
||
background: var(--surface);
|
||
border: 1px solid color-mix(in srgb, var(--line) 85%, transparent);
|
||
}
|
||
|
||
.sg-section-demo .section__inner {
|
||
max-width: none;
|
||
margin: 0;
|
||
}
|
||
|
||
.sg-section-demo .section__title {
|
||
margin: 0 0 0.65rem;
|
||
padding: 0;
|
||
border: none;
|
||
font-size: clamp(1.02rem, 1.65vw, 1.28rem);
|
||
}
|
||
|
||
.sg-section-demo .section__prose {
|
||
margin: 0;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.case-article.ds-page pre {
|
||
margin: 0.75rem 0 2rem;
|
||
}
|
||
|
||
.case-article.ds-page ul.section__prose,
|
||
.case-article.ds-page ol.section__prose {
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.case-article.ds-page .skill-task-card,
|
||
.case-article.ds-page .sg-portfolio-wrap {
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.case-article.ds-page .contact-grid {
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
</style>
|
||
<script>
|
||
(function () {
|
||
try {
|
||
var t = JSON.parse(localStorage.getItem("siteThemeV1") || "{}");
|
||
var d = document.documentElement;
|
||
var m = t.mode;
|
||
if (m !== "light" && m !== "dark" && m !== "system") m = "system";
|
||
var eff =
|
||
m === "system"
|
||
? window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
|
||
? "dark"
|
||
: "light"
|
||
: m;
|
||
d.dataset.theme = eff;
|
||
var h = typeof t.h === "number" ? t.h : typeof t.hue === "number" ? t.hue : null;
|
||
var s = typeof t.s === "number" ? t.s : typeof t.sat === "number" ? t.sat : null;
|
||
var l = typeof t.l === "number" ? t.l : typeof t.light === "number" ? t.light : null;
|
||
if (h !== null) d.style.setProperty("--theme-h", String(h));
|
||
if (s !== null) d.style.setProperty("--theme-s", String(s));
|
||
if (l !== null) {
|
||
var L = Number(l);
|
||
if (Number.isFinite(L)) d.style.setProperty("--theme-l", String(Math.min(70, Math.max(20, L))));
|
||
}
|
||
var rad = t.radius;
|
||
if (rad === "soft" || rad === "medium" || rad === "strong") {
|
||
d.dataset.radius = rad;
|
||
} else {
|
||
d.dataset.radius = "medium";
|
||
}
|
||
var f = t.font;
|
||
if (f === "onest") f = "geologica";
|
||
if (f === "anonymous") f = "mono";
|
||
if (f === "merriweather" || f === "mono" || f === "geologica") {
|
||
d.dataset.font = f;
|
||
} else {
|
||
d.dataset.font = "merriweather";
|
||
}
|
||
var mb = t.meshBg;
|
||
if (mb === "off") {
|
||
d.dataset.meshBg = "off";
|
||
} else {
|
||
d.dataset.meshBg = "on";
|
||
}
|
||
} catch (e) {}
|
||
})();
|
||
</script>
|
||
</head>
|
||
<body>
|
||
<a class="skip-link" href="#main">К основному содержимому</a>
|
||
|
||
<header class="header" data-theme-picker>
|
||
<div class="header__inner">
|
||
<a class="logo" href="../../" aria-label="На главную">НМ</a>
|
||
<div class="header__cluster">
|
||
<nav class="nav" aria-label="Разделы страницы">
|
||
<a href="../../">На главную</a>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="fab-stack" aria-label="Быстрые действия">
|
||
<div class="theme-panel-fab-wrap">
|
||
<button type="button" class="fab fab--secondary glass-neutral" data-theme-panel-btn aria-expanded="false" aria-controls="theme-panel" aria-label="Настройки стиля">
|
||
<span class="theme-panel-fab__icon theme-panel-fab__icon--open-hide" aria-hidden="true"></span>
|
||
<svg class="theme-panel-fab__icon theme-panel-fab__icon--close" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||
<path d="M6 6L18 18M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||
</svg>
|
||
</button>
|
||
<div id="theme-panel" class="theme-picker__panel glass-neutral" role="dialog" aria-label="Тема и оформление" hidden data-theme-panel>
|
||
<div class="theme-picker__stack">
|
||
<section class="theme-picker__section" aria-labelledby="theme-accent-title">
|
||
<h3 id="theme-accent-title" class="theme-picker__section-title">Акцентный цвет</h3>
|
||
<div class="colorpicker" data-colorpicker>
|
||
<div class="colorpicker__plane-box">
|
||
<canvas class="colorpicker__plane" width="320" height="180" data-sl-canvas aria-label="Насыщенность и светлость акцента"></canvas>
|
||
<div class="colorpicker__knob" data-sl-knob></div>
|
||
</div>
|
||
<div class="colorpicker__hue-wrap">
|
||
<label class="colorpicker__hue-sr" for="theme-hue-slider">Оттенок акцента</label>
|
||
<input id="theme-hue-slider" class="colorpicker__hue" type="range" min="0" max="360" value="210" data-hue-slider aria-valuemin="0" aria-valuemax="360" />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section class="theme-picker__section" aria-labelledby="theme-mode-title">
|
||
<h3 id="theme-mode-title" class="theme-picker__section-title">Тема</h3>
|
||
<div class="radius-seg theme-mode-seg" role="radiogroup" aria-labelledby="theme-mode-title">
|
||
<button type="button" class="radius-seg__btn" role="radio" aria-checked="false" data-theme-mode="system" aria-label="Как в системе"><span class="radius-seg__viz" aria-hidden="true"><span class="theme-mode-seg__ico theme-mode-seg__ico--system"></span></span></button>
|
||
<button type="button" class="radius-seg__btn" role="radio" aria-checked="false" data-theme-mode="light" aria-label="Светлая тема"><span class="radius-seg__viz" aria-hidden="true"><span class="theme-mode-seg__ico theme-mode-seg__ico--light"></span></span></button>
|
||
<button type="button" class="radius-seg__btn" role="radio" aria-checked="false" data-theme-mode="dark" aria-label="Тёмная тема"><span class="radius-seg__viz" aria-hidden="true"><span class="theme-mode-seg__ico theme-mode-seg__ico--dark"></span></span></button>
|
||
</div>
|
||
</section>
|
||
<section class="theme-picker__section" aria-labelledby="theme-radius-title">
|
||
<h3 id="theme-radius-title" class="theme-picker__section-title">Скругление углов</h3>
|
||
<div class="radius-seg" role="radiogroup" aria-labelledby="theme-radius-title">
|
||
<button type="button" class="radius-seg__btn" role="radio" aria-checked="false" data-radius-option="soft" aria-label="Слабое скругление"><span class="radius-seg__viz" aria-hidden="true"><span class="radius-seg__arc radius-seg__arc--soft"></span></span></button>
|
||
<button type="button" class="radius-seg__btn" role="radio" aria-checked="false" data-radius-option="medium" aria-label="Среднее скругление"><span class="radius-seg__viz" aria-hidden="true"><span class="radius-seg__arc radius-seg__arc--medium"></span></span></button>
|
||
<button type="button" class="radius-seg__btn" role="radio" aria-checked="false" data-radius-option="strong" aria-label="Сильное скругление"><span class="radius-seg__viz" aria-hidden="true"><span class="radius-seg__arc radius-seg__arc--strong"></span></span></button>
|
||
</div>
|
||
</section>
|
||
<section class="theme-picker__section" aria-labelledby="theme-typography-title">
|
||
<h3 id="theme-typography-title" class="theme-picker__section-title">Типографика</h3>
|
||
<div class="radius-seg" role="radiogroup" aria-labelledby="theme-typography-title">
|
||
<button type="button" class="radius-seg__btn theme-font-seg__btn" role="radio" aria-checked="false" data-font-option="geologica" aria-label="Sans"><span class="theme-font-seg__label">Sans</span></button>
|
||
<button type="button" class="radius-seg__btn theme-font-seg__btn" role="radio" aria-checked="false" data-font-option="merriweather" aria-label="Sans Serif"><span class="theme-font-seg__label">Sans Serif</span></button>
|
||
<button type="button" class="radius-seg__btn theme-font-seg__btn" role="radio" aria-checked="false" data-font-option="mono" aria-label="Mono"><span class="theme-font-seg__label">Mono</span></button>
|
||
</div>
|
||
</section>
|
||
<section class="theme-picker__section" aria-labelledby="theme-mesh-bg-title">
|
||
<h3 id="theme-mesh-bg-title" class="theme-picker__section-title">Фон страницы</h3>
|
||
<div class="radius-seg radius-seg--two" role="radiogroup" aria-labelledby="theme-mesh-bg-title">
|
||
<button type="button" class="radius-seg__btn theme-mesh-bg-seg__btn" role="radio" aria-checked="false" data-mesh-bg-option="on" aria-label="Показать фоновый градиент"><span class="theme-mesh-bg-seg__label">Вкл</span></button>
|
||
<button type="button" class="radius-seg__btn theme-mesh-bg-seg__btn" role="radio" aria-checked="false" data-mesh-bg-option="off" aria-label="Скрыть фоновый градиент"><span class="theme-mesh-bg-seg__label">Выкл</span></button>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<a class="fab fab--primary" href="../../#contact" aria-label="Связаться">
|
||
<span class="fab-primary__ico" aria-hidden="true"></span>
|
||
</a>
|
||
</div>
|
||
<div class="content-edit-fab-stack" aria-label="Правки контента" data-content-edit-ui>
|
||
<button
|
||
type="button"
|
||
class="fab fab--secondary glass-neutral content-edit-fab"
|
||
data-content-edit-mode-toggle
|
||
hidden
|
||
aria-pressed="false"
|
||
aria-label="Режим правок контента"
|
||
title="Режим правок"
|
||
>
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||
<path
|
||
d="M4 20h4l10.5-10.5a2.12 2.12 0 0 0 0-3L16.5 4.5a2.12 2.12 0 0 0-3 0L3 15v5z"
|
||
stroke="currentColor"
|
||
stroke-width="1.75"
|
||
stroke-linejoin="round"
|
||
/>
|
||
</svg>
|
||
<span class="content-edit-fab__badge" hidden aria-hidden="true">0</span>
|
||
</button>
|
||
</div>
|
||
|
||
|
||
<main id="main" class="section section--tight">
|
||
<div class="section__inner case-article ds-page">
|
||
<p class="hero__eyebrow">Документация · UI</p>
|
||
<h1 class="case-page__h1">Design System</h1>
|
||
<p class="case-page__sub">Токены, типографика, сетка, компоненты и блоки лонгрида кейса</p>
|
||
<p class="section__prose">
|
||
Переключайте тему, акцент и шрифт через плавающую панель справа. При глобальных правках стилей (токены,
|
||
компоненты, блоки лонгрида) обновляйте и эту страницу — живой каталог UI проекта.
|
||
</p>
|
||
|
||
<nav class="sg-toc" aria-label="Оглавление Design System">
|
||
<ol>
|
||
<li><a href="#colors">Цвета</a></li>
|
||
<li><a href="#fonts">Шрифты</a></li>
|
||
<li><a href="#layout">Сетка и адаптив</a></li>
|
||
<li><a href="#components">Компоненты</a></li>
|
||
<li><a href="#fab">FAB-кнопки</a></li>
|
||
<li><a href="#blocks">Блоки</a></li>
|
||
<li><a href="#mini-projects">Активность</a></li>
|
||
</ol>
|
||
</nav>
|
||
|
||
<h2 id="colors">Цвета</h2>
|
||
<p class="section__prose">
|
||
Акцент и «холод/тепло» всего UI задаются тремя числами в модели <strong>HSL</strong> (Hue — оттенок,
|
||
Saturation — насыщенность, Lightness — светлота) на <code><html></code>. Из них собирается
|
||
<code>--accent</code> и производные токены; нейтральные фоны слегка подмешивают тот же оттенок
|
||
(<code>--neutral-accent-tint</code>). Светлая/тёмная тема — <code>data-theme</code>, mesh —
|
||
<code>data-mesh-bg</code>. Меняется в FAB справа.
|
||
</p>
|
||
<h3>Что такое <code>--theme-h</code>, <code>--theme-s</code>, <code>--theme-l</code></h3>
|
||
<ul class="section__prose">
|
||
<li>
|
||
<code>--theme-h</code> — <strong>оттенок (Hue)</strong>, позиция на цветовом круге 0–360°. Сдвиг hue
|
||
перекрашивает кнопки, ссылки, mesh-градиент и квадратные акценты (<code>--accent-square-2</code> …
|
||
<code>4</code>), не трогая вручную каждый hex.
|
||
</li>
|
||
<li>
|
||
<code>--theme-s</code> — <strong>насыщенность</strong>: насколько «чистый» цвет, а не серый.
|
||
</li>
|
||
<li>
|
||
<code>--theme-l</code> — <strong>светлота</strong> акцента: темнее или ярче основной фирменный тон
|
||
(в панели обычно ограничен диапазоном, чтобы кнопки оставались читаемыми).
|
||
</li>
|
||
</ul>
|
||
<p class="section__prose">
|
||
В CSS это выглядит так:
|
||
<code>--accent: hsl(var(--theme-h) calc(var(--theme-s) * 1%) calc(var(--theme-l) * 1%))</code>.
|
||
Значения пишутся в <code>localStorage</code> (<code>siteThemeV1</code>) и подставляются inline-скриптом в
|
||
<code>head</code>, чтобы не было вспышки «чужого» цвета при загрузке.
|
||
</p>
|
||
<div class="sg-swatch-grid" aria-label="Палитра токенов">
|
||
<div class="sg-swatch">
|
||
<div class="sg-swatch__chip" style="background: var(--bg)"></div>
|
||
<span class="sg-swatch__name">--bg</span>
|
||
</div>
|
||
<div class="sg-swatch">
|
||
<div class="sg-swatch__chip" style="background: var(--bg-elevated)"></div>
|
||
<span class="sg-swatch__name">--bg-elevated</span>
|
||
</div>
|
||
<div class="sg-swatch">
|
||
<div class="sg-swatch__chip" style="background: var(--surface)"></div>
|
||
<span class="sg-swatch__name">--surface</span>
|
||
</div>
|
||
<div class="sg-swatch">
|
||
<div class="sg-swatch__chip" style="background: var(--text)"></div>
|
||
<span class="sg-swatch__name">--text</span>
|
||
</div>
|
||
<div class="sg-swatch">
|
||
<div class="sg-swatch__chip" style="background: var(--text-muted)"></div>
|
||
<span class="sg-swatch__name">--text-muted</span>
|
||
</div>
|
||
<div class="sg-swatch">
|
||
<div class="sg-swatch__chip" style="background: var(--line)"></div>
|
||
<span class="sg-swatch__name">--line</span>
|
||
</div>
|
||
<div class="sg-swatch">
|
||
<div class="sg-swatch__chip" style="background: var(--accent)"></div>
|
||
<span class="sg-swatch__name">--accent</span>
|
||
</div>
|
||
<div class="sg-swatch">
|
||
<div class="sg-swatch__chip" style="background: var(--accent-text)"></div>
|
||
<span class="sg-swatch__name">--accent-text</span>
|
||
</div>
|
||
<div class="sg-swatch">
|
||
<div class="sg-swatch__chip" style="background: var(--accent-soft)"></div>
|
||
<span class="sg-swatch__name">--accent-soft</span>
|
||
</div>
|
||
</div>
|
||
<p class="section__prose sg-label">
|
||
Дополнительно: квадратичные оттенки mesh — <code>--accent-square-2</code> … <code>4</code>; они зависят от hue.
|
||
</p>
|
||
|
||
<h2 id="fonts">Шрифты</h2>
|
||
<p class="section__prose">
|
||
Пара шрифтов — <code>data-font</code> на <code><html></code> (FAB «Типографика»): задаёт
|
||
<code>--font-accent</code> (заголовки) и <code>--font</code> (проза). Шкала в таблицах: класс, пример, назначение.
|
||
</p>
|
||
<h3>Семейства</h3>
|
||
<div class="sg-table-wrap">
|
||
<table class="sg-table">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col"><code>data-font</code></th>
|
||
<th scope="col">Стек</th>
|
||
<th scope="col">Где</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><code>merriweather</code></td>
|
||
<td>Merriweather + Alice</td>
|
||
<td>Пара по умолчанию; Alice — основной текст, Merriweather — акцентные заголовки</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>mono</code></td>
|
||
<td>Source Code Pro + PT Mono</td>
|
||
<td>Моноширинная пара</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>geologica</code></td>
|
||
<td>Geologica + PT Sans</td>
|
||
<td>Гротеск + нейтральная проза</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>Типографическая шкала</h3>
|
||
<div class="sg-table-wrap">
|
||
<table class="sg-table sg-table--type" aria-label="Типографическая шкала">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">Класс</th>
|
||
<th scope="col">Пример</th>
|
||
<th scope="col">Назначение</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><code>.hero__eyebrow</code></td>
|
||
<td class="sg-type-sample"><p class="hero__eyebrow">Подпись секции</p></td>
|
||
<td>Hero, ввод кейса</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>.hero__title</code></td>
|
||
<td class="sg-type-sample">
|
||
<p class="hero__title" style="font-size: clamp(1.5rem, 3.5vw, 2.15rem); line-height: 1.08">
|
||
Display заголовок
|
||
</p>
|
||
</td>
|
||
<td>H1 главной; на DS уменьшен для колонки</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>.section__title</code></td>
|
||
<td class="sg-type-sample"><h2 class="section__title">Секция</h2></td>
|
||
<td>«Навыки», «Портфолио», контакты; основной шрифт, без капса</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>.case-page__h1</code></td>
|
||
<td class="sg-type-sample"><p class="case-page__h1">Заголовок страницы</p></td>
|
||
<td>H1 кейса / Design System</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>.case-page__sub</code></td>
|
||
<td class="sg-type-sample"><p class="case-page__sub">Лид под заголовком</p></td>
|
||
<td>Подзаголовок под H1</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>.section__prose</code></td>
|
||
<td class="sg-type-sample"><p class="section__prose">Основной текст абзаца.</p></td>
|
||
<td>Проза, списки, ссылки в статье</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>h2</code> в <code>.case-article</code></td>
|
||
<td class="sg-type-sample"><h2 style="margin: 0">Подсекция H2</h2></td>
|
||
<td>Разделы лонгрида (как «Цвета»)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>h3</code> в <code>.case-article</code></td>
|
||
<td class="sg-type-sample"><h3 style="margin: 0">Подсекция H3</h3></td>
|
||
<td>Подразделы внутри H2; отступы в <code>main.css</code>: сверху 3.25rem, снизу 1.5rem, после <code>figure</code> 4rem, <code>h3+h3</code> 3.5rem</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>Ритм и код</h3>
|
||
<p class="section__prose">Пример ритма:</p>
|
||
<ul class="section__prose">
|
||
<li>маркированный список;</li>
|
||
<li><strong>акцент</strong> и <a href="#fonts">ссылка</a>;</li>
|
||
</ul>
|
||
<ol class="section__prose">
|
||
<li>нумерованный пункт;</li>
|
||
</ol>
|
||
<p class="section__prose">Инлайн <code>code</code> и блок:</p>
|
||
<pre><code>--font: "Alice", system-ui, serif;
|
||
--font-accent: "Merriweather", var(--font);</code></pre>
|
||
<p class="section__prose sg-label">
|
||
Заголовки используют <code>clamp()</code> — масштаб от вьюпорта; подробнее в разделе «Сетка».
|
||
</p>
|
||
|
||
<h2 id="layout">Сетка и адаптив</h2>
|
||
<p class="section__prose">
|
||
Отдельной 12-колоночной сетки нет: <strong>full-bleed</strong> layout с боковыми полями
|
||
<code>--space</code> и локальными CSS Grid внутри секций. Контентная колонка DS и кейсов —
|
||
<code>.case-article</code> (max-width 48rem).
|
||
</p>
|
||
|
||
<h3>Базовый каркас</h3>
|
||
<ul class="section__prose">
|
||
<li><code>--space: clamp(1rem, 4vw, 2.75rem)</code> — горизонтальные поля + <code>safe-area-inset</code>.</li>
|
||
<li><code>--max: none</code> — без фиксированной «коробки» 1200px; ширина = вьюпорт минус padding.</li>
|
||
<li><code>.section</code> — вертикальные отступы через <code>clamp()</code>; <code>.section__inner</code> центрирует блок.</li>
|
||
<li><code>.section--tight</code> — компактнее по вертикали (кейсы, эта страница).</li>
|
||
</ul>
|
||
|
||
<h3>Брейкпоинты</h3>
|
||
<div class="sg-table-wrap">
|
||
<table class="sg-table">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">От</th>
|
||
<th scope="col">Что меняется</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><code>860px</code></td>
|
||
<td><code>.hero__grid</code> → 2 колонки <code>1.15fr 0.85fr</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>760px</code> / <code>1180px</code></td>
|
||
<td><code>.projects-carousel</code>: <code>--projects-visible</code> 1 → 2 → 3 карточки</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>720px</code></td>
|
||
<td><code>.skills-category__grid</code> → 2 колонки</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>1024px</code></td>
|
||
<td>Навыки: 6-колоночная мозаика (<code>grid-column: span …</code>)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>560px</code> / <code>1024px</code></td>
|
||
<td><code>.contact-grid</code> → 2 → 4 колонки</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>48rem</code> / <code>28rem</code></td>
|
||
<td>Галерея кейса: ≤2 col → 1 col</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>640px</code></td>
|
||
<td><code>case-media-gallery--wide</code> схлопывается в ширину колонки</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>Hero: одна → две колонки</h3>
|
||
<p class="section__prose">Схема <code>.hero__grid</code> (сжато до 860px):</p>
|
||
<div class="sg-layout-demo" aria-hidden="true">
|
||
<div class="sg-layout-demo__hero">
|
||
<div class="sg-layout-demo__block">Текст</div>
|
||
<div class="sg-layout-demo__block">Визуал</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3>Контакты: 1 → 2 → 4</h3>
|
||
<p class="section__prose">Демо <code>.contact-grid</code> (плейсхолдеры, без переходов):</p>
|
||
<ul class="contact-grid sg-contact-demo" aria-hidden="true">
|
||
<li>
|
||
<span class="contact-card contact-card--link contact-card--brand-tg">
|
||
<span class="contact-card__corner" aria-hidden="true"></span>
|
||
<span class="contact-card__icon" aria-hidden="true"></span>
|
||
<span class="contact-card__body">
|
||
<span class="contact-card__name">1 col</span>
|
||
<span class="contact-card__hint">< 560px</span>
|
||
</span>
|
||
</span>
|
||
</li>
|
||
<li>
|
||
<span class="contact-card contact-card--link contact-card--brand-vk">
|
||
<span class="contact-card__corner" aria-hidden="true"></span>
|
||
<span class="contact-card__icon" aria-hidden="true"></span>
|
||
<span class="contact-card__body">
|
||
<span class="contact-card__name">2 col</span>
|
||
<span class="contact-card__hint">≥ 560px</span>
|
||
</span>
|
||
</span>
|
||
</li>
|
||
<li>
|
||
<span class="contact-card contact-card--link contact-card--brand-ya">
|
||
<span class="contact-card__corner" aria-hidden="true"></span>
|
||
<span class="contact-card__icon" aria-hidden="true"></span>
|
||
<span class="contact-card__body">
|
||
<span class="contact-card__name">4 col</span>
|
||
<span class="contact-card__hint">≥ 1024px</span>
|
||
</span>
|
||
</span>
|
||
</li>
|
||
<li>
|
||
<span class="contact-card contact-card--action contact-card--brand-email">
|
||
<span class="contact-card__corner contact-card__corner--copy" aria-hidden="true"></span>
|
||
<span class="contact-card__icon contact-card__icon--accent" aria-hidden="true"></span>
|
||
<span class="contact-card__body">
|
||
<span class="contact-card__name">Сетка</span>
|
||
<span class="contact-card__hint">на главной</span>
|
||
</span>
|
||
</span>
|
||
</li>
|
||
</ul>
|
||
<p class="section__prose">
|
||
Навыки: мозаика на широком экране — см.
|
||
<a href="../../#skills">главную, #skills</a>. Портфолио: карусель на
|
||
<code>100vw</code> с <code>--projects-card-width</code> и JS-прокруткой — на главной
|
||
<a href="../../#projects">#projects</a>.
|
||
</p>
|
||
|
||
<h3>Wide в кейсах</h3>
|
||
<p class="section__prose">
|
||
<code>.case-media--wide</code>, <code>.case-media-gallery--wide</code> и <code>.case-callout</code> шире
|
||
колонки статьи: <code>min(100vw − 2×padding, content + 8rem)</code>, с полями <code>--space</code>. На узком
|
||
экране (до 640px) wide схлопывается в ширину колонки. Кадры с <code>data-image-viewer</code> — обводка и hover
|
||
как у <code>.case-video-shell</code> (живые примеры — демо элементов кейса). Подпись под видео —
|
||
<code>figcaption</code> по центру колонки.
|
||
</p>
|
||
|
||
<h3>Типографика и motion</h3>
|
||
<p class="section__prose">
|
||
Fluid type: <code>clamp()</code> на <code>.hero__title</code>, <code>.section__title</code>,
|
||
<code>.case-page__h1</code>. Скругления (<code>data-radius</code>) сетку не меняют. Для длинных скринов и
|
||
анимаций учитывается <code>prefers-reduced-motion: reduce</code>.
|
||
</p>
|
||
|
||
<h2 id="components">Компоненты</h2>
|
||
<p class="section__prose">Живые примеры; оболочка (шапка, FAB, футер, cookie) уже подключена на этой странице.</p>
|
||
|
||
<h3>Кнопки</h3>
|
||
<div class="sg-stack">
|
||
<a class="btn btn--primary btn--flat" href="#components"><span class="btn__icon" aria-hidden="true"></span><span>Primary flat</span></a>
|
||
<a class="btn btn--primary btn--raised" href="#components"><span>Primary raised</span></a>
|
||
<a class="btn btn--secondary btn--flat" href="#components"><span>Secondary flat</span></a>
|
||
<a class="btn btn--secondary btn--raised" href="#components"><span>Secondary raised</span></a>
|
||
<button type="button" class="btn btn--ghost btn--flat">Ghost flat</button>
|
||
</div>
|
||
<p class="sg-label">.btn .btn--primary | secondary | ghost · .btn--flat | raised · .btn__icon</p>
|
||
|
||
<h3>FAB-кнопки</h3>
|
||
<p class="section__prose">
|
||
Плавающие кнопки в правом нижнем углу (<code>.fab-stack</code>). Иконка-only и с текстовой подписью.
|
||
</p>
|
||
<div class="sg-stack sg-stack--row">
|
||
<a class="fab fab--primary" href="#components" aria-label="Демо FAB"><span class="fab-primary__ico" aria-hidden="true"></span></a>
|
||
<button type="button" class="fab fab--primary fab--label"><span class="fab-primary__ico" aria-hidden="true"></span><span class="fab__label">Скачать в PDF</span></button>
|
||
<button type="button" class="fab fab--secondary" aria-label="Демо secondary FAB"><span class="theme-panel-fab__icon theme-panel-fab__icon--open-hide" aria-hidden="true"></span></button>
|
||
</div>
|
||
<p class="sg-label">.fab .fab--primary | secondary · .fab--label (с текстом) · .fab-primary__ico · .fab__label</p>
|
||
|
||
<h3>Теги</h3>
|
||
<ul class="hero__tags" aria-label="Демо тегов">
|
||
<li class="hero__tag">hero__tag</li>
|
||
<li class="hero__tag">UX/UI</li>
|
||
</ul>
|
||
<p class="sg-label">.hero__tags / .hero__tag</p>
|
||
<ul class="portfolio-card__tags" style="display: flex; flex-wrap: wrap; gap: 0.35rem; list-style: none; padding: 0; margin: 0.5rem 0">
|
||
<li class="portfolio-card__tag">portfolio-card__tag</li>
|
||
<li class="portfolio-card__tag">Web App</li>
|
||
</ul>
|
||
<p class="sg-label">.portfolio-card__tags / .portfolio-card__tag</p>
|
||
|
||
<h3>Карточка портфолио</h3>
|
||
<div class="sg-portfolio-wrap">
|
||
<a class="portfolio-card" href="#components" tabindex="-1" aria-hidden="true">
|
||
<div class="portfolio-card__body">
|
||
<h3 class="portfolio-card__title">portfolio-card</h3>
|
||
<p class="portfolio-card__summary">Краткое описание проекта в две-три строки для демо Design System.</p>
|
||
<ul class="portfolio-card__tags" aria-label="Теги">
|
||
<li class="portfolio-card__tag">Демо</li>
|
||
<li class="portfolio-card__tag">UI</li>
|
||
</ul>
|
||
</div>
|
||
<div class="portfolio-card__cover" aria-hidden="true">
|
||
<img
|
||
class="portfolio-card__image"
|
||
src="../../assets/shared/placeholder.webp"
|
||
alt=""
|
||
width="400"
|
||
height="225"
|
||
decoding="async"
|
||
/>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
<p class="sg-label">.portfolio-card и дочерние BEM-классы</p>
|
||
|
||
<h3>Карточка портфолио — reflect (mp-card)</h3>
|
||
<div class="sg-portfolio-wrap">
|
||
<a class="mp-card mp-card--link mp-card--reflect" href="#components" tabindex="-1" aria-hidden="true" style="--card-cover:url('../../assets/shared/placeholder.webp')">
|
||
<div class="mp-card__media" aria-hidden="true">
|
||
<img
|
||
class="mp-card__image"
|
||
src="../../assets/shared/placeholder.webp"
|
||
alt=""
|
||
width="400"
|
||
height="225"
|
||
decoding="async"
|
||
/>
|
||
</div>
|
||
<div class="mp-card__body">
|
||
<h3 class="mp-card__title">mp-card--reflect</h3>
|
||
<ul class="mp-card__tags" aria-label="Теги">
|
||
<li class="mp-card__tag">Демо</li>
|
||
<li class="mp-card__tag">UI</li>
|
||
</ul>
|
||
<p class="mp-card__desc">Картинка на всю карточку, зеркальное отражение снизу, прогрессивное размытие и тонировка снизу вверх; текст в нижней части.</p>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
<p class="sg-label">.mp-card--reflect — полноразмерная картинка, отражение и размытие снизу</p>
|
||
|
||
<h3>Заглушка при загрузке изображений</h3>
|
||
<p class="section__prose">
|
||
Пока <code>img</code> ещё не загрузился, <code>initImageLoadPlaceholder</code> вешает
|
||
<code>.is-img-loading</code> на картинку и <code>.has-img-loading</code> на обёртку. Слой фона —
|
||
<code>::before</code>, один блеск слева направо — <code>::after</code> (<code>img-ph-shine-sweep</code>).
|
||
Иконки, SVG и мелкие картинки до 40px пропускаются;
|
||
отключить точечно: <code>data-no-img-placeholder</code>. Ниже — зафиксированное демо (на сайте классы снимаются
|
||
после <code>load</code>).
|
||
</p>
|
||
<div
|
||
class="case-media case-media--content has-img-loading img-ph-demo"
|
||
role="img"
|
||
aria-label="Демо: заглушка при загрузке изображения"
|
||
></div>
|
||
<p class="sg-label">.has-img-loading · .is-img-loading · .img-ph-demo (демо заглушки)</p>
|
||
|
||
<h3>TUI braille-спиннер</h3>
|
||
<p class="section__prose">
|
||
Загрузка тяжёлых изображений в просмотрщике (<code>.case-image-viewer</code>) показывается
|
||
моноширинным braille-спиннером — как в TUI. Чистый CSS, без JS: 10 кадров переключаются
|
||
через <code>steps()</code>-анимацию. Переиспользуемый класс <code>.tui-spinner</code>
|
||
(цвет — <code>--accent</code>, шрифт — системный mono).
|
||
</p>
|
||
<div class="sg-stack sg-stack--row sg-stack--center" style="gap: 1.5rem; padding: 1.25rem; background: color-mix(in srgb, var(--surface) 70%, transparent); border: 1px solid var(--line); border-radius: var(--radius-card, 14px);">
|
||
<span class="tui-spinner" aria-label="Загрузка" role="status"><span>⠋</span><span>⠙</span><span>⠹</span><span>⠸</span><span>⠼</span><span>⠴</span><span>⠦</span><span>⠧</span><span>⠇</span><span>⠏</span></span>
|
||
<code class="tui-spinner-seq" style="font-family: ui-monospace, 'Cascadia Code', Consolas, monospace; color: var(--text-muted); font-size: 0.95rem;">⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏</code>
|
||
</div>
|
||
<p class="sg-label">.tui-spinner · кадры: ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ · prefers-reduced-motion показывает ⠋</p>
|
||
|
||
<h3>Карточка навыка</h3>
|
||
<article class="skill-task-card">
|
||
<p class="skill-task-card__task">skill-task-card__task — формулировка задачи от бизнеса.</p>
|
||
<div class="skill-task-card__solutions">
|
||
<p class="skill-task-card__solution">skill-task-card__solution — первый подход к решению.</p>
|
||
<p class="skill-task-card__solution">Второй пункт в зоне решений.</p>
|
||
</div>
|
||
</article>
|
||
<p class="sg-label">.skill-task-card__task · __solutions · __solution</p>
|
||
|
||
<h3>Callout</h3>
|
||
<p class="section__prose">
|
||
Акцентный блок лонгрида: шире колонки (как wide-медиа), текст в <code>.case-callout__content</code> чуть
|
||
крупнее основного абзаца.
|
||
</p>
|
||
<aside class="case-callout" data-callout>
|
||
<span class="case-callout__emoji case-callout__emoji--pulse" aria-hidden="true">📌</span>
|
||
<div class="case-callout__content">
|
||
<p>case-callout — важная мысль или вывод. Слева только цветные emoji (📌 💡 ✨ ✅), не текстовые символы ✎ ✓ ◆; <code>case-callout__emoji--pulse</code> для лёгкой анимации.</p>
|
||
</div>
|
||
</aside>
|
||
<p class="sg-label">.case-callout · .case-callout__emoji · .case-callout__emoji--pulse · .case-callout__content</p>
|
||
|
||
<h3>Контактные карточки</h3>
|
||
<p class="section__prose sg-label" style="margin-top: 0">
|
||
На главной в сетке четыре карточки (1→2→4 колонки). Здесь два примера — сетка принудительно в две колонки.
|
||
</p>
|
||
<ul class="contact-grid sg-contact-components">
|
||
<li>
|
||
<a class="contact-card contact-card--link contact-card--brand-tg" href="https://t.me/mamadaevv" rel="noopener noreferrer">
|
||
<span class="contact-card__corner" aria-hidden="true"></span>
|
||
<span class="contact-card__icon" aria-hidden="true">
|
||
<img src="../../assets/shared/icons/tg.svg" alt="" width="24" height="24" loading="eager" decoding="async" />
|
||
</span>
|
||
<span class="contact-card__body">
|
||
<span class="contact-card__name">contact-card--link</span>
|
||
<span class="contact-card__hint">Внешняя ссылка</span>
|
||
</span>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<button
|
||
type="button"
|
||
class="contact-card contact-card--action contact-card--brand-email"
|
||
data-contact-copy
|
||
data-email-b64="bmlrQG1hbWFkYWV2di5ydQ=="
|
||
data-hint-default="Скопировать адрес"
|
||
aria-label="Демо: скопировать email"
|
||
>
|
||
<span class="contact-card__corner contact-card__corner--copy" aria-hidden="true"></span>
|
||
<span class="contact-card__icon contact-card__icon--accent" aria-hidden="true"></span>
|
||
<span class="contact-card__body">
|
||
<span class="contact-card__name">contact-card--action</span>
|
||
<span class="contact-card__hint" data-contact-copy-hint>Скопировать адрес</span>
|
||
</span>
|
||
</button>
|
||
</li>
|
||
</ul>
|
||
<p class="sg-label">.contact-card--link · --action · --brand-*</p>
|
||
|
||
<h3>Секция с фоном</h3>
|
||
<p class="section__prose">
|
||
На главной полоса <code>.section--surface</code> визуально читается через mesh; в кейсах фон страницы
|
||
ровный — ниже компактное демо с <code>background: var(--surface)</code>.
|
||
</p>
|
||
<section class="section section--surface section--tight sg-section-demo" aria-label="Демо секции">
|
||
<div class="section__inner">
|
||
<h2 class="section__title">section__title</h2>
|
||
<p class="section__prose">Фрагмент .section.section--surface + .section__inner</p>
|
||
</div>
|
||
</section>
|
||
<p class="sg-label">.section · .section--surface · .section--tight · .section__inner</p>
|
||
|
||
<h3>Разделители</h3>
|
||
<p class="section__prose">
|
||
Горизонтальные разделители: линия или просто воздух (отступ). Модификаторы <code>--line</code>, <code>--air</code>, <code>--air-lg</code>.
|
||
</p>
|
||
<div class="sg-stack">
|
||
<hr class="sep sep--line">
|
||
<hr class="sep sep--air">
|
||
<hr class="sep sep--air-lg">
|
||
</div>
|
||
<p class="sg-label">.sep · .sep--line · .sep--air · .sep--air-lg</p>
|
||
|
||
<h2 id="blocks">Блоки</h2>
|
||
<div class="sg-blocks-card">
|
||
<p>
|
||
Разметка лонгрида кейса: медиа и галереи, карточки ссылок, видео, callout, wide-блоки и прочие partials.
|
||
Живой эталон со всеми вариантами — на отдельной странице; оттуда копируют куски при сборке нового кейса.
|
||
</p>
|
||
<p>
|
||
<a class="btn btn--secondary btn--flat" href="../template-elements-demo/">Демо элементов кейса</a>
|
||
</p>
|
||
</div>
|
||
|
||
<h2 id="mini-projects">Активность</h2>
|
||
<div class="sg-blocks-card">
|
||
<p>
|
||
На главной в секции <code>#mini-projects</code> карточки <code>mp-card</code> в masonry-сетке. Типы, клетки и живая
|
||
раскладка вынесены на отдельную страницу.
|
||
</p>
|
||
<p>
|
||
<a class="btn btn--secondary btn--flat" href="../mini-projects-showcase/">Каталог карточек активности</a>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<footer class="footer">
|
||
<div class="footer__inner">
|
||
<span class="footer__chunk footer__chunk--date">© 2015–2026<span class="footer__sep" aria-hidden="true"> · </span></span>
|
||
<span class="footer__chunk footer__chunk--main">Дизайн и разработка сайта — Николай Мамадаев</span>
|
||
<span class="footer__chunk footer__chunk--meta footer__attr"><span class="footer__sep" aria-hidden="true"> · </span><a href="../../cases/design-system/">Design System</a><span class="footer__sep" aria-hidden="true"> · </span><a href="../../cookie-policy/">Политика cookie</a></span>
|
||
<span class="footer__chunk footer__chunk--lang"><span class="footer__sep" aria-hidden="true"> · </span><nav class="lang-switch" aria-label="Выбор языка" data-lang-switch>
|
||
<a class="lang-switch__btn" data-lang="ru" href="/">RU</a>
|
||
<a class="lang-switch__btn" data-lang="en" href="/en/">EN</a>
|
||
</nav>
|
||
<script>
|
||
(function () {
|
||
var p = location.pathname.replace(/^\/+/, "");
|
||
var isEn = p.indexOf("en/") === 0;
|
||
var base = isEn ? p.slice(3) : p;
|
||
var links = document.querySelectorAll("[data-lang-switch] [data-lang]");
|
||
for (var i = 0; i < links.length; i++) {
|
||
var b = links[i];
|
||
var l = b.getAttribute("data-lang");
|
||
b.setAttribute("href", (l === "en" ? "/en/" : "/") + base);
|
||
if ((l === "en") === isEn) b.setAttribute("aria-current", "true");
|
||
}
|
||
})();
|
||
</script>
|
||
</span>
|
||
</div>
|
||
</footer>
|
||
|
||
<aside class="cookie-notice glass-neutral" hidden data-cookie-notice>
|
||
<p class="cookie-notice__text">
|
||
Сайт использует cookie для комфортной работы. Подробнее — в
|
||
<a href="../../cookie-policy/" data-cookie-policy-link>cookie policy</a>.
|
||
</p>
|
||
<button type="button" class="btn btn--secondary btn--flat cookie-notice__accept" data-cookie-accept>
|
||
<span>Принять</span>
|
||
<span class="cookie-notice__accept-icon" aria-hidden="true">
|
||
<img src="../../assets/shared/icons/сookie.svg" alt="" width="20" height="20" loading="eager" decoding="async" />
|
||
</span>
|
||
</button>
|
||
</aside>
|
||
|
||
<div id="nav-more-menu-floating" class="nav-more__floating" hidden data-nav-more-menu-floating aria-hidden="true"></div>
|
||
|
||
|
||
</body>
|
||
</html>
|