Fassung 1.6.1: flexible Editorflaeche und passende Blockeigenschaften
This commit is contained in:
1 parent
05ea703655
commit
b783d9d1e5
13 files changed
+276
-11
No files matched your search
@@ -5,7 +5,7 @@
|
||||
* what the server accepts – nothing more, nothing less. Adding a property on the server makes it
|
||||
* appear here without touching this file.
|
||||
*/
|
||||
import { devices, effectiveStyle, hoverStyle, setStyle, resetStyleDevice, clearStyle } from './editor-model.js';
|
||||
import { devices, effectiveStyle, hoverStyle, setStyle, resetStyleDevice, clearStyle, isContainer, relevantProperty } from './editor-model.js';
|
||||
import { openMedia } from './media.js';
|
||||
|
||||
export const GROUPS = {
|
||||
@@ -122,6 +122,8 @@ export function createDesignPanel({ schema, onChange, onSavePreset, getBlock, ge
|
||||
const linkedSides = new Map();
|
||||
// Lives as long as the editor session, so a look can travel between blocks and pages.
|
||||
let clipboard = null;
|
||||
let showAll = false, lastBlockId = null;
|
||||
const openGroups = new Map();
|
||||
|
||||
/** Replaces the styling of a block with a copy, or removes it when nothing is given. */
|
||||
const applyStyle = (doc, id, style) => {
|
||||
@@ -227,7 +229,7 @@ export function createDesignPanel({ schema, onChange, onSavePreset, getBlock, ge
|
||||
return wrap;
|
||||
};
|
||||
|
||||
const lengthControl = (id, value, apply, placeholder) => {
|
||||
const lengthControl = (id, value, apply, placeholder, keywords = []) => {
|
||||
const wrap = element('div', 'length-control');
|
||||
const parts = splitLength(value);
|
||||
const number = document.createElement('input');
|
||||
@@ -245,6 +247,17 @@ export function createDesignPanel({ schema, onChange, onSavePreset, getBlock, ge
|
||||
unit.addEventListener('change', () => { if (number.value !== '') push(); });
|
||||
makeScrubbable(number, { apply: push, step: 1, mergeKey: `scrub-${id}` });
|
||||
wrap.append(number, unit);
|
||||
if (keywords.length) {
|
||||
const mode = document.createElement('select');
|
||||
mode.className = 'length-mode';
|
||||
mode.setAttribute('aria-label', `${schema.properties[id.replace('style-', '')]?.label ?? 'Länge'} Vorgabe`);
|
||||
mode.append(new Option('Eigener Zahlenwert / geerbt', ''));
|
||||
for (const keyword of keywords) mode.append(new Option(keyword === 'none' ? 'Ohne Begrenzung' : 'Automatisch', keyword));
|
||||
mode.value = ['auto', 'none'].includes(value) ? value : '';
|
||||
number.hidden = unit.hidden = mode.value !== '';
|
||||
mode.addEventListener('change', () => apply(mode.value));
|
||||
wrap.prepend(mode);
|
||||
}
|
||||
return wrap;
|
||||
};
|
||||
|
||||
@@ -449,11 +462,16 @@ export function createDesignPanel({ schema, onChange, onSavePreset, getBlock, ge
|
||||
const block = getBlock();
|
||||
const deviceName = getDevice();
|
||||
if (!block) return;
|
||||
if (lastBlockId !== block.id) { showAll = false; lastBlockId = block.id; }
|
||||
|
||||
const scope = element('p', 'design-scope');
|
||||
scope.textContent = `${devices[deviceName]} · ${deviceName === 'desktop' ? 'Basis für alle Geräte' : deviceName === 'tablet' ? 'bis 1024 px, gilt auch für Mobil' : 'bis 767 px'}`;
|
||||
panel.append(scope);
|
||||
panel.append(element('p', 'field-help', 'Leere Felder erben vom größeren Gerät und zuletzt vom Theme. Werte mit eigenem Wert sind hervorgehoben und lassen sich einzeln zurücksetzen.'));
|
||||
const all = element('button', 'secondary design-context-toggle', showAll ? 'Passende Eigenschaften anzeigen' : 'Alle Eigenschaften anzeigen');
|
||||
all.type = 'button'; all.setAttribute('aria-pressed', String(showAll));
|
||||
all.addEventListener('click', () => { showAll = !showAll; onChange(); panel.querySelector('.design-context-toggle')?.focus(); });
|
||||
panel.append(all);
|
||||
|
||||
// A filter over the properties. With more than ninety of them across eight collapsed groups,
|
||||
// "where is the border thickness" is a fair question - so it can be typed instead of hunted.
|
||||
@@ -484,11 +502,16 @@ export function createDesignPanel({ schema, onChange, onSavePreset, getBlock, ge
|
||||
});
|
||||
panel.append(searchLabel, search, found);
|
||||
|
||||
for (const [group, groupLabel] of Object.entries(GROUPS)) {
|
||||
const properties = Object.entries(schema.properties).filter(([, p]) => p.group === group);
|
||||
const priority = ['image', 'gallery', 'logos'].includes(block.type) ? ['image', 'size', 'spacing'] : isContainer(block) ? ['layout', 'size', 'spacing', 'background'] : ['spacer', 'divider', 'embed'].includes(block.type) ? ['size', 'spacing'] : ['text', 'size', 'spacing'];
|
||||
const orderedGroups = [...new Set([...priority, ...Object.keys(GROUPS)])];
|
||||
for (const group of orderedGroups) {
|
||||
const groupLabel = GROUPS[group];
|
||||
const properties = Object.entries(schema.properties).filter(([key, p]) => p.group === group && (showAll || relevantProperty(block, key, p)));
|
||||
if (properties.length === 0) continue;
|
||||
const details = element('details', 'design-group');
|
||||
if (group === 'text' || group === 'spacing') details.open = true;
|
||||
const groupKey = `${block.type}:${group}`;
|
||||
details.open = openGroups.get(groupKey) ?? (group === priority[0] || group === 'spacing');
|
||||
details.addEventListener('toggle', () => { if (details.isConnected) openGroups.set(groupKey, details.open); });
|
||||
details.append(element('summary', null, groupLabel));
|
||||
const body = element('div', 'design-group-body');
|
||||
|
||||
@@ -510,7 +533,7 @@ export function createDesignPanel({ schema, onChange, onSavePreset, getBlock, ge
|
||||
continue;
|
||||
}
|
||||
if (property.type === 'color') control = colorControl(id, own ? current.value : '', apply);
|
||||
else if (property.type === 'length' || property.type === 'length-auto') control = lengthControl(id, own ? current.value : '', apply, current.value ? String(current.value) : 'erbt');
|
||||
else if (property.type === 'length' || property.type === 'length-auto') control = lengthControl(id, own ? current.value : '', apply, current.value ? String(current.value) : 'erbt', property.type === 'length-auto' ? (key.startsWith('max') ? ['none'] : ['auto']) : []);
|
||||
else if (property.type === 'number') control = numberControl(id, own ? current.value : '', property, apply);
|
||||
else if (property.type === 'choice') control = choiceControl(id, own ? current.value : '', property, apply, inheritedText);
|
||||
else if (property.type === 'font') control = fontControl(id, own ? current.value : '', apply, inheritedText);
|
||||
|
||||
@@ -141,6 +141,11 @@ export function upgradeForType(document, type) {
|
||||
* @return {{parentId: string|null, index: number}|null}
|
||||
*/
|
||||
export function insertionTarget(document, selected, type) {
|
||||
// A section starts a page area. Explicit insertion points and drag/drop can still nest it.
|
||||
if (type === 'section') {
|
||||
const top = blockPath(document.blocks, selected)[0];
|
||||
return { parentId: null, index: top ? document.blocks.findIndex(block => block.id === top.id) + 1 : document.blocks.length };
|
||||
}
|
||||
const probe = { type };
|
||||
let item = locate(document.blocks, selected);
|
||||
if (item && canContain(item.block, probe)) return { parentId: item.block.id, index: (item.block.children ?? []).length };
|
||||
@@ -533,3 +538,13 @@ export function copyStyle(document, fromId, toId) {
|
||||
target.style = structuredClone(source.style);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** UI relevance only. Existing values remain editable, including on older documents. */
|
||||
export function relevantProperty(block, key, property) {
|
||||
if (Object.values(block.style ?? {}).some(values => values[key] !== undefined || values.hover?.[key] !== undefined)) return true;
|
||||
const container = isContainer(block);
|
||||
if (property.group === 'image') return ['image', 'gallery', 'logos', 'postlist'].includes(block.type);
|
||||
if (property.group === 'text' || key === 'textShadow') return !['image', 'gallery', 'logos', 'spacer', 'divider', 'embed'].includes(block.type);
|
||||
if (['flexDirection', 'flexWrap', 'justifyContent', 'alignItems', 'gridColumns', 'gap', 'rowGap', 'columnGap'].includes(key)) return container || ['button', 'menu', 'form', 'postlist', 'contact'].includes(block.type);
|
||||
return true;
|
||||
}
|
||||
@@ -281,3 +281,30 @@ dialog p{font-size:14px;line-height:1.7;color:var(--muted)}
|
||||
|
||||
#library-search{font-size:11px;min-height:36px;padding:9px;margin-bottom:4px}
|
||||
#library-found{margin:0 0 10px;min-height:0}
|
||||
|
||||
/* Flexible workspace: both panels use their full width, with accessible resize handles. */
|
||||
.editor-layout{grid-template-columns:var(--panel-left,280px) 6px minmax(280px,1fr) 6px var(--panel-right,320px)}
|
||||
.left-panel,.right-panel{min-width:0;overflow-x:hidden}
|
||||
.left-panel{container-type:inline-size}
|
||||
.left-panel>.section-launch{flex:none;padding:12px;border-bottom:1px solid var(--line)}
|
||||
.section-launch button{white-space:normal;font-size:12px;min-height:40px}
|
||||
.section-launch .field-help{margin:6px 0 0}
|
||||
.left-panel>.panel-section:not(.structure-section){max-height:55%;overflow:auto;flex-shrink:0}
|
||||
.panel-section{padding:16px 12px}
|
||||
.library-buttons{grid-template-columns:repeat(2,minmax(0,1fr))}
|
||||
.library-buttons button{min-width:0;white-space:normal;overflow-wrap:anywhere;gap:6px}
|
||||
.library-name{overflow-wrap:anywhere}
|
||||
.library-preview{width:28px}
|
||||
@container(max-width:270px){.library-buttons{grid-template-columns:minmax(0,1fr)}.library-buttons button[data-row]{grid-column:auto}}
|
||||
.canvas-scroll{padding:0}
|
||||
.panel-resizer{background:#e4e9df;cursor:col-resize;touch-action:none;position:relative;z-index:4}
|
||||
.panel-resizer::after{content:'';position:absolute;top:45%;left:1px;width:4px;height:36px;border-radius:4px;background:#7b8d80}
|
||||
.panel-resizer:hover,.panel-resizer:focus-visible{background:#b7cbbd;outline:2px solid var(--accent);outline-offset:-2px}
|
||||
.resizing-panels{cursor:col-resize;user-select:none}
|
||||
.resizing-panels iframe{pointer-events:none}
|
||||
.length-control,.number-control{grid-template-columns:minmax(0,1fr) 74px}
|
||||
.right-panel input,.right-panel select,.right-panel textarea{min-width:0;max-width:100%}
|
||||
.length-mode{grid-column:1/-1}
|
||||
.design-context-toggle{margin:10px 0;width:100%;white-space:normal}
|
||||
@media(max-width:850px){.panel-resizer{display:none}.editor-layout{display:flex}.left-panel,.right-panel{width:50%}.canvas-scroll{padding:0}.left-panel>.panel-section:not(.structure-section){max-height:50%}}
|
||||
@media(max-width:560px){.left-panel,.right-panel{width:100%;height:620px;max-height:620px}.canvas-toolbar{padding-inline:8px}.canvas-label{display:none}}
|
||||
+66
-2
@@ -23,6 +23,7 @@ let device = 'desktop', propertyPanel = 'content', designTokens, designSchema, d
|
||||
// same factor, so it is kept here instead of being recomputed from the transform string.
|
||||
let previewScale = 1, overlay = null, pendingInsert = null;
|
||||
const collapsed = new Set();
|
||||
let renderedProperties = null;
|
||||
|
||||
function notify(message = '', recover = false) {
|
||||
$('#message').textContent = message;
|
||||
@@ -72,6 +73,7 @@ function controls() {
|
||||
document.querySelectorAll('[data-panel],[data-viewport],#tree-search,#library-search,#expand-all,#collapse-all').forEach(input => input.disabled = !ready || busy);
|
||||
$('[data-panel="design"]').disabled ||= !current;
|
||||
$('#select-page').disabled = !ready || busy;
|
||||
$('#add-root-section').disabled = !ready || busy;
|
||||
$('#open-media-library').disabled = !ready || busy;
|
||||
}
|
||||
|
||||
@@ -131,6 +133,9 @@ function highlight(scroll = false) {
|
||||
function renderProperties() {
|
||||
const block = locate(history.document.blocks, selected)?.block;
|
||||
if (!block) propertyPanel = 'content';
|
||||
const context = `${selected ?? 'page'}:${propertyPanel}`;
|
||||
if (context !== renderedProperties) $('.right-panel').scrollTop = 0;
|
||||
renderedProperties = context;
|
||||
$('#properties-title').textContent = block ? blockLabel(block) : 'Seiteneinstellungen';
|
||||
const panel = $('#properties'); panel.replaceChildren();
|
||||
document.querySelectorAll('[data-panel]').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.panel === propertyPanel)));
|
||||
@@ -325,7 +330,7 @@ function renderProperties() {
|
||||
}
|
||||
if (block.type === 'section') {
|
||||
field('Farbwelt', 'tone', block.layout?.tone || 'paper', { layout: true, choices: { paper: 'Hell · Papier', mint: 'Hellgrün · Akzent', ink: 'Dunkel · Tinte' } });
|
||||
field('Inhaltsbreite', 'width', block.layout?.width || 'normal', { layout: true, choices: { normal: 'Normal (1180 px)', narrow: 'Schmal (800 px)', wide: 'Breit (1600 px)', full: 'Randlos (volle Breite)' }, help: 'Randlos heißt ohne Rand und ohne Begrenzung – für ein Bild oder eine Farbfläche von Kante zu Kante.' });
|
||||
field('Inhaltsbreite', 'width', block.layout?.width || 'normal', { layout: true, choices: { normal: 'Websitebreite (aus dem Theme)', narrow: 'Schmal (800 px)', wide: 'Breit (1600 px)', full: 'Randlos (volle Breite)' }, help: 'Randlos nutzt die gesamte Sektion. Text und Überschriften füllen ihren Container; eine eigene Begrenzung stellst du unter Design → Größe ein.' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,9 +550,9 @@ function stopInlineEdit() {
|
||||
|
||||
function resizePreview() {
|
||||
const container = $('.canvas-scroll'), frame = $('#preview'), stage = $('#preview-stage');
|
||||
const width = { desktop: 1240, tablet: 768, mobile: 390 }[device];
|
||||
const padding = parseFloat(getComputedStyle(container).paddingLeft) * 2;
|
||||
const available = Math.max(100, container.clientWidth - padding);
|
||||
const width = { desktop: Math.max(1240, available), tablet: 768, mobile: 390 }[device];
|
||||
const scale = Math.min(1, available / width);
|
||||
const height = Math.max(550, (container.clientHeight - padding) / scale);
|
||||
previewScale = scale;
|
||||
@@ -830,6 +835,13 @@ $('#library').addEventListener('click', event => {
|
||||
addElement(button.dataset.add);
|
||||
});
|
||||
$('#cancel-insert').addEventListener('click', () => setPendingInsert(null));
|
||||
$('#add-root-section').addEventListener('click', () => {
|
||||
if (busy || !history) return;
|
||||
stopInlineEdit();
|
||||
setPendingInsert(null);
|
||||
selected = history.change(doc => insertBlock(doc, 'section', selected));
|
||||
propertyPanel = 'content'; changed();
|
||||
});
|
||||
$('#presets').addEventListener('click', event => {
|
||||
const name = event.target.closest('[data-preset]')?.dataset.preset;
|
||||
if (!name || busy || !history) return;
|
||||
@@ -1003,6 +1015,58 @@ function setViewport(name) {
|
||||
}
|
||||
document.querySelectorAll('[data-viewport]').forEach(button => button.addEventListener('click', () => setViewport(button.dataset.viewport)));
|
||||
new ResizeObserver(resizePreview).observe($('.canvas-scroll'));
|
||||
// Panel sizes are workspace preferences, independent of document history and publication.
|
||||
const panelSizes = { left: 280, right: 320 };
|
||||
try {
|
||||
const saved = JSON.parse(localStorage.getItem('kantsites.panel-widths'));
|
||||
for (const side of ['left', 'right']) if (Number.isFinite(saved?.[side])) panelSizes[side] = saved[side];
|
||||
} catch { /* Storage can be unavailable; resizing still works for this session. */ }
|
||||
const panelMinimum = { left: 240, right: 280 };
|
||||
function layoutPanels() {
|
||||
let left = Math.max(240, Math.min(560, panelSizes.left));
|
||||
let right = Math.max(280, Math.min(560, panelSizes.right));
|
||||
const budget = Math.max(520, $('.editor-layout').clientWidth - 292);
|
||||
const excess = Math.max(0, left + right - budget);
|
||||
const spare = left + right - 520;
|
||||
if (excess && spare) { left -= excess * (left - 240) / spare; right -= excess * (right - 280) / spare; }
|
||||
for (const [side, size] of Object.entries({ left, right })) {
|
||||
root.style.setProperty(`--panel-${side}`, `${size}px`);
|
||||
const handle = $(`[data-resize="${side}"]`);
|
||||
handle.setAttribute('aria-valuenow', String(Math.round(size)));
|
||||
handle.setAttribute('aria-valuemax', String(Math.floor(Math.min(560, budget - (side === 'left' ? right : left)))));
|
||||
handle.setAttribute('aria-valuetext', `${Math.round(size)} Pixel`);
|
||||
}
|
||||
}
|
||||
const persistPanels = () => { try { localStorage.setItem('kantsites.panel-widths', JSON.stringify(panelSizes)); } catch {} };
|
||||
for (const handle of document.querySelectorAll('[data-resize]')) {
|
||||
const side = handle.dataset.resize, direction = side === 'left' ? 1 : -1;
|
||||
let drag = null;
|
||||
const setSize = value => {
|
||||
panelSizes[side] = Math.max(panelMinimum[side], Math.min(Number(handle.getAttribute('aria-valuemax')), value));
|
||||
layoutPanels();
|
||||
};
|
||||
handle.addEventListener('pointerdown', event => {
|
||||
if (event.button !== 0) return;
|
||||
event.preventDefault(); handle.focus();
|
||||
drag = { x: event.clientX, size: Number(handle.getAttribute('aria-valuenow')) };
|
||||
handle.setPointerCapture(event.pointerId); root.classList.add('resizing-panels');
|
||||
});
|
||||
handle.addEventListener('pointermove', event => { if (drag) setSize(drag.size + direction * (event.clientX - drag.x)); });
|
||||
const stop = () => { drag = null; root.classList.remove('resizing-panels'); persistPanels(); };
|
||||
handle.addEventListener('pointerup', stop);
|
||||
handle.addEventListener('pointercancel', stop);
|
||||
handle.addEventListener('lostpointercapture', stop);
|
||||
handle.addEventListener('dblclick', () => { panelSizes[side] = side === 'left' ? 280 : 320; layoutPanels(); persistPanels(); });
|
||||
handle.addEventListener('keydown', event => {
|
||||
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
|
||||
event.preventDefault();
|
||||
const current = Number(handle.getAttribute('aria-valuenow'));
|
||||
setSize(event.key === 'Home' ? panelMinimum[side] : event.key === 'End' ? 560 : current + (event.key === 'ArrowRight' ? 1 : -1) * direction * (event.shiftKey ? 40 : 10));
|
||||
persistPanels();
|
||||
});
|
||||
}
|
||||
new ResizeObserver(layoutPanels).observe($('.editor-layout'));
|
||||
layoutPanels();
|
||||
window.addEventListener('keydown', event => {
|
||||
if (!history || busy || !(event.ctrlKey || event.metaKey)) return;
|
||||
const key = event.key.toLowerCase();
|
||||
|
||||
@@ -6,7 +6,7 @@ h1,h2,h3,h4,h5,h6{hyphens:manual}
|
||||
p{hyphens:auto}
|
||||
.visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.block-image{margin:0;max-width:100%}.block-image img{max-width:100%;height:auto}
|
||||
:root{color-scheme:light;--paper:#f7f8f4;--ink:#182d2b;--muted:#50625d;--line:#d7dfd7;--mint:#e5f0b6;--accent:#315b49;font-family:Arial,Helvetica,sans-serif;color:var(--ink);background:var(--paper);font-synthesis:none}
|
||||
*{box-sizing:border-box}body{margin:0}a{color:inherit;text-underline-offset:5px}a:hover{text-decoration-thickness:2px}:focus-visible{outline:3px solid var(--accent);outline-offset:6px}main:focus{outline:none}.shell{width:min(var(--k-container,1180px),calc(100% - 64px));margin-inline:auto}.site-header{border-bottom:1px solid var(--line)}.site-header.is-sticky{position:sticky;top:0;z-index:40;background:var(--paper);box-shadow:0 1px 0 var(--line),0 10px 30px color-mix(in srgb,var(--ink) 8%,transparent)}.image-link{display:block;text-decoration:none}.brand{display:inline-flex;align-items:center;gap:5px;font-weight:800;font-size:28px;letter-spacing:-1.3px;text-decoration:none}.brand-icon{width:37px;height:37px;background:var(--ink);color:var(--mint);border-radius:10px;display:grid;place-items:center;margin-right:7px;font-size:27px;letter-spacing:-2px}.brand-light{font-weight:400}.site-header nav{display:flex;gap:32px;align-items:center;font-size:14px;font-weight:600}.site-header nav a{text-decoration:none;padding:12px 0}.site-header nav [aria-current=page]{text-decoration:none;font-weight:800}.site-header nav .nav-link{border:1px solid var(--ink);padding:13px 18px;border-radius:7px}.nav-link span{margin-left:18px}.block-section{padding:70px 0}.block-section:first-child{padding:74px 0 82px}h1,h2,h3{letter-spacing:-.05em;line-height:1.05;margin:0 0 24px}h1{font-size:clamp(48px,6.6vw,84px);font-weight:500;max-width:900px}h2{font-size:clamp(32px,4vw,49px);font-weight:500;max-width:760px}h3{font-size:26px;letter-spacing:-.035em;line-height:1.2}.block-text{font-size:18px;line-height:1.7;max-width:690px;margin:0 0 23px;color:color-mix(in srgb,currentColor 82%,transparent);white-space:pre-line}.button{display:inline-flex;align-items:center;gap:32px;min-height:50px;margin-top:9px;padding:15px 21px;background:var(--ink);color:var(--paper);border-radius:7px;text-decoration:none;font-size:14px;font-weight:600}.button:hover{background:color-mix(in srgb,var(--ink) 88%,var(--paper))}.block-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(250px,100%),1fr));gap:22px;margin-top:38px;background:transparent!important}.block-column{min-width:0}.block-column .block-text{font-size:15px}.tone-mint{background:var(--mint)}.tone-ink{background:var(--ink);color:var(--paper)}.tone-ink .block-text{color:color-mix(in srgb,var(--paper) 86%,var(--ink))}.tone-ink .tone-ink .button{color:var(--ink);background:var(--mint)}.width-narrow>.section-inner{max-width:800px}.site-footer{border-top:1px solid var(--line)}.site-footer p,.site-footer>span{font-size:12px;color:var(--muted)}.site-footer .brand{font-size:22px}.skip-link{position:fixed;left:16px;top:-100px;z-index:10;background:var(--ink);color:white;padding:16px}.skip-link:focus{top:16px}img{display:block;max-width:100%;height:auto;border-radius:12px}.error-page{padding-block:100px}.error-page h1{max-width:800px}
|
||||
*{box-sizing:border-box}body{margin:0}a{color:inherit;text-underline-offset:5px}a:hover{text-decoration-thickness:2px}:focus-visible{outline:3px solid var(--accent);outline-offset:6px}main:focus{outline:none}.shell{width:min(var(--k-container,1180px),calc(100% - 64px));margin-inline:auto}.site-header{border-bottom:1px solid var(--line)}.site-header.is-sticky{position:sticky;top:0;z-index:40;background:var(--paper);box-shadow:0 1px 0 var(--line),0 10px 30px color-mix(in srgb,var(--ink) 8%,transparent)}.image-link{display:block;text-decoration:none}.brand{display:inline-flex;align-items:center;gap:5px;font-weight:800;font-size:28px;letter-spacing:-1.3px;text-decoration:none}.brand-icon{width:37px;height:37px;background:var(--ink);color:var(--mint);border-radius:10px;display:grid;place-items:center;margin-right:7px;font-size:27px;letter-spacing:-2px}.brand-light{font-weight:400}.site-header nav{display:flex;gap:32px;align-items:center;font-size:14px;font-weight:600}.site-header nav a{text-decoration:none;padding:12px 0}.site-header nav [aria-current=page]{text-decoration:none;font-weight:800}.site-header nav .nav-link{border:1px solid var(--ink);padding:13px 18px;border-radius:7px}.nav-link span{margin-left:18px}.block-section{padding:70px 0}.block-section:first-child{padding:74px 0 82px}h1,h2,h3{letter-spacing:-.05em;line-height:1.05;margin:0 0 24px}h1{font-size:clamp(48px,6.6vw,84px);font-weight:500}h2{font-size:clamp(32px,4vw,49px);font-weight:500}h3{font-size:26px;letter-spacing:-.035em;line-height:1.2}.block-text{font-size:18px;line-height:1.7;margin:0 0 23px;color:color-mix(in srgb,currentColor 82%,transparent);white-space:pre-line}.button{display:inline-flex;align-items:center;gap:32px;min-height:50px;margin-top:9px;padding:15px 21px;background:var(--ink);color:var(--paper);border-radius:7px;text-decoration:none;font-size:14px;font-weight:600}.button:hover{background:color-mix(in srgb,var(--ink) 88%,var(--paper))}.block-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(250px,100%),1fr));gap:22px;margin-top:38px;background:transparent!important}.block-column{min-width:0}.block-column .block-text{font-size:15px}.tone-mint{background:var(--mint)}.tone-ink{background:var(--ink);color:var(--paper)}.tone-ink .block-text{color:color-mix(in srgb,var(--paper) 86%,var(--ink))}.tone-ink .tone-ink .button{color:var(--ink);background:var(--mint)}.width-narrow>.section-inner{max-width:800px}.site-footer{border-top:1px solid var(--line)}.site-footer p,.site-footer>span{font-size:12px;color:var(--muted)}.site-footer .brand{font-size:22px}.skip-link{position:fixed;left:16px;top:-100px;z-index:10;background:var(--ink);color:white;padding:16px}.skip-link:focus{top:16px}img{display:block;max-width:100%;height:auto;border-radius:12px}.error-page{padding-block:100px}.error-page h1{max-width:800px}
|
||||
@media(max-width:650px){.shell{width:calc(100% - 36px)}.site-header nav{width:100%;justify-content:space-between;gap:12px;font-size:12px}.site-header nav .nav-link{padding:10px}.nav-link span{margin-left:6px}.block-section,.block-section:first-child{padding-block:48px}h1{font-size:49px}.block-text{font-size:16px}.site-footer{align-items:flex-start;flex-direction:column;gap:10px}.site-footer p{margin:0}.block-row{gap:16px}}
|
||||
|
||||
/* Menus. Submenus open with details/summary, so the navigation works without JavaScript. */
|
||||
|
||||
Reference in new issue
Block a user