- canvas стартует с opacity:0 и плавно проявляется (0.9s) при .is-ready - initPageMeshShader ставит is-ready после первого кадра - modulepreload page-mesh-shader.js для параллельной загрузки с main.js - пересборка страниц (build:pages)
1257 lines
56 KiB
HTML
1257 lines
56 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" 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 Portfolio: colors, typography, grid, interface components."
|
||
/>
|
||
<title>Design System — Portfolio</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="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
|
||
<link rel="stylesheet" href="/css/main.css" />
|
||
<link rel="canonical" href="https://mamadaevv.site/en/cases/design-system/" />
|
||
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/design-system/" />
|
||
<link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/design-system/" />
|
||
<link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/design-system/" />
|
||
<meta name="author" content="Nikolai Mamadaev" />
|
||
<meta name="robots" content="noindex, nofollow" />
|
||
<meta name="theme-color" content="#0f1115" />
|
||
|
||
<!-- Open Graph -->
|
||
<meta property="og:type" content="article" />
|
||
<meta property="og:site_name" content="Nikolai Mamadaev — Product Designer" />
|
||
<meta property="og:locale" content="en_US" />
|
||
<meta property="og:locale:alternate" content="ru_RU" />
|
||
<meta property="og:title" content="Design System — Portfolio" />
|
||
<meta property="og:description" content="Design System Portfolio: colors, typography, grid, interface components." />
|
||
<meta property="og:url" content="https://mamadaevv.site/en/cases/design-system/" />
|
||
<meta property="og:image" content="https://mamadaevv.site/assets/og/og-default.png" />
|
||
<meta property="og:image:width" content="1200" />
|
||
<meta property="og:image:height" content="630" />
|
||
<meta property="og:image:alt" content="Design System — Portfolio" />
|
||
|
||
<!-- Twitter / X -->
|
||
<meta name="twitter:card" content="summary_large_image" />
|
||
<meta name="twitter:title" content="Design System — Portfolio" />
|
||
<meta name="twitter:description" content="Design System Portfolio: colors, typography, grid, interface components." />
|
||
<meta name="twitter:image" content="https://mamadaevv.site/assets/og/og-default.png" />
|
||
<meta name="twitter:image:alt" content="Design System — Portfolio" />
|
||
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@graph": [
|
||
{
|
||
"@type": "Person",
|
||
"@id": "https://mamadaevv.site/#person",
|
||
"name": "Nikolai Mamadaev",
|
||
"url": "https://mamadaevv.site",
|
||
"jobTitle": "Product Designer",
|
||
"description": "Product designer with 10+ years of experience. Designing digital products, interfaces and design systems.",
|
||
"knowsAbout": [
|
||
"UX/UI design",
|
||
"product design",
|
||
"design systems",
|
||
"prototyping",
|
||
"Figma",
|
||
"no-code"
|
||
],
|
||
"sameAs": [
|
||
"https://t.me/mamadaevv",
|
||
"https://vk.com/mamadaevv",
|
||
"https://github.com/mamadaevv"
|
||
]
|
||
},
|
||
{
|
||
"@type": "WebSite",
|
||
"@id": "https://mamadaevv.site/#website",
|
||
"url": "https://mamadaevv.site/",
|
||
"name": "Nikolai Mamadaev — Product Designer",
|
||
"inLanguage": "en",
|
||
"author": {
|
||
"@id": "https://mamadaevv.site/#person"
|
||
},
|
||
"publisher": {
|
||
"@id": "https://mamadaevv.site/#person"
|
||
}
|
||
},
|
||
{
|
||
"@type": "CreativeWork",
|
||
"@id": "https://mamadaevv.site/en/cases/design-system/#page",
|
||
"url": "https://mamadaevv.site/en/cases/design-system/",
|
||
"name": "Design System — Portfolio",
|
||
"description": "Design System Portfolio: colors, typography, grid, interface components.",
|
||
"isPartOf": {
|
||
"@id": "https://mamadaevv.site/#website"
|
||
},
|
||
"author": {
|
||
"@id": "https://mamadaevv.site/#person"
|
||
},
|
||
"inLanguage": "en"
|
||
},
|
||
{
|
||
"@type": "BreadcrumbList",
|
||
"@id": "https://mamadaevv.site/en/cases/design-system/#breadcrumbs",
|
||
"itemListElement": [
|
||
{
|
||
"@type": "ListItem",
|
||
"position": 1,
|
||
"name": "Nikolai Mamadaev — Product Designer",
|
||
"item": "https://mamadaevv.site/"
|
||
},
|
||
{
|
||
"@type": "ListItem",
|
||
"position": 2,
|
||
"name": "Cases",
|
||
"item": "https://mamadaevv.site/cases/"
|
||
},
|
||
{
|
||
"@type": "ListItem",
|
||
"position": 3,
|
||
"name": "Design System — Portfolio",
|
||
"item": "https://mamadaevv.site/cases/design-system/"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
</script>
|
||
|
||
<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 .mp-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>
|
||
<svg class="liquid-glass-defs" aria-hidden="true" width="0" height="0" focusable="false" style="position:absolute;width:0;height:0;overflow:hidden;pointer-events:none">
|
||
<filter id="liquid-glass-displacement" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB">
|
||
<feImage href="../../../assets/liquid-glass/displacement.png" xlink:href="../../../assets/liquid-glass/displacement.png" preserveAspectRatio="none" result="lgMap"/>
|
||
<feDisplacementMap in="SourceGraphic" in2="lgMap" scale="14" xChannelSelector="R" yChannelSelector="G"/>
|
||
</filter>
|
||
<filter id="liquid-glass-displacement-btn" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB">
|
||
<feImage href="../../../assets/liquid-glass/displacement.png" xlink:href="../../../assets/liquid-glass/displacement.png" preserveAspectRatio="none" result="lgMapBtn"/>
|
||
<feDisplacementMap in="SourceGraphic" in2="lgMapBtn" scale="6" xChannelSelector="R" yChannelSelector="G"/>
|
||
</filter>
|
||
</svg>
|
||
|
||
<a class="skip-link" href="#main">Skip to main content</a>
|
||
|
||
<header class="header" data-theme-picker>
|
||
<div class="header__inner">
|
||
<a class="logo" href="../../" aria-label="Home">NM</a>
|
||
<div class="header__cluster">
|
||
<nav class="nav" aria-label="Page sections">
|
||
<a href="../../">Home</a>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="fab-stack" aria-label="Quick Actions">
|
||
<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="Style Settings">
|
||
<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="Theme and Design" 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">Accent Color</h3>
|
||
<div class="colorpicker" data-colorpicker>
|
||
<div class="colorpicker__plane-box">
|
||
<canvas class="colorpicker__plane" width="320" height="180" data-sl-canvas aria-label="Saturation and brightness of the accent"></canvas>
|
||
<div class="colorpicker__knob" data-sl-knob></div>
|
||
</div>
|
||
<div class="colorpicker__hue-wrap">
|
||
<label class="colorpicker__hue-sr" for="theme-hue-slider">Accent shade</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">Theme</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="As in the system"><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="Light-colored theme"><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="Dark Theme"><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">Corner Rounding</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="Slight rounding"><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="Average rounding"><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="Significant rounding"><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">Typography</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">Page Background</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="Show background gradient"><span class="theme-mesh-bg-seg__label">On</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="Hide the background gradient"><span class="theme-mesh-bg-seg__label">Off</span></button>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<a class="fab fab--primary" href="../../#contact" aria-label="Contact Us">
|
||
<span class="fab-primary__ico" aria-hidden="true"></span>
|
||
</a>
|
||
</div>
|
||
<div class="content-edit-fab-stack" aria-label="Content Edits" 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="Content Editing Mode"
|
||
title="Edit Mode"
|
||
>
|
||
<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">Documentation · UI</p>
|
||
<h1 class="case-page__h1">Design System</h1>
|
||
<p class="case-page__sub">Tokens, typography, grid, components, and longgrid blocks</p>
|
||
<p class="section__prose">
|
||
Switch between themes, accents, and fonts using the floating panel on the right. When making global style changes (tokens,
|
||
components, longgrid blocks), be sure to update this page as well—it’s the project’s live UI catalog.
|
||
</p>
|
||
|
||
<nav class="sg-toc" aria-label="Table of Contents: Design System">
|
||
<ol>
|
||
<li><a href="#colors">Colors</a></li>
|
||
<li><a href="#fonts">Fonts</a></li>
|
||
<li><a href="#layout">Grid and Responsive Design</a></li>
|
||
<li><a href="#components">Components</a></li>
|
||
<li><a href="#fab">FAB Buttons</a></li>
|
||
<li><a href="#blocks">Blocks</a></li>
|
||
<li><a href="#activity">Activity</a></li>
|
||
</ol>
|
||
</nav>
|
||
|
||
<h2 id="colors">Colors</h2>
|
||
<p class="section__prose">
|
||
The accent and “cool/warm” tones of the entire UI are defined by three numbers in the <strong>HSL</strong> model (Hue,
|
||
Saturation, Lightness) at <code><html></code>. From these,
|
||
<code>--accent</code> and derived tokens; neutral backgrounds are slightly tinted with the same hue
|
||
(<code>--neutral-accent-tint</code>). Light/dark theme — <code>data-theme</code>, mesh —
|
||
<code>data-mesh-bg</code>. Can be changed via the FAB on the right.
|
||
</p>
|
||
<h3>What is <code>--theme-h</code>, <code>--theme-s</code>, <code>--theme-l</code></h3>
|
||
<ul class="section__prose">
|
||
<li>
|
||
<code>--theme-h</code> — <strong>hue (Hue)</strong>, position on the color wheel 0–360°. A hue shift
|
||
recolors buttons, links, the mesh gradient, and square accents (<code>--accent-square-2</code> …
|
||
<code>4</code>), without having to manually adjust each hex value.
|
||
</li>
|
||
<li>
|
||
<code>--theme-s</code> — <strong>Saturation</strong>: how “pure” the color is, rather than gray.
|
||
</li>
|
||
<li>
|
||
<code>--theme-l</code> — Accent <strong>lightness</strong>: darker or lighter than the primary brand color
|
||
(usually limited by the panel’s range to ensure buttons remain readable).
|
||
</li>
|
||
</ul>
|
||
<p class="section__prose">
|
||
In CSS, it looks like this:
|
||
<code>--accent: hsl(var(--theme-h) calc(var(--theme-s) * 1%) calc(var(--theme-l) * 1%))</code>.
|
||
Values are written in <code>localStorage</code> (<code>siteThemeV1</code>) and are inserted by an inline script into
|
||
<code>head</code>to prevent a “foreign” color from flashing during loading.
|
||
</p>
|
||
<div class="sg-swatch-grid" aria-label="Token Palette">
|
||
<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">
|
||
Additionally: quadratic mesh shades — <code>--accent-square-2</code> … <code>4</code>; they depend on hue.
|
||
</p>
|
||
|
||
<h2 id="fonts">Fonts</h2>
|
||
<p class="section__prose">
|
||
A pair of fonts — <code>data-font</code> on <code><html></code> (FAB “Typography”): sets
|
||
<code>--font-accent</code> (headings) and <code>--font</code> (body text). Scale in tables: class, example, purpose.
|
||
</p>
|
||
<h3>Font families</h3>
|
||
<div class="sg-table-wrap">
|
||
<table class="sg-table">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col"><code>data-font</code></th>
|
||
<th scope="col">Stack</th>
|
||
<th scope="col">Where</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><code>merriweather</code></td>
|
||
<td>Merriweather + Alice</td>
|
||
<td>Default pair; Alice—body text, Merriweather—emphasized headings</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>mono</code></td>
|
||
<td>Source Code Pro + PT Mono</td>
|
||
<td>Monospace pair</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>geologica</code></td>
|
||
<td>Geologica + PT Sans</td>
|
||
<td>Sans-serif + neutral body text</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>Typographic scale</h3>
|
||
<div class="sg-table-wrap">
|
||
<table class="sg-table sg-table--type" aria-label="Typographic scale">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">Class</th>
|
||
<th scope="col">Example</th>
|
||
<th scope="col">Purpose</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><code>.hero__eyebrow</code></td>
|
||
<td class="sg-type-sample"><p class="hero__eyebrow">Section caption</p></td>
|
||
<td>Hero, case input</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 main heading
|
||
</p>
|
||
</td>
|
||
<td>H1 for the main page; reduced on DS for the column</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>.section__title</code></td>
|
||
<td class="sg-type-sample"><h2 class="section__title">Section</h2></td>
|
||
<td>“Skills,” “Portfolio,” contacts; main font, no caps</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>.case-page__h1</code></td>
|
||
<td class="sg-type-sample"><p class="case-page__h1">Page title</p></td>
|
||
<td>H1 for case studies / Design System</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>.case-page__sub</code></td>
|
||
<td class="sg-type-sample"><p class="case-page__sub">Lead text under the heading</p></td>
|
||
<td>Subheading below H1</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>.section__prose</code></td>
|
||
<td class="sg-type-sample"><p class="section__prose">Main paragraph text.</p></td>
|
||
<td>Prose, lists, and links in the article</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>h2</code> in <code>.case-article</code></td>
|
||
<td class="sg-type-sample"><h2 style="margin: 0">H2 subsection</h2></td>
|
||
<td>Long-form sections (such as “Colors”)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>h3</code> in <code>.case-article</code></td>
|
||
<td class="sg-type-sample"><h3 style="margin: 0">H3 subsection</h3></td>
|
||
<td>Subsections within H2; margins in <code>main.css</code>: 3.25rem top, 1.5rem bottom, after <code>figure</code> 4rem, <code>h3+h3</code> 3.5rem</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>Rhythm and Code</h3>
|
||
<p class="section__prose">Rhythm example:</p>
|
||
<ul class="section__prose">
|
||
<li>bulleted list;</li>
|
||
<li><strong>emphasis</strong> and <a href="#fonts">link</a>;</li>
|
||
</ul>
|
||
<ol class="section__prose">
|
||
<li>numbered item;</li>
|
||
</ol>
|
||
<p class="section__prose">Inline <code>code</code> and block:</p>
|
||
<pre><code>--font: "Alice", system-ui, serif;
|
||
--font-accent: "Merriweather", var(--font);</code></pre>
|
||
<p class="section__prose sg-label">
|
||
Headings use <code>clamp()</code> — scale relative to the viewport; see the “Grid” section for more details.
|
||
</p>
|
||
|
||
<h2 id="layout">Grid and Responsive</h2>
|
||
<p class="section__prose">
|
||
There is no separate 12-column grid: a <strong>full-bleed</strong> layout with side margins
|
||
<code>--space</code> and local CSS Grid within sections. The content column for DS and case studies —
|
||
<code>.case-article</code> (max-width 48rem).
|
||
</p>
|
||
|
||
<h3>Basic framework</h3>
|
||
<ul class="section__prose">
|
||
<li><code>--space: clamp(1rem, 4vw, 2.75rem)</code> — horizontal margins + <code>safe-area-inset</code>.</li>
|
||
<li><code>--max: none</code> — no fixed 1200px “box”; width = viewport minus padding.</li>
|
||
<li><code>.section</code> — vertical spacing via <code>clamp()</code>; <code>.section__inner</code> centers the block.</li>
|
||
<li><code>.section--tight</code> — More compact vertically (case studies, this page).</li>
|
||
</ul>
|
||
|
||
<h3>Breakpoints</h3>
|
||
<div class="sg-table-wrap">
|
||
<table class="sg-table">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">From</th>
|
||
<th scope="col">What Changes</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><code>860px</code></td>
|
||
<td><code>.hero__grid</code> → 2 columns <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 cards</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>720px</code></td>
|
||
<td><code>.skills-category__grid</code> → 2 columns</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>1024px</code></td>
|
||
<td>Skills: 6-column mosaic (<code>grid-column: span …</code>)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>560px</code> / <code>1024px</code></td>
|
||
<td><code>.contact-grid</code> → 2 → 4 columns</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>48rem</code> / <code>28rem</code></td>
|
||
<td>Case gallery: ≤2 col → 1 col</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>640px</code></td>
|
||
<td><code>case-media-gallery--wide</code> collapses to the width of a column</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>Hero: one → two columns</h3>
|
||
<p class="section__prose">Layout <code>.hero__grid</code> (compressed to 860px):</p>
|
||
<div class="sg-layout-demo" aria-hidden="true">
|
||
<div class="sg-layout-demo__hero">
|
||
<div class="sg-layout-demo__block">Text</div>
|
||
<div class="sg-layout-demo__block">Visual</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3>Contacts: 1 → 2 → 4</h3>
|
||
<p class="section__prose">Demo <code>.contact-grid</code> (placeholders, no transitions):</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>
|
||
|
||
|
||
|
||
|
||
<span class="contact-card contact-card--link contact-card--brand-tg"><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>
|
||
|
||
|
||
|
||
|
||
<span class="contact-card contact-card--link contact-card--brand-vk"><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>
|
||
|
||
|
||
|
||
|
||
<span class="contact-card contact-card--link contact-card--brand-ya"><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>
|
||
|
||
|
||
|
||
|
||
<span class="contact-card contact-card--action contact-card--brand-email"><span class="contact-card__body"><span class="contact-card__name">Grid</span>
|
||
on<span class="contact-card__hint"> the homepage</span>
|
||
|
||
|
||
</span></span></li>
|
||
</ul>
|
||
<p class="section__prose">
|
||
Skills: mosaic layout on wide screens—see
|
||
<a href="../../#skills">the homepage, #skills</a>. Portfolio: carousel on
|
||
<code>100vw</code> with <code>--projects-card-width</code> and JS scrolling — on the homepage
|
||
<a href="../../#projects">#projects</a>.
|
||
</p>
|
||
|
||
<h3>Wide in case studies</h3>
|
||
<p class="section__prose">
|
||
<code>.case-media--wide</code>, <code>.case-media-gallery--wide</code> and <code>.case-callout</code> wider
|
||
article columns: <code>min(100vw − 2×padding, content + 8rem)</code>, with margins <code>--space</code>. On a narrow screen (up to 640px), "wide" collapses to the width of the column. Images with <code>data-image-viewer</code> — the outline and hover effects
|
||
are the same as <code>.case-video-shell</code> (live examples — case study element demo). The caption below the video is
|
||
<code>figcaption</code> centered in the column.
|
||
</p>
|
||
|
||
<h3>Typography and Motion</h3>
|
||
<p class="section__prose">
|
||
Fluid type: <code>clamp()</code> on <code>.hero__title</code>, <code>.section__title</code>,
|
||
<code>.case-page__h1</code>. Rounded corners (<code>data-radius</code>) do not change the grid. For long screenshots and
|
||
animations, the <code>prefers-reduced-motion: reduce</code>.
|
||
</p>
|
||
|
||
<h2 id="components">Components</h2>
|
||
<p class="section__prose">Live examples; the shell (header, FAB, footer, cookie) is already integrated on this page.</p>
|
||
|
||
<h3>Buttons</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 buttons</h3>
|
||
<p class="section__prose">
|
||
Floating buttons in the lower-right corner (<code>.fab-stack</code>). Icon-only and with a text label.
|
||
</p>
|
||
<div class="sg-stack sg-stack--row">
|
||
<a class="fab fab--primary" href="#components" aria-label="FAB Demo"><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">Download as PDF</span></button>
|
||
<button type="button" class="fab fab--secondary" aria-label="Secondary FAB Demo"><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 (with text) · .fab-primary__ico · .fab__label</p>
|
||
|
||
<h3>Tags</h3>
|
||
<ul class="hero__tags" aria-label="Tag Demo">
|
||
<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="mp-card__tags" aria-label="Tag Demo" style="display: flex; flex-wrap: wrap; gap: 0.35rem 0.45rem; list-style: none; padding: 0; margin: 0.5rem 0">
|
||
<li class="mp-card__tag">mp-card__tag</li>
|
||
<li class="mp-card__tag">Web App</li>
|
||
</ul>
|
||
<p class="sg-label">.mp-card__tags / .mp-card__tag</p>
|
||
|
||
<h3>Portfolio Card</h3>
|
||
<div class="sg-portfolio-wrap">
|
||
<a class="mp-card mp-card--link mp-card--stack-bottom" href="#components" tabindex="-1" aria-hidden="true">
|
||
<div class="mp-card__media" aria-hidden="true">
|
||
<img
|
||
class="mp-card__image"
|
||
src="../../../assets/shared/placeholder.webp"
|
||
alt=""
|
||
width="400"
|
||
height="225"
|
||
loading="lazy"
|
||
decoding="async"
|
||
/>
|
||
</div>
|
||
<div class="mp-card__body">
|
||
<h3 class="mp-card__title">mp-card — portfolio</h3>
|
||
<ul class="mp-card__tags" aria-label="Tags">
|
||
<li class="mp-card__tag">Demo</li>
|
||
<li class="mp-card__tag">UI</li>
|
||
</ul>
|
||
<p class="mp-card__desc">A brief two- or three-line description of the project for the Design System demo.</p>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
<p class="sg-label">.mp-card .mp-card--stack-bottom — text on top, cover at the bottom</p>
|
||
<script>
|
||
(function () {
|
||
function hashStr(s) {
|
||
var h = 0;
|
||
for (var i = 0; i < s.length; i++) { h = (h * 31 + s.charCodeAt(i)) >>> 0; }
|
||
return h;
|
||
}
|
||
function colorTags(root) {
|
||
var tags = root.querySelectorAll('.mp-card--stack-bottom .mp-card__tag');
|
||
for (var i = 0; i < tags.length; i++) {
|
||
var t = tags[i];
|
||
var key = (t.textContent || '').trim().toLowerCase();
|
||
if (!key) continue;
|
||
var hue = hashStr(key) % 360;
|
||
t.style.setProperty('--tag-hue', String(hue));
|
||
}
|
||
}
|
||
colorTags(document);
|
||
})();
|
||
</script>
|
||
|
||
<h3>Placeholder while images are loading</h3>
|
||
<p class="section__prose">
|
||
While <code>img</code> hasn't loaded yet, <code>initImageLoadPlaceholder</code> it
|
||
<code>.is-img-loading</code> on the image and <code>.has-img-loading</code> the wrapper. The background layer is
|
||
<code>::before</code>, a single glow from left to right— <code>::after</code> (<code>img-ph-shine-sweep</code>).
|
||
Icons, SVGs, and small images up to 40px are skipped;
|
||
disable selectively: <code>data-no-img-placeholder</code>. Below is a fixed demo (on the website, the classes are removed
|
||
after <code>load</code>).
|
||
</p>
|
||
<div
|
||
class="case-media case-media--content has-img-loading img-ph-demo"
|
||
role="img"
|
||
aria-label="Demo: Placeholder when loading an image"
|
||
></div>
|
||
<p class="sg-label">.has-img-loading · .is-img-loading · .img-ph-demo (placeholder demo)</p>
|
||
|
||
<h3>TUI Braille spinner</h3>
|
||
<p class="section__prose">
|
||
Loading large images in the viewer (<code>.case-image-viewer</code>) is indicated by a monospaced Braille spinner—just like in TUI. Pure CSS, no JS: 10 frames cycle through an animation. Reusable class: <code>steps()</code>an animation. Reusable class <code>.tui-spinner</code>
|
||
(color — <code>--accent</code>, font—system 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="Loading" 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 · frames: ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ · prefers-reduced-motion displays ⠋</p>
|
||
|
||
<h3>Skill card</h3>
|
||
<article class="skill-task-card">
|
||
<p class="skill-task-card__task">skill-task-card__task — the task description provided by the business.</p>
|
||
<div class="skill-task-card__solutions">
|
||
<p class="skill-task-card__solution">skill-task-card__solution — the first approach to the solution.</p>
|
||
<p class="skill-task-card__solution">The second item in the solutions section.</p>
|
||
</div>
|
||
</article>
|
||
<p class="sg-label">.skill-task-card__task · __solutions · __solution</p>
|
||
|
||
<h3>Callout</h3>
|
||
<p class="section__prose">
|
||
Longread accent block: wider than the column (like wide media), text <code>.case-callout__content</code> slightly
|
||
larger than the main paragraph.
|
||
</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 — an important idea or conclusion. On the left, only colored emojis (📌 💡 ✨ ✅), not text symbols ✎ ✓ ◆; <code>case-callout__emoji--pulse</code> for a subtle animation.</p>
|
||
</div>
|
||
</aside>
|
||
<p class="sg-label">.case-callout · .case-callout__emoji · .case-callout__emoji--pulse · .case-callout__content</p>
|
||
|
||
<h3>Contact Cards</h3>
|
||
<p class="section__prose sg-label" style="margin-top: 0">
|
||
On the home page, there are four cards in a grid (1→2→4 columns). Here are two examples—the grid is forced into two columns.
|
||
</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></a>
|
||
|
||
|
||
|
||
<a class="contact-card contact-card--link contact-card--brand-tg" href="https://t.me/mamadaevv" rel="noopener noreferrer"><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></a>
|
||
|
||
|
||
<a class="contact-card contact-card--link contact-card--brand-tg" href="https://t.me/mamadaevv" rel="noopener noreferrer"><span class="contact-card__body"><span class="contact-card__name">contact-card--link</span>
|
||
<span class="contact-card__hint">External link</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="Copy the address"
|
||
aria-label="Demo: Copy 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>Copy address</span>
|
||
|
||
</span></button>
|
||
</li>
|
||
</ul>
|
||
<p class="sg-label">.contact-card--link · --action · --brand-*</p>
|
||
|
||
<h3>Section with a background</h3>
|
||
<p class="section__prose">
|
||
On the homepage, the strip <code>.section--surface</code> is visually presented using mesh; in other cases, the page background is
|
||
solid—see the compact demo below with <code>background: var(--surface)</code>.
|
||
</p>
|
||
<section class="section section--surface section--tight sg-section-demo" aria-label="Section Demos">
|
||
<div class="section__inner">
|
||
<h2 class="section__title">section__title</h2>
|
||
<p class="section__prose">Snippet: .section.section--surface + .section__inner</p>
|
||
</div>
|
||
</section>
|
||
<p class="sg-label">.section · .section--surface · .section--tight · .section__inner</p>
|
||
|
||
<h3>Separators</h3>
|
||
<p class="section__prose">
|
||
Horizontal separators: a line or simply white space (indentation). Modifiers <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">Blocks</h2>
|
||
<div class="sg-blocks-card">
|
||
<p>
|
||
Longgrid case layout: media and galleries, link cards, videos, callouts, wide blocks, and other partials.
|
||
A live example with all variations is available on a separate page; use it to copy sections when building a new case.
|
||
</p>
|
||
<p>
|
||
<a class="btn btn--secondary btn--flat" href="../template-elements-demo/">Case element demo
|
||
</a></p>
|
||
</div>
|
||
|
||
<h2 id="activity">Activity</h2>
|
||
<div class="sg-blocks-card">
|
||
<p>
|
||
On the homepage in the section <code>#mini-projects</code> card <code>mp-card</code> section on the homepage in a masonry grid. Types, cells, and the live
|
||
layout are on a separate page.
|
||
</p>
|
||
<p>
|
||
<a class="btn btn--secondary btn--flat" href="../mini-projects-showcase/">Activity Card Catalog
|
||
</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">Website design and development — Nikolay Mamadaev</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 policy</a></span> ·<span class="footer__chunk footer__chunk--lang"><span class="footer__sep" aria-hidden="true">
|
||
</span><nav class="lang-switch" aria-label="Choose language" data-lang-switch>
|
||
<a class="lang-switch__btn" data-lang="ru" href="/">Русский</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 l = links[i].getAttribute("data-lang");
|
||
links[i].setAttribute("href", (l === "en" ? "/en/" : "/") + base);
|
||
}
|
||
})();
|
||
</script>
|
||
</span></div>
|
||
</footer>
|
||
|
||
<aside class="cookie-notice glass-neutral" hidden data-cookie-notice>
|
||
<p class="cookie-notice__text">
|
||
This site uses cookies to ensure a smooth user experience. For more information, see the
|
||
<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>Accept</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>
|