diff --git a/README.md b/README.md index 826f7c2..17a651d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# The Agentic Interface Framework — website +# The Agent Interface Framework — website A three-page static site for the framework *Eight primitives for the layer where humans meet agentic software*. No build step, no runtime @@ -8,11 +8,10 @@ palette, sharp corners, inline Phosphor icons. ## Pages - **Home** (`index.html`) — hero, "the interface is the unsolved half" - intro with a CTA into the test, the eight primitives as exclusive + intro with a CTA into the test, the eight primitives as accordions (one-liner, description, feature cards with image - placeholders), contributors, and a contribute CTA that opens a - mail-app form. -- **Test** (`quicktest.html`) — The Agentic Interface Test: Quick test + placeholders), contributors, and a contribute-by-email line. +- **Test** (`quicktest.html`) — The Agent Interface Test: Quick test (16 questions) and Deep dive (48 questions) as switchable modes, all questions on one page with a sticky "Generate score" bar; results open in a modal with a level stepper (Chatbot → Fully Agentic), a radar @@ -38,9 +37,8 @@ palette, sharp corners, inline Phosphor icons. z-scale, motion) and shared components (`.btn`, `.numbox`, `.input`, page-header band), plus all Home styles. - `ait.css` — test-page styles, all `ait-` prefixed. -- `script.js` — Home interactions: contribute form (opens the visitor's - mail app addressed to parikshit@thesys.dev), animated exclusive - accordions, scroll reveals. Home remains fully readable with +- `script.js` — Home interactions: animated accordions (any number open + at once) and scroll reveals. Home remains fully readable with JavaScript disabled. - `ait-questions.js` — test data (`window.AIT_DATA`): 8 primitives, 16 + 48 questions with tooltips and fixes, 5 maturity levels. diff --git a/ait-questions.js b/ait-questions.js index f91ec4d..834fb3c 100644 --- a/ait-questions.js +++ b/ait-questions.js @@ -1,4 +1,4 @@ -// The Agentic Interface Test — question data. +// The Agent Interface Test — question data. // Every fix string is authored here at build time and never generated. window.AIT_DATA = (function () { 'use strict'; diff --git a/ait.css b/ait.css index beb7808..405ae14 100644 --- a/ait.css +++ b/ait.css @@ -128,7 +128,7 @@ .ait-row { border-bottom: 1px solid var(--hairline); - padding: 0.7rem 0.25rem; + padding: 1.15rem 0.25rem; } /* Deep-dive sections: the rows only carry bottom borders, so the @@ -609,17 +609,28 @@ html[data-theme="dark"] .ait-radar-data { fill: rgba(242, 242, 242, 0.1); } } @media (max-width: 640px) { - /* Two fixed 13rem tabs exceed narrow viewports — go fluid. */ + /* Fluid tabs with a comfortable tap height on touch screens. */ .ait-mode-tabs { display: flex; } - .ait-mode-tab { min-width: 0; flex: 1; } + .ait-mode-tab { min-width: 0; flex: 1; min-height: 42px; justify-content: center; } + + /* Number tile on top, question below it; Yes/No sits on the text's + left edge with the "?" on the right at a matching size. */ .ait-row-main { + flex-direction: column; + align-items: stretch; + gap: 0.8rem; + } + .ait-row-textwrap { flex-direction: column; align-items: flex-start; - gap: 0.6rem; + gap: 0.55rem; + max-width: none; + } + .ait-row-controls { + justify-content: space-between; } - .ait-seg-group { align-self: stretch; } - .ait-seg { flex: 1; } - .ait-seg-label { width: 100%; } + .ait-tip-btn { width: 2rem; height: 2rem; } + .ait-name-row .ait-name-input { width: 100%; max-width: none; } .ait-genbar { flex-direction: column; align-items: stretch; gap: 0.5rem; } .ait-generate { width: 100%; } .ait-modal { padding: 1.5rem 1.1rem 1.25rem; } diff --git a/ait.js b/ait.js index b9f9fda..5768bba 100644 --- a/ait.js +++ b/ait.js @@ -1,4 +1,4 @@ -// The Agentic Interface Test — app logic. Plain script, no dependencies, +// The Agent Interface Test — app logic. Plain script, no dependencies, // no network. All questions render on one page; results open in a modal. (function () { 'use strict'; @@ -68,7 +68,7 @@ // Title line shared by the modal and the canvas scorecard. function scoreTitle(name, score) { - return (name ? name + '\u2019s Agentic Interface Score' : 'Your Agentic Interface Score') + + return (name ? name + '\u2019s Agent Interface Score' : 'Your Agent Interface Score') + ' : ' + score + '/100'; } diff --git a/components.js b/components.js index 8575e2f..bc35be1 100644 --- a/components.js +++ b/components.js @@ -18,6 +18,7 @@ 'arrow-up': 'M205.66 117.66a8 8 0 0 1-11.32 0L136 59.31V216a8 8 0 0 1-16 0V59.31L61.66 117.66a8 8 0 0 1-11.32-11.32l72-72a8 8 0 0 1 11.32 0l72 72a8 8 0 0 1 0 11.32Z', check: 'M229.66 77.66l-128 128a8 8 0 0 1-11.32 0l-56-56a8 8 0 0 1 11.32-11.32L96 188.69 218.34 66.34a8 8 0 0 1 11.32 11.32Z', x: 'M205.66 194.34a8 8 0 0 1-11.32 11.32L128 139.31l-66.34 66.35a8 8 0 0 1-11.32-11.32L116.69 128 50.34 61.66a8 8 0 0 1 11.32-11.32L128 116.69l66.34-66.35a8 8 0 0 1 11.32 11.32L139.31 128Z', + list: 'M224 128a8 8 0 0 1-8 8H40a8 8 0 0 1 0-16h176a8 8 0 0 1 8 8ZM40 72h176a8 8 0 0 0 0-16H40a8 8 0 0 0 0 16Zm176 112H40a8 8 0 0 0 0 16h176a8 8 0 0 0 0-16Z', moon: 'M233.54 142.23a8 8 0 0 0-8-2 88.08 88.08 0 0 1-109.8-109.8 8 8 0 0 0-10-10 104.84 104.84 0 0 0-52.91 37A104 104 0 0 0 136 224a103.09 103.09 0 0 0 62.52-20.88 104.84 104.84 0 0 0 37-52.91 8 8 0 0 0-1.98-7.98Zm-44.64 48.11A88 88 0 0 1 65.66 67.11a89 89 0 0 1 31.4-26A106 106 0 0 0 96 56a104.11 104.11 0 0 0 104 104 106 106 0 0 0 14.92-1.06 89 89 0 0 1-26.02 31.4Z', monitor: 'M208 40H48a24 24 0 0 0-24 24v112a24 24 0 0 0 24 24h160a24 24 0 0 0 24-24V64a24 24 0 0 0-24-24Zm8 136a8 8 0 0 1-8 8H48a8 8 0 0 1-8-8V64a8 8 0 0 1 8-8h160a8 8 0 0 1 8 8ZM168 224a8 8 0 0 1-8 8H96a8 8 0 0 1 0-16h64a8 8 0 0 1 8 8Z', sun: 'M120 40V16a8 8 0 0 1 16 0v24a8 8 0 0 1-16 0Zm72 88a64 64 0 1 1-64-64 64.07 64.07 0 0 1 64 64Zm-16 0a48 48 0 1 0-48 48 48.05 48.05 0 0 0 48-48ZM58.34 69.66a8 8 0 0 0 11.32-11.32l-16-16a8 8 0 0 0-11.32 11.32Zm0 116.68-16 16a8 8 0 0 0 11.32 11.32l16-16a8 8 0 0 0-11.32-11.32ZM192 72a8 8 0 0 0 5.66-2.34l16-16a8 8 0 0 0-11.32-11.32l-16 16A8 8 0 0 0 192 72Zm5.66 114.34a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32-11.32ZM48 128a8 8 0 0 0-8-8H16a8 8 0 0 0 0 16h24a8 8 0 0 0 8-8Zm80 80a8 8 0 0 0-8 8v24a8 8 0 0 0 16 0v-24a8 8 0 0 0-8-8Zm112-88h-24a8 8 0 0 0 0 16h24a8 8 0 0 0 0-16Z' @@ -54,23 +55,46 @@ '' + ''; + '' + + ''; + + // Mobile hamburger: collapses the nav into a panel under the bar. + var headerEl = this.querySelector('.site-header'); + var navToggle = this.querySelector('.nav-toggle'); + function setMenu(open) { + headerEl.classList.toggle('menu-open', open); + document.documentElement.classList.toggle('menu-locked', open); + navToggle.setAttribute('aria-expanded', String(open)); + navToggle.innerHTML = iconSVG(open ? 'x' : 'list', 18); + } + navToggle.addEventListener('click', function () { + setMenu(!headerEl.classList.contains('menu-open')); + }); + this.querySelector('.menu-scrim').addEventListener('click', function () { setMenu(false); }); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') setMenu(false); + }); + setMenu(false); // Star count: fetched from the GitHub API, cached for an hour. // On any failure the count stays hidden and the button still links. diff --git a/index.html b/index.html index 4740182..f780b97 100644 --- a/index.html +++ b/index.html @@ -21,9 +21,9 @@ - The Agentic Interface Framework + The Agent Interface Framework - + @@ -45,7 +45,7 @@
-

Agentic Interface
Framework

+

Agent Interface
Framework

A best-practice framework for taking your AI app
from chatbot to agentic software.

@@ -70,7 +70,7 @@

The eight primitives

The eight primitives are the building blocks of an agentic interface. Each one names a category of concrete, proven patterns — from expressing intent to recovering from failure. Applied together, they produce a product where people can direct the agent, watch it work, trust its results, and keep what it makes: an interface, not just a chat box. Open a primitive to see its patterns.

-
+
1

Guide intent

@@ -150,7 +150,7 @@

Structured inputs

-
+
2

Reveal progress

@@ -262,7 +262,7 @@

Async & background work

-
+
3

Show, don’t tell

@@ -342,7 +342,7 @@

Accessible generated UI

-
+
4

Keep control

@@ -446,7 +446,7 @@

Respectful surfacing

-
+
5

Earn trust

@@ -550,7 +550,7 @@

Agent identity

-
+
6

Expect failure

@@ -635,7 +635,7 @@

Refusal surfaces

-
+
7

Make artifacts

@@ -729,7 +729,7 @@

Portability

-
+
8

Build a home

@@ -887,31 +887,15 @@

Feedback & steering

Written by practitioners

Engineers, designers, and product leaders who build agentic software and have watched the same problems recur across very different products.

    +
  • Parikshit
  • +
  • Rabi
  • Abhishek
  • Aditya
  • -
  • Parikshit
  • Prakhar
  • -
  • Rabi
  • Zahle

This is a starting point, not a finished specification. If these primitives describe what you have already learned, sign on. If they miss something, say what, and show the pattern.

-

- -

- +

To contribute, write to us at hello@thesys.dev.

diff --git a/quicktest.html b/quicktest.html index 6b92efb..5db7e76 100644 --- a/quicktest.html +++ b/quicktest.html @@ -23,9 +23,9 @@ - The Agentic Interface Test — The Agentic Interface Framework + The Agent Interface Test — The Agent Interface Framework - + @@ -36,7 +36,7 @@
diff --git a/resources.html b/resources.html index b0758ab..1c21d0e 100644 --- a/resources.html +++ b/resources.html @@ -20,9 +20,9 @@ - Resources — The Agentic Interface Framework + Resources — The Agent Interface Framework - + @@ -46,10 +46,9 @@

Resources

Agent Interface

By OpenUI

-

The open-source, production-ready, best-practice implementation of the Agentic Interface Framework.

+

The open-source, production-ready, best-practice implementation of the Agent Interface Framework.

Explore Agent Interface -
diff --git a/script.js b/script.js index 27dcfd8..c0b217b 100644 --- a/script.js +++ b/script.js @@ -1,48 +1,11 @@ -// Home-page interactions: contribute form, accordions, scroll reveals. +// Home-page interactions: accordions, scroll reveals. // --------------------------------------------------------------- -// Contribute CTA: reveal the form, submit via the user's mail app -// --------------------------------------------------------------- -const contribBtn = document.getElementById('contrib-btn'); -const contribForm = document.getElementById('contrib-form'); - -function openContribute(focus = true) { - if (!contribForm) return; - contribForm.hidden = false; - contribBtn.setAttribute('aria-expanded', 'true'); - if (focus) contribForm.querySelector('input[name="name"]').focus(); -} - -if (contribBtn && contribForm) { - contribBtn.addEventListener('click', () => { - if (contribForm.hidden) openContribute(); - else { - contribForm.hidden = true; - contribBtn.setAttribute('aria-expanded', 'false'); - } - }); - - contribForm.addEventListener('submit', (e) => { - e.preventDefault(); - const name = contribForm.elements.name.value.trim(); - const message = contribForm.elements.message.value.trim(); - const subject = 'Contributing to the Agentic Interface Framework'; - const body = (name ? 'Name: ' + name + '\n\n' : '') + message; - window.location.href = - 'mailto:parikshit@thesys.dev?subject=' + encodeURIComponent(subject) + - '&body=' + encodeURIComponent(body); - }); - - // Deep link: arriving at ./#contribute opens the form in place. - if (location.hash === '#contribute') openContribute(false); -} - -// --------------------------------------------------------------- -// Accordions: exclusive open, animated as expand-then-fade. +// Accordions: any number open at once, animated as expand-then-fade. // The panel's height eases open first; once it has expanded, the // content fades in. Collapse animates height and opacity together. -// With prefers-reduced-motion (or no JS) the native details -// behavior and its name="" exclusivity apply instead. +// With prefers-reduced-motion (or no JS) the native details toggle +// applies instead. // --------------------------------------------------------------- const accItems = Array.from(document.querySelectorAll('details.acc-item')); const ACC_REDUCED = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; @@ -88,12 +51,6 @@ function accExpand(item) { accSettle(body, 330, () => { body.style.cssText = ''; delete item.dataset.animating; - // A sibling asked this panel to close while it was mid-expand - // (exclusivity would otherwise leave two panels open). - if (item.dataset.closeAfter) { - delete item.dataset.closeAfter; - accCollapse(item); - } }); }); } @@ -114,28 +71,7 @@ function accCollapse(item) { }); } -// Closes `item` instantly and keeps `anchorEl` (the clicked summary) -// at the same viewport position. Used for open siblings ABOVE the -// clicked row: an animated collapse there would drag the clicked row -// up under the cursor, which reads as a scroll glitch. The correction -// is the row's measured displacement, so any scroll clamping the -// browser applies when the document shrinks is accounted for. -function accCollapseAbove(item, anchorEl) { - const body = item.querySelector('.acc-body'); - body.style.cssText = ''; - delete item.dataset.animating; - delete item.dataset.closeAfter; - const before = anchorEl.getBoundingClientRect().top; - item.open = false; - const shift = anchorEl.getBoundingClientRect().top - before; - if (shift) window.scrollBy(0, shift); -} - if (accItems.length && !ACC_REDUCED) { - // JS drives exclusivity so sibling closes can animate; the native - // exclusive-accordion attribute would snap them shut instead. - accItems.forEach((d) => d.removeAttribute('name')); - accItems.forEach((item) => { const summary = item.querySelector('summary'); const body = item.querySelector('.acc-body'); @@ -146,25 +82,51 @@ if (accItems.length && !ACC_REDUCED) { if (item.open) { accCollapse(item); } else { - accItems.forEach((other) => { - if (other === item || !other.open) return; - // "other precedes item": collapse instantly with scroll - // compensation so the clicked row stays pinned; siblings - // below can animate freely without shifting the row. - if (other.compareDocumentPosition(item) & Node.DOCUMENT_POSITION_FOLLOWING) { - accCollapseAbove(other, summary); - } else if (other.dataset.animating) { - other.dataset.closeAfter = '1'; - } else { - accCollapse(other); - } - }); accExpand(item); } }); }); } +// --------------------------------------------------------------- +// Feature carousels: on mobile the card grids scroll horizontally; +// a numbered square pager under each grid tracks and drives them. +// (The pager is display:none on desktop, where the grid is a grid.) +// --------------------------------------------------------------- +document.querySelectorAll('.feature-grid').forEach((grid) => { + const cards = grid.querySelectorAll('.feature'); + if (cards.length < 2) return; + const pager = document.createElement('div'); + pager.className = 'feature-pager'; + const tiles = []; + cards.forEach((card, i) => { + const b = document.createElement('button'); + b.type = 'button'; + b.className = 'numbox'; + b.textContent = String(i + 1); + b.setAttribute('aria-label', 'Go to card ' + (i + 1) + ' of ' + cards.length); + b.addEventListener('click', () => { + grid.scrollTo({ + left: card.offsetLeft - cards[0].offsetLeft, + behavior: ACC_REDUCED ? 'auto' : 'smooth' + }); + }); + pager.appendChild(b); + tiles.push(b); + }); + const setActive = () => { + // Stride = real card spacing (cards are narrower than the padded grid). + const stride = cards.length > 1 ? cards[1].offsetLeft - cards[0].offsetLeft : grid.clientWidth; + const ix = Math.min(Math.round(grid.scrollLeft / stride), tiles.length - 1); + tiles.forEach((t, i) => t.classList.toggle('active', i === ix)); + }; + // Direct call, not rAF-deferred: throttled webviews freeze rAF and + // the active tile would never update. The work is a few class toggles. + grid.addEventListener('scroll', setActive, { passive: true }); + setActive(); + grid.after(pager); +}); + // --------------------------------------------------------------- // Scroll-entry reveals, with a hard fallback: if IntersectionObserver // is unavailable, everything is shown immediately. diff --git a/styles.css b/styles.css index 009671e..4f5a4cf 100644 --- a/styles.css +++ b/styles.css @@ -255,6 +255,20 @@ body::after { right: calc(50% - var(--container-max) / 2); } background: var(--ink); } +.nav-toggle { + display: none; + align-items: center; + justify-content: center; + width: 2.2rem; + height: 2.2rem; + background: none; + border: none; + color: var(--ink); + cursor: pointer; +} + +.nav-toggle svg { display: block; } + .gh-btn { display: inline-flex; align-items: center; @@ -290,6 +304,17 @@ html[data-theme="dark"] .gh-btn:hover { .gh-btn svg { display: block; } +.gh-label { display: none; } + +.menu-note { display: none; } + +/* Page scroll is locked while the mobile menu is open. */ +html.menu-locked { overflow: hidden; } + +/* Scrim behind the open mobile menu: dark veil over the page with the + site's halftone texture fading out from top to bottom. */ +.menu-scrim { display: none; } + /* ---------- hero ---------- */ .hero { @@ -549,6 +574,25 @@ html[data-theme="dark"] .gh-btn:hover { color: var(--ink-soft); } +/* Mobile carousel pager: square numbered tiles under each card set. */ +.feature-pager { + display: none; + justify-content: center; + gap: 0.5rem; + margin-top: 1.1rem; +} + +.feature-pager .numbox { + cursor: pointer; + transition: background 150ms, border-color 150ms, color 150ms; +} + +.feature-pager .numbox.active { + background: var(--ink); + border-color: var(--ink); + color: var(--on-ink); +} + .ph { aspect-ratio: 16 / 10; display: flex; @@ -632,18 +676,6 @@ html[data-theme="dark"] .gh-btn:hover { border-color: var(--ink); } -textarea.input { resize: vertical; } - -.field-label { - display: block; - font-family: var(--font); - font-size: var(--label-sm); - font-weight: 500; - letter-spacing: 0.02em; - color: var(--muted); - margin-bottom: 0.5rem; -} - /* ---------- contributors + contribute ---------- */ .contributors { @@ -687,31 +719,15 @@ textarea.input { resize: vertical; } margin-bottom: 1.5rem; } -.contrib-form { - margin-top: 2.25rem; - max-width: 34rem; - display: grid; - gap: 1.25rem; -} - -/* display:grid above would otherwise defeat the hidden attribute */ -.contrib-form[hidden] { display: none; } - -.contrib-form label { display: block; } - -.form-row { - display: flex; - align-items: center; - gap: 1.25rem; - flex-wrap: wrap; +.contrib-mail { + color: var(--ink); + font-weight: 500; + text-decoration: underline; + text-underline-offset: 3px; + text-decoration-thickness: 1px; } -.form-note { - font-family: var(--font); - font-size: var(--label-sm); - letter-spacing: 0.01em; - color: var(--muted); -} +.contrib-mail:hover { text-decoration-thickness: 2px; } /* ---------- page header band (Test, Resources) ---------- */ @@ -751,6 +767,7 @@ textarea.input { resize: vertical; } flex-direction: column; align-items: flex-start; gap: 0.75rem; + min-width: 0; /* grid items must be able to shrink below content size */ border: 1px solid var(--hairline); background: var(--surface); padding: 1.5rem; @@ -792,13 +809,6 @@ html[data-theme="dark"] .res-card:not(.res-feature):focus-within { .res-feature .btn { margin-top: 0.5rem; } -.res-feature .ph { - width: 22rem; - max-width: 40%; - flex-shrink: 0; - margin-bottom: 0; -} - .res-by { font-family: var(--font); font-size: var(--label-md); @@ -997,10 +1007,87 @@ html.js .reveal.in { :root { --container-pad: 1.5rem; } body { font-size: 16px; } - .header-inner { flex-direction: column; gap: 0; } - .wordmark { padding: 0.8rem 0 0.1rem; align-self: flex-start; } - .tabs { gap: 1.4rem; } - .tab { padding: 0.6rem 0.1rem; } + /* Single-row bar; the nav collapses into a hamburger panel. */ + .wordmark { padding: 0.8rem 0; } + .nav-toggle { display: inline-flex; align-self: center; } + .tabs { + display: none; + position: absolute; + top: 100%; + left: 0; + right: 0; + flex-direction: column; + align-items: stretch; + gap: 0; + background: var(--bg); + border-bottom: 1px solid var(--hairline); + padding: 4rem 1.25rem 1.25rem; + } + .menu-note { + display: block; + font-family: var(--font); + font-size: var(--label-md); + color: var(--muted); + text-align: center; + padding: 0 0.5rem; + margin-top: 1rem; + } + .menu-note a { + color: var(--ink); + font-weight: 500; + text-decoration: underline; + text-underline-offset: 2px; + } + /* With the menu open, the bar goes solid so it reads as one + surface with the panel (the frosted 60% tint would otherwise + blend with the dark scrim and look gray). */ + .site-header.menu-open { + background: var(--bg); + backdrop-filter: none; + -webkit-backdrop-filter: none; + border-bottom-color: transparent; + } + .site-header.menu-open .tabs { display: flex; } + .tab { + align-self: stretch; + padding: 0.8rem 0.5rem; + font-size: 0.95rem; + } + .tab + .tab { border-top: 1px solid var(--hairline); } + .gh-btn { + margin: 4rem 0 0; + align-self: stretch; + justify-content: flex-start; + gap: 0.6rem; + padding: 0.7rem 0.9rem; + } + .gh-label { display: inline; } + .gh-btn .gh-stars { + margin-left: auto; + background: var(--chip-bg); + color: var(--ink-soft); + font-size: var(--label-md); + padding: 0.2rem 0.55rem; + } + /* Sibling of the header (a fixed child would be trapped by the + header's backdrop-filter containing block). Below the header's + z-index so the panel stays on top. */ + .site-header.menu-open + .menu-scrim { + display: block; + position: fixed; + inset: 0; + z-index: calc(var(--z-header) - 1); + background: rgba(17, 17, 17, 0.78); + } + .menu-scrim::after { + content: ""; + position: absolute; + inset: 0; + background-image: radial-gradient(rgba(255, 255, 255, 0.55) 1px, transparent 1.5px); + background-size: 5px 5px; + -webkit-mask-image: linear-gradient(to bottom, #000, transparent 75%); + mask-image: linear-gradient(to bottom, #000, transparent 75%); + } .hero { padding: 6.5rem 0 5.5rem; } /* The desktop line break pins "Agentic Interface" to one line, @@ -1011,14 +1098,34 @@ html.js .reveal.in { .contributors { padding: 4.5rem 0 5rem; } .page-header { padding: 4rem 0 2.75rem; } - .feature-grid { grid-template-columns: 1fr; } + /* Feature cards become a swipeable carousel with a numbered pager. */ + .feature-grid { + display: flex; + /* Full viewport width with a small gutter, escaping the accordion + body's indent; cards fill the padded width. */ + margin: 0 calc(50% - 50vw); + padding: 0 1.1rem; + scroll-padding: 0 1.1rem; + overflow-x: auto; + scroll-snap-type: x mandatory; + scrollbar-width: none; + -webkit-overflow-scrolling: touch; + } + .feature-grid::-webkit-scrollbar { display: none; } + .feature { flex: 0 0 100%; scroll-snap-align: start; } + .feature-pager { display: flex; } + + /* No hover on touch: resource CTAs always visible. */ + .res-cta { opacity: 1; transform: none; } .contrib-names { grid-template-columns: repeat(2, minmax(0, 1fr)); } .res-grid { grid-template-columns: 1fr; } - .res-feature { flex-direction: column; align-items: flex-start; } + .res-feature { flex-direction: column; align-items: stretch; gap: 1.25rem; } + .res-feature .btn { align-self: flex-start; } .acc-title { font-size: 1.15rem; } .acc-item summary { gap: 0.9rem; padding: 1.25rem 0.1rem; } - .acc-body { padding-left: 2.8rem; } + /* Body text uses the full width instead of hanging under the title. */ + .acc-body { padding-left: 0.1rem; padding-right: 0.1rem; } .site-footer { padding: 3rem 0 9rem; } }