/* 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; --color-header-bg: #0a0c11; --color-header-text: #e6e9f2; --color-header-link-hover: rgba(255, 255, 255, 0.08); --color-footer-bg: #0a0c11; --color-footer-text: #c8cde0; --color-footer-text-hover: #ffffff; --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;}/* 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; }}/* 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: .8rem; } .header .main-nav a {  font-size: 1.2rem;  padding: .4rem .8rem; } .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;}/** Try Solid Hero Layout */.try-solid { min-height: calc(100vh - 12.5rem); display: flex; align-items: center; gap: var(--space-8); justify-content: space-around; flex-direction: column;}.try-solid .hero-text h1 { font-size: 3.2rem; line-height: 1.2; margin: 0; color: var(--color-text);}.try-solid .hero-text h2 { font-size: 2.1rem; margin: 0; color: var(--color-text);}.try-solid .hero-text p { margin: .8rem 0 1.6rem; color: var(--color-text-subtle);}.try-solid .hero-image img { width: 100%; height: auto;}@media (min-width: 781px) { .try-solid { flex-direction: row; flex-wrap: wrap; justify-content: center; gap: 1rem; } .try-solid .hero-image, .try-solid .hero-text { width: 83.2%; max-width: 51.2rem; } .try-solid .hero-text { margin-top: -8rem; } .try-solid .hero-text p { max-width: 16rem; }}@media (max-width: 780px) { .try-solid { flex-direction: column-reverse; } .try-solid .hero-text { text-align: center; } .try-solid .hero-image { width: 100%; }}/** Try Solid Button */.try-solid-button { background-color: var(--color-brand); color: var(--color-brand-contrast); display: inline-flex; align-items: center; gap: 0.8rem; padding: 0.4rem 2rem; text-decoration: none; border-radius: var(--radius-sm); font-weight: bold; font-size: 1.1rem; transition: all var(--motion-med) ease; box-shadow: var(--shadow-brand);}.try-solid-button::after { content: ""; display: inline-block; width: 1.8rem; height: 1.6rem; flex-shrink: 0; background-image: url("/assets/img/solid-emblem.svg"); background-size: contain; background-repeat: no-repeat; background-position: center;}.try-solid-button:hover,.try-solid-button:focus { background-color: var(--color-brand-hover); color: var(--color-brand-contrast); transform: translateY(-1px); box-shadow: var(--shadow-brand-hover);}/** Tim Berners-Lee Quote Styling */.tim-berners-lee-quote { background: 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 0 2rem 0; padding: 2.5rem 2rem; background: 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, .try-solid-button { transition: none !important; } .page-hero__cta:hover, .try-solid-button:hover { transform: none; }}/** 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 { margin: 0 0 2rem 0; padding: 2.5rem 2rem; background: 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;}.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-hero__cta--ghost { background: transparent; color: var(--color-brand);}.apps-hero__cta--ghost:hover,.apps-hero__cta--ghost:focus-visible { background: var(--color-brand); color: var(--color-brand-contrast);}.apps-hero__note { margin: 0; font-size: 0.95rem; color: var(--color-text-muted);}.apps-hero__note a { color: var(--color-brand);}/** 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 TOC to the left of the apps grid so the  category picker is always next to the apps, not floating above  them. Track width ~220px matches the longest category label  ("Content, Notes, Blogging and Publishing") at the base font.  The :has() guard keeps the grid single-column when JS hasn't run  (or has failed) and .apps-categories-nav is still [hidden] —  otherwise no-JS visitors would see an empty left rail and a  narrower apps column. `display: block` fallback for the ~0.1%  of traffic on browsers that predate :has() stays single-column  too, which is the safer degrade. */ .apps-layout { display: block; } .apps-layout:has(.apps-categories-nav:not([hidden])) { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 2rem; align-items: start; } .apps-layout__sidebar { align-self: start; } .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 becomes a sticky side-rail inside the apps-layout left column. Stacking order when everything is pinned during scroll: site header (z-index 1001) on top, sticky toolbar (z-index 9) below it, then this TOC rail (z-index 10 — sits ABOVE the toolbar stylesheet-wise but their bounding rects never overlap because TOC's `top` pushes it below the toolbar). The `top` offset reads from `--apps-toolbar-sticky-offset`, which a tiny script on load (see apps.html) measures from the toolbar's real rendered height so it tracks font-scaling and zoom. The 7rem fallback covers a two-row toolbar under the default font and is what no-JS / pre-measurement visitors see. `max-height` lets very long category lists scroll inside the rail rather than stretching the page. */@media (min-width: 1025px) { .apps-categories-nav { margin: 0; position: sticky; top: var(--apps-toolbar-sticky-offset, 7rem); z-index: 10; max-height: calc(100vh - var(--apps-toolbar-sticky-offset, 7rem) - 1rem); overflow-y: auto; }}.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);}/* 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; background: 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;}/** Toolbar (search + existing sort/filter) */.apps-toolbar { display: grid; grid-template-columns: minmax(220px, 1fr) auto; gap: 1.25rem 1.5rem; align-items: start; margin: 0 0 1.6rem 0; padding: 1.25rem 1.6rem; background: var(--color-surface-muted); border: 1px solid var(--color-border-muted); border-radius: var(--radius-md); /* Stick to the top of the viewport as the reader scrolls the grid.  Keeps search + sort + filter one glance away without a trip back  up the page. `top: 0` sits it flush under the site header; the  site header is z-index: 1001 so the toolbar passes under it at  its z-index: 9, and the side-rail TOC (z-index: 10) passes under  the toolbar too. Opaque surface background so tile content doesn't  bleed through. */ position: sticky; top: 0; z-index: 9;}/* Shared row metrics so every label and every form control in the toolbar renders on a single, predictable baseline. Heights lock in a consistent vertical rhythm and line-height keeps the label text boxes the same height as the inputs. */.apps-toolbar { --apps-toolbar-label-line-height: 1.4; --apps-toolbar-control-height: 2.5rem; --apps-toolbar-row-gap: 0.4rem;}.apps-toolbar__search { display: flex; flex-direction: column; gap: var(--apps-toolbar-row-gap); min-width: 0;}.apps-toolbar__label,.apps-toolbar .control-group > label { margin: 0; font-weight: 700; line-height: var(--apps-toolbar-label-line-height); color: var(--color-text);}.apps-toolbar__search-input { box-sizing: border-box; height: var(--apps-toolbar-control-height); padding: 0.55rem 0.8rem; font: inherit; line-height: var(--apps-toolbar-label-line-height); 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-toolbar__search-input:focus { outline: none; border-color: var(--color-brand); box-shadow: var(--focus-ring-brand);}.apps-toolbar__hint { font-size: 0.85rem; color: var(--color-text-muted);}.sort-controls { margin-bottom: 1.6rem; padding: .8rem 1.6rem; background: var(--color-surface-muted); border-radius: var(--radius-md); border: 1px solid var(--color-border-muted); display: flex; gap: 1.6rem; flex-wrap: wrap; align-items: flex-end;}/* Reset the .sort-controls chrome when it's nested inside the new toolbar: the outer .apps-toolbar already provides padding + border + background, so we flatten this inner block. Selector is made more specific than .sort-controls so it wins regardless of rule order. Also switch align-items from flex-end to flex-start so each control-group stacks its label + control from the top of the row, matching the search column and letting labels + inputs share a single Y baseline. */.apps-toolbar .apps-toolbar__controls.sort-controls { margin: 0; padding: 0; background: transparent; border: 0; border-radius: 0; align-items: flex-start;}/* Stack the toolbar on narrow screens and let selects / search shrink to fit. The desktop grid is 2 columns (search | controls); on small screens each row becomes full width and the selects stretch. */@media (max-width: 781px) { .apps-toolbar { grid-template-columns: 1fr; /* Sticky toolbar on a narrow phone would eat ~40% of the  viewport's vertical real estate as the search + controls stack  to 3+ rows. Drop back to static positioning here. */ position: static; } .apps-toolbar__search, .apps-toolbar__controls { min-width: 0; width: 100%; } .apps-toolbar__search-input, .sort-controls select { width: 100%; min-width: 0; } .apps-toolbar .apps-toolbar__controls.sort-controls { gap: 1rem; } .control-group { flex: 1 1 180px; }}.control-group { display: flex; flex-direction: column; gap: .4rem; margin: 0;}.control-group > label { font-weight: 700; color: var(--color-text);}/* Inside the apps toolbar the control-group gap and label line-height must match the search column so .apps-toolbar labels and inputs share a single baseline at desktop. */.apps-toolbar .control-group { gap: var(--apps-toolbar-row-gap);}.control-group--check { justify-content: flex-end;}/* The checkbox control-group has no label + control pair, so with the toolbar flex row set to align-items:flex-start the lone checkbox would jump up to the label baseline instead of resting on the input row. Pull it back down so it aligns with the bottom of the selects, matching the visual rhythm of the other controls. */.apps-toolbar .control-group--check { align-self: flex-end;}.sort-controls select { box-sizing: border-box; height: var(--apps-toolbar-control-height, 2.5rem); padding: .5rem .7rem; font: inherit; line-height: var(--apps-toolbar-label-line-height, 1.4); color: var(--color-text); border: 1px solid var(--color-border-input); border-radius: var(--radius-sm); background: var(--color-bg); min-width: 200px;}.sort-controls select:focus { outline: none; border-color: var(--color-brand); box-shadow: var(--focus-ring-brand);}.top-app-tag { color: var(--color-accent-warm); font-size: 1.6rem; margin-left: .8rem; flex-shrink: 0;}.checkbox-label { display: flex; align-items: center; cursor: pointer; color: var(--color-text-subtle); gap: .8rem; margin-bottom: .2rem;}.checkbox-label input[type="checkbox"] { margin: 0; cursor: pointer;}/** 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-toolbar__search-input, .sort-controls select { 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. */
