openvibers/openvibe.shared kit-layout.css

kit-layout.css

main 12.8 KB · 137 lines · css

1/* openvibe-shared/kit-layout.css: the Kit's fluid scale, its layout primitives and an opt-in classless base
2 * (plan L54 K1/K2). Load it after kit.css; a page that uses none of it loads neither this nor its bytes.
3 *
4 * The fluid scale (Utopia's idea): type steps and spaces that grow with the screen between 320 and 1240 px, by clamp()
5 * and with no breakpoints. Base 16 px at 320 and 18 px at 1240; ratio 1.2 then 1.25.
6 * --step--2 … --step-5 font sizes
7 * --space-3xs … --space-3xl gaps and padding (¼, ½, ¾, 1, 1½, 2, 3, 4 and 6 times the base)
8 *
9 * The primitives (Every Layout's idea): each a few lines, set by custom properties, and aware of their own width
10 * rather than the screen's, so they behave the same in a sidebar, a card or a whole page.
11 * .ov-stack children one under another, --gap apart
12 * .ov-cluster children in a wrapping row (tags, buttons, a toolbar); --gap, --justify, --align
13 * .ov-sidebar a side part --side wide beside the rest; it goes under once the rest would be under --min
14 * (the side part is the first child; .ov-sidebar--end when it is the last)
15 * .ov-switcher side by side until the box is narrower than --min, then all stacked: never two and one
16 * .ov-grid as many columns of at least --min as fit
17 * .ov-rail one row that scrolls sideways, items --item wide, snapping
18 * .ov-cover a box at least --min tall with one child (.is-centre) centred in it
19 * .ov-frame a box of --ratio that crops a picture or a video to fill it
20 *
21 * The classless base (Pico's idea): put .ov-base on <body> or <main> and plain HTML (headings, text, links, lists,
22 * quotes, code, tables, forms) looks finished on the site's theme, light or dark. Every selector is inside :where(),
23 * so it weighs nothing against a site's own rules.
24 */
25:root {
26 --step--2: clamp(0.6944rem, 0.6856rem + 0.0444vw, 0.72rem);
27 --step--1: clamp(0.8333rem, 0.8101rem + 0.1159vw, 0.9rem);
28 --step-0: clamp(1rem, 0.9565rem + 0.2174vw, 1.125rem);
29 --step-1: clamp(1.2rem, 1.1283rem + 0.3587vw, 1.4063rem);
30 --step-2: clamp(1.44rem, 1.3295rem + 0.5527vw, 1.7578rem);
31 --step-3: clamp(1.728rem, 1.5648rem + 0.8161vw, 2.1973rem);
32 --step-4: clamp(2.0736rem, 1.8395rem + 1.1704vw, 2.7466rem);
33 --step-5: clamp(2.4883rem, 2.1597rem + 1.6433vw, 3.4332rem);
34 --space-3xs: clamp(0.25rem, 0.2391rem + 0.0543vw, 0.2813rem);
35 --space-2xs: clamp(0.5rem, 0.4783rem + 0.1087vw, 0.5625rem);
36 --space-xs: clamp(0.75rem, 0.7174rem + 0.163vw, 0.8438rem);
37 --space-s: clamp(1rem, 0.9565rem + 0.2174vw, 1.125rem);
38 --space-m: clamp(1.5rem, 1.4348rem + 0.3261vw, 1.6875rem);
39 --space-l: clamp(2rem, 1.913rem + 0.4348vw, 2.25rem);
40 --space-xl: clamp(3rem, 2.8696rem + 0.6522vw, 3.375rem);
41 --space-2xl: clamp(4rem, 3.8261rem + 0.8696vw, 4.5rem);
42 --space-3xl: clamp(6rem, 5.7391rem + 1.3043vw, 6.75rem);
43}
44
45/* ── Primitives ── */
46.ov-stack { display: flex; flex-direction: column; justify-content: flex-start; gap: var(--gap, var(--space-s)); }
47.ov-stack > * { margin-block: 0; }
48.ov-cluster { display: flex; flex-wrap: wrap; gap: var(--gap, var(--space-xs)); justify-content: var(--justify, flex-start); align-items: var(--align, center); }
49.ov-sidebar { display: flex; flex-wrap: wrap; gap: var(--gap, var(--space-m)); align-items: var(--align, flex-start); }
50.ov-sidebar > :first-child, .ov-sidebar--end > :last-child { flex-basis: var(--side, 18rem); flex-grow: 1; }
51.ov-sidebar > :last-child, .ov-sidebar--end > :first-child { flex-basis: 0; flex-grow: 999; min-inline-size: var(--min, 55%); }
52.ov-sidebar--end > :first-child { order: 0; }
53.ov-switcher { display: flex; flex-wrap: wrap; gap: var(--gap, var(--space-s)); }
54.ov-switcher > * { flex-grow: 1; flex-basis: calc((var(--min, 32rem) - 100%) * 999); min-inline-size: 0; }
55.ov-grid { display: grid; gap: var(--gap, var(--space-s)); grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--min, 16rem)), 1fr)); }
56.ov-rail { display: grid; grid-auto-flow: column; grid-auto-columns: var(--item, min(80%, 18rem)); gap: var(--gap, var(--space-s)); overflow-x: auto; overscroll-behavior-x: contain;
57 scroll-snap-type: x mandatory; scroll-padding-inline: var(--pad, 0px); padding-inline: var(--pad, 0px); padding-block-end: var(--space-2xs); scrollbar-width: thin; }
58.ov-rail > * { scroll-snap-align: start; min-inline-size: 0; }
59.ov-cover { display: flex; flex-direction: column; min-block-size: var(--min, 100svh); padding: var(--pad, var(--space-m)); box-sizing: border-box; }
60.ov-cover > * { margin-block: var(--gap, var(--space-s)); }
61.ov-cover > :first-child:not(.is-centre) { margin-block-start: 0; }
62.ov-cover > :last-child:not(.is-centre) { margin-block-end: 0; }
63.ov-cover > .is-centre { margin-block: auto; }
64.ov-frame { aspect-ratio: var(--ratio, 16 / 9); overflow: hidden; display: flex; justify-content: center; align-items: center; max-inline-size: 100%; }
65.ov-frame > :is(img, video, iframe, canvas, svg) { inline-size: 100%; block-size: 100%; object-fit: cover; }
66
67/* ── The classless base, under .ov-base ── */
68:where(.ov-base) { color: var(--ovk-text); font-family: var(--ovk-font); font-size: var(--step-0); line-height: 1.6; -webkit-text-size-adjust: 100%; }
69:where(.ov-base) :where(h1, h2, h3, h4, h5, h6) { margin: 1.4em 0 .5em; line-height: 1.15; letter-spacing: -.015em; text-wrap: balance; color: var(--ovk-text); }
70:where(.ov-base) :where(h1) { font-size: var(--step-5); letter-spacing: -.03em; margin-block-start: 0; }
71:where(.ov-base) :where(h2) { font-size: var(--step-3); }
72:where(.ov-base) :where(h3) { font-size: var(--step-2); }
73:where(.ov-base) :where(h4) { font-size: var(--step-1); }
74:where(.ov-base) :where(h5, h6) { font-size: var(--step-0); }
75:where(.ov-base) :where(p, ul, ol, dl, blockquote, pre, table, figure, details) { margin: 0 0 1em; }
76:where(.ov-base) :where(p, li, dd, blockquote) { max-inline-size: 68ch; }
77:where(.ov-base) :where(small) { font-size: var(--step--1); color: var(--ovk-muted); }
78:where(.ov-base) :where(a) { color: var(--ovk-accent); text-decoration-thickness: 1px; text-underline-offset: .18em; }
79:where(.ov-base) :where(a:hover) { text-decoration-thickness: 2px; }
80:where(.ov-base) :where(a, button, input, select, textarea, summary):focus-visible { outline: 2px solid var(--ovk-accent); outline-offset: 2px; }
81:where(.ov-base) :where(ul, ol) { padding-inline-start: 1.3em; }
82:where(.ov-base) :where(li + li) { margin-block-start: .3em; }
83:where(.ov-base) :where(hr) { border: 0; border-block-start: 1px solid var(--ovk-line); margin: var(--space-l) 0; }
84:where(.ov-base) :where(blockquote) { padding: .2em 0 .2em 1em; border-inline-start: 3px solid var(--ovk-accent); color: var(--ovk-muted); }
85:where(.ov-base) :where(code, kbd, samp, pre) { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .88em; }
86:where(.ov-base) :where(:not(pre) > code, kbd) { padding: .1em .35em; border-radius: 6px; background: var(--ovk-panel-2); }
87:where(.ov-base) :where(pre) { padding: var(--space-xs) var(--space-s); overflow-x: auto; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius-sm); background: var(--ovk-panel); line-height: 1.5; }
88:where(.ov-base) :where(img, video, svg, canvas, iframe) { max-inline-size: 100%; }
89:where(.ov-base) :where(img, video) { block-size: auto; border-radius: var(--ovk-radius-sm); }
90:where(.ov-base) :where(table) { inline-size: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; display: block; overflow-x: auto; }
91:where(.ov-base) :where(th, td) { padding: .55em .7em; border-block-end: 1px solid var(--ovk-line); text-align: start; vertical-align: top; }
92:where(.ov-base) :where(th) { font-size: var(--step--1); font-weight: 700; letter-spacing: .03em; color: var(--ovk-muted); }
93:where(.ov-base) :where(details) { padding: .6em .9em; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius-sm); background: var(--ovk-panel); }
94:where(.ov-base) :where(summary) { cursor: pointer; font-weight: 700; }
95:where(.ov-base) :where(label) { display: block; margin-block-end: .3em; font-weight: 600; }
96:where(.ov-base) :where(input:not([type=checkbox], [type=radio], [type=range], [type=color]), select, textarea) {
97 box-sizing: border-box; inline-size: 100%; min-block-size: var(--ovk-tap); padding: .5em .8em; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius-sm);
98 background: var(--ovk-input); color: var(--ovk-text); font: inherit; }
99:where(.ov-base) :where(textarea) { min-block-size: 6em; resize: vertical; }
100:where(.ov-base) :where(input:not([type=checkbox], [type=radio], [type=range], [type=color], [type=hidden]), select, textarea) { margin-block-end: 1em; }
101:where(.ov-base) :where(input[type=range]) { accent-color: var(--ovk-fill); }
102/* Radios and checkboxes, drawn (4.219.0; the owner, 11-12: "update the little dots … make sure all basic elements are
103 styled … custom with the theme system"): the site's fill when chosen, a ring that grows its dot, a tick that pops. */
104:where(.ov-base) :where(input[type=radio], input[type=checkbox]) { appearance: none; -webkit-appearance: none; display: inline-grid; place-items: center;
105 inline-size: 1.25em; block-size: 1.25em; margin: 0 .45em 0 0; vertical-align: -.25em; flex: none;
106 border: 2px solid color-mix(in srgb, var(--ovk-text) 32%, transparent); background: transparent; cursor: pointer;
107 transition: border-color var(--ov-dur-2, .2s) ease, background-color var(--ov-dur-2, .2s) ease; }
108:where(.ov-base) :where(input[type=radio]) { border-radius: 50%; }
109:where(.ov-base) :where(input[type=checkbox]) { border-radius: .35em; }
110:where(.ov-base) :where(input[type=radio])::before { content: ""; inline-size: .5em; block-size: .5em; border-radius: 50%; background: var(--ovk-fill);
111 transform: scale(0); transition: transform var(--ov-dur-2, .2s) var(--ov-ease-out, cubic-bezier(.2, .9, .3, 1)); }
112:where(.ov-base) :where(input[type=checkbox])::before { content: ""; inline-size: .72em; block-size: .72em; background: var(--ovk-on-fill);
113 clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); transform: scale(0);
114 transition: transform var(--ov-dur-2, .2s) var(--ov-ease-out, cubic-bezier(.2, .9, .3, 1)); }
115:where(.ov-base) :where(input[type=radio], input[type=checkbox]):hover { border-color: color-mix(in srgb, var(--ovk-fill) 70%, transparent); }
116:where(.ov-base) :where(input[type=radio]:checked) { border-color: var(--ovk-fill); }
117:where(.ov-base) :where(input[type=checkbox]:checked) { border-color: var(--ovk-fill); background: var(--ovk-fill); }
118:where(.ov-base) :where(input[type=radio]:checked, input[type=checkbox]:checked)::before { transform: scale(1); }
119:where(.ov-base) :where(input[type=radio], input[type=checkbox]):focus-visible { outline: 2px solid var(--ovk-accent); outline-offset: 2px; }
120:where(.ov-base) :where(label:has(> input[type=radio], > input[type=checkbox])) { display: flex; align-items: center; gap: .1em; font-weight: 500; cursor: pointer; }
121:where(.ov-base) :where(input, textarea)::placeholder { color: var(--ovk-muted); opacity: .85; }
122:where(.ov-base) :where(input:not([type=checkbox], [type=radio], [type=range], [type=color]), select, textarea):focus { outline: none; border-color: var(--ovk-accent);
123 box-shadow: 0 0 0 3px color-mix(in srgb, var(--ovk-accent) 24%, transparent); }
124/* A plain details (no class of its own) gets a chevron that turns; a site's own details keeps its own. */
125:where(.ov-base) :where(details:not([class]) > summary:not([class])) { display: flex; align-items: center; justify-content: space-between; gap: .6em; list-style: none; }
126:where(.ov-base) :where(details:not([class]) > summary:not([class]))::-webkit-details-marker { display: none; }
127:where(.ov-base) :where(details:not([class]) > summary:not([class]))::after { content: ""; flex: none; inline-size: .5em; block-size: .5em; margin-inline-end: .2em;
128 border-inline-end: 2px solid currentColor; border-block-end: 2px solid currentColor; opacity: .6; transform: rotate(45deg);
129 transition: transform var(--ov-dur-2, .2s) var(--ov-ease-out, ease); }
130:where(.ov-base) :where(details[open]:not([class]) > summary:not([class]))::after { transform: rotate(225deg); }
131:where(.ov-base) :where(details[open]:not([class]) > summary:not([class])) { margin-block-end: .5em; }
132:where(.ov-base) :where(button, input[type=submit], input[type=button]) { min-block-size: var(--ovk-tap); padding: .5em 1.1em; border: 0; border-radius: 999px; cursor: pointer;
133 background: var(--ovk-fill); color: var(--ovk-on-fill); font: inherit; font-weight: 700; }
134:where(.ov-base) :where(button:disabled, input:disabled, select:disabled, textarea:disabled) { opacity: .55; cursor: not-allowed; }
135:where(.ov-base) :where(fieldset) { margin: 0 0 1em; padding: var(--space-xs) var(--space-s); border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius-sm); }
136:where(.ov-base) :where(mark) { padding: 0 .2em; border-radius: 4px; background: color-mix(in srgb, var(--ovk-warn) 35%, transparent); color: inherit; }
137