/* Font *//** Lato (400) (Lato full) */@font-face { font-family: 'Lato'; src: url('/assets/fonts/lato/Lato-Regular.woff2') format('woff2'),  url('/assets/fonts/lato/Lato-Regular.ttf') format('truetype'); font-weight: normal; font-style: normal; font-display: swap;}/** Lato Latin Italic (400) (Latin only subset) */@font-face { font-family: 'Lato'; src: url('/assets/fonts/lato/LatoLatin-Italic.woff2') format('woff2'),  url('/assets/fonts/lato/LatoLatin-Italic.ttf') format('truetype'); font-weight: normal; font-style: italic; font-display: swap;}/** Lato Latin Semibold (600) (Latin only subset) */@font-face { font-family: 'Lato'; src: url('/assets/fonts/lato/LatoLatin-Semibold.woff2') format('woff2'),  url('/assets/fonts/lato/LatoLatin-Semibold.ttf') format('truetype'); font-weight: bold; font-style: normal; font-display: swap;}/** Lato Heavy (800) (Latin only subset) */@font-face { font-family: 'Lato'; src: url('/assets/fonts/lato/LatoLatin-Heavy.woff2') format('woff2'),  url('/assets/fonts/lato/LatoLatin-Heavy.ttf') format('truetype'); font-weight: 800; font-style: normal; font-display: swap;}/* Design tokens Plain CSS custom properties. Surface-level tokens (background, text, borders, shadows) swap on [data-theme="dark"] further down so header / footer / breadcrumb / banner / homepage / apps reference them without caring which mode is active. Any page that still hard- codes a hex value should migrate to one of these. */:root { /* Colour — light theme */ --color-bg: #ffffff; --color-surface: #fafafa; --color-surface-muted: #f8f9fa; --color-surface-soft: #f4f4f4; --color-surface-quote: #e9ecef; --color-text: #202542; --color-text-muted: #666; --color-text-subtle: #495057; --color-text-inverse: #ffffff; --color-text-footer: #d8d8d8; --color-text-quote: #2c3e50; --color-brand: #7C4DFF; --color-brand-hover: #6d3dee; --color-brand-contrast: #ffffff; --color-brand-tint: #f4f0ff; --color-accent-warm: #ff6b35; --color-accent-warm-tint: #fff9ed; --color-border: #e0e0e0; --color-border-subtle: #e1e5e9; --color-border-muted: #e9ecef; --color-border-faint: #d9d9d9; --color-border-input: #ced4da; --color-link: #1a73e8; --color-link-ical: #0066cc; --color-link-ical-hover: #0052a3; /* Logo chip surface stays light in both themes so upstream-app  logos (which were authored for a white background) remain  legible when we wrap them in our square chip on dark mode. */ --color-logo-chip: #ffffff; --color-header-bg: #202542; --color-header-text: #ffffff; --color-header-link-hover: rgba(255, 255, 255, 0.1); --color-footer-bg: #202542; --color-footer-text: #d8d8d8; --color-footer-text-hover: #ffffff; /* Spacing */ --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem; --space-5: 1.5rem; --space-6: 2rem; --space-7: 3rem; --space-8: 3.2rem; /* Radii */ --radius-sm: 4px; --radius-md: 8px; --radius-lg: 12px; --radius-pill: 999px; /* Shadows */ --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.06); --shadow-2: 0 4px 8px rgba(0, 0, 0, 0.08); --shadow-2-strong: 0 4px 8px rgba(0, 0, 0, 0.1); --shadow-3: 0 8px 24px rgba(0, 0, 0, 0.12); --shadow-quote: 0 4px 12px rgba(0, 0, 0, 0.1); --shadow-brand: 0 2px 4px rgba(124, 77, 255, 0.2); --shadow-brand-hover: 0 4px 8px rgba(124, 77, 255, 0.3); --shadow-hero: 0 10px 30px rgba(32, 37, 66, 0.08); /* Focus ring */ --focus-ring-brand: 0 0 0 3px rgba(124, 77, 255, 0.35); /* Type scale */ --font-body: 'Lato', 'Helvetica Neue', Arial, sans-serif; --fs-xs: 0.85rem; --fs-sm: 0.95rem; --fs-base: 1rem; --fs-md: 1.125rem; --fs-lg: 1.25rem; --fs-xl: 1.6rem; --fs-display: 2.4rem; --line-height-body: 1.625; /* Motion */ --ease: cubic-bezier(0.2, 0.7, 0.3, 1); --motion-fast: 120ms; --motion-med: 220ms; --motion-slow: 320ms;}/* Dark theme — manual toggle. Inverts surfaces, softens shadows, brightens brand so contrast holds against a near-black background. The tokens above are the only ones that need to change; everything else (spacing, radii, type scale, motion) is theme-agnostic. */[data-theme="dark"] { --color-bg: #0f1115; --color-surface: #1a1d25; --color-surface-muted: #161922; --color-surface-soft: #1f2330; --color-surface-quote: #1a1d25; --color-text: #e6e9f2; --color-text-muted: #a0a8bf; --color-text-subtle: #8a91a6; --color-text-inverse: #0f1115; --color-text-footer: #c8cde0; --color-text-quote: #d0d5e6; --color-brand: #a98bff; --color-brand-hover: #bfa6ff; --color-brand-contrast: #0f1115; --color-brand-tint: #1e1a33; --color-accent-warm: #ff8a5c; --color-accent-warm-tint: #2a1f16; --color-border: #2a2f3d; --color-border-subtle: #242836; --color-border-muted: #242836; --color-border-faint: #3a4050; --color-border-input: #3a4050; --color-link: #79b4ff; --color-link-ical: #79b4ff; --color-link-ical-hover: #a9cdff; /* Logo chip stays white so upstream-app artwork reads the same in  both themes; see the light-theme note for rationale. */ --color-logo-chip: #ffffff; /* Header + footer chrome is deliberately not overridden here. The  light-theme values (--color-header-bg #202542, matching footer  tokens) are already dark enough that re-darkening them in dark  mode produced no visible improvement — it only widened the  contrast gulf between the chrome and the body surface. Letting  the :root values cascade means the header / footer render the  same in both themes; only the body surfaces swap. */ --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4); --shadow-2: 0 4px 12px rgba(0, 0, 0, 0.5); --shadow-2-strong: 0 4px 12px rgba(0, 0, 0, 0.55); --shadow-3: 0 8px 32px rgba(0, 0, 0, 0.55); --shadow-quote: 0 4px 16px rgba(0, 0, 0, 0.5); --shadow-brand: 0 2px 6px rgba(169, 139, 255, 0.35); --shadow-brand-hover: 0 4px 10px rgba(169, 139, 255, 0.45); --shadow-hero: 0 10px 30px rgba(0, 0, 0, 0.6); --focus-ring-brand: 0 0 0 3px rgba(169, 139, 255, 0.5);}/* Dark mode is opt-in only: users activate it via the header theme toggle (which sets [data-theme="dark"] + persists to localStorage). We do NOT track prefers-color-scheme — a system-dark preference on its own should not flip the site to dark without a deliberate choice. Light is the default for every first-time visitor. *//* Defaults */body { background-color: var(--color-header-bg); font-family: var(--font-body); font-weight: normal; line-height: var(--line-height-body); margin: 0;}h1 { font-weight: 800;}h2, h3, h4, h5, h6 { font-weight: bold;}/* Baseline anchor colour. Without this, unstyled inline <a> tags (e.g. links inside paragraphs on /about.html) inherit the browser default `#0000ee`, which fails AA contrast on the dark theme's `--color-bg: #0f1115` (ratio ~2.0:1). Routing through the `--color-link` token gives dark mode the lighter `#79b4ff` it already defines for theme-aware link colours. */a { color: var(--color-link);}/* Layout */main { background-color: var(--color-bg); color: var(--color-text); padding: 3.1rem 3.2rem 3.2rem 3.1rem;}main > article::after { clear: both; content: " "; display: block;}main > article h1 { margin-top: 0;}/** Max content width */@media (min-width: 1280px) { .header, body .breadcrumb > ol, main > *, .footer {  margin: 0 auto;  max-width: calc(1280px - 6.2rem); }}/* Generic CSS Classes *//** Articles */.article-section { clear: both; margin-top: 3.2rem;}/** Details */.details-box { border: 1px solid var(--color-border-subtle); border-radius: var(--radius-sm); padding: 0.5em 0.5em 0;}.details-box summary { margin: -0.5em -0.5em 0; padding: 0.5em;}.details-box[open] { padding: 0.5em;}.details-box[open] summary { border-bottom: 1px solid var(--color-border-subtle); margin-bottom: 0.5em;}/** Hidden */.hidden { display: none !important;}/** Images */.article-image-left { float: left; padding: 0 1.6rem 3.2rem 0; width: 32rem;}.article-image-right { float: right; padding: 0 0 3.2rem 1.6rem; width: 32rem;}.article-image-small { width: 22.4rem;}.article-image-top { margin-top: -3.8rem;}/* Tour / hand-illustrated marketing SVGs (about.html story graphics and similar). The source art is densely coloured with hard fills chosen against a white page — on dark mode the combined effect can clash with a #0f1115 body. We leave the artwork untouched and wrap it in a neutral light chip with a soft shadow so both themes render the illustration against the surface it was designed for. */.tour-illustration { background: var(--color-logo-chip); border-radius: var(--radius-md); padding: 0.75rem; box-shadow: var(--shadow-1); box-sizing: border-box;}@media (max-width: 820px) { .article-image-left, .article-image-right {  float: none;  padding: 0 0 1.6rem; } .article-image-top { margin-top: 0; }}@media (max-width: 580px) { .article-image-left, .article-image-right {  width: 100%; }}/** Lists */.unstyled-list { list-style-type: none; margin: 0; padding: 0;}/** Quotes */.quote { margin: 0; padding: 0;}.quote .quote-author::before { content: "— ";}/** Tables */.table-caption { caption-side: bottom; text-align: left; padding: 1rem 0;}/** iCalendar Download */.ical-download-button { background-color: var(--color-link-ical); color: var(--color-text-inverse); display: inline-flex; align-items: center; gap: .2rem; padding: .2rem .4rem; text-decoration: none; border-radius: var(--radius-sm); font-weight: bold; transition: background-color 0.3s ease;}.ical-download-button::before { content: url("/assets/img/icon/calendar-days.svg"); display: inline-block; width: 1.2rem; height: 1.2rem; flex-shrink: 0; filter: brightness(0) invert(1);}.ical-download-button:hover,.ical-download-button:focus { background-color: var(--color-link-ical-hover); color: var(--color-text-inverse);}/** Tiles */.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--space-5); list-style: none; padding: 0; margin: var(--space-5) 0;}.tiles li { border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-5); background-color: var(--color-surface); transition: box-shadow 0.3s ease;}.tiles li:hover { box-shadow: var(--shadow-2-strong);}.tiles .tile-header { display: flex; align-items: center; gap: var(--space-4);}.tiles .tile-header img { width: 60px; height: 60px; object-fit: contain; flex-shrink: 0;}/* Tile icons shipped from /assets/img/icon/ are plain FontAwesome paths that render black by default. On dark mode (either the explicit [data-theme="dark"] override or the prefers-color-scheme fallback) invert the raster so the glyph reads as white/brand on the dark surface. Scoped to the small icon slot (.tiles img[src*="/icon/"]) so upstream app-logo PNGs under /assets/img/logo are untouched. */[data-theme="dark"] .tiles .tile-header img[src*="/icon/"] { filter: invert(0.92);}.tiles h2, .tiles h3 { margin: 0; color: var(--color-text); font-size: var(--fs-lg); flex: 1;}.tiles dl { margin: 0.5rem 0 0 0;}.tiles dt { font-weight: bold; margin-top: 0.75rem; margin-bottom: 0.25rem; color: var(--color-text);}.tiles dt:first-child { margin-top: 0;}.tiles dd { margin: 0 0 0.25rem 0; color: var(--color-text-muted);}.tiles dd:last-child { margin-bottom: 0;}.tiles.tile-links li { padding: 0; display: flex; flex-direction: column;}.tiles.tile-links a { color: inherit; display: flex; flex-direction: column; padding: 1.5rem; text-decoration: none; height: calc(100% - 3rem); width: calc(100% - 3rem); flex-grow: 1;}.tiles.tile-links .tile-links-nav-hint { font-weight: bold; margin-top: auto; padding-top: 1rem;}.tile-links-nav-hint::after { content: url("/assets/img/icon/chevron-right.svg"); display: inline-block; width: 1rem; height: 1.5rem; margin-left: .2rem; transition: margin-left var(--motion-slow) ease; vertical-align: middle;}.tiles.tile-links a:hover .tile-links-nav-hint::after { margin-left: 0.5rem;}/* Header Styles *//* Basic header layout */.header { align-items: center; background-color: var(--color-header-bg); color: var(--color-header-text); display: flex; flex-direction: row; gap: var(--space-6); justify-content: space-between; padding: 1.5rem 3.2rem 1.6rem 3rem; position: relative; /* A modest stacking index so page-level sticky bars (e.g. /apps  category TOC at z-index 10) never paint over the site chrome  while the header is still in-flow at the top of the document.  Kept deliberately small so hamburger / nav overlays (z-index  999-1000) still land above when they open. */ z-index: 20;}.header a { color: var(--color-header-text); text-decoration: none; text-wrap: nowrap;}.header a:hover,.header a:focus { color: var(--color-header-text);}/* Homepage link */.header .homepage-link { align-items: center; display: flex;}.header .homepage-link a { align-items: center; display: flex; flex-direction: row; gap: 1.6rem; font-size: var(--fs-xl); font-style: normal;}.header .homepage-link img { width: 3.2rem;}/* Navigation styles */.header .main-nav { align-items: center; display: flex; transition: all var(--motion-slow) ease; position: fixed; top: 0; left: -100%; width: 100%; height: 100vh; background-color: var(--color-header-bg); z-index: 999; justify-content: center;}.header .main-nav.nav-open { left: 0; transition: left var(--motion-slow) ease;}/* Disable transitions during viewport changes to prevent unwanted animations */.header .main-nav.resizing { transition: none !important;}.header .main-nav ul { align-self: center; display: flex; flex-direction: column; gap: var(--space-6); list-style: none; margin: 0; padding: 0;}.header .main-nav li { margin: 0;}.header .main-nav a { display: block; font-size: var(--fs-xl); padding: .8rem 1.6rem; border-radius: var(--radius-sm); text-align: center; transition: background-color var(--motion-slow) ease;}.header .main-nav a:hover { background-color: var(--color-header-link-hover);}/* Hamburger menu */.header .hamburger-menu { display: flex; flex-direction: column; justify-content: space-around; width: 30px; height: 30px; background: transparent; border: none; cursor: pointer; padding: 0; z-index: 1000;}.header .hamburger-menu.active { position: fixed; top: 2.55rem; right: 3.2rem;}.header .hamburger-menu span { width: 100%; height: 3px; background-color: var(--color-header-text); border-radius: 3px; transition: all var(--motion-slow) ease;}.header .hamburger-menu.active span:nth-child(1) { transform: rotate(45deg) translate(7px, 7px);}.header .hamburger-menu.active span:nth-child(2) { opacity: 0;}.header .hamburger-menu.active span:nth-child(3) { transform: rotate(-45deg) translate(7px, -7px);}/* Theme toggle Icon button that flips data-theme on <html> between "light" and "dark" and persists the choice to localStorage. Sits between the nav links and the hamburger on desktop; hidden on mobile where the hamburger menu exposes the same control inside the slide-out sheet. */.header .theme-toggle { align-items: center; background: transparent; border: 1px solid transparent; border-radius: var(--radius-pill); color: var(--color-header-text); cursor: pointer; display: inline-flex; height: 2.25rem; width: 2.25rem; justify-content: center; padding: 0; transition: background-color var(--motion-med) ease, border-color var(--motion-med) ease; /* Sit between the nav links and the hamburger; keep above the  slide-out nav overlay on mobile. */ position: relative; z-index: 1000;}.header .theme-toggle:hover,.header .theme-toggle:focus-visible { background: var(--color-header-link-hover); outline: none;}.header .theme-toggle:focus-visible { border-color: var(--color-header-text);}.header .theme-toggle__icon { width: 1.25rem; height: 1.25rem; fill: none; /* Intentionally no `display` here — the per-icon rules below own  visibility. A `display: block` at this specificity (0,2,0) would  outrank the `.theme-toggle__icon--sun { display: none }` (0,1,0)  rule and leave both icons visible. */}/* Show the moon in light mode (click → switch to dark); show the sun in dark mode (click → switch to light). Drives state purely from the data-theme attribute so the server-rendered HTML is accurate before any JS runs. Dark mode is opt-in via the toggle, so there is no prefers-color-scheme fallback — see _layouts/default.html bootstrap script. */.header .theme-toggle__icon--sun { display: none; }.header .theme-toggle__icon--moon { display: block; }[data-theme="dark"] .header .theme-toggle__icon--sun { display: block; }[data-theme="dark"] .header .theme-toggle__icon--moon { display: none; }/* Keep the toggle visible on mobile next to the hamburger so the theme switch is one tap away without opening the slide-out nav. z-index already pulls it above the nav overlay. */@media (max-width: 1024px) { .header .theme-toggle { margin-right: 0.5rem; } /* When the hamburger becomes `.active` it jumps to `position: fixed`  at `right: 3.2rem`, taking it out of flow. The flex row then  collapses around the remaining in-flow items (logo + toggle) and  `justify-content: space-between` slides the toggle to the right  edge — directly under the fixed hamburger. Shift the toggle left  by a hamburger-width-plus-gap while the menu is open so the two  controls sit side-by-side instead of stacking. */ .header:has(.hamburger-menu.active) .theme-toggle { margin-right: var(--space-7); }}/* "Get Started" primary CTA — a filled, pill-shaped button at the front of the nav. Brand-coloured to pull attention on first visit. At mobile / overlay width it inherits the block layout of the other nav items; the desktop breakpoint below restores the pill. */.header .main-nav__cta { background: var(--color-brand); color: var(--color-brand-contrast); font-weight: 700; border-radius: var(--radius-pill, 999px); box-shadow: var(--shadow-brand); transition: background-color var(--motion-med) ease, transform var(--motion-med) ease, box-shadow var(--motion-med) ease;}.header .main-nav__cta:hover,.header .main-nav__cta:focus-visible { background: var(--color-brand-hover); color: var(--color-brand-contrast); transform: translateY(-1px); box-shadow: var(--shadow-brand-hover);}.header .main-nav__cta:focus-visible { outline: 2px solid var(--color-brand-contrast); outline-offset: 2px;}/* Dropdown trigger — looks like a regular nav link plus a caret. */.header .main-nav__trigger { align-items: center; background: transparent; border: 0; color: var(--color-header-text); cursor: pointer; display: inline-flex; font: inherit; gap: 0.4rem; padding: 0.8rem 1.6rem; border-radius: var(--radius-sm); font-size: var(--fs-xl); transition: background-color var(--motion-med) ease;}.header .main-nav__trigger:hover,.header .main-nav__trigger:focus-visible { background: var(--color-header-link-hover); outline: none;}.header .main-nav__trigger:focus-visible { box-shadow: 0 0 0 2px var(--color-header-text);}.header .main-nav__caret { transition: transform var(--motion-med) ease;}.header .main-nav__trigger[aria-expanded="true"] .main-nav__caret { transform: rotate(180deg);}/* Submenu — a plain list that sits inside the mobile overlay as a nested, slightly-smaller list under its trigger, and floats as a dropdown on desktop (see the ≥1025px block). The JS toggle sets aria-expanded on the trigger and flips [hidden] on the matched submenu. */.header .main-nav__submenu { list-style: none; margin: 0.35rem 0 0.5rem 0; padding: 0;}.header .main-nav__submenu[hidden] { display: none;}.header .main-nav__submenu a { font-size: 1rem; font-weight: 400; opacity: 0.85;}.header .main-nav__submenu a:hover,.header .main-nav__submenu a:focus-visible { opacity: 1;}/* Desktop navigation styles */@media (min-width: 1025px) { .header .main-nav {  position: static !important;  width: auto !important;  height: auto !important;  background-color: transparent !important;  left: 0 !important;  display: block !important; } .header .main-nav ul {  flex-direction: row;  gap: var(--space-3); } /* Extra breathing room after the primary CTA so the filled pill  reads as a distinct action and doesn't crowd the secondary  dropdown triggers that follow it. */ .header .main-nav__cta-item {  margin-right: var(--space-4); } .header .main-nav a {  font-size: 1.2rem;  padding: .4rem .8rem; } .header .main-nav__trigger {  font-size: 1.2rem;  padding: .4rem .8rem; } /* Get Started pill tightens on desktop. */ .header .main-nav__cta {  padding: .45rem 1.1rem;  font-size: 1.1rem; } /* Floating dropdown panel. Position relative to the <li> so the  menu anchors below the trigger rather than the nav row. */ .header .main-nav__group {  position: relative; } .header .main-nav__submenu {  position: absolute;  top: calc(100% + 0.25rem);  left: 0;  min-width: 14rem;  padding: 0.5rem;  margin: 0;  background: var(--color-header-bg);  border: 1px solid var(--color-border-muted);  border-radius: var(--radius-md);  box-shadow: var(--shadow-3);  z-index: 1000;  /* Re-assert column stacking — the ancestor `.main-nav ul` rule   switches to flex-direction:row at desktop for the top-level   nav, which would otherwise cascade into the dropdown and   lay the items out in a single row. */  display: flex;  flex-direction: column;  gap: 0; } .header .main-nav__submenu li + li {  margin-top: 0.1rem; } .header .main-nav__submenu a {  display: block;  padding: 0.5rem 0.75rem;  font-size: 1rem;  border-radius: var(--radius-sm);  text-align: left; } .header .main-nav__submenu a:hover, .header .main-nav__submenu a:focus-visible {  background: var(--color-header-link-hover);  outline: none; } .header .hamburger-menu {  display: none !important; }}/* Event Banner */.event-banner { background-color: var(--color-brand); color: var(--color-brand-contrast); text-align: center; padding: 0.6rem var(--space-4); font-size: var(--fs-base); line-height: 1.4;}.event-banner a { color: var(--color-brand-contrast); text-decoration: underline; font-weight: bold;}.event-banner a:hover,.event-banner a:focus { color: var(--color-brand-contrast);}/* Footer Styles */.footer { background-color: var(--color-footer-bg); color: var(--color-footer-text); font-size: var(--fs-base); padding: 3.2rem 3.2rem 3.1rem 3.1rem;}.footer a { color: var(--color-footer-text); text-decoration: none; transition: color var(--motion-slow) ease;}.footer a:hover,.footer a:focus { color: var(--color-footer-text-hover); text-decoration: underline;}.footer .title { font-size: 1.5rem; font-weight: bold;}/* Footer nav styles */.footer .footer-nav { display: grid; grid-template-columns: repeat(4, 1fr); grid-template-rows: auto auto; gap: .4rem var(--space-6); margin: 0; padding: 0;}.footer .footer-nav dt { font-size: 1.2rem;}.footer .footer-nav dd { margin: 0; padding: 0;}.footer .footer-nav dd ul { list-style: none; margin: 0; padding: 0;}/* Position each dt and dd pair in the grid */.footer .footer-nav dt:nth-child(1) { grid-column: 1; grid-row: 1;}.footer .footer-nav dd:nth-child(2) { grid-column: 1; grid-row: 2;}.footer .footer-nav dt:nth-child(3) { grid-column: 2; grid-row: 1;}.footer .footer-nav dd:nth-child(4) { grid-column: 2; grid-row: 2;}.footer .footer-nav dt:nth-child(5) { grid-column: 3; grid-row: 1;}.footer .footer-nav dd:nth-child(6) { grid-column: 3; grid-row: 2;}.footer .footer-nav dt:nth-child(7) { grid-column: 4; grid-row: 1;}.footer .footer-nav dd:nth-child(8) { grid-column: 4; grid-row: 2;}/* Footer social nav styles */.footer .social-nav { margin-top: var(--space-8);}.footer .social-nav ul { align-items: center; display: flex; flex-direction: row; gap: var(--space-4); list-style: none; margin: 0; padding: 0;}.footer .social-nav li { margin: 0;}.footer .social-nav a { align-items: center; display: flex; padding: 0.5rem 0.75rem;}.footer .social-nav .homepage-link a { padding: 0;}.footer .social-nav img { width: 1.6rem;}.footer .social-nav .homepage-link img { width: 3.2rem;}.footer .social-nav .icon { filter: invert(.9);}.footer .social-nav a:hover .icon { filter: invert(1);}@media (max-width: 780px) { .footer .footer-nav {  grid-template-columns: 1fr;  grid-template-rows: repeat(8, auto); } .footer .footer-nav dt:not(:first-child) {  margin-top: var(--space-6); }  /* Reset positioning for mobile layout - stack vertically */ .footer .footer-nav dt:nth-child(1), .footer .footer-nav dt:nth-child(3), .footer .footer-nav dt:nth-child(5), .footer .footer-nav dt:nth-child(7) {  grid-column: 1; }  .footer .footer-nav dd:nth-child(2), .footer .footer-nav dd:nth-child(4), .footer .footer-nav dd:nth-child(6), .footer .footer-nav dd:nth-child(8) {  grid-column: 1; }  /* Position each element in its own row */ .footer .footer-nav dt:nth-child(1) { grid-row: 1; } .footer .footer-nav dd:nth-child(2) { grid-row: 2; } .footer .footer-nav dt:nth-child(3) { grid-row: 3; } .footer .footer-nav dd:nth-child(4) { grid-row: 4; } .footer .footer-nav dt:nth-child(5) { grid-row: 5; } .footer .footer-nav dd:nth-child(6) { grid-row: 6; } .footer .footer-nav dt:nth-child(7) { grid-row: 7; } .footer .footer-nav dd:nth-child(8) { grid-row: 8; } .footer .social-nav ul {  flex-direction: column; }}/* Breadcrumb Styles */.breadcrumb { background-color: var(--color-bg); color: var(--color-text); border-bottom: 1px solid var(--color-surface-soft); font-size: var(--fs-base); padding: .5rem 3.2rem .6rem 3rem;}.breadcrumb ol { align-items: flex-start; display: flex; flex-wrap: wrap; justify-content: flex-start; margin: 0; padding: 0;}.breadcrumb a { align-items: center; color: var(--color-brand); display: flex; justify-content: center; margin: 0 var(--space-4) 0 0; padding: 0.3rem 0; text-decoration: none;}.breadcrumb a:hover,.breadcrumb a:focus { text-decoration: underline;}.breadcrumb li { align-items: center; display: flex;}.breadcrumb li a[href="#"] { color: var(--color-text); cursor: default; pointer-events: none;}.breadcrumb li + li::before { color: var(--color-border-faint); content: "/"; margin: 0 var(--space-4) 0 0;}/** Homepage hero — uses the shared `.page-hero` pattern with a * `.page-hero--split` modifier that adds a two-column grid on desktop * (text on the left, illustration on the right). The illustration is * decorative (aria-hidden) so the hero reads exactly like the hero on * /about, /for_users etc. for assistive tech; the figure just adds * visual weight on wide viewports. Below 781px the figure stacks above * the text so the CTA row stays near the top of the viewport on * mobile. *//* Figure inside a hero. Generic so every hero page that adds a `.page-hero__figure` child picks up the sizing rules automatically, without a per-page selector. Mobile default centres the illustration; the ≥781px `.page-hero--split` rule further down pulls it to flex-end so it hugs the hero card's right padding. */.page-hero__figure { display: flex; align-items: center; justify-content: center;}.page-hero__figure img { width: 100%; height: auto; max-width: 32rem;}@media (min-width: 781px) { /* Use flexbox (not grid) so the text and figure naturally hug the  opposite edges of the hero card no matter how wide the card  gets. With a grid 1fr/1fr the text column cell and the figure  column cell grow past their natural content widths on wide  viewports, leaving a visible asymmetric empty band between the  capped content and the gap. `justify-content: space-between`  pushes text flush to the hero's left padding and figure flush  to the hero's right padding, so the gap in the middle absorbs  the extra width instead. */ .page-hero--split { display: flex; flex-direction: row; gap: 2.5rem; align-items: center; justify-content: space-between; } .page-hero--split .page-hero__content { /* Cap the text at a readable measure; flex keeps the remainder  of the row available for the figure. */ max-width: 36rem; flex: 0 1 36rem; min-width: 0; } .page-hero--split .page-hero__figure { flex: 0 1 auto; /* Each figure on each hero page brings its own max-width below.  Pull to the right end so the image's right edge sits at the  hero card's right padding, mirroring the text's left edge at  the left padding. */ justify-content: flex-end; }}@media (max-width: 780px) { .page-hero--split { display: flex; flex-direction: column-reverse; gap: 1.5rem; } .page-hero--split .page-hero__figure img { max-width: 20rem; margin: 0 auto; }}/** Tim Berners-Lee Quote Styling */.tim-berners-lee-quote { /* See `.page-hero` note: explicit background-color so axe-core can  compute text contrast against a defined colour rather than a  gradient. */ background-color: var(--color-surface-quote); background-image: linear-gradient(135deg, var(--color-surface-muted) 0%, var(--color-surface-quote) 100%); border-radius: var(--radius-md); box-shadow: var(--shadow-quote); font-size: 1.2rem;}.tim-berners-lee-quote blockquote { padding: 2.5rem 2rem;}.tim-berners-lee-quote p { color: var(--color-text-quote); font-style: italic; margin: 0 auto; max-width: 60rem;}.tim-berners-lee-quote footer { margin: 1.5rem auto 0 auto; max-width: 60rem; text-align: right; color: var(--color-text);}/** Shared page hero pattern — used on the homepage, /about, * /for_users, /for_developers, /for_organisations, /community, * /get_a_pod. Mirrors .apps-hero on /apps so the whole site shares * one hero voice. Eyebrow + headline + lede + optional CTA row. */.page-hero { /* `margin: 0 auto 2rem auto` instead of `0 0 2rem 0` so the hero  honours the `main > * { margin: 0 auto }` centering at ≥1280px  (where `main > *` caps children at `calc(1280px - 6.2rem)`).  The previous explicit zero left/right margins at higher class  specificity kept the hero left-aligned while the sibling  quote / tile sections centred, producing the "hero is wider  on the left but not the right" asymmetry @jeswr flagged. */ margin: 0 auto 2rem auto; padding: 2.5rem 2rem; /* Set background-color explicitly so axe-core's color-contrast rule  can compute text contrast. The `background` shorthand resets  background-color to transparent, which makes axe return  "incomplete" for every text node sitting on the gradient. */ background-color: var(--color-bg); background-image: linear-gradient(135deg, var(--color-brand-tint) 0%, var(--color-bg) 70%); border: 1px solid var(--color-border); border-radius: calc(var(--radius-md) * 1.5); box-shadow: var(--shadow-hero);}.page-hero__content { max-width: 60rem;}.page-hero__eyebrow { margin: 0 0 0.5rem 0; font-size: 0.85rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-brand);}.page-hero__headline { margin: 0 0 0.75rem 0; font-size: clamp(1.8rem, 2.4vw + 1rem, 2.6rem); line-height: 1.15; color: var(--color-text);}.page-hero__lede { margin: 0 0 1.25rem 0; font-size: 1.1rem; line-height: 1.55; color: var(--color-text-subtle);}.page-hero__actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 0;}.page-hero__cta { display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; max-width: 100%; padding: 0.75rem 1.4rem; background: var(--color-brand); color: var(--color-brand-contrast); font-weight: 700; text-decoration: none; overflow-wrap: anywhere; border: 1px solid var(--color-brand); border-radius: var(--radius-sm); transition: background-color var(--motion-med) ease, transform var(--motion-med) ease, box-shadow var(--motion-med) ease;}.page-hero__cta:hover,.page-hero__cta:focus-visible { background: var(--color-brand-hover); border-color: var(--color-brand-hover); color: var(--color-brand-contrast); transform: translateY(-1px); box-shadow: var(--shadow-2-strong);}.page-hero__cta:focus-visible { outline: none; box-shadow: var(--focus-ring-brand), var(--shadow-2-strong);}.page-hero__cta--ghost { background: transparent; color: var(--color-brand);}.page-hero__cta--ghost:hover,.page-hero__cta--ghost:focus-visible { background: var(--color-brand); color: var(--color-brand-contrast);}@media (max-width: 781px) { .page-hero { padding: 1.75rem 1.25rem; } .page-hero__headline { font-size: 1.6rem; } .page-hero__lede { font-size: 1rem; } .page-hero__actions { flex-direction: column; align-items: stretch; } .page-hero__cta { width: 100%; }}@media (prefers-reduced-motion: reduce) { .page-hero__cta { transition: none !important; } .page-hero__cta:hover { transform: none; }}/** Pod-service tiles (/get_a_pod). * * Uses the site-wide `.tiles.tile-links` pattern so each tile is a * whole-card clickable link to the provider's sign-up page. The inner * `.pod-tile__meta` is a one-line plain-text summary (e.g. "Hosted in * the EU"); off-site reference links to the organisation and hosting * jurisdiction live in a sibling `<p class="app-source">` paragraph * outside the outer anchor, matching the pattern already used on the * /apps page so nothing is nested inside an `<a>`. */.pod-tile__meta { margin: 0; color: var(--color-text-muted); font-size: 0.95rem;}.pod-tile__tag { display: inline-block; margin-left: 0.5rem; padding: 0.1rem 0.5rem; vertical-align: middle; font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-accent-warm); background: var(--color-accent-warm-tint); border-radius: var(--radius-pill, 999px);}/* `.app-source` is defined in apps.css for the /apps tiles. Re-declare a compact version here so pod tiles get the same styling without pulling apps.css onto every page. The selector is scoped to .pod-tiles so it cannot drift with the apps page if that page later specialises its own rules. */.pod-tiles .app-source { margin: 0.4rem 1.25rem 1.25rem 1.25rem; font-size: 0.9em; color: var(--color-text-muted);}.pod-tiles .app-source a { color: var(--color-brand); text-decoration: underline; text-underline-offset: 2px;}.pod-tiles .app-source a:hover,.pod-tiles .app-source a:focus-visible { color: var(--color-brand-hover);}.pod-tiles .app-source a:focus-visible { outline: 2px solid var(--color-brand); outline-offset: 2px;}/** Apps page * * Apps-specific visual layer. All colour / radius / shadow / focus * values come from the site-wide :root tokens in base.css; only * apps-page chrome (hero gradient, featured tint, sort controls) lives * here. The previous `--apps-*` namespace was page-scoped to avoid * leaking into the rest of the site; now the tokens live on :root and * apps.css just consumes them like everyone else. *//** Hero */.apps-hero { /* Mirror .page-hero in homepage.css: auto horizontal margins so the  hero centres within `main > *` at the ≥1280px breakpoint, matching  sibling sections. Previous `0 0 2rem 0` left the hero left-aligned  while the rest of the article centred. */ margin: 0 auto 2rem auto; padding: 2.5rem 2rem; /* See note in homepage.css `.page-hero`: split background-color from  background-image so axe-core's color-contrast rule can compute  contrast against a defined colour rather than a gradient. */ background-color: var(--color-bg); background-image: linear-gradient(135deg, var(--color-brand-tint) 0%, var(--color-bg) 70%); border: 1px solid var(--color-border); border-radius: calc(var(--radius-md) * 1.5); box-shadow: var(--shadow-hero);}.apps-hero__content { max-width: 60rem;}/* When the apps hero opts into the shared `.page-hero--split` flex layout (see homepage.css), cap the text column so it balances against the figure instead of taking the full 60rem. */@media (min-width: 781px) { .apps-hero.page-hero--split .apps-hero__content { max-width: 36rem; flex: 0 1 36rem; min-width: 0; }}.apps-hero__eyebrow { margin: 0 0 0.5rem 0; font-size: 0.85rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-brand);}.apps-hero__headline { margin: 0 0 0.75rem 0; font-size: clamp(1.8rem, 2.4vw + 1rem, 2.6rem); line-height: 1.15; color: var(--color-text);}.apps-hero__lede { margin: 0 0 1.25rem 0; font-size: 1.1rem; line-height: 1.55; color: var(--color-text-subtle);}.apps-hero__actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 0 0 1rem 0;}.apps-hero__cta { display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; max-width: 100%; padding: 0.75rem 1.4rem; background: var(--color-brand); color: var(--color-brand-contrast); font-weight: 700; text-decoration: none; overflow-wrap: anywhere; border: 1px solid var(--color-brand); border-radius: var(--radius-sm); transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;}.apps-hero__cta:hover,.apps-hero__cta:focus-visible { background: var(--color-brand-hover); border-color: var(--color-brand-hover); color: var(--color-brand-contrast); transform: translateY(-1px); box-shadow: var(--shadow-2-strong);}.apps-hero__cta:focus-visible { outline: none; box-shadow: var(--focus-ring-brand), var(--shadow-2-strong);}/** Apps layout — side-rail TOC + grid */.apps-layout { /* Mobile default: single column. The TOC sits above the grid,  scrolling horizontally as a pill bar (see .apps-categories-nav  rules further down). */ display: block;}@media (min-width: 1025px) { /* Desktop: pin the sidebar (search + TOC) to the left of the apps  grid so both stay one glance away from the apps. Track width  ~220px matches the longest category label ("Content, Notes,  Blogging and Publishing") at the base font. */ .apps-layout { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 2rem; align-items: start; } /* Pin the whole sidebar (search + TOC) to the viewport top as the  reader scrolls the apps grid. `top: 1rem` leaves a touch of room  below the site header; z-index stays well below the header's  1001 so it never eclipses the main-nav. `max-height` fits inside  the viewport so a very long category list scrolls inside the  rail rather than stretching the page. */ .apps-layout__sidebar { position: sticky; top: 1rem; align-self: start; z-index: 10; max-height: calc(100vh - 2rem); overflow-y: auto; } .apps-layout__main { min-width: 0; /* allow the inner grid tracks to shrink */ }}/** Category TOC */.apps-categories-nav { margin: 0 0 1.5rem 0; padding: 1rem 1.25rem; background: var(--color-bg); border: 1px solid var(--color-border); border-radius: var(--radius-md);}/* Desktop: the nav sits inside the sticky sidebar (.apps-layout__sidebar owns the sticky + max-height + overflow-y). No per-nav sticky is needed because the whole sidebar pins as one unit, which is what keeps search + TOC aligned while the reader scrolls. */@media (min-width: 1025px) { .apps-categories-nav { margin: 0; }}.apps-categories-nav__label { margin: 0 0 0.5rem 0; font-weight: 700; color: var(--color-text);}/* Category list: a plain grid of text links — no pill chrome. The earlier design wrapped each link in a grey rounded-pill card; in practice that added visual weight without improving scanning, so the presentation is now a simple two-column grid inside the sidebar (desktop) or a single horizontally-scrolling row (tablet / mobile). Hover / focus is indicated with colour + a subtle brand underline, not a filled shape. */.apps-categories-nav__list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.35rem 0.75rem; margin: 0; padding: 0; list-style: none;}/* Desktop rail: single-column grid so each link has a full, easy hit target while the TOC stays narrow. */@media (min-width: 1025px) { .apps-categories-nav__list { grid-template-columns: minmax(0, 1fr); gap: 0.25rem; }}/* Everything below the side-rail breakpoint (mobile AND tablet). The TOC stays above the grid here, and the category list collapses into a single horizontally-scrolling row rather than wrapping into multiple rows that push the apps grid far down the viewport. ≤781px refines these with narrower padding; this block is the shared base. */@media (max-width: 1024px) { .apps-categories-nav__list { display: flex; flex-wrap: nowrap; overflow-x: auto; padding-bottom: 0.25rem; -webkit-overflow-scrolling: touch; } .apps-categories-nav__link { flex-shrink: 0; }}.apps-categories-nav__link { display: block; padding: 0.35rem 0.5rem; font-size: 0.95rem; color: var(--color-text); text-decoration: none; transition: color 0.15s ease;}.apps-categories-nav__link:hover { color: var(--color-brand); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 2px;}/* Keep a real focus outline for keyboard users — the browser default outline gets nuked by some UAs, so draw our own brand-coloured one. Outlines (rather than box-shadow) work correctly on display:block inline-text links and don't get clipped when the rail has overflow-y: auto. */.apps-categories-nav__link:focus-visible { color: var(--color-brand); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 2px; outline: 2px solid var(--color-brand); outline-offset: 2px;}/** Category sections */.apps-category-sections { display: flex; flex-direction: column; gap: 2.25rem; margin: 1.5rem 0;}.apps-category { /* Small clearance covers the sticky site header and leaves a touch  of breathing room. On desktop the TOC now lives in the left  sidebar (see .apps-layout above), so we no longer need to budget  for a pinned horizontal pill bar above the heading. */ scroll-margin-top: 1rem;}@media (min-width: 1025px) { .apps-category { /* Keep 1rem as the base; the sidebar doesn't occlude the  heading, so no extra offset is needed. Scoped to the  breakpoint anyway so a future in-column pinned bar can bump  this without touching the mobile value. */ scroll-margin-top: 1rem; }}.apps-category__heading { margin: 0 0 1rem 0; padding: 0 0 0.4rem 0; font-size: 1.35rem; color: var(--color-text); border-bottom: 2px solid var(--color-brand); display: inline-block;}/* Optional one-line blurb injected under a category heading from the first tile in the group carrying data-category-intro. Kept narrow so it reads like a caption next to the rail of tiles rather than a competing paragraph. */.apps-category__intro { margin: 0.25rem 0 1rem; max-width: 60ch; color: var(--color-text-subtle); font-size: 0.95rem;}.apps-category__grid { margin: 0;}/* Flat grid used when the user picks a name-based sort. Inherits the .tiles grid rules from base.css so spacing is consistent. */.apps-sorted-grid { margin: 1.5rem 0;}.apps-no-results { margin: 2rem 0; padding: 1.25rem; text-align: center; color: var(--color-text-subtle); background: var(--color-surface-muted); border: 1px dashed var(--color-border); border-radius: var(--radius-md);}/* Quiet meta sentence below the grid: endorsement disclaimer + a pointer to the For Developers page for the full listing criteria and PR instructions. Kept small and subtle so the grid itself stays the primary content. */.apps-disclaimer { margin: 2.5rem auto 0 auto; max-width: 60rem; font-size: 0.9rem; color: var(--color-text-muted); text-align: center;}/* Tiles with the .hidden flag are removed from the visual grid. */.tiles li.hidden { display: none !important;}/** Featured / Editor's picks row */.apps-featured { margin: 0 0 2rem 0; padding: 1.5rem; /* See `.apps-hero` note: split background-color from background-image  so axe-core can compute text contrast against a defined colour. */ background-color: var(--color-bg); background-image: linear-gradient(135deg, var(--color-accent-warm-tint) 0%, var(--color-bg) 75%); border: 1px solid var(--color-border); border-radius: var(--radius-md);}.apps-featured__header { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem 1rem; margin: 0 0 1rem 0;}.apps-featured__heading { margin: 0; font-size: 1.25rem; color: var(--color-text); display: inline-flex; align-items: center; gap: 0.4rem;}.apps-featured__star { color: var(--color-accent-warm); font-size: 1.35rem; line-height: 1;}.apps-featured__blurb { margin: 0; color: var(--color-text-muted); font-size: 0.95rem;}.apps-featured__grid { margin: 0;}/** Tile polish (apps page only) *//* Consistent logo slot: centred icon, soft radius, object-fit contain for non-square upstream assets. Scoped to tiles inside the apps page so other .tiles instances (e.g. on for_users / for_developers) are untouched. */.apps-category__grid .tile-header img,.apps-sorted-grid .tile-header img,.apps-featured__grid .tile-header img { width: 60px; height: 60px; padding: 0.3rem; object-fit: contain; background: var(--color-logo-chip); border: 1px solid var(--color-border); border-radius: 10px; flex-shrink: 0;}/* Wide / non-square logo fallback used by tiles that opt into a wider slot (e.g. the ODI file manager wordmark-style logo). */.tiles .tile-logo { width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; padding: 0.3rem; background: var(--color-logo-chip); border: 1px solid var(--color-border); border-radius: 10px; flex-shrink: 0;}.tiles .tile-logo--wide { width: 100px;}.tiles .tile-logo img { width: 100%; height: 100%; object-fit: contain; /* Cancel the generic .tile-header img rule above so the wide wrapper  is actually wider than 60px. */ border: 0; padding: 0; background: transparent; border-radius: 0;}/* Smooth hover lift for apps-page tiles. Base.css already applies a box-shadow on hover; we add a subtle translate for movement feel. */.apps-category__grid li,.apps-sorted-grid li,.apps-featured__grid li { transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease;}.apps-category__grid li:hover,.apps-sorted-grid li:hover,.apps-featured__grid li:hover { transform: translateY(-2px); border-color: var(--color-brand);}/* "Open app →" chevron hint rendered at the bottom of the tile anchor. Uses ::after on the <a> so no extra markup is needed. Scoped to the direct-child tile anchor (> li > a) so the pseudo- element does not leak onto descendant anchors such as the secondary .app-source link. */.apps-category__grid > li > a::after,.apps-sorted-grid > li > a::after,.apps-featured__grid > li > a::after { content: "Open app \2192"; margin-top: auto; padding-top: 1rem; font-weight: 700; color: var(--color-brand); letter-spacing: 0.01em; font-size: 0.95rem; opacity: 0.85; transition: opacity 0.2s ease, transform 0.2s ease;}.apps-category__grid > li:hover > a::after,.apps-sorted-grid > li:hover > a::after,.apps-featured__grid > li:hover > a::after { opacity: 1; transform: translateX(3px);}/* Clear keyboard focus-visible outline using the brand accent. */.apps-category__grid a:focus-visible,.apps-sorted-grid a:focus-visible,.apps-featured__grid a:focus-visible { outline: 3px solid var(--color-brand); outline-offset: 3px; border-radius: var(--radius-sm);}/* Editor's-pick pill: restyle the existing .top-app-tag <span> so it reads as a pill next to the app name rather than a bare star. The ★ glyph stays, with a tinted chip surface for readability. */.apps-category__grid .top-app-tag,.apps-sorted-grid .top-app-tag,.apps-featured__grid .top-app-tag { display: inline-flex; align-items: center; justify-content: center; width: 1.6rem; height: 1.6rem; margin-left: 0.6rem; font-size: 1rem; color: var(--color-accent-warm); background: var(--color-accent-warm-tint); border-radius: 50%;}/* Keep inline links inside app descriptions on the same line. Selector applies to both the no-JS flat list (#apps-list) and the JS-built category sections (.apps-category__grid) via the shared .tiles wrapper class. */.tiles li p a { display: inline !important; vertical-align: baseline;}/* Source-code paragraph rendered below the primary tile link so its anchor is not nested inside the outer tile <a>. */.tiles li .app-source { margin: 0 1.5rem 1.5rem 1.5rem; font-size: 0.9em;}/* Reset the tile-level "a is a big flex block" rules (see .tiles.tile-links a in base.css) for the source-code anchor so it renders as a normal inline link. */.tiles li .app-source a { display: inline !important; padding: 0; width: auto; height: auto; flex-grow: 0; color: var(--color-link); text-decoration: underline;}.tiles li .app-source a:focus-visible { outline: 2px solid var(--color-link); outline-offset: 2px;}/** Sidebar search (replacement for the retired toolbar) */.apps-sidebar__search { display: flex; flex-direction: column; gap: 0.4rem; margin: 0 0 1rem 0;}.apps-sidebar__search-label { margin: 0; font-weight: 700; font-size: 0.95rem; color: var(--color-text);}.apps-sidebar__search-input { box-sizing: border-box; width: 100%; height: 2.5rem; padding: 0.55rem 0.8rem; font: inherit; line-height: 1.4; color: var(--color-text); background: var(--color-bg); border: 1px solid var(--color-border-input); border-radius: var(--radius-sm); transition: border-color 0.15s ease, box-shadow 0.15s ease;}.apps-sidebar__search-input:focus { outline: none; border-color: var(--color-brand); box-shadow: var(--focus-ring-brand);}/* "Editor's pick ★" marker on featured tiles. Retained from the old toolbar block because it lives on tiles, not on the retired sort/filter controls. */.top-app-tag { color: var(--color-accent-warm); font-size: 1.6rem; margin-left: .8rem; flex-shrink: 0;}/** Responsive tail (mobile) * * The desktop-first base.css grid is mostly fine, but on narrow * viewports we want: hero pads less, featured row lets tiles fill, * category TOC loses its sticky behaviour (covered above by the * media-query scope), category section grids collapse to one column. */@media (max-width: 781px) { .apps-hero { padding: 1.75rem 1.25rem; } .apps-hero__headline { font-size: 1.6rem; } .apps-hero__lede { font-size: 1rem; } .apps-hero__actions { flex-direction: column; align-items: stretch; } .apps-hero__cta { width: 100%; } .apps-categories-nav { padding: 0.8rem 1rem; } /* The horizontal-scroll pill rules used to live only inside this  ≤781px block, but that left 782–1024px (tablet) wrapping the  pills into multiple rows and pushing the grid far down the page.  They have moved to a broader `max-width: 1024px` block below so  the whole single-column range (everything before the side-rail  kicks in) shares one scrolling-pill-bar UX. */ .apps-featured { padding: 1.1rem; } .apps-category__heading { font-size: 1.2rem; } .tiles li.hidden { /* No-op: display:none wins regardless of flex direction. */ display: none !important; }}@media (max-width: 580px) { /* Force single-column grid on tiny viewports so tile content gets  full width instead of cramming into a 300px minmax column that  ends up producing two 50%-width columns on 360px-wide phones.  Use minmax(0, 1fr) so the track cannot grow beyond the grid  container from a tile's intrinsic min-content width. */ .apps-category__grid, .apps-sorted-grid, .apps-featured__grid, #apps-list { grid-template-columns: minmax(0, 1fr); } /* Let the tile-header (logo + title flex row) wrap onto two lines  when the combined min-width of the wide logo + shortest word in  the title exceeds the tile body width on phone-size viewports.  Covers both the JS-built category/sorted/featured grids and the  no-JS flat fallback list (#apps-list). */ .apps-category__grid .tile-header, .apps-sorted-grid .tile-header, .apps-featured__grid .tile-header, #apps-list .tile-header { flex-wrap: wrap; } /* Allow long/compound title words to break so the title itself  cannot push the row beyond the tile. */ .apps-category__grid .tile-header h3, .apps-sorted-grid .tile-header h3, .apps-featured__grid .tile-header h3, #apps-list .tile-header h3 { min-width: 0; overflow-wrap: anywhere; } /* Shrink the wide wordmark logo slot on phones so the 100px fixed  width does not squeeze the title below its min-content. */ .tiles .tile-logo--wide { width: 72px; }}/** Reduced motion */@media (prefers-reduced-motion: reduce) { .apps-hero__cta, .apps-categories-nav__link, .apps-category__grid li, .apps-sorted-grid li, .apps-featured__grid li, .apps-category__grid > li > a::after, .apps-sorted-grid > li > a::after, .apps-featured__grid > li > a::after, .apps-sidebar__search-input { transition: none !important; } .apps-category__grid li:hover, .apps-sorted-grid li:hover, .apps-featured__grid li:hover, .apps-hero__cta:hover { transform: none; } /* The chevron hint also translates on tile hover; neutralise that. */ .apps-category__grid > li:hover > a::after, .apps-sorted-grid > li:hover > a::after, .apps-featured__grid > li:hover > a::after, .apps-category__grid > li > a::after, .apps-sorted-grid > li > a::after, .apps-featured__grid > li > a::after { transform: none; }}/* Dark colour-scheme support now piggybacks on the site-wide tokens * in base.css — every colour / border / shadow above references a * --color-* / --shadow-* / --focus-ring-* variable, so switching the * root theme flips the apps page along with the chrome. If a specific * apps-only adjustment is ever needed for dark mode (e.g. a different * hero gradient), add an override under `[data-theme="dark"] .apps-hero` * below rather than hard-coding hex values here. */
