/* ==========================================================================
   OBSCURA OS - UNIFIED THEME SYSTEM & COLORWAYS
   Supports:
   1. Cobalt (Default; still stored as "botanical" so saved choices keep working)
   2. Deep & Royal Purple
   3. Monochrome Slate
   Supports both Light and Dark modes with instant client-side switching.
   Zero server calls. Zero flicker.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. THE SYSTEM

   Type, space, corners and motion, each as one short scale. A value on
   this site comes from here or it is a mistake: design-lint.mjs and
   motion-lint.mjs count the literals that are left and fail when there
   are more of them than there were.
   -------------------------------------------------------------------------- */

/* Two faces from one superfamily, served from /fonts on this origin, so a
   page load asks no third party for anything. Source Sans 3 is the face of
   everything a person reads or works in; Source Serif 4 is for the H1 and
   H2 of a page that is read rather than used. Both are Latin subsets made
   with fontTools from the Adobe releases, with the layout features the
   site does not use taken out. The hinting stays in: without it Chrome
   set a t or an o a pixel apart from its neighbours at 13 to 16px.

     source-sans-3-latin.woff2          23.2 KB  weight 200 to 900
     source-sans-3-latin-italic.woff2   22.9 KB  fetched only for italics
     source-serif-4-latin.woff2         33.6 KB  weight 400 to 700, optical
                                                 size fixed at 32

   The serif was one 120 KB file with a live optical-size axis. Headings are
   22 to 48px, and the cut for 32px serves all of them, so the axis was
   fixed there and three quarters of the file went with it. A first visit
   now costs 57 KB of type, preloaded from _headers, where it cost 120 KB.
   The system faces follow each name, so a missing file costs a flash of
   the fallback and nothing else. Licensed under the SIL Open Font License;
   the text is in /fonts/OFL.txt. */
@font-face {
  font-family: "Source Sans 3";
  font-style: normal;
  font-weight: 200 900;
  font-display: swap;
  src: url(/fonts/source-sans-3-latin.woff2) format("woff2");
  unicode-range: U+0020-007E, U+00A0-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2013-2014, U+2018-201A, U+201C-201E, U+2020-2022, U+2026, U+2030, U+2032-2033, U+2039-203A, U+2044, U+20AC, U+2122, U+2212, U+2215;
}
@font-face {
  font-family: "Source Sans 3";
  font-style: italic;
  font-weight: 200 900;
  font-display: swap;
  src: url(/fonts/source-sans-3-latin-italic.woff2) format("woff2");
  unicode-range: U+0020-007E, U+00A0-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2013-2014, U+2018-201A, U+201C-201E, U+2020-2022, U+2026, U+2030, U+2032-2033, U+2039-203A, U+2044, U+20AC, U+2122, U+2212, U+2215;
}
@font-face {
  font-family: "Source Serif 4";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/fonts/source-serif-4-latin.woff2) format("woff2");
  unicode-range: U+0020-007E, U+00A0-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2013-2014, U+2018-201A, U+201C-201E, U+2020-2022, U+2026, U+2030, U+2032-2033, U+2039-203A, U+2044, U+20AC, U+2122, U+2212, U+2215;
}

/* The readable face, for somebody who finds the letters easier to tell
   apart in it: Atkinson Hyperlegible, drawn by the Braille Institute for
   low vision, where b, d, p and q, I, l and 1, and O and 0 are each
   shaped unlike the others, which is also what helps many readers with
   dyslexia. Chosen in Settings (theme.js, data-font). Nothing below names
   it but the rule under data-font, so a browser never fetches it for
   anybody who has not chosen it. Latin, in regular and bold and their
   italics, from the Braille Institute's release as packaged by Fontsource;
   licensed under the SIL Open Font License, the text of which is in
   /fonts/OFL-atkinson-hyperlegible.txt.

     atkinson-hyperlegible-latin-400-normal.woff2   16.8 KB
     atkinson-hyperlegible-latin-700-normal.woff2   17.1 KB
     atkinson-hyperlegible-latin-400-italic.woff2   17.9 KB
     atkinson-hyperlegible-latin-700-italic.woff2   18.1 KB */
@font-face {
  font-family: "Atkinson Hyperlegible";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/atkinson-hyperlegible-latin-400-normal.woff2) format("woff2");
}
@font-face {
  font-family: "Atkinson Hyperlegible";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/atkinson-hyperlegible-latin-700-normal.woff2) format("woff2");
}
@font-face {
  font-family: "Atkinson Hyperlegible";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/atkinson-hyperlegible-latin-400-italic.woff2) format("woff2");
}
@font-face {
  font-family: "Atkinson Hyperlegible";
  font-style: italic;
  font-weight: 700;
  font-display: swap;
  src: url(/fonts/atkinson-hyperlegible-latin-700-italic.woff2) format("woff2");
}

/* What the text is set in for the moment before the webfonts arrive: a
   face every system has, scaled to take the same room as Source Sans 3 and
   Source Serif 4, so the swap changes the letters and moves nothing. The
   figures are the webfonts' own at weight 400, the body's (Source Sans 3's
   file defaults to 200, which is far narrower), against Arial's and
   Times's metrics: their average advance over a line of English and
   figures, and their ascent and descent, worked out with fontTools from
   the files in /fonts. Before
   this, every line on a slow first visit reflowed when the face arrived. */
@font-face {
  font-family: "Source Sans 3 Fallback";
  src: local("Arial"), local("Helvetica"), local("Liberation Sans");
  size-adjust: 91.4%; ascent-override: 112.1%; descent-override: 43.8%; line-gap-override: 0%;
}
@font-face {
  font-family: "Source Serif 4 Fallback";
  src: local("Times New Roman"), local("Times"), local("Liberation Serif");
  size-adjust: 104.9%; ascent-override: 98.7%; descent-override: 31.9%; line-gap-override: 0%;
}

:root {
  /* Faces. --font-ui is the one face of the interface, headings included;
     --font-display is the serif, kept for what is a document rather than
     the interface: a letter, a manuscript, a printed book. Headings were
     set in it on some pages and in the sans on others, so moving from
     /apps to an application changed the face of its title (the owner,
     21 October 2026: "messy"). --font-headline is the heading face, and it
     is the sans. The other names are kept because 298 pages name --font-sans
     and 113 name --font-serif in their own styles, and every one of them now
     lands on one of these two rather than on whatever the system has:
     --font-serif used to point at Iowan, Palatino or Georgia while the
     headlines were in Source Serif 4, so a page could show two serifs. */
  --font-ui: "Source Sans 3", "Source Sans 3 Fallback", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: "Source Serif 4", "Source Serif 4 Fallback", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --font-sans: var(--font-ui);
  --font-serif: var(--font-display);
  --font-headline: var(--font-ui);
  /* The system monospace, for figures, codes and keys only. */
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* The type scale. Nine sizes and no others. Settings' text size
     multiplies them (Comfort, below); a page that sizes its own text in
     pixels multiplies by var(--text-scale, 1) to follow it. */
  --text-2xs: 12px;
  --text-xs: 13px;
  --text-sm: 14px;
  --text-md: 16px;
  --text-lg: 18px;
  --text-xl: 22px;
  --text-2xl: 28px;
  --text-3xl: 36px;
  --text-4xl: 48px;
  --leading-ui: 1.35;
  --leading-prose: 1.6;
  --measure: 68ch;

  /* Space, on a 4px grid. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --space-9: 96px;

  /* Corners: a control, a card or field, a dialog, a pill. --radius-xl is
     the dialog's corner under an older name, kept for the pages that use
     it. A page that declares its own radii in its own :root still wins. */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 12px;
  --radius-pill: 999px;

  /* One stroke for every line icon, on a 24px grid. */
  --icon-stroke: 1.5;

  /* Motion. Five durations and four curves, and nothing else anywhere.

       --dur-1   90ms  a press, a toggle, a checkbox
       --dur-2  150ms  hover, colour, the focus ring, a theme change
       --dur-3  220ms  menus, popovers, dialogs and notices coming in
       --dur-4  300ms  panels, a page arriving, an application opening
       --dur-5  450ms  the one entrance on the front door, once a visit

     Leaving takes two thirds of arriving (the -out values), because what
     goes away no longer needs looking at. --ease-standard is for anything
     that arrives or moves, --ease-exit for anything leaving, --ease-emph
     only for large entrances, and --ease-spring only for something small
     landing - a notice, a tick, an icon arriving in its tile - where a
     hair of overshoot reads as weight. --stagger is the step between the
     items of a list, which never runs to more than eight.

     --dur-turn and --dur-orbit are the two durations that are not
     entrances: a whole turn of the mark (motion.js) while it is pointed at,
     and a whole turn of the slow turn every copy of the mark is always in.
     Both are loops, not something anybody waits for. */
  --dur-1: 90ms;
  --dur-2: 150ms;
  --dur-3: 220ms;
  --dur-4: 300ms;
  --dur-5: 450ms;
  --dur-2-out: 100ms;
  --dur-3-out: 150ms;
  --dur-4-out: 200ms;
  --stagger: 35ms;
  /* The same step, kept for a gentle fade where --stagger is nought. */
  --dur-gentle-step: 35ms;
  --dur-turn: 1400ms;
  --dur-orbit: 10000ms;
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);
  --ease-exit: cubic-bezier(0.4, 0, 1, 1);
  --ease-emph: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-spring: cubic-bezier(0.32, 1.22, 0.52, 1);
  /* Counting a figure up (OBSCURA.countUp): long enough to be read as a
     count, and in figures that do not change width as they turn over. */
  --count-dur: 600ms;
  --count-ease: var(--ease-emph);
  /* A loading placeholder (.os-skel) waits this long before it shows, and
     its light crosses it once in --dur-sweep, for as long as it is there. */
  --dur-wait: 300ms;
  --dur-sweep: 1200ms;

  /* The names the site used before these, for the pages and scripts that
     still say them. Nothing new should. */
  --ease-out: var(--ease-emph);
  --ease-in-out: var(--ease-standard);
  --ease-surface: var(--ease-standard);
  --motion-fast: var(--dur-1);
  --motion-base: var(--dur-2);
  --motion-slow: var(--dur-4);
  --dur-fast: var(--dur-2);
}

/* --------------------------------------------------------------------------
   1. COLORWAY: Cobalt (Default)
   -------------------------------------------------------------------------- */
:root,
:root[data-color-theme="botanical"] {
  --paper: #F8F7F4;
  --paper-raised: #FFFFFF;
  --paper-inset: #F1EFEA;
  --rule: #E5E2DB;
  --rule-strong: #CBC7BE;
  --ink: #17171B;
  --ink-soft: #4A4A54;
  --ink-mid: #4A4A54;
  --ink-faint: #6B6B73;
  --accent: #2E54C8;
  --accent-strong: #1F3FA6;
  --accent-soft: #E4EAF8;
  --accent-border: rgba(46, 84, 200, 0.35);
  --good: #3A7149;
  --good-soft: #D9EADD;
  --danger: #A6432F;
  --danger-soft: #F0DAD3;
  /* Warning amber. Unlike the accent it does not follow the colourway,
     because a warning that changes colour with the decoration stops
     reading as a warning. Several pages had it hard coded for a dark
     background, which left them at 2.25 against a light one. */
  --warn: #7E5812;
  --warn-soft: #F6EBD5;
  /* The focus ring, and the border of anything drawn in the accent. Both
     were a gold left over from the Botanical colourway, so a blue site had
     a gold ring and gold outlines; every other colourway already used its
     own accent here. The ring is the accent itself, which is 6.1:1 on this
     paper and 7.9:1 on the dark one, well over the 3:1 a focus indicator
     needs, and it is the same colour as the ring command-palette.js draws
     on every page, so a page that styles its own ring with this token no
     longer draws a second colour. */
  --focus: #2E54C8;
  --card-bg: var(--paper-raised);
}

:root[data-theme="dark"],
:root[data-color-theme="botanical"][data-theme="dark"] {
  --paper: #121215;
  --paper-raised: #19191E;
  --paper-inset: #0E0E11;
  --rule: #2A2A31;
  --rule-strong: #3B3B45;
  --ink: #ECECF1;
  --ink-soft: #B8B8C3;
  --ink-mid: #B8B8C3;
  --ink-faint: #878792;
  --accent: #8AA4FF;
  --accent-strong: #A9BCFF;
  --accent-soft: #1B2340;
  --accent-border: rgba(138, 164, 255, 0.35);
  --good: #7FCB93;
  --good-soft: #1E3324;
  --danger: #E08669;
  --danger-soft: #3A2420;
  --warn: #E0B05F;
  --warn-soft: #332710;
  --focus: #8AA4FF;
  --card-bg: var(--paper-raised);
}

/* --------------------------------------------------------------------------
   2. COLORWAY: Royal Purple & Royal Purple
   -------------------------------------------------------------------------- */
:root[data-color-theme="purple"] {
  --paper: #F3F1F7 !important;
  --paper-raised: #FAF9FD !important;
  --paper-inset: #E8E4F0 !important;
  --rule: #D1CADE !important;
  --rule-strong: #B4A9C9 !important;
  --ink: #1D172A !important;
  --ink-soft: #52476A !important;
  --ink-mid: #52476A !important;
  --ink-faint: #6B6180 !important;
  --accent: #7839D6 !important;
  --accent-strong: #5924A8 !important;
  --accent-soft: #E8DCFC !important;
  --accent-border: rgba(120, 57, 214, 0.35) !important;
  --good: #2B7442 !important;
  --good-soft: #D7EDE0 !important;
  --danger: #B33927 !important;
  --danger-soft: #F4DFDB !important;
  --focus: #7839D6 !important;
  --card-bg: var(--paper-raised) !important;
}

:root[data-color-theme="purple"][data-theme="dark"] {
  --paper: #110D1A !important;
  --paper-raised: #191424 !important;
  --paper-inset: #0C0913 !important;
  --rule: #2F2542 !important;
  --rule-strong: #493B63 !important;
  --ink: #ECE6F6 !important;
  --ink-soft: #B6ABCE !important;
  --ink-mid: #B6ABCE !important;
  --ink-faint: #8D80A6 !important;
  --accent: #A86CF8 !important;
  --accent-strong: #C497FA !important;
  --accent-soft: #2B1842 !important;
  --accent-border: rgba(168, 108, 248, 0.4) !important;
  --good: #5ED995 !important;
  --good-soft: #153424 !important;
  --danger: #E68875 !important;
  --danger-soft: #381E19 !important;
  --focus: #A86CF8 !important;
  --card-bg: var(--paper-raised) !important;
}

/* --------------------------------------------------------------------------
   3. COLORWAY: Monochrome Slate
   -------------------------------------------------------------------------- */
:root[data-color-theme="monochrome"] {
  --paper: #F5F5F5 !important;
  --paper-raised: #FFFFFF !important;
  --paper-inset: #EAEAEA !important;
  --rule: #D2D2D2 !important;
  --rule-strong: #B0B0B0 !important;
  --ink: #151515 !important;
  --ink-soft: #484848 !important;
  --ink-mid: #484848 !important;
  --ink-faint: #686868 !important;
  --accent: #262626 !important;
  --accent-strong: #000000 !important;
  --accent-soft: #E5E5E5 !important;
  --accent-border: rgba(40, 40, 40, 0.35) !important;
  --good: #3A6E4F !important;
  --good-soft: #DDEEE3 !important;
  --danger: #A8382B !important;
  --danger-soft: #F2DCD9 !important;
  --focus: #262626 !important;
  --card-bg: var(--paper-raised) !important;
}

:root[data-color-theme="monochrome"][data-theme="dark"] {
  --paper: #121212 !important;
  --paper-raised: #1C1C1C !important;
  --paper-inset: #0A0A0A !important;
  --rule: #2C2C2C !important;
  --rule-strong: #444444 !important;
  --ink: #EFEFEF !important;
  --ink-soft: #B2B2B2 !important;
  --ink-mid: #B2B2B2 !important;
  --ink-faint: #8E8E8E !important;
  --accent: #E0E0E0 !important;
  --accent-strong: #FFFFFF !important;
  --accent-soft: #282828 !important;
  --accent-border: rgba(224, 224, 224, 0.35) !important;
  --good: #68D391 !important;
  --good-soft: #1B3324 !important;
  --danger: #E28578 !important;
  --danger-soft: #381F1C !important;
  --focus: #E0E0E0 !important;
  --card-bg: var(--paper-raised) !important;
}

/* --------------------------------------------------------------------------
   4. COLORWAY: Atlas Blue
   Cool slate paper with a deep steel-blue accent. Reads as calm and
   technical without going cold — pairs well with dense tool UIs.
   -------------------------------------------------------------------------- */
:root[data-color-theme="atlas"] {
  --paper: #ECEFF4 !important;
  --paper-raised: #F6F8FB !important;
  --paper-inset: #E1E7F0 !important;
  --rule: #C4D0E0 !important;
  --rule-strong: #9CAEC6 !important;
  --ink: #16222F !important;
  --ink-soft: #42566C !important;
  --ink-mid: #42566C !important;
  --ink-faint: #596776 !important;
  --accent: #2C5D96 !important;
  --accent-strong: #1E4676 !important;
  --accent-soft: #D2E0F1 !important;
  --accent-border: rgba(44, 93, 150, 0.35) !important;
  --good: #2F7358 !important;
  --good-soft: #D6EAE1 !important;
  --danger: #A63F3F !important;
  --danger-soft: #F2DBDB !important;
  --focus: #2C5D96 !important;
  --card-bg: var(--paper-raised) !important;
}

:root[data-color-theme="atlas"][data-theme="dark"] {
  --paper: #0E151E !important;
  --paper-raised: #161F2B !important;
  --paper-inset: #090F16 !important;
  --rule: #26364A !important;
  --rule-strong: #3B5170 !important;
  --ink: #E4EBF3 !important;
  --ink-soft: #B0BFD1 !important;
  --ink-mid: #B0BFD1 !important;
  --ink-faint: #8798AC !important;
  --accent: #6BA6E8 !important;
  --accent-strong: #93C2F5 !important;
  --accent-soft: #1B2C42 !important;
  --accent-border: rgba(107, 166, 232, 0.35) !important;
  --good: #6FC7A2 !important;
  --good-soft: #16302A !important;
  --danger: #E58A8A !important;
  --danger-soft: #3A2224 !important;
  --focus: #6BA6E8 !important;
  --card-bg: var(--paper-raised) !important;
}

/* --------------------------------------------------------------------------
   5. COLORWAY: Verdant
   A cooler, more saturated green than Botanical — emerald accent on a
   crisp mint-tinted paper, rather than Botanical's warm sage and gold.
   -------------------------------------------------------------------------- */
:root[data-color-theme="verdant"] {
  --paper: #EAF1EC !important;
  --paper-raised: #F4F9F5 !important;
  --paper-inset: #DEEAE1 !important;
  --rule: #BDD4C5 !important;
  --rule-strong: #95B4A0 !important;
  --ink: #12241A !important;
  --ink-soft: #3C5847 !important;
  --ink-mid: #3C5847 !important;
  --ink-faint: #556B5E !important;
  --accent: #146B50 !important;
  --accent-strong: #0C4E39 !important;
  --accent-soft: #CCE7DA !important;
  --accent-border: rgba(23, 121, 90, 0.35) !important;
  --good: #29714A !important;
  --good-soft: #D5EBDD !important;
  --danger: #A8442F !important;
  --danger-soft: #F1DCD6 !important;
  --focus: #17795A !important;
  --card-bg: var(--paper-raised) !important;
}

:root[data-color-theme="verdant"][data-theme="dark"] {
  --paper: #0B1710 !important;
  --paper-raised: #12211A !important;
  --paper-inset: #071009 !important;
  --rule: #21382B !important;
  --rule-strong: #35543F !important;
  --ink: #E2EFE7 !important;
  --ink-soft: #ABC5B5 !important;
  --ink-mid: #ABC5B5 !important;
  --ink-faint: #829C8C !important;
  --accent: #45C48D !important;
  --accent-strong: #6FDCA9 !important;
  --accent-soft: #12301F !important;
  --accent-border: rgba(69, 196, 141, 0.35) !important;
  --good: #64D69C !important;
  --good-soft: #133024 !important;
  --danger: #E2896F !important;
  --danger-soft: #35211C !important;
  --focus: #45C48D !important;
  --card-bg: var(--paper-raised) !important;
}


/* --------------------------------------------------------------------------
   9. COLORWAY: Tide
   Sea glass and deep teal: a cool blue-green that is neither Atlas's navy nor Verdant's leaf.
   -------------------------------------------------------------------------- */
:root[data-color-theme="tide"] {
  --paper: #EDF3F3 !important;
  --paper-raised: #F7FBFB !important;
  --paper-inset: #DFEBEB !important;
  --rule: #C2D6D6 !important;
  --rule-strong: #98B5B5 !important;
  --ink: #122223 !important;
  --ink-soft: #3C5657 !important;
  --ink-mid: #3C5657 !important;
  --ink-faint: #546C6D !important;
  --accent: #127179 !important;
  --accent-strong: #0B585F !important;
  --accent-soft: #CDE6E7 !important;
  --accent-border: rgba(18, 113, 121, 0.35) !important;
  --good: #2F7358 !important;
  --good-soft: #D6EAE1 !important;
  --danger: #A6423A !important;
  --danger-soft: #F2DEDB !important;
  --focus: #127179 !important;
  --card-bg: var(--paper-raised) !important;
}

:root[data-color-theme="tide"][data-theme="dark"] {
  --paper: #0A1617 !important;
  --paper-raised: #122021 !important;
  --paper-inset: #060F10 !important;
  --rule: #22373A !important;
  --rule-strong: #375457 !important;
  --ink: #E2EFEF !important;
  --ink-soft: #ABC4C5 !important;
  --ink-mid: #ABC4C5 !important;
  --ink-faint: #7A9496 !important;
  --accent: #4FC5CE !important;
  --accent-strong: #7FDCE2 !important;
  --accent-soft: #0F3034 !important;
  --accent-border: rgba(79, 197, 206, 0.35) !important;
  --good: #6FC7A2 !important;
  --good-soft: #16302A !important;
  --danger: #E9897F !important;
  --danger-soft: #37211E !important;
  --focus: #4FC5CE !important;
  --card-bg: var(--paper-raised) !important;
}

/* --------------------------------------------------------------------------
   10. COLORWAY: Ember
   Warm orange on a pale clay paper. The accent is deep enough to read as text on white, which a bright orange is not.
   -------------------------------------------------------------------------- */
:root[data-color-theme="ember"] {
  --paper: #FBF2EC !important;
  --paper-raised: #FEF9F5 !important;
  --paper-inset: #F3E5DA !important;
  --rule: #E2CDBD !important;
  --rule-strong: #C4A48C !important;
  --ink: #2A1A10 !important;
  --ink-soft: #5C4436 !important;
  --ink-mid: #5C4436 !important;
  --ink-faint: #75594A !important;
  --accent: #9E4310 !important;
  --accent-strong: #7E340B !important;
  --accent-soft: #F7DECB !important;
  --accent-border: rgba(158, 67, 16, 0.35) !important;
  --good: #3D6F49 !important;
  --good-soft: #DBEADD !important;
  --danger: #A33526 !important;
  --danger-soft: #F5DCD7 !important;
  --focus: #9E4310 !important;
  --card-bg: var(--paper-raised) !important;
}

:root[data-color-theme="ember"][data-theme="dark"] {
  --paper: #17100B !important;
  --paper-raised: #211812 !important;
  --paper-inset: #0F0A06 !important;
  --rule: #3A2A1F !important;
  --rule-strong: #57402F !important;
  --ink: #F5E7DB !important;
  --ink-soft: #CBB2A0 !important;
  --ink-mid: #CBB2A0 !important;
  --ink-faint: #A38C7C !important;
  --accent: #F08A4B !important;
  --accent-strong: #FBA76D !important;
  --accent-soft: #3A2413 !important;
  --accent-border: rgba(240, 138, 75, 0.35) !important;
  --good: #7FCB93 !important;
  --good-soft: #1E3324 !important;
  --danger: #EC8A7A !important;
  --danger-soft: #38211D !important;
  --focus: #F08A4B !important;
  --card-bg: var(--paper-raised) !important;
}

/* --------------------------------------------------------------------------
   11. COLORWAY: Ink
   Near-black indigo with a cool grey paper. The quietest of the coloured ways, for reading.
   -------------------------------------------------------------------------- */
:root[data-color-theme="ink"] {
  --paper: #F2F3F7 !important;
  --paper-raised: #FAFBFD !important;
  --paper-inset: #E6E8EF !important;
  --rule: #CFD2DE !important;
  --rule-strong: #A6ABBD !important;
  --ink: #14161F !important;
  --ink-soft: #3F4457 !important;
  --ink-mid: #3F4457 !important;
  --ink-faint: #585E73 !important;
  --accent: #333C66 !important;
  --accent-strong: #242B4D !important;
  --accent-soft: #DCDFEE !important;
  --accent-border: rgba(51, 60, 102, 0.35) !important;
  --good: #2F7358 !important;
  --good-soft: #D6EAE1 !important;
  --danger: #A33B44 !important;
  --danger-soft: #F2DADD !important;
  --focus: #333C66 !important;
  --card-bg: var(--paper-raised) !important;
}

:root[data-color-theme="ink"][data-theme="dark"] {
  --paper: #0C0D14 !important;
  --paper-raised: #14161F !important;
  --paper-inset: #07080D !important;
  --rule: #242838 !important;
  --rule-strong: #3A4056 !important;
  --ink: #E7E9F2 !important;
  --ink-soft: #B3B8CC !important;
  --ink-mid: #B3B8CC !important;
  --ink-faint: #838AA3 !important;
  --accent: #8D97D8 !important;
  --accent-strong: #AAB3E8 !important;
  --accent-soft: #1C2036 !important;
  --accent-border: rgba(141, 151, 216, 0.35) !important;
  --good: #7FCB93 !important;
  --good-soft: #1E3324 !important;
  --danger: #E58A93 !important;
  --danger-soft: #341F24 !important;
  --focus: #8D97D8 !important;
  --card-bg: var(--paper-raised) !important;
}

/* --------------------------------------------------------------------------
   12. COLORWAY: Sepia
   Old paper and brown ink. Warm, low-contrast-looking and still well above the contrast floor.
   -------------------------------------------------------------------------- */
:root[data-color-theme="sepia"] {
  --paper: #F6EFE2 !important;
  --paper-raised: #FCF7ED !important;
  --paper-inset: #EBE1CF !important;
  --rule: #DACBB2 !important;
  --rule-strong: #BCA684 !important;
  --ink: #241C10 !important;
  --ink-soft: #544733 !important;
  --ink-mid: #544733 !important;
  --ink-faint: #6B5C45 !important;
  --accent: #7A5417 !important;
  --accent-strong: #5E4010 !important;
  --accent-soft: #EEDCBE !important;
  --accent-border: rgba(122, 84, 23, 0.35) !important;
  --good: #3D6F49 !important;
  --good-soft: #DDEADC !important;
  --danger: #9E3F2C !important;
  --danger-soft: #F1DCD5 !important;
  --focus: #7A5417 !important;
  --card-bg: var(--paper-raised) !important;
}

:root[data-color-theme="sepia"][data-theme="dark"] {
  --paper: #16120B !important;
  --paper-raised: #1F1A11 !important;
  --paper-inset: #0E0B06 !important;
  --rule: #362C1E !important;
  --rule-strong: #51432E !important;
  --ink: #F2E8D6 !important;
  --ink-soft: #C8B99E !important;
  --ink-mid: #C8B99E !important;
  --ink-faint: #968871 !important;
  --accent: #D8AC5E !important;
  --accent-strong: #E9C684 !important;
  --accent-soft: #372A14 !important;
  --accent-border: rgba(216, 172, 94, 0.35) !important;
  --good: #8AC998 !important;
  --good-soft: #20321F !important;
  --danger: #E3907B !important;
  --danger-soft: #37221B !important;
  --focus: #D8AC5E !important;
  --card-bg: var(--paper-raised) !important;
}

/* --------------------------------------------------------------------------
   13. COLORWAY: High contrast
   For eyes that need the most difference between the words and the
   paper: black on white, and white on black, with every rule, edge and
   field border drawn dark enough to see (4.5:1 or more, where the other
   colourways keep a rule at a quiet 1.3:1). The accent is a deep blue in
   light and a yellow in dark, the two colours that stay furthest from the
   paper for the most kinds of colour vision. The faint ink, the one the
   other colourways keep for captions, is still 10:1 here. The focus ring
   is drawn wider (below, after the ring itself).
   -------------------------------------------------------------------------- */
:root[data-color-theme="contrast"] {
  --paper: #FFFFFF !important;
  --paper-raised: #FFFFFF !important;
  --paper-inset: #EFEFEF !important;
  --rule: #767676 !important;
  --rule-strong: #000000 !important;
  --ink: #000000 !important;
  --ink-soft: #1F1F1F !important;
  --ink-mid: #1F1F1F !important;
  --ink-faint: #383838 !important;
  --accent: #0036A8 !important;
  --accent-strong: #00267A !important;
  --accent-soft: #DCE5FA !important;
  --accent-border: rgba(0, 54, 168, 0.7) !important;
  --good: #155A27 !important;
  --good-soft: #DCEFE1 !important;
  --danger: #9C1A08 !important;
  --danger-soft: #FBE0DB !important;
  --warn: #5A3C00 !important;
  --warn-soft: #FFF0D0 !important;
  --focus: #0036A8 !important;
  --card-bg: var(--paper-raised) !important;
}

:root[data-color-theme="contrast"][data-theme="dark"] {
  --paper: #000000 !important;
  --paper-raised: #0D0D0D !important;
  --paper-inset: #000000 !important;
  --rule: #8F8F8F !important;
  --rule-strong: #FFFFFF !important;
  --ink: #FFFFFF !important;
  --ink-soft: #EBEBEB !important;
  --ink-mid: #EBEBEB !important;
  --ink-faint: #CCCCCC !important;
  --accent: #FFD60A !important;
  --accent-strong: #FFE45C !important;
  --accent-soft: #2E2600 !important;
  --accent-border: rgba(255, 214, 10, 0.7) !important;
  --good: #7EE0A0 !important;
  --good-soft: #0E2A18 !important;
  --danger: #FF8A7A !important;
  --danger-soft: #3A0F0A !important;
  --warn: #FFC66B !important;
  --warn-soft: #2E1F00 !important;
  --focus: #FFD60A !important;
  --card-bg: var(--paper-raised) !important;
}

/* --------------------------------------------------------------------------
   TEXT THAT SITS ON AN ACCENT BACKGROUND

   A number of buttons carry an inline style of the shape
   background:var(--accent-strong); color:#fff. That is fine while the accent
   is dark, and invisible the moment it is not - which is every dark mode,
   and Monochrome Slate worst of all, where the accent is nearly white. The
   button was still there; it just had white text on a white pill.

   --on-accent carries the readable colour for whichever theme is in play,
   and the attribute selectors below reach the inline styles, which nothing
   in a stylesheet can otherwise override.
   -------------------------------------------------------------------------- */
/* Six pages (LOCKBOX, REDLINE, SCRUB, SEAL, X-RAY and the security page)
   ask for --paper-2, a name from an earlier palette that no colourway
   defines, and fell back to the fixed dark tone they carry inline: a
   near-black card on a light page. It resolves to the raised paper tone
   here, so it follows every colourway and mode like the rest. */
:root { --paper-2: var(--paper-raised); }

:root { --on-accent: #FFFFFF; }
:root[data-theme="dark"] { --on-accent: var(--paper); }

/* The same problem for a filled danger button. In dark mode --danger is a
   pale salmon, so white text on it came out at 2.7 to 1. Text on a danger
   fill takes this instead, and it flips with the mode like --on-accent. */
:root { --on-danger: #FFFFFF; }
:root[data-theme="dark"] { --on-danger: var(--paper); }

/* The same swap when the dark values come from the device setting rather
   than the attribute. Choosing "System" in Settings removes data-theme so
   the prefers-color-scheme rules further down take over, and every token
   went dark except this one: it stayed white, so a Save or Join button on a
   pale dark-mode accent had white text on it. Light opts out, as below. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --on-accent: var(--paper); --on-danger: var(--paper); }
}

/* Five colours for the series of a chart, in this order and never cycled:
   blue, rust, teal, ochre, violet. Each mode's set was checked against its
   paper for lightness, chroma, contrast and the separation of neighbours
   under the three common kinds of colour blindness (first drawn by
   OBSCURA Research's charts). */
:root { --chart-1: #2f6db5; --chart-2: #c0562f; --chart-3: #1f8ea8; --chart-4: #a8841f; --chart-5: #8a4fb5; }
:root[data-theme="dark"] { --chart-1: #4f86d0; --chart-2: #d0684a; --chart-3: #2f99b5; --chart-4: #ad8728; --chart-5: #9d70cc; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --chart-1: #4f86d0; --chart-2: #d0684a; --chart-3: #2f99b5; --chart-4: #ad8728; --chart-5: #9d70cc; }
}

/* A sheet of paper: the colours of a page in a document, which stay those
   of paper in dark mode as Word's and Google Docs' do, so what is on the
   screen is what prints and a colour is judged against white. Docs uses
   them for its pages unless View, Dark page, is ticked. They are the light
   colourway's own values and do not change with the mode. */
:root {
  --print-paper: #FFFFFF;
  --print-ink: #17171B;
  --print-ink-soft: #4A4A54;
  --print-ink-faint: #6B6B73;
  --print-rule: #E5E2DB;
  --print-rule-strong: #CBC7BE;
  --print-inset: #F1EFEA;
  --print-accent: #2E54C8;
  --print-accent-strong: #1F3FA6;
  --print-accent-soft: #E4EAF8;
}

/* The product marks' colour (the tiles on the front door, /workspace and
   /other-suites). A mark brings only a hue, --ico-h, an OKLCH angle; these
   make the rest of it, one set for light and one for dark:

     --ico-l, --ico-c   the line: OKLCH lightness and chroma. One lightness
                        for every hue gives every mark the same contrast,
                        which is what OKLCH is for.
     --ico-tint         how much of the line is mixed into the tile's paper
                        for the square behind it, at its foot; --ico-tint-top
                        at its head, so the square is lit from above.
     --ico-plane        the strength of the plane inside the line.

   The line clears 4.1:1 against its tint in light and 6.1:1 in dark on every
   colourway; contrast.mjs measures it and wants 3:1. Monochrome takes the
   chroma to nought, below, so every mark there is grey. */
:root { --ico-l: 0.5; --ico-c: 0.13; --ico-tint: 13%; --ico-tint-top: 7%; --ico-plane: 0.2; }
:root[data-theme="dark"] { --ico-l: 0.83; --ico-c: 0.1; --ico-tint: 19%; --ico-tint-top: 26%; --ico-plane: 0.24; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --ico-l: 0.83; --ico-c: 0.1; --ico-tint: 19%; --ico-tint-top: 26%; --ico-plane: 0.24; }
}
:root[data-color-theme="monochrome"] { --ico-c: 0; }

[style*="var(--accent"][style*="color:#fff" i],
[style*="var(--accent"][style*="color: #fff" i] {
  color: var(--on-accent) !important;
}

/* The same trap in the page stylesheets: a button filled with the accent
   colour and given white text. Every one of these goes white on white in a
   dark theme, and invisibly so, because the button is still there.
   .vs-btn.primary is the Choose Folder button vault-settings.js writes into
   the Settings page, with the same white text baked into its stylesheet. */
.btn-primary,
button.btn-primary,
.action-btn.primary,
button.action-btn.primary,
a.action-btn.primary,
.tab-btn.active,
.vs-btn.primary {
  color: var(--on-accent) !important;
}

/* --------------------------------------------------------------------------
   6. COLORWAY: Signal Yellow
   Bright paper, deep gold accents. Yellow on white has almost no contrast,
   so the accent is a dark gold and the bright yellow is kept for fills.
   -------------------------------------------------------------------------- */
:root[data-color-theme="yellow"] {
  --paper: #FBF6E4 !important;
  --paper-raised: #FFFCF0 !important;
  --paper-inset: #F3ECD6 !important;
  --rule: #E2D5AB !important;
  --rule-strong: #C4B172 !important;
  --ink: #2A2308 !important;
  --ink-soft: #5A4E22 !important;
  --ink-mid: #5A4E22 !important;
  --ink-faint: #76693B !important;
  --accent: #7E6106 !important;
  --accent-strong: #6B5200 !important;
  --accent-soft: #FAE9A6 !important;
  --accent-border: rgba(138, 107, 0, 0.35) !important;
  --good: #48761D !important;
  --good-soft: #E7F1CF !important;
  --danger: #A8442F !important;
  --danger-soft: #F3DDD7 !important;
  --focus: #8A6B00 !important;
  --card-bg: var(--paper-raised) !important;
}

:root[data-color-theme="yellow"][data-theme="dark"] {
  --paper: #16130A !important;
  --paper-raised: #1F1A0D !important;
  --paper-inset: #0F0D06 !important;
  --rule: #3A3116 !important;
  --rule-strong: #5A4C25 !important;
  --ink: #F4EDD6 !important;
  --ink-soft: #C9BC92 !important;
  --ink-mid: #C9BC92 !important;
  --ink-faint: #948861 !important;
  --accent: #F0C24B !important;
  --accent-strong: #FFD976 !important;
  --accent-soft: #30260E !important;
  --accent-border: rgba(240, 194, 75, 0.35) !important;
  --good: #A9CE6A !important;
  --good-soft: #22300F !important;
  --danger: #E2896F !important;
  --danger-soft: #35211C !important;
  --focus: #F0C24B !important;
  --card-bg: var(--paper-raised) !important;
}

/* --------------------------------------------------------------------------
   7. COLORWAY: Ember Red
   -------------------------------------------------------------------------- */
:root[data-color-theme="red"] {
  --paper: #FBEEEC !important;
  --paper-raised: #FFF7F5 !important;
  --paper-inset: #F3E0DC !important;
  --rule: #E7C6BF !important;
  --rule-strong: #CE9A90 !important;
  --ink: #2B1512 !important;
  --ink-soft: #5E3630 !important;
  --ink-mid: #5E3630 !important;
  --ink-faint: #835A52 !important;
  --accent: #A62B1C !important;
  --accent-strong: #841F13 !important;
  --accent-soft: #F6D3CC !important;
  --accent-border: rgba(166, 43, 28, 0.35) !important;
  --good: #29714A !important;
  --good-soft: #D5EBDD !important;
  --danger: #8E1B0E !important;
  --danger-soft: #F3CDC6 !important;
  --focus: #A62B1C !important;
  --card-bg: var(--paper-raised) !important;
}

:root[data-color-theme="red"][data-theme="dark"] {
  --paper: #180E0C !important;
  --paper-raised: #221310 !important;
  --paper-inset: #0F0806 !important;
  --rule: #3D211C !important;
  --rule-strong: #5E332B !important;
  --ink: #F5E3DF !important;
  --ink-soft: #C9A79F !important;
  --ink-mid: #C9A79F !important;
  --ink-faint: #A07F77 !important;
  --accent: #F0705A !important;
  --accent-strong: #FF9481 !important;
  --accent-soft: #351813 !important;
  --accent-border: rgba(240, 112, 90, 0.35) !important;
  --good: #64D69C !important;
  --good-soft: #133024 !important;
  --danger: #FF7F68 !important;
  --danger-soft: #3A1712 !important;
  --focus: #F0705A !important;
  --card-bg: var(--paper-raised) !important;
}

/* --------------------------------------------------------------------------
   8. COLORWAY: Rose
   -------------------------------------------------------------------------- */
:root[data-color-theme="pink"] {
  --paper: #FCEEF4 !important;
  --paper-raised: #FFF7FA !important;
  --paper-inset: #F5E0EA !important;
  --rule: #ECC6D8 !important;
  --rule-strong: #D399B4 !important;
  --ink: #2C1220 !important;
  --ink-soft: #62334C !important;
  --ink-mid: #62334C !important;
  --ink-faint: #855870 !important;
  --accent: #A81E63 !important;
  --accent-strong: #86134E !important;
  --accent-soft: #F8D3E4 !important;
  --accent-border: rgba(168, 30, 99, 0.35) !important;
  --good: #29714A !important;
  --good-soft: #D5EBDD !important;
  --danger: #983B28 !important;
  --danger-soft: #F3DDD7 !important;
  --focus: #A81E63 !important;
  --card-bg: var(--paper-raised) !important;
}

:root[data-color-theme="pink"][data-theme="dark"] {
  --paper: #170B12 !important;
  --paper-raised: #211018 !important;
  --paper-inset: #0E060A !important;
  --rule: #3C1E2E !important;
  --rule-strong: #5C3047 !important;
  --ink: #F7E1EC !important;
  --ink-soft: #CDA3BA !important;
  --ink-mid: #CDA3BA !important;
  --ink-faint: #A07A8F !important;
  --accent: #F274AB !important;
  --accent-strong: #FF9AC4 !important;
  --accent-soft: #331623 !important;
  --accent-border: rgba(242, 116, 171, 0.35) !important;
  --good: #64D69C !important;
  --good-soft: #133024 !important;
  --danger: #E2896F !important;
  --danger-soft: #35211C !important;
  --focus: #F274AB !important;
  --card-bg: var(--paper-raised) !important;
}


/* --------------------------------------------------------------------------
   System & Base Polish
   -------------------------------------------------------------------------- */
:root {
  /* Older names for the system's own, from when the site loaded Spectral,
     Public Sans and IBM Plex Mono from Google. */
  --os-radius-sm: var(--radius-sm);
  --os-radius-md: var(--radius-md);
  --os-radius-lg: var(--radius-lg);
  --os-font-display: var(--font-display);
  --os-font-ui: var(--font-ui);
  --os-font-mono: var(--font-mono);
}

/* Smooth color switching only during active theme toggles */
html.theme-transition,
html.theme-transition *,
html.theme-transition *:before,
html.theme-transition *:after {
  transition: background-color var(--dur-2) var(--ease-standard), border-color var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard) !important;
  transition-delay: 0s !important;
}

/* Universal scrollbars respecting current colorway */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
::-webkit-scrollbar-track {
  background: var(--paper-inset);
}
::-webkit-scrollbar-thumb {
  background: var(--rule-strong);
  border-radius: var(--radius-sm);
}
::-webkit-scrollbar-thumb:hover {
  background: var(--accent);
}

/* Accessible focus ring. The ring itself is !important, so a page that
   took outlines away still shows where the keyboard is. Where it sits is
   not: seventy-six controls draw theirs inside (outline-offset below 0)
   because the box they are in clips anything outside it, a tab strip or a
   segmented control, and an !important 2px here put every one of those
   rings outside again, where it was cut off. 2px outside is the default,
   and a control that says otherwise is now believed. */
*:focus-visible {
  outline: 2px solid var(--accent) !important;
}
:where(*):focus-visible {
  outline-offset: 2px;
}

/* High contrast: a ring a step wider, text in a field's placeholder dark
   enough to read, and a link in running text underlined, so it is never
   told from the words around it by its colour alone. */
:root[data-color-theme="contrast"] *:focus-visible {
  outline-width: 3px !important;
}
:root[data-color-theme="contrast"] ::placeholder {
  color: var(--ink-faint);
  opacity: 1;
}
:root[data-color-theme="contrast"] :is(p, li, dd, td) > a:not([class]) {
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* The small things, said once for every page (12 October 2026). Each is
   in :where(), so it weighs nothing and any rule a page or an application
   writes for itself still wins.

   Selected text takes the colourway's own soft accent, in light and dark,
   rather than the browser's blue, which matched none of them. */
::selection {
  background: var(--accent-soft);
  color: var(--ink);
}
/* A heading breaks where it reads well, and never leaves one word on a
   line of its own; running text avoids a last line of a single short
   word. Not inside something being typed in (Docs, Notes, a slide): there
   a balanced line would move the words already written as each new one
   arrives.
   The longhand, so a line a page keeps on one line (white-space: nowrap,
   inherited from a bar or a menu) stays on one line. */
:where(h1, h2, h3, h4, legend) { text-wrap-style: balance; }
:where(p, li, dd, figcaption, blockquote, div.sub, div.hint, div.note) { text-wrap-style: pretty; }
/* A drop zone's two centred lines, balanced like a heading's. */
:where(.dropzone, .drop, [class$="-drop"], [id$="Drop"]) { text-wrap-style: balance; }
:where([contenteditable]:not([contenteditable="false"]), [contenteditable]:not([contenteditable="false"]) *) { text-wrap-style: auto; }
/* Figures in a table, a meter or a result line up in columns. */
:where(td, th, output, meter, progress, time) { font-variant-numeric: tabular-nums; }
/* A figure and its unit, or a short phrase, kept on one line ("7 days"). */
.ob-nobreak { white-space: nowrap; }
/* What can be pressed shows a hand, and what cannot be pressed yet does
   not. */
:where(button:not(:disabled), summary, [role="button"]:not([aria-disabled="true"]), select:not(:disabled),
  input:is([type="checkbox"], [type="radio"], [type="file"], [type="color"], [type="range"], [type="submit"], [type="button"], [type="reset"]):not(:disabled),
  label:has(> input:is([type="checkbox"], [type="radio"]):not(:disabled))) { cursor: pointer; }
:where(button:disabled, [role="button"][aria-disabled="true"], input:disabled, select:disabled, textarea:disabled) { cursor: default; }
/* Firefox has no ::-webkit-scrollbar, so the bars above were its own
   grey there; this gives them the colourway's colours too. */
@supports not selector(::-webkit-scrollbar) {
  :root { scrollbar-color: var(--rule-strong) var(--paper-inset); }
}

/* There was a paid tier here once. It was removed: OBSCURA OS is free and
   every page says so. What was left behind was a hundred and ten lines
   styling a header badge, an upgrade link and a "pro active" state for
   markup that no page has carried for months, topped by a rule hiding all
   of it in case an old page still did. None of them do, so none of this is
   here any more. */


/* ==========================================================================
   UNIFIED OS TOPBAR & NAVIGATION BAR STYLING
   Sticky topbar, always visible without scrolling.
   Suite: HOME, WORKSPACE SUITE, SOVEREIGN SUITE
   ========================================================================== */
.os-topbar {
  position: sticky;
  top: 0;
  z-index: 9999;
  /* At the body's own level on every page, so its width does not depend on
     the wrapper a page happens to use: 1040 to match .app-wrapper, then
     the edge of the screen. */
  width: 100%;
  max-width: 1040px;
  box-sizing: border-box;
  margin-left: auto;
  margin-right: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 18px;
  background: var(--paper-raised);
  border-bottom: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
  border-right: 1px solid var(--rule);
  border-top: none;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  margin-bottom: 24px;
  gap: 12px;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.05);
}

.os-nav-tabs {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--paper-inset);
  padding: 4px 6px;
  border-radius: var(--radius-md);
  border: 1px solid var(--rule);
  max-width: 100%;
  white-space: nowrap;
}
.os-nav-tabs::-webkit-scrollbar {
  display: none;
}
/* Three words, in the same face as the page. The bar used to carry six
   tabs in bold monospace capitals, each one opening a menu of every tool
   in its suite; the menus are gone and the words can be words. */
.nav-tab-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px 14px;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--ink-soft);
  transition: background-color var(--dur-2) var(--ease-standard), border-color var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard), opacity var(--dur-2) var(--ease-standard), transform var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard);
  text-transform: none;
  font-family: var(--font-sans);
  text-decoration: none;
  white-space: nowrap;
  flex-shrink: 0;
  line-height: 1.2;
}
.nav-tab-btn:hover {
  color: var(--ink);
  background: var(--paper-raised);
}
.nav-tab-btn.active {
  color: var(--accent-strong);
  background: var(--paper-raised);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  border: 1px solid var(--rule);
  font-weight: 600;
}

.os-brand-group {
  display: flex;
  align-items: center;
  gap: 12px;
}

.os-brand-col {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.os-brand-head {
  display: flex;
  align-items: center;
  gap: 8px;
  position: relative;
}
/* New on /whats-new since this browser last read it (os-nav.js). Placed
   over the bar just past the name, so arriving after the paint moves
   nothing; the box is a finger's width on a touch screen, the dot in it
   eight pixels. */
.os-new-dot {
  position: absolute; left: 100%; top: 50%; width: 28px; height: 28px; margin-left: 2px;
  transform: translateY(-50%); display: grid; place-items: center; border-radius: var(--radius-pill);
  animation: os-fade-in var(--dur-4) var(--ease-standard) backwards;
}
.os-new-dot::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.os-new-dot:hover::before { background: var(--accent-strong); }
.os-new-dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 0; }
@media (pointer: coarse) { .os-new-dot { width: 44px; height: 44px; margin-left: -6px; } }
/* A link to /whats-new in the page itself: the same dot after its words,
   and the words for a screen reader. */
.os-new-link::after { content: ""; display: inline-block; width: 7px; height: 7px; margin-left: 6px; vertical-align: 0.15em; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.os-new-said { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }


.brand-title {
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: var(--text-lg);
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
}

.brand-title span {
  color: var(--accent-strong);
}

.os-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}

.btn-search {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--paper-inset);
  border: 1px solid var(--rule);
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  color: var(--ink-soft);
  font-size: var(--text-xs);
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--dur-2) var(--ease-standard), border-color var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard), opacity var(--dur-2) var(--ease-standard), transform var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard);
  font-family: inherit;
}

.btn-search:hover {
  background: var(--paper-raised);
  color: var(--ink);
  border-color: var(--rule-strong);
}

.search-kbd {
  font-family: var(--font-mono, "IBM Plex Mono", monospace);
  font-size: var(--text-2xs);
  background: var(--paper);
  border: 1px solid var(--rule);
  padding: 1px 5px;
  border-radius: var(--radius-sm);
  color: var(--ink-faint);
}

.btn-settings {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--paper-inset);
  border: 1px solid var(--rule);
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  color: var(--ink-soft);
  font-size: var(--text-xs);
  font-weight: 600;
  text-decoration: none;
  transition: background-color var(--dur-2) var(--ease-standard), border-color var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard), opacity var(--dur-2) var(--ease-standard), transform var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard);
}

.btn-settings:hover {
  background: var(--paper-raised);
  color: var(--ink);
  border-color: var(--rule-strong);
}

/* The header needs about 860px for the brand, the five tabs and the two
   controls to share one row. Below that (tablets in portrait, phones, and
   the 900px reading column on the text pages) the tabs take a second row
   of their own. The brand never breaks onto two lines. */
.brand-title {
  white-space: nowrap;
}
.os-brand-col {
  min-width: 0;
}
@media (max-width: 760px) {
  .os-topbar {
    flex-wrap: wrap;
    row-gap: 10px;
  }
  .os-nav-tabs {
    order: 3;
    width: 100%;
    justify-content: flex-start;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  /* Centred when the five tabs fit, and still scrollable to the first
     tab when they do not (justify-content: center would hide it). */
  .os-nav-tabs .nav-tab-btn:first-child {
    margin-left: auto;
  }
  .os-nav-tabs .nav-tab-btn:last-child {
    margin-right: auto;
  }
}
@media (max-width: 768px) {
  .os-topbar {
    top: 4px;
    padding: 8px 10px;
    gap: 8px;
    margin-bottom: 20px;
  }
  .nav-tab-btn {
    padding: 6px 10px;
    font-size: var(--text-xs);
  }
}
@media (max-width: 560px) {
  /* Phones. The header scrolls away with the page rather than pinning
     two rows to the top of a small screen. The two controls become icon
     buttons (their labels stay for screen readers) so the brand and the
     controls share the first row, and the tab row gets the room it needs
     by dropping HOME, which the brand mark already covers. */
  .os-topbar {
    position: static;
  }
  .btn-search,
  .btn-settings {
    position: relative;
    gap: 0;
    padding: 8px 9px;
  }
  .btn-search > span:not(.search-kbd),
  .btn-settings > span {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  .os-nav-tabs {
    gap: 4px;
    padding: 4px;
  }
  .nav-tab-btn {
    padding: 6px 10px;
    font-size: var(--text-xs);
  }
}


/* ---------------------------------------------------------------
   Narrow-screen corrections.

   The site footer is a single flex row of links. Adding About,
   Terms and Refunds pushed it to six items, which overflowed the
   viewport on a phone and made every page scroll sideways. The
   footer's inner container carries an inline style attribute, so
   these need !important to reach it — that is the reason, not
   laziness.

   Tables are given their own horizontal scroll rather than being
   allowed to widen the page: the pricing matrix on the Pass page
   was pushing 211px of overflow on a 390px screen.
   --------------------------------------------------------------- */
@media (max-width: 760px) {
  .os-footer {
    flex-direction: column !important;
    align-items: flex-start !important;
    gap: 12px !important;
  }
  .os-footer > div,
  .os-footer nav {
    display: flex !important;
    flex-wrap: wrap !important;
    gap: 8px 14px !important;
    max-width: 100% !important;
    /* A wrapping flex line stretches its items to the tallest one in it, so
       one two-word link made its whole row tall once a floor was set. */
    align-items: center;
    align-content: center;
  }
  /* Every footer link was 43px: one pixel under the floor the rest of the
     site keeps, on the row that is the only way off a good many pages. */
  .os-footer a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    box-sizing: border-box;
    padding: 2px 0;
  }

  /* Wide content scrolls inside itself instead of widening the page. Not
     inside an application (body.*-app): Plan's sheet and Gantt chart, and
     tables and canvases in the other apps, are laid out to exact widths and
     scroll in their own panes, and this made the sheet a block whose header
     no longer lined up with its rows. */
  body:not([class*="-app"]) table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  /* Nothing should be able to force the document wider than the screen. */
  html, body { max-width: 100%; overflow-x: hidden; }
  body:not([class*="-app"]) :is(img, canvas, video, svg) { max-width: 100%; height: auto; }
  pre, code { white-space: pre-wrap; word-break: break-word; }
}


/* ---------------------------------------------------------------
   Prose pages: privacy, terms, refunds, about, why.

   These put the shared site topbar inside a 680px reading column.
   The topbar needs roughly 710px for its three groups (brand, nav,
   controls), so it was being squeezed into 636px: the OBSCURA OS
   wordmark wrapped onto three lines and the header read as broken.

   Fix is to widen the shell and keep the reading measure separately,
   so paragraphs stay comfortable while the header gets its room. The
   page's own <style> block loads after theme.css, so overriding its
   max-width needs !important. That is the reason, not laziness.
   --------------------------------------------------------------- */
/* One column, meant the same way on every page. Some pages set
   box-sizing: border-box on everything and some do not, and a max-width
   of 900 means 900 of content on one and 900 including the gutters on
   the other — which is how two page shapes ended up with margins
   twenty-two pixels apart while both claimed the same number.

   And the same column as .app-wrapper, the other page shape: 1040 wide
   with its 22px gutters inside it. At 900 it put this kind of page's
   left edge 48px to the right of Help's, Settings' and every tool's, so
   moving from one to the other made the whole page jump sideways. */
body > .wrap { max-width: 1040px !important; box-sizing: border-box; }

/* The content of these pages now sits inside <main>, so the reading measure
   has to be set one level deeper as well. Without the second selector the
   text ran the full 900px of the shell, which is far too long a line to read
   comfortably - and nothing would have said so, because no check can see a
   paragraph that is merely too wide. A screenshot comparison caught it. */
body > .wrap > :is(h1, h2, h3, p, ul, ol, dl, blockquote),
body > .wrap > main > :is(h1, h2, h3, p, ul, ol, dl, blockquote) { max-width: 700px; }

body > .wrap > .os-topbar,
body > .wrap > footer,
body > .wrap > main { max-width: none; }


/* Phone: reclaim the side padding, and drop the keyboard hint that
   cannot apply on a touch device. */
@media (max-width: 560px) {
  .search-kbd { display: none; }
}

/* One gutter on a narrow screen, whatever shape the page is. There were
   three: sixteen on a tool page, twelve on a guide, and twenty-two on the
   blocks the build writes in under a tool — so a phone showed the same
   six-pixel step the desktop had, in the same place. */
@media (max-width: 768px) {
  body > .wrap { padding-left: 16px !important; padding-right: 16px !important; }
  body > .os-faq-link,
  body > .os-rel { padding-left: 16px; padding-right: 16px; }
}

/* ---------------------------------------------------------------
   The skip link is injected on every page and had no styling on the
   prose pages, so "Skip to main content" was rendering as visible
   body text in the top-left corner of five pages. It should be
   reachable by keyboard and invisible otherwise.
   --------------------------------------------------------------- */
.obscura-skip-link,
.skip-link {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.obscura-skip-link:focus,
.skip-link:focus {
  position: fixed !important;
  left: 12px;
  top: 12px;
  width: auto;
  height: auto;
  margin: 0;
  clip: auto;
  overflow: visible;
  z-index: 10000;
  padding: 10px 16px;
  background: var(--paper-raised, #fff);
  color: var(--ink, #1f2e23);
  border: 2px solid var(--accent-strong, #7a5a22);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  text-decoration: none;
}


/* Dark-mode legibility for app chrome that hardcodes white text on a
   brand colour. In the dark themes those brand colours are light, so the
   text takes the on-accent tone instead. */
/* Tabs and modes in the tool pages that said nothing under the pointer:
   AIRGAP, CANARY, PHANTOM and SHARD's tabs, PROOF's, PAPERKEY's,
   STEGOSCAN's, REGEX's modes, MERGE's steps and Forms' modes. The one
   not chosen darkens its words a step, as the bar's tabs do. */
:where(.tab-btn, .pf-tab, .pk-tab, .sg-tab, .rx-mode, .mg-step, .mode-btn):not(.active):not(.on):not(.is-on):not([aria-selected="true"]):not(:disabled):hover {
  color: var(--ink);
  background-color: var(--paper-raised);
}
button.btn-danger:not(:disabled):hover { filter: brightness(1.08); }

.mode-btn.active,
[style*="background:var(--"][style*="color:#fff" i],
[style*="background: var(--"][style*="color:#fff" i],
[style*="background:var(--"][style*="color: #fff" i],
[style*="background: var(--"][style*="color: #fff" i] { color: var(--on-accent) !important; }



/* An anchor (or a label round a file input) styled as a primary button on
   a page whose own CSS only styles button.btn-primary had no background at
   all, and once its text
   took the on-accent tone it disappeared. :where() keeps this at zero
   specificity so any page that does style its links still wins. */
:where(a.btn-primary, label.btn-primary) { display:inline-flex; align-items:center; gap:8px; background:var(--accent-strong); border:1px solid var(--accent-strong); padding:10px 18px; border-radius:var(--radius-md); font-family:var(--font-sans); font-size:var(--text-sm); font-weight:600; text-decoration:none; }
/* A button that a narrow screen stretches to full width keeps its words in
   the middle, whichever rule made it a flex box. */
:where(.btn-primary, .btn-secondary) { justify-content: center; }
:where(a.btn-primary, label.btn-primary):hover { opacity:.92; }


/* ------------------------------------------------------------------
   Workspace apps on phones and small tablets. The apps were laid out
   for a laptop: a slim suite toolbar, an app header with its actions
   on one line, a tool row, and a stage with a side panel. Below 768px
   each of those rows now wraps or scrolls instead of overflowing.
   ------------------------------------------------------------------ */
@media (max-width: 768px) {
  .header-actions { flex-wrap: wrap; gap: 6px; width: 100%; justify-content: flex-start; }
  .sheets-statusbar, .docs-statusbar { flex-wrap: wrap; row-gap: 4px; height: auto; font-size: var(--text-2xs); }
}


/* ============================================================
   THE REPORT PANE
   Thirteen tools end with a <pre class="out"> holding the working, and
   until now .out matched nothing at all: they were relying on a bare
   <pre>, which does not wrap and does not scroll, so one line longer
   than the panel pushed the whole PAGE sideways. DGA's cluster table is
   88 characters and did exactly that - 54px of horizontal scroll on a
   1280px screen, on every element of the page, to read one table.

   Containment only. The two pages that style .out themselves (cipher
   wraps, glyph sets its own face) come later in the cascade and still
   win, and the eleven that do not are left looking exactly as they did.
   A report is a fixed-width document - the columns line up because they
   are padded to line up - so it scrolls rather than wraps: wrapping a
   padded table is how you turn it into rubble.
   ============================================================ */
.out {
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/* ============================================================
   MOTION, SWITCHED DOWN
   Every hover and press on the site uses short, specific transitions
   (never "all"), so a colour change does not also animate layout.
   ============================================================ */
/* Motion is switched by an attribute on <html>, not by the media query
   directly. theme.js reads the system preference and writes data-motion
   before the first paint, so the default still honours somebody who has
   asked their system for less - but it can now be overridden in Settings,
   which a media query alone could never allow. Somebody whose machine says
   "reduce" because of one app is not condemned to a static site here.

   Less motion is not no feedback. Everything that moved is kept still, but
   a change of state is still shown, as a fade of 100ms and nothing else: a
   panel that arrives fades in where it will sit, a dialog fades up in
   place, and nothing loops. That is what WCAG asks for (no movement that
   was not asked for) without leaving a page that changes in a blink.

   Three things do it. Every entrance keyframe below rises by --os-rise,
   or grows from --os-pop, and those are nought and one here, so the same
   animations become fades. Every duration
   an entrance uses is cut to --dur-fade by the rule after this one, and
   only an element that opted in with --os-fade gets even that; everything
   else, a page's own keyframes included, ends at once, as it always did.
   And a transition may animate opacity and nothing else. */
@property --os-fade {
  syntax: "<time>";
  inherits: false;
  initial-value: 0.001ms;
}
:root[data-motion="none"] {
  --os-rise: 0px;
  --os-rise-sm: 0px;
  --os-rise-lg: 0px;
  --os-pop: 1;
  --stagger: 0ms;
  --dur-fade: 100ms;
}
:root[data-motion="none"] *,
:root[data-motion="none"] *::before,
:root[data-motion="none"] *::after {
  /* --os-fade is not inherited, so an element that asks for a fade gets
     one and its children do not. */
  animation-duration: var(--os-fade) !important;
  animation-iteration-count: 1 !important;
  scroll-behavior: auto !important;
  view-transition-name: none !important;
}
:root[data-motion="none"]:not([data-motion-gentle]) *,
:root[data-motion="none"]:not([data-motion-gentle]) *::before,
:root[data-motion="none"]:not([data-motion-gentle]) *::after {
  animation-delay: 0s !important;
  /* A duration is meaningless to an animation driven by scrolling: it
     runs as far as the page has scrolled, whatever it is told, so every
     reveal below still slid and faded for somebody who had switched
     motion off - and sat half transparent until they scrolled further.
     Back on the document's own clock, the duration above ends it at once. */
  animation-timeline: auto !important;
  transition-property: opacity !important;
  transition-duration: var(--dur-fade) !important;
  transition-delay: 0s !important;
}
/* Gentle: a device asking for reduced motion, with nothing chosen in
   Settings (theme.js). Everything that arrives still fades, at --dur-4
   rather than in a blink, and a list still fades in a step at a time,
   because the delays are kept; a section scrolled to fades up as it
   comes into view. The distances above stay nought, so none of it moves
   - a fade is all there is - and a colour changes rather than a position
   under the pointer. Anything that does not ask for a fade still ends at
   once, as above. */
:root[data-motion="none"][data-motion-gentle] {
  --dur-fade: var(--dur-4);
  --stagger: var(--dur-gentle-step);
}
:root[data-motion="none"][data-motion-gentle] *,
:root[data-motion="none"][data-motion-gentle] *::before,
:root[data-motion="none"][data-motion-gentle] *::after {
  transition-property: opacity, color, background-color, border-color, fill, stroke !important;
  transition-duration: var(--dur-2) !important;
}
/* Nothing under the pointer moves either: a hover or a press changes a
   colour or a line, and never a position. */
:root[data-motion="none"] :is(a, button, summary, [role="button"], .os-row, .boot-tile, .panel, .tile, .btn, .btn-primary, .btn-secondary, .action-btn):is(:hover, :active, :focus-visible) {
  transform: none !important;
  translate: none !important;
  scale: none !important;
}
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
/* The letters as every page has drawn them since command-palette.js
   first set this, but from the first frame rather than a beat after the
   page loaded, when the script ran and every line of text repainted. */
html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }

/* ---------------------------------------------------------------
   Comfort: the choices in Settings that theme.js puts on <html>
   before the first paint (data-text-size, data-font, data-density).

   The size of the text is the type scale above, multiplied. It is not
   a zoom: the page keeps its width and its layout, and only the words
   grow, which is what lets a phone at 360px still fit its page, and
   what a zoom of the whole page could not do. The four largest sizes,
   the headings', grow by half as much, so a heading still fits its
   line. A document's own text, in Docs or a slide, is the document's
   size and is not changed: it is the work, not the interface.
   --------------------------------------------------------------- */
:root[data-text-size] {
  --text-2xs: calc(12px * var(--text-scale));
  --text-xs: calc(13px * var(--text-scale));
  --text-sm: calc(14px * var(--text-scale));
  --text-md: calc(16px * var(--text-scale));
  --text-lg: calc(18px * var(--text-scale));
  --text-xl: calc(22px * (1 + (var(--text-scale) - 1) / 2));
  --text-2xl: calc(28px * (1 + (var(--text-scale) - 1) / 2));
  --text-3xl: calc(36px * (1 + (var(--text-scale) - 1) / 2));
  --text-4xl: calc(48px * (1 + (var(--text-scale) - 1) / 2));
  font-size: calc(100% * var(--text-scale));
}
:root[data-text-size="large"] { --text-scale: 1.125; }
:root[data-text-size="larger"] { --text-scale: 1.25; }
:root[data-text-size="largest"] { --text-scale: 1.5; }

/* The readable face, in place of both of the site's own, with a little
   more room between words and lines. Figures and code keep the
   monospace. */
:root[data-font="readable"] {
  --font-ui: "Atkinson Hyperlegible", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  --font-display: "Atkinson Hyperlegible", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  --leading-prose: 1.7;
  word-spacing: 0.06em;
}

/* Compact: the space scale from 12px up, a step closer, for somebody who
   wants more on the screen at once. A tap target stays 44px on a touch
   screen whatever this says; those are set by their own rules, not by
   the space between things. */
:root[data-density="compact"] {
  --space-3: 8px;
  --space-4: 12px;
  --space-5: 16px;
  --space-6: 24px;
  --space-7: 32px;
  --space-8: 48px;
  --space-9: 64px;
  --leading-prose: 1.5;
}
:root[data-density="compact"][data-font="readable"] { --leading-prose: 1.6; }

/* Read aloud (read-aloud.js): the button beside a selection. A control
   of the page's own kind, raised just enough to be read as floating over
   the text, and drawn above everything but a dialog. */
.os-speak {
  position: fixed;
  z-index: 2147482000;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 36px;
  padding: 0 var(--space-3);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius-md);
  background: var(--paper-raised);
  color: var(--ink);
  font: 600 var(--text-sm)/1 var(--font-ui);
  box-shadow: var(--elev-2);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  transition: border-color var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard);
}
.os-speak svg { width: 16px; height: 16px; flex: none; }
.os-speak .os-speak-off, .os-speak.is-reading .os-speak-on { display: none; }
.os-speak.is-reading .os-speak-off { display: inline; }
.os-speak:hover { border-color: var(--accent); color: var(--accent-strong); }
.os-speak.is-reading { border-color: var(--accent); color: var(--accent-strong); }
@media (pointer: coarse) { .os-speak { min-height: 44px; } }
.os-speak-say {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
@media print { .os-speak { display: none !important; } }

/* An element carrying the hidden attribute is hidden, whatever a class
   rule says about its display. The browser's own [hidden] rule is a bare
   element rule, so anything like .field { display: flex } quietly beats
   it and the element stays on screen while the code believes it is gone.
   The selector is doubled to outweigh a class rule; it is deliberately
   not !important, so the older pages that show an element by setting
   style.display directly still work. This has to hold everywhere, so it
   lives here rather than in one page at a time. */
[hidden][hidden] { display: none; }

/* The page's own ground. theme.css defines the colours but never painted
   with them, so a page that did not set its own body background got the
   browser's white while the text tokens went dark, and three pages were
   white on white in dark mode and unreadable. Every page loads this file,
   so the base belongs here; any page that wants a different ground still
   sets its own body rule, which comes later in the cascade and wins. */
body {
  /* Sixteen pages kept the browser's 8px margin, so their bar stood 8px in
     from the edges and the top of the window and their pages sat 16px
     narrower than the rest. */
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  /* Seven pages set no face on the body and fell back to the browser's
     serif; every other page names the same sans in its own stylesheet. */
  font-family: var(--font-sans, system-ui, sans-serif);
}

/* ---------------------------------------------------------------
   Touch screens.

   Every size above is chosen for a mouse pointer, which lands where
   it is aimed. A finger does not: the navigation tabs came out 25px
   tall on a phone and the workspace mini toolbar 20px, which is why
   switching sections took two attempts. Padding grows the hit area
   without changing the type size or the look on a desktop.
   --------------------------------------------------------------- */
@media (pointer: coarse) {
  /* Apple's 44pt floor. The header controls and the FAQ rows were
     between 18 and 34px tall, which is a miss on a phone. */
  .brand-title { display: inline-flex; align-items: center; min-height: 44px; }
  .btn-search, .btn-settings { min-height: 44px; min-width: 44px; }
  .nav-tab-btn { min-height: 44px; display: inline-flex; align-items: center; }
  .os-faq summary { min-height: 44px; display: flex; align-items: center; }
  .nav-tab-btn {
    padding-top: 10px;
    padding-bottom: 10px;
  }
  /* This block used to lower these two back to 40px, twelve lines after
     setting them to 44. The later rule won, so the floor stated above was
     dead code and the two controls in the header of every page on the site
     were four pixels under it. Nothing measured tap targets, so nothing
     said so. */
}


/* ==========================================================================
   Keyboard reachable file pickers.

   Several tools hide their file input with display:none and open it by
   clicking a drop zone. display:none takes the input out of the tab order
   and out of the accessibility tree, so on those pages there was no way at
   all to choose a file without a mouse: the tool simply could not be used.

   This hides the input visually while leaving it focusable and announced,
   and puts a focus ring on the drop zone around it so a keyboard user can
   see where they are. Dropping and clicking still work exactly as before.
   ========================================================================== */
.obs-file-a11y {
  position: absolute;
  width: 1px !important;
  height: 1px !important;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
/* Only when the hidden input itself has the keyboard's focus: the zone
   used to be outlined whenever anything inside it had focus, which drew a
   box round a whole row of buttons after an ordinary click on one. */
:has(> .obs-file-a11y:focus-visible) {
  outline: 2px solid var(--accent-strong);
  outline-offset: 2px;
}


/* ---------------------------------------------------------------------------
   Dark mode that does not wait for JavaScript.

   theme.js sets data-theme on the root element, and until now that was the
   only thing that ever made the site dark. So a phone set to dark still got
   a white page whenever the script had not run yet, or a stale light
   preference was sitting in storage from an earlier visit. These rules make
   the operating system setting enough on its own, for every colourway.

   A deliberate choice still wins either way. data-theme of light opts out of
   the block below, and data-theme of dark above turns dark on whatever the
   system is doing.

   color-scheme is the other half of it. It is what tells the browser to draw
   its own parts to match: scrollbars, the default form controls, and the
   strip behind the status bar on a phone.
   --------------------------------------------------------------------------- */

:root { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { color-scheme: dark; }

  :root:not([data-color-theme]):not([data-theme="light"]) {
    --paper: #121215;
    --paper-raised: #19191E;
    --paper-inset: #0E0E11;
    --rule: #2A2A31;
    --rule-strong: #3B3B45;
    --ink: #ECECF1;
    --ink-soft: #B8B8C3;
    --ink-mid: #B8B8C3;
    --ink-faint: #878792;
    --accent: #8AA4FF;
    --accent-strong: #A9BCFF;
    --accent-soft: #1B2340;
    --accent-border: rgba(138, 164, 255, 0.35);
    --good: #7FCB93;
    --good-soft: #1E3324;
    --danger: #E08669;
    --danger-soft: #3A2420;
    --warn: #E0B05F;
    --warn-soft: #332710;
    --focus: #8AA4FF;
    --card-bg: var(--paper-raised);
  }

  :root[data-color-theme="botanical"]:not([data-theme="light"]) {
    --paper: #121215;
    --paper-raised: #19191E;
    --paper-inset: #0E0E11;
    --rule: #2A2A31;
    --rule-strong: #3B3B45;
    --ink: #ECECF1;
    --ink-soft: #B8B8C3;
    --ink-mid: #B8B8C3;
    --ink-faint: #878792;
    --accent: #8AA4FF;
    --accent-strong: #A9BCFF;
    --accent-soft: #1B2340;
    --accent-border: rgba(138, 164, 255, 0.35);
    --good: #7FCB93;
    --good-soft: #1E3324;
    --danger: #E08669;
    --danger-soft: #3A2420;
    --warn: #E0B05F;
    --warn-soft: #332710;
    --focus: #8AA4FF;
    --card-bg: var(--paper-raised);
  }

  :root[data-color-theme="purple"]:not([data-theme="light"]) {
    --paper: #110D1A !important;
    --paper-raised: #191424 !important;
    --paper-inset: #0C0913 !important;
    --rule: #2F2542 !important;
    --rule-strong: #493B63 !important;
    --ink: #ECE6F6 !important;
    --ink-soft: #B6ABCE !important;
    --ink-mid: #B6ABCE !important;
    --ink-faint: #8D80A6 !important;
    --accent: #A86CF8 !important;
    --accent-strong: #C497FA !important;
    --accent-soft: #2B1842 !important;
    --accent-border: rgba(168, 108, 248, 0.4) !important;
    --good: #5ED995 !important;
    --good-soft: #153424 !important;
    --danger: #E68875 !important;
    --danger-soft: #381E19 !important;
    --focus: #A86CF8 !important;
    --card-bg: var(--paper-raised) !important;
  }

  :root[data-color-theme="monochrome"]:not([data-theme="light"]) {
    --paper: #121212 !important;
    --paper-raised: #1C1C1C !important;
    --paper-inset: #0A0A0A !important;
    --rule: #2C2C2C !important;
    --rule-strong: #444444 !important;
    --ink: #EFEFEF !important;
    --ink-soft: #B2B2B2 !important;
    --ink-mid: #B2B2B2 !important;
    --ink-faint: #8E8E8E !important;
    --accent: #E0E0E0 !important;
    --accent-strong: #FFFFFF !important;
    --accent-soft: #282828 !important;
    --accent-border: rgba(224, 224, 224, 0.35) !important;
    --good: #68D391 !important;
    --good-soft: #1B3324 !important;
    --danger: #E28578 !important;
    --danger-soft: #381F1C !important;
    --focus: #E0E0E0 !important;
    --card-bg: var(--paper-raised) !important;
  }

  :root[data-color-theme="atlas"]:not([data-theme="light"]) {
    --paper: #0E151E !important;
    --paper-raised: #161F2B !important;
    --paper-inset: #090F16 !important;
    --rule: #26364A !important;
    --rule-strong: #3B5170 !important;
    --ink: #E4EBF3 !important;
    --ink-soft: #B0BFD1 !important;
    --ink-mid: #B0BFD1 !important;
    --ink-faint: #8798AC !important;
    --accent: #6BA6E8 !important;
    --accent-strong: #93C2F5 !important;
    --accent-soft: #1B2C42 !important;
    --accent-border: rgba(107, 166, 232, 0.35) !important;
    --good: #6FC7A2 !important;
    --good-soft: #16302A !important;
    --danger: #E58A8A !important;
    --danger-soft: #3A2224 !important;
    --focus: #6BA6E8 !important;
    --card-bg: var(--paper-raised) !important;
  }

  :root[data-color-theme="verdant"]:not([data-theme="light"]) {
    --paper: #0B1710 !important;
    --paper-raised: #12211A !important;
    --paper-inset: #071009 !important;
    --rule: #21382B !important;
    --rule-strong: #35543F !important;
    --ink: #E2EFE7 !important;
    --ink-soft: #ABC5B5 !important;
    --ink-mid: #ABC5B5 !important;
    --ink-faint: #829C8C !important;
    --accent: #45C48D !important;
    --accent-strong: #6FDCA9 !important;
    --accent-soft: #12301F !important;
    --accent-border: rgba(69, 196, 141, 0.35) !important;
    --good: #64D69C !important;
    --good-soft: #133024 !important;
    --danger: #E2896F !important;
    --danger-soft: #35211C !important;
    --focus: #45C48D !important;
    --card-bg: var(--paper-raised) !important;
  }

  :root[data-color-theme="yellow"]:not([data-theme="light"]) {
    --paper: #16130A !important;
    --paper-raised: #1F1A0D !important;
    --paper-inset: #0F0D06 !important;
    --rule: #3A3116 !important;
    --rule-strong: #5A4C25 !important;
    --ink: #F4EDD6 !important;
    --ink-soft: #C9BC92 !important;
    --ink-mid: #C9BC92 !important;
    --ink-faint: #948861 !important;
    --accent: #F0C24B !important;
    --accent-strong: #FFD976 !important;
    --accent-soft: #30260E !important;
    --accent-border: rgba(240, 194, 75, 0.35) !important;
    --good: #A9CE6A !important;
    --good-soft: #22300F !important;
    --danger: #E2896F !important;
    --danger-soft: #35211C !important;
    --focus: #F0C24B !important;
    --card-bg: var(--paper-raised) !important;
  }

  :root[data-color-theme="red"]:not([data-theme="light"]) {
    --paper: #180E0C !important;
    --paper-raised: #221310 !important;
    --paper-inset: #0F0806 !important;
    --rule: #3D211C !important;
    --rule-strong: #5E332B !important;
    --ink: #F5E3DF !important;
    --ink-soft: #C9A79F !important;
    --ink-mid: #C9A79F !important;
    --ink-faint: #A07F77 !important;
    --accent: #F0705A !important;
    --accent-strong: #FF9481 !important;
    --accent-soft: #351813 !important;
    --accent-border: rgba(240, 112, 90, 0.35) !important;
    --good: #64D69C !important;
    --good-soft: #133024 !important;
    --danger: #FF7F68 !important;
    --danger-soft: #3A1712 !important;
    --focus: #F0705A !important;
    --card-bg: var(--paper-raised) !important;
  }

  :root[data-color-theme="pink"]:not([data-theme="light"]) {
    --paper: #170B12 !important;
    --paper-raised: #211018 !important;
    --paper-inset: #0E060A !important;
    --rule: #3C1E2E !important;
    --rule-strong: #5C3047 !important;
    --ink: #F7E1EC !important;
    --ink-soft: #CDA3BA !important;
    --ink-mid: #CDA3BA !important;
    --ink-faint: #A07A8F !important;
    --accent: #F274AB !important;
    --accent-strong: #FF9AC4 !important;
    --accent-soft: #331623 !important;
    --accent-border: rgba(242, 116, 171, 0.35) !important;
    --good: #64D69C !important;
    --good-soft: #133024 !important;
    --danger: #E2896F !important;
    --danger-soft: #35211C !important;
    --focus: #F274AB !important;
    --card-bg: var(--paper-raised) !important;
  }

  :root[data-color-theme="tide"]:not([data-theme="light"]) {
    --paper: #0A1617 !important;
    --paper-raised: #122021 !important;
    --paper-inset: #060F10 !important;
    --rule: #22373A !important;
    --rule-strong: #375457 !important;
    --ink: #E2EFEF !important;
    --ink-soft: #ABC4C5 !important;
    --ink-mid: #ABC4C5 !important;
    --ink-faint: #7A9496 !important;
    --accent: #4FC5CE !important;
    --accent-strong: #7FDCE2 !important;
    --accent-soft: #0F3034 !important;
    --accent-border: rgba(79, 197, 206, 0.35) !important;
    --good: #6FC7A2 !important;
    --good-soft: #16302A !important;
    --danger: #E9897F !important;
    --danger-soft: #37211E !important;
    --focus: #4FC5CE !important;
    --card-bg: var(--paper-raised) !important;
  }

  :root[data-color-theme="ember"]:not([data-theme="light"]) {
    --paper: #17100B !important;
    --paper-raised: #211812 !important;
    --paper-inset: #0F0A06 !important;
    --rule: #3A2A1F !important;
    --rule-strong: #57402F !important;
    --ink: #F5E7DB !important;
    --ink-soft: #CBB2A0 !important;
    --ink-mid: #CBB2A0 !important;
    --ink-faint: #A38C7C !important;
    --accent: #F08A4B !important;
    --accent-strong: #FBA76D !important;
    --accent-soft: #3A2413 !important;
    --accent-border: rgba(240, 138, 75, 0.35) !important;
    --good: #7FCB93 !important;
    --good-soft: #1E3324 !important;
    --danger: #EC8A7A !important;
    --danger-soft: #38211D !important;
    --focus: #F08A4B !important;
    --card-bg: var(--paper-raised) !important;
  }

  :root[data-color-theme="ink"]:not([data-theme="light"]) {
    --paper: #0C0D14 !important;
    --paper-raised: #14161F !important;
    --paper-inset: #07080D !important;
    --rule: #242838 !important;
    --rule-strong: #3A4056 !important;
    --ink: #E7E9F2 !important;
    --ink-soft: #B3B8CC !important;
    --ink-mid: #B3B8CC !important;
    --ink-faint: #838AA3 !important;
    --accent: #8D97D8 !important;
    --accent-strong: #AAB3E8 !important;
    --accent-soft: #1C2036 !important;
    --accent-border: rgba(141, 151, 216, 0.35) !important;
    --good: #7FCB93 !important;
    --good-soft: #1E3324 !important;
    --danger: #E58A93 !important;
    --danger-soft: #341F24 !important;
    --focus: #8D97D8 !important;
    --card-bg: var(--paper-raised) !important;
  }

  :root[data-color-theme="sepia"]:not([data-theme="light"]) {
    --paper: #16120B !important;
    --paper-raised: #1F1A11 !important;
    --paper-inset: #0E0B06 !important;
    --rule: #362C1E !important;
    --rule-strong: #51432E !important;
    --ink: #F2E8D6 !important;
    --ink-soft: #C8B99E !important;
    --ink-mid: #C8B99E !important;
    --ink-faint: #968871 !important;
    --accent: #D8AC5E !important;
    --accent-strong: #E9C684 !important;
    --accent-soft: #372A14 !important;
    --accent-border: rgba(216, 172, 94, 0.35) !important;
    --good: #8AC998 !important;
    --good-soft: #20321F !important;
    --danger: #E3907B !important;
    --danger-soft: #37221B !important;
    --focus: #D8AC5E !important;
    --card-bg: var(--paper-raised) !important;
  }

  :root[data-color-theme="contrast"]:not([data-theme="light"]) {
    --paper: #000000 !important;
    --paper-raised: #0D0D0D !important;
    --paper-inset: #000000 !important;
    --rule: #8F8F8F !important;
    --rule-strong: #FFFFFF !important;
    --ink: #FFFFFF !important;
    --ink-soft: #EBEBEB !important;
    --ink-mid: #EBEBEB !important;
    --ink-faint: #CCCCCC !important;
    --accent: #FFD60A !important;
    --accent-strong: #FFE45C !important;
    --accent-soft: #2E2600 !important;
    --accent-border: rgba(255, 214, 10, 0.7) !important;
    --good: #7EE0A0 !important;
    --good-soft: #0E2A18 !important;
    --danger: #FF8A7A !important;
    --danger-soft: #3A0F0A !important;
    --warn: #FFC66B !important;
    --warn-soft: #2E1F00 !important;
    --focus: #FFD60A !important;
    --card-bg: var(--paper-raised) !important;
  }
}

/* ==================================================================
   Motion.

   The tokens are at the top of this file (section 0). What follows is
   where each one is spent, and only on the two properties the compositor
   can animate without laying the page out again, transform and opacity
   (colour and border changes ride along, they are cheap). Nothing here
   animates width, height, padding or position, and nothing runs on a
   timer, so this costs nothing while the page is idle.

   Every selector is wrapped in :where(), which has no specificity, so
   any page that already styles its own controls keeps its own rules.
   The data-motion="none" block above turns all of it into fades for
   anybody who has asked for that, from Settings or from their system.
   ================================================================== */
:root {
  /* How far a thing rises as it arrives: 14px for a page, a title block,
     a panel or a dialog; 8px for a row in a list; 24px for a section
     revealed as it is scrolled to. All nought with motion switched down.
     They were 8, 4 and 12, which on a large screen read as no movement at
     all (the owner, 22 October 2026: "where are the animations"). */
  --os-rise: 14px;
  --os-rise-sm: 8px;
  --os-rise-lg: 24px;
  /* How small something small starts as it arrives - a tile's icon, a
     tick, a box being checked - before it grows to its size on the
     spring. One with motion switched down, so it simply fades. */
  --os-pop: 0.85;
  /* The steps of the title block: the heading, the line under it and the
     first panel, one after another. */
  --os-step: 40ms;
  /* The fade that stands in for every entrance with motion switched down.
     Declared here too so a rule can name it whatever the setting. */
  --dur-fade: 100ms;
}


/* Page to page. */
/* Off (the owner, 7 October 2026: "the top bar is buggy and blurry").
   A navigation between pages was a cross-document view transition, and
   the bar, the mark and the active tab were carried across it as named
   snapshots: pictures of the old bar scaled into the new one. On an
   iPhone the bar is a different size on different pages, so for a third
   of a second it was a stretched bitmap, soft and moving. The next page
   now simply appears, as an application's next screen does. The fade
   between colourways (html.theme-swap, below) is a same-page transition
   and stays. */
/* With motion off theme.js skips the transition on both pages; this holds
   any it could not reach (one started on the page itself) to a cut. */
:root[data-motion="none"]::view-transition-group(*),
:root[data-motion="none"]::view-transition-old(*),
:root[data-motion="none"]::view-transition-new(*) { animation: none !important; }
@keyframes os-fade-out { to { opacity: 0; } }
@keyframes os-fade-in { from { opacity: 0; } }

/* Links settle instead of snapping. */
:where(a) {
  transition: color var(--dur-2) var(--ease-standard), opacity var(--dur-2) var(--ease-standard);
}
/* A link no component has coloured takes the site's link colour, not the
   browser's #0000EE: the applications on every industry page, the copy on
   /desktop, /fieldkit and Keep's footnote were all in it. At no specificity,
   so every link a component styles keeps its own. */
:where(a:any-link) { color: var(--accent-strong); text-underline-offset: 3px; }

/* Anything that acts like a button gives a press.

   A scale is a ratio, and a ratio presses a wide button much harder than a
   narrow one: at 0.94 a 28px icon moves a pixel and a full-width button on
   a phone moves nineteen, which reads as rubber rather than as a button
   going down. 0.98 is felt on a small button and still reads as a button
   on a wide one. It goes down in --dur-1 and comes back in --dur-2: the
   transition on :active is the one used going down, the one on the
   button itself the one used coming back. */

:where(button, [role="button"], .btn, .btn-primary, .action-btn, .tab-btn, .nav-tab-btn, .mode-btn, .btn-search, .btn-settings, input[type="submit"], input[type="button"], summary)  {
  transition: transform var(--dur-2) var(--ease-standard),
              opacity var(--dur-2) var(--ease-standard),
              background-color var(--dur-2) var(--ease-standard),
              border-color var(--dur-2) var(--ease-standard),
              color var(--dur-2) var(--ease-standard),
              box-shadow var(--dur-2) var(--ease-standard);
}

/* Not on a part of a drawing: an SVG element placed by its transform
   attribute (a person's box in Roots's charts) would be moved by any CSS
   transform to the drawing's corner while pressed, and the press would
   end somewhere else. */
:where(button, [role="button"]:not(svg *), .btn, .btn-primary, .action-btn, .tab-btn, .nav-tab-btn, .mode-btn, .btn-search, .btn-settings, input[type="submit"], input[type="button"], summary):active  { transform: scale(0.98); transition-duration: var(--dur-1); }

:where(button, [role="button"], .btn, .btn-primary, .action-btn, .tab-btn, .nav-tab-btn, .mode-btn, .btn-search, .btn-settings, input[type="submit"], input[type="button"], summary):disabled, :where(button, [role="button"], .btn, .btn-primary, .action-btn, .tab-btn, .nav-tab-btn, .mode-btn, .btn-search, .btn-settings, input[type="submit"], input[type="button"], summary)[aria-disabled="true"]  { transform: none; }

/* Fields. */
:where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), textarea, select) {
  transition: border-color var(--dur-2) var(--ease-standard),
              box-shadow var(--dur-2) var(--ease-standard),
              background-color var(--dur-2) var(--ease-standard);
}

/* The focus ring eases in rather than appearing. */
:where(:focus-visible) {
  transition: outline-offset var(--dur-1) var(--ease-standard), box-shadow var(--dur-1) var(--ease-standard);
}


/* Questions at the foot of a page: the visible twin of its FAQPage
   structured data. Search engines require the answers to be on the page,
   not only in the JSON-LD, and readers get them too. */
/* A link in the body of a guide, made visible.

   It was not. theme.css sets links to inherit their colour and carry no
   underline, and each of the classes that wants a visible link adds its
   own treatment on top - .os-faq-link, .faq-open, .ref-row and the rest
   all do. The guide body had no such rule, so every internal link inside
   one rendered in exactly the body colour with no decoration at all: the
   same rgb, the same weight, nothing to distinguish it. Eighty-nine
   guides, and the links out of them to the tools they describe were
   invisible to a reader.

   That is not restraint, it is a missing rule, and it cost the thing the
   guides exist to do. The treatment is the one the rest of the site
   already uses for a link in running text. */
.gd p a, .gd li a, .gd td a, .gd-answer a { color: var(--accent-strong); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.gd p a:hover, .gd li a:hover, .gd td a:hover, .gd-answer a:hover { text-decoration-thickness: 2px; }

/* The questions on a tool page, in one fold.

   A tool page should be the tool, and a heading with a dozen folded
   questions under every one of them read as a manual, which is why these
   were taken off and put on /faq. Closed, this is one line - the same
   line that used to point at /faq, in the same words - so the page still
   ends with the tool; open, it is the accordion, on the page somebody
   searching that question should have landed on.

   A <summary> holding an <h2> needs its own row built, because the
   default marker is a list-item marker and a block-level heading inside
   the summary puts it on a line of its own above the words. */
details.os-faq-fold { padding: 18px 26px; }
details.os-faq-fold > summary { list-style: none; cursor: pointer; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 6px 0; min-height: 44px; box-sizing: border-box; color: var(--ink); }
details.os-faq-fold > summary::-webkit-details-marker { display: none; }
details.os-faq-fold > summary h2 { font-family: var(--font-headline); font-size: var(--text-xl); font-weight: 700; margin: 0; }
details.os-faq-fold > summary::after { content: "+"; flex: 0 0 auto; font: var(--text-xl)/1 var(--font-mono, monospace); color: var(--accent-strong); transition: transform var(--dur-3) var(--ease-standard); }
details.os-faq-fold[open] > summary::after { transform: rotate(45deg); }
details.os-faq-fold > summary:hover, details.os-faq-fold > summary:hover h2 { color: var(--accent-strong); }
details.os-faq-fold .os-faq { max-width: none; margin: 14px 0 0; padding: 0; }
details.os-faq-fold .faq-list { max-width: none; }

.os-faq { max-width: 760px; margin: 40px auto 0; padding: 0 20px; }
.os-faq h2 { font-family: var(--font-headline); font-size: var(--text-xl); margin: 0 0 12px; }
.os-faq details { border-top: 1px solid var(--rule); padding: 12px 0; }
.os-faq details:last-of-type { border-bottom: 1px solid var(--rule); }
.os-faq summary { cursor: pointer; font-weight: 600; list-style: none; display: flex; justify-content: space-between; gap: 12px; color: var(--ink); }
.os-faq summary::-webkit-details-marker { display: none; }
.os-faq summary::after { content: "+"; flex: 0 0 auto; color: var(--accent, currentColor); }
.os-faq details[open] summary::after { content: "\2212"; }
.os-faq p { margin: 10px 0 0; color: var(--ink-soft); line-height: 1.6; }

/* The full list of tools on a suite page: one line each, every tool the
   header menu knows about, so the suite page (which search engines and
   first-time visitors read) is never shorter than the menu. */


/* ---------------------------------------------------------------
Tablets: the stacked bar scrolls with the page.

Between 600px and 1040px the header takes two rows, about 100px,
and pinned to the top of an iPad that is a tenth of the screen
gone for the whole visit. Pinning only pays when the bar is one
row, so the stacked bar scrolls away like any other block.
--------------------------------------------------------------- */
@media (min-width: 601px) and (max-width: 1040px) {
  .os-topbar,
  .os-topbar.is-stacked { position: static; }
}

/* ---------------------------------------------------------------
Touch: no zoom on tap, and room for a thumb.

iOS Safari zooms the whole page when a form control set below
16px is tapped, and the page stays zoomed after it. Most of the
site's fields are 12px to 15px, which is why filling anything in
on an iPhone ended with a pinch. On a touch screen every text
control is 16px, which is the one size that does not trigger it.
Checkboxes and the like keep their size.

This was written `(max-width: 767px) and (pointer: coarse)`, and
767 is the bug. An iPhone turned on its side is 844 pixels wide
and an iPad is 744 to 1024, so the rule excluded a phone in
landscape and every tablet but the mini - the fields on /sheets
and /docs measured 12px on all of them, and tapping the formula
bar on an iPad zoomed the page and left it there. A coarse
pointer is a finger whatever the screen is wide, and a finger
wants 16px type regardless, so the width goes. A tablet has more
room for the larger control than a phone does, not less.

The footer links, the previous and next strip and the licence
line are 13px text with no padding: right for a pointer, a
gamble for a finger. On a touch screen they get a taller hit
area without moving anything, by padding inside a negative
margin.
--------------------------------------------------------------- */
@media (pointer: coarse) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="color"]):not([type="hidden"]),
  select,
  textarea {
    font-size: var(--text-md) !important;
  }
}
@media (pointer: coarse) {
  /* Footer links are the last thing on a page and the one place a thumb
     has nothing else to aim at, so they take the floor outright rather
     than padding that happens to add up. 12px either side of a 20px line
     does reach 44, but the home page's footer sets 13.5px type and its
     line box landed the row at 40. A short label is narrow as well as
     short: "FAQ" measured 27px across, so the width is held open too. */
  .os-footer a,
  .boot-foot a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    min-height: 44px;
    min-width: 44px;
    padding: 0 6px;
    margin: 0 -6px;
  }
  .os-faq summary { padding: 6px 0; margin: -6px 0; }
  #ppBadge { min-height: 44px; }

  /* ---------------------------------------------------------------
     A phone is not a narrow desktop. Everything below is a hit area
     that measured under 44px on a real handset, found by walking every
     page with touch emulation rather than by resizing a window.
     --------------------------------------------------------------- */

  /* A floor means the whole control, not the whole control plus whatever
     padding it happens to carry. A page that never reset box-sizing leaves
     these on content-box, where min-height is added to the padding and the
     border instead of absorbed by them, so the number in the stylesheet is
     not the number on the screen: the related-tool cards asked for 52 and
     rendered at 78, and the Settings button in the header asked for 44 and
     rendered at 58. Every selector below is one the floors are set on. */
input, select, textarea, button, summary, .os-footer a, .boot-foot a, #obLicenceNote a, .os-faq summary, .brand-title, .btn-search, .btn-settings, .nav-tab-btn, .ref-row, .seg button, .tab-btn, .pf-tab, .os-jump a, .btn {
    box-sizing: border-box;
  }

  /* A tick box at 13px is a coin toss. These are the controls that
     decide what a tool does, so they get a real target, and the label
     beside one grows with it. */
  input[type="checkbox"],
  input[type="radio"] {
    width: 22px;
    height: 22px;
    min-width: 22px;
    min-height: 22px;
    flex: none;
    accent-color: var(--accent-strong);
  }
  label > input[type="checkbox"],
  label > input[type="radio"] {
    margin-right: 8px;
  }
  label:has(> input[type="checkbox"]),
  label:has(> input[type="radio"]) {
    min-height: 44px;
    display: flex;
    align-items: center;
  }

  /* A slider you cannot get hold of is a slider you cannot use. */
  input[type="range"] {
    min-height: 44px;
  }

  /* Every other field. Apps written before the phone mattered set their
     own heights around 32 to 39px, which is under the floor on all of
     them, so the floor is set once here rather than app by app. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="hidden"]):not([type="color"]),
  select,
  textarea,
  button {
    min-height: 44px;
  }
  /* Except where a control is deliberately tiny and sits inside a row of
     its own, where 44 would break the layout it lives in. The close cross
     on a tab still grows, because a 17px cross on a phone is a dare. */
  .nl-ac div,
  td button,
  th button { min-height: 0; }
  .nl-tab .nl-x { min-height: 30px; min-width: 30px; }
  .nl-tab { padding-left: 15px; }

  /* The reference rows in the questions page were a shade under. */
  .ref-row { min-height: 48px; display: flex; align-items: center; }

  /* The workspace mini toolbar's own brand link and its tabs. */

  /* A brand mark with its wordmark hidden is 27px. */
.brand-title { min-width: 44px; justify-content: center; }

  /* The shared button shape. 12px of padding on a 15px line comes to 42,
     which is close enough to look right and two pixels short of usable.
     The box-sizing goes with it: a page that never reset it leaves .btn on
     content-box, where a 44px floor means 44 plus the padding and the
     border, and the buy buttons on the teams page came out at 70. */
  .btn { min-height: 44px; box-sizing: border-box; }

  /* A control that has to stay small to read as what it is: the tick on a
     request step is a 22px circle and a 44px one would read as a button.
     The circle keeps its size and an invisible box around it takes the
     press, which is what a thumb actually lands on. */
  .ds-tick { position: relative; }
  .ds-tick::after {
    content: "";
    position: absolute;
    top: -11px;
    left: -11px;
    right: -11px;
    bottom: -11px;
  }

  /* Segmented choices, which the settings page is built out of. */
  .seg button,
  .tab-btn,
  .pf-tab { min-height: 44px; }
}

/* The row.
   ---------------------------------------------------------------------------
   One shape, used everywhere. The site had cards, grids, pills, tiles and
   tables all doing the same job - "here is a thing, go to it" - and each one
   had to be learned separately. This is that job, once: an optional coloured
   tile, a label, an optional line under it, an optional count, and a chevron
   saying it goes somewhere.

   Two densities, because a list of five and a list of eighty-six are not the
   same problem. Loose is the front-door shape: separate raised rows with air
   between them, comfortable, and far too tall to use eighty-six times. Tight
   is one card with hairlines between the rows, which is what a long list
   wants - eighty-six loose rows is six thousand pixels of scrolling, and the
   same eighty-six tight is a third of that and easier to read.
   --------------------------------------------------------------------------- */
.os-list { display: flex; flex-direction: column; gap: 12px; width: 100%; }
/* Some of these lists are a real <ul>, because a list of links is a list and
   a screen reader should be able to say how many. The <li> becomes the cell
   and the row fills it, rather than the <li> being taken out of the layout
   with display:contents - which does the same job and has a history of
   costing the list its semantics in the process, which was the only reason
   for keeping the <ul>. */
.os-list > li { display: flex; margin: 0; padding: 0; }
.os-list > li > .os-row { flex: 1 1 auto; min-width: 0; }
.os-row {
  display: flex;
  align-items: center;
  gap: 14px;
  box-sizing: border-box;
  min-height: 76px;
  padding: 14px 20px 14px 22px;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--paper-raised);
  color: var(--ink);
  text-decoration: none;
  text-align: left;
  box-shadow: var(--elev-1);
  transition: transform var(--dur-3) var(--ease-standard), box-shadow var(--dur-3) var(--ease-standard);
}
/* A row can be a button (the folder row on the front door): same shape,
   no chevron, because it does not go anywhere. */
button.os-row { width: 100%; font: inherit; cursor: pointer; appearance: none; -webkit-appearance: none; }
button.os-row[aria-disabled="true"] { cursor: default; }
.os-row:hover { transform: translateY(-1px); box-shadow: var(--elev-2); }
.os-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.os-row-ico {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 30px; height: 30px; border-radius: var(--radius-md);
  background: var(--accent-soft); color: var(--accent-strong);
}
.os-row-ico svg { width: 18px; height: 18px; }
.os-row-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.os-row-text b {
  font-family: var(--font-ui); font-size: var(--text-md); font-weight: 600;
  letter-spacing: -.006em; line-height: 1.3;
}
.os-row-sub {
  font-size: var(--text-sm); line-height: 1.5; color: var(--ink-soft);
  letter-spacing: var(--track-body);
}
.os-row-meta {
  flex: none; font: 500 var(--text-2xs) var(--font-mono, ui-monospace, monospace);
  letter-spacing: .06em; color: var(--ink-faint);
}
/* The chevron is drawn rather than shipped. It appears once per row, and the
   sitemap alone has a hundred and thirty-eight of them: an inline SVG there
   costs thirty-four kilobytes of markup to say the same three strokes a
   rotated corner says for nothing. Only on a link, because the chevron's
   whole claim is that the row goes somewhere. */
a.os-row::after {
  content: ""; flex: none; box-sizing: border-box;
  width: 7px; height: 7px; margin: 0 3px 0 2px;
  border-right: 1.9px solid currentColor; border-top: 1.9px solid currentColor;
  border-radius: var(--radius-sm);
  transform: rotate(45deg);
  color: var(--ink-faint); opacity: .7;
}

/* Tight: one card, hairlines between. */
.os-list.tight {
  gap: 0;
  border-radius: var(--radius-md);
  background: var(--paper-raised);
  box-shadow: var(--elev-1);
  overflow: hidden;
}
/* background-COLOUR, not background: a row's background-image is left to
   whatever a page gives it. */
.os-list.tight .os-row {
  min-height: 56px;
  padding: 11px 16px 11px 18px;
  border-radius: 0;
  box-shadow: none;
  background-color: transparent;
  border-top: 1px solid var(--rule);
}
.os-list.tight > .os-row:first-child,
.os-list.tight > li:first-child > .os-row { border-top: 0; }
/* A row inside a card does not lift: the card's hairlines are drawn by the
   rows themselves, so lifting one tears the grid it is part of. It gets the
   tint and the chevron instead. */
.os-list.tight .os-row:hover { transform: none; background-color: var(--paper-inset); box-shadow: none; }
.os-list.tight .os-row:active { transform: none; }
.os-list.tight .os-row-text b { font-size: var(--text-md); }
.os-list.tight .os-row-ico { width: 26px; height: 26px; border-radius: var(--radius-sm); }
.os-list.tight .os-row-ico svg { width: 15px; height: 15px; }

/* Two up, on a screen wide enough to read two. A tight list of eighty-six is
   still four thousand pixels in one column, which is a scroll, not an index.
   Grid rather than CSS columns because columns break a row in half at the
   bottom of the first one. The hairlines are placed by position: every row
   gets a top rule except the first of each column, and the right-hand column
   gets a left rule, so the card keeps its single outline however many rows
   land in it. */
@media (min-width: 900px) {
  .os-list.tight.two { display: grid; grid-template-columns: 1fr 1fr; align-content: start; }
  .os-list.tight.two > .os-row:nth-child(2),
  .os-list.tight.two > li:nth-child(2) > .os-row { border-top: 0; }
  .os-list.tight.two > .os-row:nth-child(even),
  .os-list.tight.two > li:nth-child(even) > .os-row { border-left: 1px solid var(--rule); }
  /* An odd count leaves the last cell empty, and an empty cell inside a card
     reads as a row that failed to render rather than as the end of the list.
     The odd one out takes the whole width instead, which closes the card. */
  .os-list.tight.two > .os-row:nth-child(odd):last-child { grid-column: 1 / -1; }
  .os-list.tight.two > li:nth-child(odd):last-child { grid-column: 1 / -1; }
}

:root[data-color-theme="monochrome"] .os-row-ico { background: #6b6b6b !important; }


@media (max-width: 560px) {
  .os-row { min-height: 68px; padding: 12px 16px 12px 18px; border-radius: var(--radius-md); gap: 12px; }
  .os-row-text b { font-size: var(--text-lg); }
}
:root[data-motion="none"] .os-row { transition: none; }
:root[data-motion="none"] .os-row:hover { transform: none; }

/* The group tile.
   A hundred and thirty-eight tools under twenty-nine headings is a long list,
   and a long list of identical rows is read by nobody. The tile carries the
   group's colour and its glyph; the colour is computed in group-icons.mjs so
   that a white glyph clears 3:1 on every one of them, and it is passed in as
   --ico rather than written into a class, because twenty-nine classes for
   twenty-nine colours is a stylesheet nobody can keep in step with the list.
   ------------------------------------------------------------------------ */
.os-group-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  color: var(--accent-strong);
  vertical-align: -6px;
  margin-right: 9px;
}
.os-group-ico svg { width: 16px; height: 16px; }
/* The group's own colour is kept on /sitemap, where it is what lets an eye
   find one heading of twenty-nine. Everywhere else a heading is one of a
   handful and the colour was decoration: the accent on its own tint, as
   the tiles have. */
body:has(.os-sitemap-all) .os-group-ico { background: var(--ico, var(--accent-strong)); color: #fff; }

/* Monochrome is a colourway somebody chose because they wanted no colour.
   The tiles go grey and the glyph does the work on its own. */
:root[data-color-theme="monochrome"] body:has(.os-sitemap-all) .os-group-ico { background: #6b6b6b !important; }

/* A tile is a block of saturated colour, and on a page somebody has asked to
   be still, it should not shout. Slightly softened, never removed: the glyph
   is the label and it has to stay legible. */
@media (prefers-contrast: less) {
  .os-group-ico { opacity: .92; }
}

/* A heading that has to be an h2 for the document's outline but should
   look like the smaller heading it replaced. Skipping a level reads as a
   missing section to a crawler and to a screen reader alike. */
.as-h3 { font-size: var(--text-lg); font-family: var(--font-sans); font-weight: 700; margin: 0; }

/* Related tools at the foot of a tool page. Hand-picked neighbours, written
   into the static HTML so a crawler and a first-time reader both see where to
   go next: os-nav.js adds prev/next at runtime, but that is invisible to a
   crawler and useless to anyone who arrived on this page from a search result. */
/* The same column as the page above it. These blocks are written in after
   the closing </main> by the build, so they take their width from here
   rather than from the page, and 760 against the page's 944 left every
   tool page with two left edges seventy pixels apart. */
/* width: 100% matters. Two of the apps lay their page out as a flex
   column, and in a flex column a pair of auto side margins does not
   centre a full-width block — it shrinks the block to its text and
   centres that, which is how the questions line on Sheets ended up three
   hundred pixels in from everything else. */
.os-rel { max-width: 944px; width: 100%; box-sizing: border-box; margin: 40px auto 0; padding: 0 22px; }

/* On a tool page these blocks are siblings of <main> and have to draw
   their own column. On a page that keeps them inside <main> the column is
   already drawn, and drawing it again centres them inside it: the
   questions link on a prose page sat a hundred pixels right of the prose
   it belonged to. Inside, they take the parent's width and nothing else. */
main .os-rel, main .os-faq-link {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding-left: 0;
  padding-right: 0;
}
.os-rel h2 { font-family: var(--font-headline); font-size: var(--text-xl); margin: 0 0 14px; }
/* The list itself is an .os-row list now, the same one the sitemap and the
   suite pages use, so the foot of a tool page and the index it came from are
   the same shape. All that is left here is the column it sits in. */
.os-rel ul { list-style: none; margin: 0; padding: 0; }

/* The foot of a guide: the cluster it is filed under, and the industry
   pages that list it. One line under the rows, quieter than they are. */
.gd-more-foot { font-size: var(--text-sm); line-height: 1.6; color: var(--ink-soft); margin: 12px 0 0; }
.gd-more-foot a { color: var(--accent-strong); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }

/* The link to a section of a guide, beside its heading. It carries no text
   of its own, so the heading reads the same to a screen reader's list of
   headings and to anything that hashes the page's words; the mark is drawn
   here. Faint until the heading is pointed at or the link is focused, and
   always faintly there on a screen with no pointer to hover with. */
.gd-anchor {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; margin: -10px 0 -10px -4px; vertical-align: middle;
  color: var(--ink-faint); text-decoration: none; opacity: 0;
  transition: opacity var(--dur-2) var(--ease-standard);
}
.gd-anchor::before { content: "#"; font: 500 var(--text-md)/1 var(--font-mono, monospace); }
h2:hover > .gd-anchor, .gd-anchor:focus-visible { opacity: 1; }
.gd-anchor:hover { color: var(--accent-strong); }
@media (hover: none) { .gd-anchor { opacity: .55; } }
h2[id] { scroll-margin-top: 78px; }

/* /guides: each cluster is a panel the row of links at the top jumps to. */
.gd-cluster { scroll-margin-top: 78px; }
.gd-cluster .gd-cluster-lead { margin: -4px 0 16px; }
.gd-jump-n { margin-left: 7px; font: 500 var(--text-2xs)/1 var(--font-mono, monospace); color: var(--ink-faint); }

/* ==========================================================================
   SURFACES

   The interface language. Three ideas, and every value is a token, so all
   thirteen colourways and both modes follow it without a second definition.

   1. A raised thing separates by light, not by a line: a hairline and a soft
      shadow instead of a 1px border. That is what makes a page read as
      layers rather than as a stack of boxes.
   2. Two faces from one family: Source Sans 3 for the interface and Source
      Serif 4 for the headline of a page that is read. Both are served from
      this origin (section 0), so no font host is asked for anything.
   3. Corners on a scale: 4 for a control, 8 for a card, a field or a
      button, 12 for a dialog, and a pill only for chips and the bar.

   Most pages carry their own <style> block, which the browser reads after
   this file and which therefore wins a tie. The component rules below are
   written as ":root .panel" rather than ".panel" so they carry one more
   class of specificity and land on those pages too. A page that genuinely
   wants its own treatment can still say so with an id or the same prefix.
   ========================================================================== */

:root {
  /* Elevation. The half-pixel ring is the hairline; the two shadows are a
     tight contact shadow and a wide ambient one. */
  /* A hairline, not a shadow. Cards used to float on a soft blur, which
     is the look of a consumer app; a precise one-pixel edge is the look
     of an instrument, and the lift is kept for the moment of pointing. */
  --hairline: rgba(24, 20, 12, 0.10);
  --hairline-strong: rgba(24, 20, 12, 0.16);
  --elev-1: 0 0 0 1px var(--hairline);
  --elev-2: 0 0 0 1px var(--hairline-strong), 0 14px 30px -20px rgba(24, 20, 12, 0.28);
  --elev-sunk: inset 0 0 0 1px var(--hairline-strong);

  /* Tracking. Large type needs to be pulled in; small type does not.
     These were -0.026, -0.021 and -0.011em for the system grotesques, which
     are set loose; Source Sans is spaced close already, and the same values
     ran its letters together. */
  --track-display: -0.018em;
  --track-head: -0.01em;
  --track-body: 0em;
}

/* In the dark a shadow says nothing, so the hairline does the work alone. */
:root[data-theme="dark"] {
  --hairline: rgba(255, 255, 255, 0.09);
  --hairline-strong: rgba(255, 255, 255, 0.14);
  --elev-1: inset 0 0 0 1px var(--hairline);
  --elev-2: inset 0 0 0 1px var(--hairline-strong), 0 18px 36px -18px rgba(0, 0, 0, 0.62);
}

:root h1, :root h2, :root h3,
:root .page-title, :root .brand-title {
  font-family: var(--font-ui);
  letter-spacing: var(--track-head);
}

/* The headlines. Page titles, the front door's, and the headings of the
   prose pages and of the blocks under a tool - the lines that say what a
   page is. A panel's own heading inside a tool is part of the tool and
   stays in the interface face, as does the wordmark. A serif is set looser
   than a grotesque at the same size, so the tracking comes back towards
   zero and the weight down a step. */
:root .page-title, :root .boot-title,
:root .wrap #obscuraMainContent > h2, :root .os-rel h2, :root .os-faq h2,
:root details.os-faq-fold > summary h2 {
  font-family: var(--font-headline);
  font-weight: 600;
  letter-spacing: -0.012em;
  font-optical-sizing: auto;
}
:root h1, :root .page-title { letter-spacing: var(--track-display); }
/* A headline that wraps is one block, not two lines with a paragraph's
   gap between them: several pages set their title at 1.4 or more, which on
   a phone put a line of air through the middle of the name of the page. */
:root .page-title, :root .boot-title { line-height: 1.15; }

/* The bar the page scrolls under. It is a material rather than a card: the
   page shows through it, so it belongs to the window and not to the content. */
:root .os-topbar {
  background: var(--paper);
  /* Nearly solid: where the blur does not render (it is dropped under
     load, in some embedded views and in reduced-transparency modes) an 82%
     bar let headings and buttons read straight through it. */
  /* Solid. It was a frosted material (94% paper over a 20px blur), which
     on a phone read as a smeared bar over the page, and frosted glass is
     not this site's look. */
  border: 0;
  border-radius: 0;
  box-shadow: inset 0 -1px 0 var(--hairline);
  margin-bottom: 28px;
}
/* The bar runs the width of the window, as the front door's and the
   applications' do, with what is in it kept to the page's column. Held to
   1040px, its hairline stopped short of both edges on a wide screen, and
   the bar read as a strip laid on the page rather than the window's own. */
@media (min-width: 769px) {
  /* :where keeps this to two classes' weight, so the front door's own
     window bar (home-shell.css, .hx-page .os-topbar) still has the last
     word on it. */
  :where(:root) .os-topbar:not(.slim) {
    max-width: none;
    padding-left: max(18px, calc((100% - 1040px) / 2 + 18px));
    padding-right: max(18px, calc((100% - 1040px) / 2 + 18px));
  }
}

:root .panel,
:root .card {
  background: var(--paper-raised);
  border: 0;
  border-radius: var(--radius-md);
  box-shadow: var(--elev-1);
}

:root .btn,
:root .btn-primary,
:root button.btn-primary,
:root .btn-secondary,
:root button.btn-secondary,
:root .action-btn {
  border-radius: var(--radius-md);
  font-family: var(--font-sans);
  letter-spacing: var(--track-body);
  transition: transform var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard), background-color var(--dur-2) var(--ease-standard);
}
:root .btn-primary,
:root button.btn-primary,
:root .action-btn.primary {
  border: 0;
  box-shadow: 0 1px 2px rgba(16, 16, 24, 0.22);
}
/* A filled button's fill, at no specificity at all, so any page or tool.css
   that fills its own wins. Without it a button.btn-primary on a page that
   does not load tool.css - the guides with a calculator in them - had the
   browser's grey behind the label this sheet colours white: 1.15 to 1. */
:where(button.btn-primary) { background: var(--accent-strong); }

/* The colour is set here and not left to whatever came before, because
   `.btn` a few hundred lines up sets `color: var(--on-accent)` - white -
   on the assumption that a button carries the accent behind it. A
   secondary button does not: it carries paper. So `class="btn
   btn-secondary"` produced white text on a white background, an
   invisible label on a button that was otherwise perfectly fine, and
   the only reason it was not visible on most of the site is that
   tool.css happens to set the colour again and is loaded after this on
   the pages that carry it. On a page without tool.css - the front door
   among them - the label simply was not there.

   Neither the contrast checker nor the crawler could have found it:
   both read the pages as they are served, and every use of the pair was
   either on a page that loads tool.css or written by script at runtime. */
:root .btn-secondary,
:root button.btn-secondary,
:root a.btn-secondary {
  background: var(--paper-raised);
  color: var(--ink);
  border: 0;
  box-shadow: 0 0 0 0.5px var(--hairline-strong), 0 1px 2px rgba(16, 16, 24, 0.05);
}
:root .btn:active,
:root .btn-primary:active,
:root button.btn-primary:active,
:root .btn-secondary:active,
:root button.btn-secondary:active { transform: scale(0.98); transition-duration: var(--dur-1); }

:root .field input:where(:not([type="checkbox"]):not([type="radio"])),
:root .field textarea,
:root .field select {
  background: var(--paper-raised);
  border: 0;
  border-radius: var(--radius-md);
  box-shadow: var(--elev-sunk);
}
:root .field input:focus,
:root .field textarea:focus,
:root .field select:focus {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--accent);
}

/* A drop target is an invitation, not a cut-out. The dashes go and a wash of
   the accent stands in for them. */
:root .dropzone {
  border: 0;
  border-radius: var(--radius-md);
  background: var(--paper-inset);
  transition: background-color var(--dur-2) var(--ease-standard);
}
:root .dropzone:hover { background: var(--accent-soft); }

/* Every drop target on the site, by name. A dashed rectangle is a cut-out in
   the page; the language here says a surface instead, so the dashes go and a
   wash of the accent stands in for them. They are listed rather than matched
   on a substring because a dropdown's wrapper would be caught by that. */
:root .ab-drop,
:root .ar-drop,
:root .at-drop,
:root .bl-drop,
:root .bs-drop,
:root .ce-drop,
:root .cp-drop,
:root .cr-drop,
:root .dm-drop,
:root .drop,
:root .dropzone,
:root .ds-drop,
:root .ex-drop,
:root .fl-drop,
:root .in-drop,
:root .lb-drop,
:root .mb-drop,
:root .mg-drop,
:root .pc-drop,
:root .pk-drop,
:root .rl-drop,
:root .sc-drop,
:root .seal-drop,
:root .sh-drop,
:root .sn-drop,
:root .st-drop,
:root .sv-drop,
:root .vl-drop,
:root .wm-drop,
:root .xr-drop {
  /* A place to drop a file has to look like an edge, or it reads as a
     coloured banner somebody put above the tool. The ring is an outline
     rather than a border so adding it moves nothing on the page, and it
     is dashed because that is what a target you can drop onto has looked
     like since before this site existed. */
  border: 0 !important;
  border-radius: var(--radius-md);
  background: var(--paper-inset);
  outline: 1px dashed var(--rule-strong);
  outline-offset: -5px;
  cursor: pointer;
  transition: background-color var(--dur-2) var(--ease-standard),
              outline-color var(--dur-2) var(--ease-standard),
              box-shadow var(--dur-2) var(--ease-standard);
}
:root .ab-drop:hover,
:root .ar-drop:hover,
:root .at-drop:hover,
:root .bl-drop:hover,
:root .bs-drop:hover,
:root .ce-drop:hover,
:root .cp-drop:hover,
:root .cr-drop:hover,
:root .dm-drop:hover,
:root .drop:hover,
:root .dropzone:hover,
:root .ds-drop:hover,
:root .ex-drop:hover,
:root .fl-drop:hover,
:root .in-drop:hover,
:root .lb-drop:hover,
:root .mb-drop:hover,
:root .mg-drop:hover,
:root .pc-drop:hover,
:root .pk-drop:hover,
:root .rl-drop:hover,
:root .sc-drop:hover,
:root .seal-drop:hover,
:root .sh-drop:hover,
:root .sn-drop:hover,
:root .st-drop:hover,
:root .sv-drop:hover,
:root .vl-drop:hover,
:root .wm-drop:hover,
:root .xr-drop:hover {
  background: color-mix(in srgb, var(--accent-strong) 12%, var(--paper-raised));
  outline-color: var(--accent-strong);
}

/* A file is over it. The ring closes on the file and the surface comes up
   to meet it, so the difference between "you may drop this" and "drop it
   here" is visible without reading anything. */
:root .drop.over,
:root .dropzone.over,
:root [class$="-drop"].over {
  outline: 2px dashed var(--accent-strong);
  outline-offset: -4px;
  background: color-mix(in srgb, var(--accent-strong) 16%, var(--paper-raised));
  box-shadow: var(--elev-2);
}

/* A note is a quieter surface, not a rule down the margin. The bar was the
   one piece of furniture here that belonged to a different vocabulary. */
:root .status-line,
:root .tool-note {
  border-left: 0;
  border-radius: var(--radius-md);
  background: var(--paper-inset);
  padding: 10px 14px;
}

/* The suite tabs read as a segmented control, so they are set like one:
   the group is a sunk track and the current tab is the raised thing in it. */
:root .os-nav-tabs {
  background: var(--paper-inset);
  border: 0;
  border-radius: var(--radius-pill);
  box-shadow: var(--elev-sunk);
  padding: 3px 4px;
}
:root .nav-tab-btn {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: var(--track-body);
  text-transform: none;
  border-radius: var(--radius-pill);
  padding: 7px 14px;
}
:root .nav-tab-btn.active {
  font-weight: 600;
  background: var(--paper-raised);
  border: 0;
  box-shadow: 0 0 0 0.5px var(--hairline), 0 1px 2px rgba(16, 16, 24, 0.10);
}
:root .btn-search,
:root .btn-settings {
  background: var(--paper-raised);
  border: 0;
  border-radius: var(--radius-pill);
  box-shadow: 0 0 0 0.5px var(--hairline-strong);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  letter-spacing: var(--track-body);
  padding: 7px 14px;
}
:root .btn-search:hover,
:root .btn-settings:hover { box-shadow: 0 0 0 0.5px var(--rule-strong); }

/* Three tabs fit on one line in the sans at thirteen; the block that put
   them back into small monospace capitals existed because six did not. */
:root .os-nav-tabs { flex: 0 1 auto; min-width: 0; }

/* A neutral note becomes a quiet surface. The ones whose left bar carries a
   severity - a warning, a finding, a flag - keep it, because there the bar is
   the signal and not the decoration. */
:root .status-line,
:root .tool-note,
:root .note,
:root .seal-note,
:root .br-note,
:root .cf-note,
:root .cn-note,
:root .dc-note,
:root .ds-note,
:root .fl-note,
:root .lb-note,
:root .lg-note,
:root .mn-note,
:root .pk-note,
:root .rl-note,
:root .rt-note,
:root .th-note,
:root .wm-note,
:root .wt-note,
:root .xr-note {
  border-left: 0 !important;
  border-radius: var(--radius-md);
  background: var(--paper-inset);
  padding: 10px 14px;
}
/* A status line with nothing to say was an empty grey bar on every tool
   page; a surface with nothing on it is not drawn. */
:root .status-line:empty, :root .note:empty { display: none; }


/* A focus-visible safety net.
 *
 * Most controls here carry their own focus style. The ones that do not are the
 * ones nobody styles: a bare <input type="date">, a <input type="time">, a
 * <summary>. Those were reachable by keyboard and gave no sign at all of where
 * you were, which makes a page unusable without a mouse.
 *
 * :where() carries no specificity, so this never overrides a control that has
 * its own focus style - it only fills in for one that has none. It is
 * :focus-visible rather than :focus, so clicking a button does not draw a ring
 * a mouse user has no use for.
 */
:where(a[href], button, input, select, textarea, summary, [tabindex]:not([tabindex="-1"])):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* The pickers are the exception, and they are the ones that needed this most.
 * Tabbing into an <input type="time"> or type="date" puts focus on one of the
 * control's own internal segments, not on the input. It is still what
 * document.activeElement reports, but it matches neither :focus nor
 * :focus-visible - so the rule above never fired for precisely the controls
 * that had no ring of their own, and a keyboard user got no sign at all of
 * where they were. :focus-within is the one that matches an element holding
 * the focus rather than being it. A ring after a click on a date picker is no
 * loss; being unable to see where you are is.
 */
:where(input[type="date"], input[type="time"], input[type="datetime-local"],
       input[type="month"], input[type="week"], input[type="color"],
       input[type="file"], input[type="range"]):focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}


/* The footer's link row wraps at every width, not just under 760px.
 *
 * mkpage writes flex-wrap into the inline style; the pages written before it
 * did not, so their six footer links sat on one unbreakable line. There was a
 * rule for this already, inside @media (max-width: 760px) - and the line is
 * 764px wide, so at exactly 768px, an iPad held upright, it cleared the
 * breakpoint and overflowed a 744px column. The whole document scrolled
 * sideways by eight pixels. It was clean at 700 and at 900, which is how it
 * survived both a phone audit and a desktop one.
 *
 * Wrapping is never the wrong behaviour for a row of links, so this is not
 * conditional on anything. The inline style sets display and gap and nothing
 * else, so a plain rule reaches it without out-specifying anything. */
.os-footer > div {
  flex-wrap: wrap;
  min-width: 0;
}

/* A suite page you can scan.
 *
 * Eighty-six tools in one grid of codenames is not a list, it is a wall - the
 * owner of this site could not tell what was on it, which is the only evidence
 * that matters. Filed by what each one is about, the same eighty-six are
 * fifteen headings of about six, with a row of jump links above them. */
.os-groups, .os-tool-groups { margin: 18px 0 0; }
/* The suite pages put this inside .boot, which is a centred flex column, so
   an item with no width of its own is sized to its widest content rather
   than to the column. On a phone the jump row stops wrapping and becomes a
   sideways scroller, whose widest content is all fifteen chips in a line -
   two and a half thousand pixels - and the whole page was dragged out to
   that width and centred on it, so every heading and half of every tile sat
   off the left of the screen. min-width lets the scroller be narrower than
   the chips inside it, which is the entire point of a scroller. */
.os-tool-groups { width: 100%; min-width: 0; box-sizing: border-box; }
.os-group { margin: 0 0 28px; scroll-margin-top: 78px; }
.os-group:last-child { margin-bottom: 6px; }
/* The heading the hero links down to, clear of the sticky bar like the rest. */
#allToolsHeading { scroll-margin-top: 78px; }
/* Tile, then the name, then the count against the right edge of the list it
   counts. The name takes the slack rather than the gap doing it, so the count
   lands on the card's edge however long the heading is. */
.os-group-head {
  display: flex;
  align-items: center;
  gap: 0;
  margin: 0 0 12px;
  font: 600 var(--text-lg)/1.3 var(--font-sans);
  letter-spacing: var(--track-head);
}
.os-group-name { flex: 1 1 auto; min-width: 0; color: var(--ink); letter-spacing: -.012em; }
.os-group-n {
  flex: none;
  font: 500 var(--text-xs)/1 var(--font-mono, ui-monospace, monospace);
  /* The soft ink, not the faint: a group below the fold is still part way
     through its reveal, at 85% opacity, and the faint ink fell under 4.5:1. */
  color: var(--ink-soft);
}

/* The jump row. Real anchors to real ids, so it works with no script. */
.os-jump { display: flex; flex-wrap: wrap; gap: 7px; margin: 0 0 24px; }
.os-jump a {
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  background: var(--paper-inset);
  color: var(--ink-soft);
  text-decoration: none;
  font: 500 var(--text-xs)/1.3 var(--font-sans);
  letter-spacing: var(--track-body);
}
.os-jump a:hover { background: var(--rule); color: var(--ink); }
/* The same colour as the heading it jumps to, so the row reads as a way into
   the list rather than a second copy of it. */
.os-jump-dot {
  display: inline-block; flex: none;
  width: 8px; height: 8px; border-radius: var(--radius-sm); margin-right: 7px;
  background: var(--ico, var(--accent-strong));
}
:root[data-color-theme="monochrome"] .os-jump-dot { background: #6b6b6b; }
/* A tablet is a finger too, whatever its width. */
@media (pointer: coarse) { .os-jump a { min-height: 44px; min-width: 44px; justify-content: center; } }
/* Twenty-nine pills that wrap are four rows on a desktop and fourteen on a
   phone - a thousand pixels of index in front of the list it indexes, which
   is the opposite of a shortcut. One swipeable row instead: every pill keeps
   its 44px, and the fade on the right edge is what says there is more of it.
   Not behind a disclosure, because a shortcut you have to find first is not
   one. */
@media (max-width: 640px) {
  .os-jump {
    flex-wrap: nowrap;
    overflow-x: auto;
    gap: 8px;
    margin-right: -16px;
    padding-right: 40px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 44px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 44px), transparent);
  }
  .os-jump::-webkit-scrollbar { display: none; }
  .os-jump a { flex: none; min-height: 44px; scroll-snap-align: start; }
}

/* The "what this is" panel, folded to one line until it is wanted. The
   summary is the heading it replaces, in the heading's type, with the same
   disclosure mark the questions below it use. */
details.os-about { padding-top: 0; padding-bottom: 0; }
details.os-about > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 52px;
  padding: 14px 0;
  font-family: var(--font-headline);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--ink);
}
details.os-about > summary::-webkit-details-marker { display: none; }
details.os-about > summary::after {
  content: "+";
  flex: none;
  font: 400 var(--text-xl)/1 var(--font-sans);
  color: var(--ink-faint);
}
details.os-about[open] > summary::after { content: "\2013"; }
details.os-about[open] > summary { border-bottom: 1px solid var(--rule); margin-bottom: 14px; }
details.os-about > :last-child { margin-bottom: 18px; }

/* The codename, above the heading rather than in front of it.
 *
 * An <h1> is the strongest thing a page says about itself, and on 126 tool
 * pages it was spending that on a word that exists only on this site. The
 * searchable phrase leads in the markup; the codename keeps its place,
 * because people who already know the site look for it by name. It was a
 * boxed chip at the end of the heading, which read as a keyword tag stuck
 * on a title; it is a small label above the heading now, and the heading
 * reads as a sentence on its own. */
.page-title:has(> .h1-code) {
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-start;
  gap: var(--space-2);
}
.h1-code {
  display: block;
  text-transform: uppercase;
  white-space: nowrap;
  margin: 0;
  padding: 0;
  border: 0;
  font: 600 var(--text-2xs)/1.35 var(--font-mono);
  letter-spacing: 0.08em;
  color: var(--ink-faint);
}

/* The line above the footer that says what is free and what is Pro
   (licence-note.js). It was a tinted box of its own width, 760px centred
   under panels 1000px wide, so it lined up with nothing. It is a plain
   line now, from the left edge the page and the footer share (both 1040px
   wide with a 22px gutter, 16px on a phone). */
#obLicenceNote { max-width: 1040px; margin: var(--space-5) auto var(--space-5); padding: 0 22px; text-align: left; box-sizing: border-box; }
@media (max-width: 760px) { #obLicenceNote { padding: 0 16px; } }
/* The page shell above it drops its bottom padding, so the gap is this
   margin and not the two added together. */
.app-wrapper:has(+ #obLicenceNote) { padding-bottom: 0; }
#obLicenceNote .obln-line { margin: 0; max-width: 700px; font-size: var(--text-sm); line-height: var(--leading-prose); color: var(--ink-soft); text-wrap: pretty; }
#obLicenceNote .obln-free { color: var(--ink); font-weight: 600; }
#obLicenceNote a { color: var(--accent-strong); text-decoration: underline; text-underline-offset: 2px; }

/* ---------------------------------------------------------------
   The privacy badge (privacyproof.js).

   Its styles lived in a string inside the script, with their own
   literal sizes, colours and curves. They are here now, from the
   tokens. On a page with a footer the badge is part of the footer,
   where it says its piece once and covers nothing; on an application
   with no footer it stays in the corner, and each application puts
   it in its own status bar (docs.html, pdf.css and the rest).
   --------------------------------------------------------------- */
#ppBadge {
  position: fixed;
  left: var(--space-4);
  bottom: var(--pp-bottom, var(--space-4));
  bottom: calc(var(--pp-bottom, var(--space-4)) + env(safe-area-inset-bottom, 0px));
  z-index: 2147482000;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--rule-strong);
  background: var(--paper-raised);
  color: var(--ink);
  font: 600 var(--text-2xs)/1 var(--font-ui);
  cursor: pointer;
  box-shadow: 0 4px 16px rgba(0, 0, 0, .18);
  transition: border-color var(--dur-2) var(--ease-standard),
              transform var(--dur-4) var(--ease-standard),
              opacity var(--dur-3) var(--ease-standard);
}
#ppBadge:hover { border-color: var(--accent-strong); }
#ppBadge svg { width: 14px; height: 14px; }
#ppBadge .ppdot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 0 3px var(--good-soft);
  /* One ring when the page opens, the way a light that has just been
     checked would: once, and then it is still. */
  animation: ppPing var(--dur-5) var(--ease-emph) var(--dur-4);
}
@keyframes ppPing {
  0% { box-shadow: 0 0 0 0 var(--good-soft); }
  60% { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 3px var(--good-soft); }
}
/* On a phone the badge is over the page, not beside it, so it shrinks to
   a dot and a shield and steps out of the way while somebody reads down. */
@media (max-width: 640px) {
  #ppBadge { left: var(--space-3); bottom: calc(var(--pp-bottom, var(--space-3)) + env(safe-area-inset-bottom, 0px)); padding: 7px 10px; min-height: 44px; min-width: 44px; justify-content: center; }
  #ppBadge .pplabel { display: none; }
}
#ppBadge.pp-away { transform: translateY(calc(100% + 28px)); opacity: 0; pointer-events: none; }
/* An application whose whole page scrolls, on a phone: the badge is the
   last line of the page rather than a corner its words pass under
   (privacyproof.js). */
html body #ppBadge.pp-at-end {
  position: static; display: flex; width: max-content; transform: none;
  margin: var(--space-4) var(--space-4) calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
}
/* In the footer: one more item in the row, with no shadow and no corner
   of the screen to itself. */
#ppBadge.pp-in-foot {
  position: static;
  z-index: auto;
  box-shadow: none;
  border-color: var(--rule);
  background: transparent;
  color: var(--ink-soft);
  min-height: 32px;
  padding: 0 var(--space-3);
  font-weight: 500;
  font-size: var(--text-xs);
}
#ppBadge.pp-in-foot:hover { color: var(--ink); border-color: var(--rule-strong); }
/* In an application's drawer on a phone (data-pp-slot): a line of the
   drawer, with its words, rather than a dot over the last row. */
#ppBadge.pp-in-slot {
  position: static;
  z-index: auto;
  transform: none;
  box-shadow: none;
  align-self: flex-start;
  border-color: var(--rule);
  background: transparent;
  color: var(--ink-soft);
  min-height: 44px;
  margin: var(--space-3) var(--space-2) 0;
}
#ppBadge.pp-in-slot .pplabel { display: inline; }
#ppBadge.pp-in-slot:hover { color: var(--ink); border-color: var(--rule-strong); }
.os-foot-pp { flex-basis: 100%; display: flex; justify-content: center; margin: 0 0 var(--space-2); }
/* Beside the tagline it checks (privacyproof.js): words in the line, not a
   box of their own. */
.os-foot-brand .os-foot-pp { flex-basis: auto; display: inline-flex; margin: 0; }
.boot-foot .os-foot-pp { justify-content: flex-start; margin: 0; }
/* At the foot of a paid application's lock screen, which has no footer of
   its own (privacyproof.js): the last line of it, under what it does. */
.pro-lock > .os-foot-pp { justify-content: flex-start; margin: var(--space-6) 0 0; }
:is(.os-foot-brand, .boot-foot) #ppBadge.pp-in-foot { border-color: transparent; padding: 0 var(--space-1); min-height: 44px; gap: 6px; }
:is(.os-foot-brand, .boot-foot) #ppBadge.pp-in-foot:hover { border-color: transparent; text-decoration: underline; text-underline-offset: 2px; }
:is(.os-foot-brand, .boot-foot) #ppBadge.pp-in-foot svg { display: none; }
@media (max-width: 640px) {
  #ppBadge.pp-in-foot { min-height: 44px; padding: 0 var(--space-4); }
  :is(.os-foot-brand, .boot-foot) #ppBadge.pp-in-foot { padding: 0; }
  #ppBadge.pp-in-foot .pplabel { display: inline; }
}
/* What the footer's line says is a count that changes, so its figures
   keep their width, and it wraps rather than running off a phone. */
#ppBadge.pp-in-foot { max-width: 100%; line-height: 1.3; text-align: left; font-variant-numeric: tabular-nums; }
#ppBadge[data-sent] .ppdot { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-soft); }

/* The panel it opens, and the receipt a paid application saves
   (pro-code/obscura-receipt.js), which is laid out the same way. */
#ppOverlay, #rcOverlay { position: fixed; inset: 0; z-index: 2147482100; background: rgba(0, 0, 0, .5); display: flex; align-items: center; justify-content: center; padding: 5vh 16px; }
#ppPanel, #rcPanel { position: relative; width: min(560px, 96vw); max-height: 90vh; overflow: auto; background: var(--paper-raised); border: 1px solid var(--rule-strong); border-radius: var(--radius-lg); box-shadow: 0 30px 80px rgba(0, 0, 0, .45); color: var(--ink); }
.pp-hd { padding: 20px 24px 4px; display: flex; align-items: center; gap: var(--space-3); }
.pp-hd svg { width: 24px; height: 24px; color: var(--accent-strong); flex: none; }
.pp-hd h2 { margin: 0; font: 600 var(--text-lg)/1.3 var(--font-headline); }
.pp-body { padding: var(--space-2) 24px 20px; }
.pp-lede { font-size: var(--text-sm); line-height: var(--leading-prose); color: var(--ink-soft); margin: var(--space-2) 0 var(--space-4); }
.pp-stat { display: flex; align-items: baseline; gap: 10px; padding: 14px var(--space-4); border-radius: var(--radius-md); background: var(--good-soft); border: 1px solid color-mix(in srgb, var(--good) 30%, transparent); margin-bottom: var(--space-4); }
.pp-stat b { font: 700 var(--text-2xl)/1 var(--font-mono); color: var(--good); }
.pp-stat span { font-size: var(--text-xs); color: var(--ink); }
.pp-test { border: 1px solid var(--rule); border-radius: var(--radius-md); padding: 14px var(--space-4); margin-bottom: var(--space-4); }
.pp-test .pp-onoff { display: flex; align-items: center; gap: 9px; font-weight: 600; font-size: var(--text-sm); margin-bottom: 6px; }
.pp-test .pp-onoff .d { width: 10px; height: 10px; border-radius: 50%; background: var(--good); }
.pp-test .pp-onoff .d[data-off] { background: var(--danger); }
.pp-test p { margin: 0; font-size: var(--text-xs); line-height: var(--leading-prose); color: var(--ink-soft); }
.pp-net + .pp-net { margin-top: var(--space-5); }
.pp-net h3 { font: 600 var(--text-2xs)/1 var(--font-ui); letter-spacing: .06em; text-transform: uppercase; color: var(--ink-soft); margin: 0 0 var(--space-2); }
.pp-net table { width: 100%; border-collapse: collapse; font-size: var(--text-xs); }
.pp-net td { padding: 6px var(--space-2); border-bottom: 1px solid var(--rule); vertical-align: top; }
.pp-net td.n { text-align: right; font-family: var(--font-mono); color: var(--ink-soft); white-space: nowrap; }
.pp-net .none { color: var(--ink-soft); font-size: var(--text-xs); }
.pp-net .none.pp-after { margin-top: var(--space-2); }
.pp-foot { padding: var(--space-3) 24px 20px; border-top: 1px solid var(--rule); font-size: var(--text-xs); color: var(--ink-soft); line-height: var(--leading-prose); }
.pp-x { position: absolute; top: 14px; right: var(--space-4); min-width: 32px; min-height: 32px; background: none; border: 0; color: var(--ink-soft); font-size: var(--text-xl); cursor: pointer; line-height: 1; }
.pp-x:focus-visible, .pp-foot a:focus-visible, #ppFile a:focus-visible, #ppFile button:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.pp-stat[data-sent] { background: var(--paper-inset); border-color: var(--danger); }
.pp-stat[data-sent] b { color: var(--danger); }
.pp-stat { flex-wrap: wrap; }
.pp-stat span { flex: 1 1 220px; line-height: var(--leading-prose); }
.pp-files h3 { font: 600 var(--text-2xs)/1 var(--font-ui); letter-spacing: .06em; text-transform: uppercase; color: var(--ink-soft); margin: 0 0 var(--space-2); }
.pp-files ul { list-style: none; margin: 0 0 var(--space-4); padding: 0; font-size: var(--text-xs); }
.pp-files li { padding: var(--space-2) 0 var(--space-2) var(--space-3); border-left: 3px solid var(--good); margin-bottom: var(--space-2); overflow-wrap: anywhere; }
.pp-files li[data-sent] { border-left-color: var(--danger); }
.pp-files .pp-n { color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.pp-files .pp-v { display: block; margin-top: 2px; line-height: var(--leading-prose); }
.pp-every { margin-top: var(--space-4); }
.pp-req { list-style: none; margin: 0 0 var(--space-4); padding: 0; font-size: var(--text-xs); }
.pp-req li { display: grid; grid-template-columns: 4.5em minmax(0, 1fr) auto; gap: var(--space-2); padding: 6px 0; border-bottom: 1px solid var(--rule); align-items: baseline; }
.pp-req li[data-off] .pp-p { color: var(--danger); }
.pp-req .pp-t, .pp-req .pp-s { font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--ink-soft); white-space: nowrap; }
.pp-req .pp-s.is-sent { color: var(--ink); font-weight: 600; }
.pp-req .pp-w { min-width: 0; }
.pp-req .pp-u { display: block; font-family: var(--font-mono); overflow-wrap: anywhere; }
.pp-req .pp-p { display: block; color: var(--ink-soft); }
.pp-req code { font-family: var(--font-mono); overflow-wrap: anywhere; }
.pp-net .none[data-sent] { color: var(--danger); }
.pp-limits { margin: 0; font-size: var(--text-xs); line-height: var(--leading-prose); color: var(--ink-soft); }
.pp-foot a { color: var(--accent-strong); font-weight: 600; }
.rc-stamp { display: flex; gap: var(--space-2); align-items: flex-start; font-size: var(--text-xs); line-height: var(--leading-prose); color: var(--ink); margin: var(--space-4) 0; }
.rc-stamp input { margin-top: 3px; flex: none; }
.rc-acts { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-4); }
.rc-acts .btn { min-height: 44px; }
/* Prove the redaction (pro-code/redaction-proof.js), in the same panel */
.rp-field { display: block; font: 600 var(--text-2xs)/1 var(--font-ui); letter-spacing: .06em; text-transform: uppercase; color: var(--ink-soft); margin: 0 0 var(--space-2); }
#rpTerms { display: block; width: 100%; box-sizing: border-box; min-height: 104px; resize: vertical; background: var(--paper-inset); color: var(--ink); border: 1px solid var(--rule-strong); border-radius: var(--radius-md); padding: 10px 12px; font: 500 var(--text-sm)/1.5 var(--font-mono); }
#rpTerms:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
#rpOut:empty { display: none; }
#rpOut ul { margin-bottom: 0; }

/* /verify, checking a receipt (verify-receipt.js). */
.rv-drop { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); margin: 14px 0 4px; padding: var(--space-5); border: 2px dashed var(--rule-strong); border-radius: var(--radius-lg); background: var(--paper-raised); }
.rv-drop.is-over { border-color: var(--accent); background: var(--accent-soft); }
.rv-drop p { margin: 0; }
.rv-big { font-weight: 650; font-size: var(--text-md); }
.rv-file { position: relative; cursor: pointer; }
.rv-drop label[for] { margin: 6px 0 0; }
.rv-drop input[type=text] { max-width: 420px; padding: 10px 12px; font-size: var(--text-sm); }
.rv-verdict { font-weight: 600; font-size: var(--text-md); line-height: 1.5; margin: 0 0 8px; }
.rv-bad { color: var(--danger); }
.rv-list { margin: 0 0 8px; padding-left: 20px; font-size: var(--text-sm); line-height: 1.6; color: var(--ink-soft); }
.rv-h { font: 600 var(--text-2xs)/1 var(--font-mono, monospace); letter-spacing: .06em; text-transform: uppercase; color: var(--ink-soft); margin: 20px 0 6px; }
.rv-form { margin: 0 0 8px; }
.rv-form label:not(.btn) { margin: 0 0 6px; }
.rv-inline { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: stretch; }
.rv-inline input[type=text] { flex: 1 1 220px; min-width: 0; max-width: 420px; padding: 10px 12px; font-size: var(--text-sm); }
.rv-inline .btn, .rv-form .rv-file { min-height: 44px; }
.rv-said { margin: 6px 0 0; font-size: var(--text-sm); line-height: 1.6; color: var(--ink-soft); overflow-wrap: anywhere; }
.rv-said:empty { display: none; }
.rv-said.rv-bad { color: var(--danger); }
.rv-list.rv-places { margin: 0; }
#ppFile .pp-all { color: var(--accent-strong); font-weight: 600; }
@media (max-width: 480px) {
  #ppPanel, #rcPanel { width: 100%; }
  .pp-hd, .pp-body, .pp-foot { padding-left: var(--space-4); padding-right: var(--space-4); }
  .pp-req li { grid-template-columns: minmax(0, 1fr) auto; }
  .pp-req .pp-t { display: none; }
}
.pp-x { min-width: 44px; min-height: 44px; top: 8px; right: 8px; }

/* The note that appears when a file is opened: how much this page has
   sent anywhere since, counted rather than claimed. */
#ppFile { position: fixed; left: var(--space-4); bottom: calc(var(--pp-bottom, var(--space-4)) + 48px); z-index: 2147482000; max-width: min(420px, calc(100vw - 32px)); padding: 10px var(--space-3); border-radius: var(--radius-md); border: 1px solid var(--rule-strong); background: var(--paper-raised); color: var(--ink); font: 500 var(--text-xs)/1.45 var(--font-ui); box-shadow: 0 4px 16px rgba(0, 0, 0, .18); display: flex; gap: 10px; align-items: flex-start; }
#ppFile b { font-family: var(--font-mono); color: var(--good); }
#ppFile b[data-sent] { color: var(--danger); }
#ppFile a { color: var(--accent-strong); font-weight: 600; }
#ppFile button { flex: none; min-width: 32px; min-height: 32px; border: 0; background: none; color: inherit; font-size: var(--text-lg); line-height: 1; cursor: pointer; }
@media (max-width: 640px) {
  #ppFile { left: 10px; right: 10px; max-width: none; bottom: calc(var(--pp-bottom, 10px) + 54px); }
  #ppFile button { min-width: 44px; min-height: 44px; }
}

/* ---------------------------------------------------------------
   Page chrome, lifted out of the pages themselves.

   Each of these was written out identically on ten or more pages - and
   the same rule in forty-four files is a rule that gets changed in forty
   of them. Generated by .github/scripts/lift-css.mjs; a rule whose body
   differed between pages was left where it was.
   --------------------------------------------------------------- */
.wrap { max-width:760px;margin:0 auto;padding:34px 22px 90px; }
/* The ninety pixels at the foot of a page are for the foot of a page. On a
   tool page the questions link and the related tools are written in after
   it, so that space became a hundred and twenty pixels of nothing in the
   middle of the page. Where something follows, it does not need them. */
body > .wrap:has(+ .os-faq-link),
body > .wrap:has(+ .os-rel) { padding-bottom: 20px; }
.sub { color:var(--ink-soft,#c9bcb2);font-size:var(--text-md);line-height:1.6;margin:0 0 8px; }
.app-wrapper { max-width: 1040px; margin: 0 auto; padding: 24px 22px 80px; }
/* A phone gets its margins back: 22px either side of a 390px screen leaves
   346px for a tool. Forty-two pages carried this for themselves and the
   rest of the site never had it. Sixteen rather than twelve, so this shape
   of page has the same gutter as the other two. */
@media (max-width: 768px) { .app-wrapper { padding: 14px 16px 75px; } }
.btn-search:hover, .btn-settings:hover { border-color: var(--accent); color: var(--ink); }
.page-header { margin-bottom: 24px; }
.page-title { font-size: var(--text-3xl); font-weight: 700; letter-spacing: -0.02em; margin-bottom: 6px; }
.page-sub { font-size: var(--text-sm); color: var(--ink-soft); max-width: 780px; }
/* A link in the line under a page's title was the same grey as the words
   round it, with no underline: on /help, five links read as a sentence. */
.page-sub a { color: var(--accent-strong); text-decoration: underline; text-underline-offset: 3px; }
.page-sub a:hover { text-decoration-thickness: 2px; }
/* When a guide was written and last changed, under its lead (schema.mjs,
   which gives the same two dates to search engines). */
.os-dated { font-size: var(--text-xs); color: var(--ink-soft); margin-top: var(--space-2); }
/* The Share button under a guide's dates (chrome.mjs, share.js). Quiet, and
   the height of a thumb; it hands the address to the device's own share
   sheet, or copies it. */
.os-share { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-2); font-size: var(--text-xs); color: var(--ink-soft); }
.os-share-btn { display: inline-flex; align-items: center; gap: var(--space-2); min-height: 44px; padding: 0 var(--space-3); border: 1px solid var(--rule); border-radius: var(--radius-sm); background: transparent; color: var(--ink-soft); font: inherit; cursor: pointer; transition: border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
.os-share-btn:hover, .os-share-btn:focus-visible { border-color: var(--accent); color: var(--ink); }
.os-share-btn svg { width: 1em; height: 1em; stroke-width: var(--icon-stroke); }
.os-share-url { flex: 1 1 auto; min-width: 0; max-width: 420px; min-height: 44px; padding: 0 var(--space-3); border: 1px solid var(--rule); border-radius: var(--radius-sm); background: var(--paper-inset); color: var(--ink); font: inherit; }
/* A phone: a 34px title wraps a short name onto three lines. tool.css and
   forty-odd pages each say this for themselves; the apps that carry their
   own stylesheet (Forms, Drive, Calendar) had nothing, so it lives here. */
@media (max-width: 768px) {
  .page-title { font-size: var(--text-2xl); }
  .page-sub { font-size: var(--text-sm); }
}
.panel { background: var(--paper-raised); border: 1px solid var(--rule); border-radius: var(--radius-md); padding: 24px 26px; margin-bottom: 24px; }
.panel h2 { font-size: var(--text-lg); font-weight: 700; margin-bottom: 6px; }
.panel .sub { font-size: var(--text-sm); color: var(--ink-soft); margin-bottom: 18px; }
.dropzone { border: 2px dashed var(--rule-strong); border-radius: var(--radius-md); padding: 36px 20px; text-align: center; background: var(--paper-inset); cursor: pointer; transition: border-color var(--dur-2) var(--ease-standard), background-color var(--dur-2) var(--ease-standard); }
.dropzone:hover { border-color: var(--accent-strong); background: var(--paper-raised); }
/* The drop zone's inside, the sheet a page was drawn on, the redaction
   boxes, the preset row and the certificate card all belonged to a tool
   that was rebuilt. The class names went with it; these rules did not. */
.action-row { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 20px; }
.field { display:flex; flex-direction:column; gap:6px; margin-bottom:14px; }

/* A date or time control has a large intrinsic minimum: it has to be able to
   show "dd/mm/yyyy, --:--", which at the 16px the touch rules give it to stop
   iOS zooming on focus is about 320px. A .field is nearly always a grid or
   flex item, and such an item will not shrink below the intrinsic width of
   what is inside it, so on a 320px phone - where the column is 236px - the
   control and its label stood 42px past the right edge of the screen. html
   and body carry overflow-x:hidden, so there was no scrollbar to reveal it:
   on /epoch, /timeline, /comms, /custody, /decision and /statement it was
   simply cut off.

   Scoped to these controls, and not applied to .field in general. A plain
   `.field { min-width: 0 }` fixed those six pages and broke /speech, where
   the wrapper being shrinkable let a content-sized grid track collapse and
   took four number fields down to 24px wide - well under a thumb.
   tap-targets.mjs caught that. The wrapper and the control both need it, so
   each type gets its own rule rather than one selector list, because one
   unsupported :has() would invalidate a whole list and take the others with
   it. The descendant form rather than the child form, and the wrapper's
   descendants too, because /timeline puts a date and a time control side by
   side in a div inside the field: a child selector missed it and the row
   stayed 64px off the screen. The wrapper's own label and any div it holds,
   rather than every descendant, so nothing here can shrink a button below a
   thumb - which is the mistake this whole comment exists to record. */
.field:has(input[type="datetime-local"]), .field:has(input[type="datetime-local"]) > label, .field:has(input[type="datetime-local"]) > div { min-width: 0; }
.field:has(input[type="date"]), .field:has(input[type="date"]) > label, .field:has(input[type="date"]) > div { min-width: 0; }
.field:has(input[type="time"]), .field:has(input[type="time"]) > label, .field:has(input[type="time"]) > div { min-width: 0; }
.field:has(input[type="month"]), .field:has(input[type="month"]) > label, .field:has(input[type="month"]) > div { min-width: 0; }
.field:has(input[type="week"]), .field:has(input[type="week"]) > label, .field:has(input[type="week"]) > div { min-width: 0; }
.field:has(select), .field:has(select) > label, .field:has(select) > div { min-width: 0; }
.field input[type="datetime-local"] { min-width: 0; }
.field input[type="date"] { min-width: 0; }
.field input[type="time"] { min-width: 0; }
.field input[type="month"] { min-width: 0; }
.field input[type="week"] { min-width: 0; }
.field select { min-width: 0; }
.field label { font-size:var(--text-xs); font-weight:600; color:var(--ink-soft); }
/* Not a tick box or a radio: those are drawn by the browser at the size the
   touch floor gives them, and at width:100% one stretched across the field
   and pushed its own words off the right of a phone. */
.field input:where(:not([type="checkbox"]):not([type="radio"])), .field textarea, .field select { font-family: inherit; font-size:var(--text-sm); color:var(--ink); background:var(--paper-inset); border:1px solid var(--rule-strong); border-radius:var(--radius-sm); padding:9px 12px; width:100%; box-sizing:border-box; }
.field textarea { min-height:110px; resize:vertical; line-height:1.55; }
.field input:focus, .field textarea:focus, .field select:focus { outline:none; border-color:var(--accent); }

/* A text box that is not inside a .field.

   .field is the site's form wrapper and it styles the controls inside
   it, which covers most of them. It does not cover a search box sitting
   on its own in a toolbar, and the class those reach for - .txt - was
   styled in six pages' own <style> blocks and in neither shared sheet.
   So on the pages that did not repeat the rule, the box was the
   browser's own: a two-pixel inset border, square corners, no padding
   and nineteen pixels tall. Six pages were like that, the workbench and
   /start among them, which are the two places somebody is most likely
   to type first, and /waitlist, which is where they give an address.

   Nineteen pixels is also well under the forty-four a finger needs, and
   the tap-target check never saw it because that check measures the
   things you press and not the things you type into.

   Selecting by id here rather than inventing a class, because these are
   six controls that already exist in six shipped pages and renaming
   them in the HTML to match a stylesheet is the tail wagging the dog.
   A page that already styles .txt itself still wins: it comes later. */
.txt,
#cdDeckInput,
#lgSearch,
#peLayerSearch,
#waitmail {
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--ink);
  background: var(--paper-inset);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius-sm);
  padding: 9px 12px;
  min-height: 40px;
  box-sizing: border-box;
}
.txt:focus,
#cdDeckInput:focus,
#lgSearch:focus,
#peLayerSearch:focus,
#waitmail:focus { outline: none; border-color: var(--accent); }

/* Forty-four where it is a finger doing the typing. The same floor the
   buttons keep, and for the same reason. */
@media (pointer: coarse) {
  .txt,
  #cdDeckInput,
  #lgSearch,
  #peLayerSearch,
  #waitmail { min-height: 44px; }
}
.grid2 { display:grid; grid-template-columns:1fr 1fr; gap:0 16px; }
.grid3 { display:grid; grid-template-columns:1fr 2fr 1fr; gap:0 16px; }
/* A grid of .field boxes carries its own rhythm - each field has a margin
   under it - so the column gap is all it needs. A grid of plain label and
   input pairs has no such margin, and the pages that hold those were each
   setting a row gap of their own. */
.grid2:not(:has(> .field)), .grid3:not(:has(> .field)) { row-gap: 12px; }
/* Two columns need a screen with room for two. Forty-odd pages said this
   for themselves; fifteen more used .grid2 and never said it at all, and
   put two columns of inputs on a 390px phone. */
@media (max-width: 700px) { .grid2, .grid3 { grid-template-columns: 1fr; } }
.notes { font-size:var(--text-xs); color:var(--ink-faint); line-height:1.5; }
.digest { font-family:var(--font-mono); font-size:var(--text-2xs); word-break:break-all; color:var(--ink-soft); margin-top:4px; }
.status-line { font-size:var(--text-xs); color:var(--ink-soft); border-left:3px solid var(--rule); padding:2px 0 2px 12px; margin:14px 0 0; }
.tool-note { font-size:var(--text-xs); line-height:1.55; color:var(--ink-soft); border-left:3px solid var(--rule); padding:2px 0 2px 12px; margin:12px 0; }
.tool-note strong { color:var(--ink); }
.warn-panel { border-color: var(--danger, #A6432F); }
.warn-panel h2 { color: var(--danger, #A6432F); }
.btn { display:inline-flex;align-items:center;justify-content:center;gap:8px;font:600 var(--text-md) var(--font-sans,system-ui);padding:12px 20px;border-radius:var(--radius-md);border:1px solid var(--rule-strong,#5a4d45);background:var(--accent-strong,#c2603f);color:var(--on-accent,#fff);cursor:pointer; }
.btn.ghost { background:transparent;color:var(--ink); }
.btn.small { font-size:var(--text-xs);padding:6px 12px; }
.btn[disabled] { opacity:.5;cursor:default; }
.actions { display:flex;gap:12px;flex-wrap:wrap;margin-top:14px;align-items:center; }
.card { border:1px solid var(--rule,#3a302b);border-radius:var(--radius-md);background:var(--paper-raised,#1e1712);padding:16px 18px;margin-top:16px; }
.res { border-bottom:1px solid var(--rule,#3a302b);padding:12px 0; }
.res:last-child { border-bottom:none; }
/* A long hash still breaks anywhere it must; a word like object-src is
   not broken in the middle just because it could be. */
.mono { font:600 var(--text-sm) var(--font-mono,monospace);color:var(--ink);overflow-wrap:anywhere; }
.chip { display:inline-block;font:600 var(--text-2xs) var(--font-mono,monospace);background:var(--paper-inset,#241c17);border:1px solid var(--rule-strong,#5a4d45);color:var(--ink-soft,#c9bcb2);border-radius:var(--radius-sm);padding:3px 7px;margin:2px 4px 2px 0; }
.chip.bad { color:var(--danger,#d2683f); }
.chip.ok { color:#4c9e6b; }
.clean-note { font-size:var(--text-2xs);color:#4c9e6b;font-weight:600; }
.note { font-size:var(--text-xs);color:var(--ink-faint,#8a7d73);line-height:1.6;margin-top:26px;border-left:3px solid var(--rule,#3a302b);padding-left:12px; }
.err { color:var(--danger,#d2683f);font-size:var(--text-sm);margin-top:10px; }
.hide { display:none!important; }
.sev { font:600 var(--text-2xs) var(--font-mono,monospace);letter-spacing:.06em;text-transform:uppercase;border-radius:var(--radius-sm);padding:3px 7px;flex:0 0 auto; }
.sev.high { background:rgba(210,104,63,.16);color:var(--danger,#d2683f);border:1px solid var(--danger,#d2683f); }
.sev.med { background:rgba(200,150,60,.14);color:#c8963c;border:1px solid #c8963c; }
.sev.low { background:rgba(140,130,120,.12);color:var(--ink-soft,#c9bcb2);border:1px solid var(--rule-strong,#5a4d45); }
.drop { border:1px dashed var(--rule-strong,#5a4d45);border-radius:var(--radius-md);padding:26px 18px;text-align:center;color:var(--ink-soft,#c9bcb2);font-size:var(--text-sm);cursor:pointer;background:var(--paper-raised,#1e1712); }
.drop.over { border-color:var(--accent-strong,#c2603f); }
.lockmsg { font-size:var(--text-sm);color:var(--ink-soft,#c9bcb2);margin-top:14px;line-height:1.6;border-top:1px dashed var(--rule-strong,#5a4d45);padding-top:14px; }
.badge-lic { display:inline-block;font:600 var(--text-2xs)/1 var(--font-mono,monospace);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-soft,#c9bcb2);border:1px solid var(--rule-strong,#5a4d45);border-radius:var(--radius-sm);padding:5px 8px;margin-bottom:18px; }

/* ---------------------------------------------------------------
   The front door, and the three suite pages behind it.

   The mark, the name, one line, a short list of big rows, one line of
   small links. Nothing else, because these pages are the menu and a menu
   that explains itself is not one. It lived inline on index.html until
   the suite pages took the same shape; four copies of a layout is how a
   site ends up with four layouts.
   --------------------------------------------------------------- */
.boot{min-height:100vh;min-height:100dvh;width:100%;max-width:640px;margin:0 auto;padding:44px 24px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:34px;box-sizing:border-box;text-align:center;font-family:var(--font-sans,system-ui,sans-serif);-webkit-font-smoothing:antialiased;}
.boot-head{display:flex;flex-direction:column;align-items:center;gap:10px;}
.boot-slogan{margin:0;font-size:var(--text-lg);line-height:1.5;letter-spacing:var(--track-body);color:var(--ink-soft);}
.boot-slogan a{color:var(--accent-strong);text-decoration:underline;text-underline-offset:3px;}
.boot-slogan a:hover{text-decoration-thickness:2px;}
/* The rows: the one shape the whole site is made of, met here first. A
   coloured mark beside each name, and the name in the reading face. */
/* The list of suites on the front door and the groups on a suite page were
   the same rows, at the size the page needed. Both are grids of tiles now,
   so the rules that sized a row inside them have nothing left to size. */
/* The front door's own three additions: a caption under the slogan, the
   palette made visible as a search row, and the recently used list. The
   search row is drawn as a field rather than a card so it reads as a place
   to type, which a click makes true. The recent list is the tight card the
   rest of the site uses, under a small mono label, and is hidden until the
   browser has opened a tool. */
/* The caption is a sentence now, not three words in capitals between
   dots: it is where the front door says, once, what the site promises. */
/* The one way into the desktop (/os) from the front door: a line, not a
   tile, because the desktop is the site itself run another way, not a
   product beside the others. */
/* Who the site is for, and what Pro is: under the slogan, before the
   menu, because a first visitor was told where everything was and never
   that fifteen of the things behind the tiles are sold, for how much, or
   who they are for. One sentence each, the price in it, and two ways on:
   all the applications, or the free week. Sign in is the bar's own button,
   for the subscriber who came back on another computer. */
.boot-pro-line{margin:4px 0 0;max-width:58ch;font-size:var(--text-sm);line-height:var(--leading-ui);color:var(--ink-soft);text-wrap:pretty;font-variant-numeric:tabular-nums;}
.boot-pro-line a{color:var(--accent-strong);font-weight:600;text-decoration:underline;text-underline-offset:3px;}
.boot-pro-acts{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:8px 10px;margin:6px 0 0;}
.boot-pro-acts > a{display:inline-flex;align-items:center;justify-content:center;gap:8px;box-sizing:border-box;min-height:44px;padding:0 18px;border-radius:var(--radius-md);font:600 var(--text-sm)/1.2 var(--font-ui);text-decoration:none;}
.boot-pro-acts > .btn-primary{background:var(--accent-strong);}
.boot-pro-acts > a:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
.boot-pro-acts > .boot-pro-signin{padding:0 14px;}
@media (max-width:560px){
  .boot-pro-acts{flex-direction:column;align-items:stretch;width:100%;}
  .boot-pro-acts > a{width:100%;}
}
/* The same on /apps, where the page reads from the left. */
.boot-page .apps-pro-line { margin: 0; max-width: 70ch; text-wrap: balance; }
.boot-page .apps-pro-acts { justify-content: flex-start; margin: 0; }
/* /apps narrowed to one job: a sunk track with the chosen job raised in
   it, as the bar's tabs are set, and the count of each beside its name. */
.boot-page .apps-jobs { display: flex; flex-wrap: wrap; gap: 2px; align-self: flex-start; max-width: 100%; margin: 0; padding: 3px; border-radius: var(--radius-md); background: var(--paper-inset); box-shadow: var(--elev-sunk); }
.boot-page .apps-jobs[hidden] { display: none; }
.apps-jobs button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 44px; min-width: 44px; padding: 0 14px; border: 0; border-radius: var(--radius-sm); background: transparent; color: var(--ink-soft); font: 500 var(--text-xs)/1 var(--font-sans); letter-spacing: var(--track-body); cursor: pointer; transition: background-color var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard); }
.apps-jobs button:hover { color: var(--ink); }
.apps-jobs button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.apps-jobs button[aria-pressed="true"] { font-weight: 600; color: var(--ink); background: var(--paper-raised); box-shadow: 0 0 0 0.5px var(--hairline), 0 1px 2px rgba(16, 16, 24, 0.10); }
.boot-page .apps-jobs-say { margin: 0; min-height: 1.4em; font-size: var(--text-xs); color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.boot-grid .boot-tile[hidden] { display: none; }
@media (max-width: 560px) { .apps-jobs button { flex: 1 1 auto; justify-content: center; padding: 0 10px; } }
/* /apps: each application by its mark, its name and its one line,
   four to a row, as a launcher lays them out. */
.boot-grid.apps-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
.apps-grid .apps-tile { gap: 4px; min-height: 0; padding: 18px 18px 16px; }
.apps-grid .apps-tile .boot-tile-ico { margin-bottom: 10px; }
.apps-grid .apps-tile .boot-tile-sub { display: block; overflow: visible; -webkit-line-clamp: unset; line-clamp: none; font-size: var(--text-sm); line-height: 1.4; color: var(--ink-soft); }
/* Four to a row, and two on a narrower screen; the seventeenth starts a
   row of its own at its own width, as a launcher lays out its last icon,
   rather than being stretched across the row as the front door does with
   an odd one out. */
.boot-grid.apps-grid > .apps-tile.boot-tile:last-child { grid-column: auto; }
.boot-page .apps-free { margin: 0; max-width: 62ch; font-size: var(--text-sm); line-height: 1.55; color: var(--ink-soft); }
.boot-page .apps-free a { color: var(--accent-strong); text-decoration: underline; text-underline-offset: 3px; }
@media (max-width: 900px) { .boot-grid.apps-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .apps-jobs button { flex: 0 0 auto; } }
/* On a phone the six choices wrapped into a ragged four and two inside
   their tray. As two even columns, every label fits at 360px and the
   pressed one reads as one cell of a set. */
@media (max-width: 560px) {
  .boot-page .apps-jobs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); align-self: stretch; }
  .boot-page .apps-jobs button { justify-content: center; white-space: nowrap; }
}
@media (max-width: 420px) {
  .apps-grid .apps-tile { padding: 14px 14px 13px; }
}
/* The count, below the menu it counts. */
.boot-search{display:flex;align-items:center;gap:14px;box-sizing:border-box;width:100%;min-height:58px;padding:12px 20px;border:1px solid var(--rule);border-radius:var(--radius-md);background:var(--paper-inset);color:var(--ink-faint);font:inherit;font-size:var(--text-md);letter-spacing:var(--track-body);text-align:left;cursor:text;transition:border-color var(--dur-2) var(--ease-standard),box-shadow var(--dur-2) var(--ease-standard);}
.boot-search svg{width:20px;height:20px;flex:none;}
.boot-search:hover{border-color:var(--rule-strong);}
.boot-search:focus-visible{outline:2px solid var(--accent);outline-offset:3px;}
.boot-keys{flex:none;display:inline-flex;gap:4px;}
/* Under the search row: the other way in, a file put down on the page
   (home-drop.js). A finger can neither drag a file nor paste one, so on
   a touch screen it is not said. */
.boot-drop-note{margin:-10px 0 0;padding:0 4px;font-size:var(--text-xs);line-height:var(--leading-ui);color:var(--ink-soft);}
@media (pointer:coarse){.boot-drop-note{display:none;}}
.boot-keys kbd{font:500 var(--text-2xs) var(--font-mono,ui-monospace,monospace);padding:3px 7px;border:1px solid var(--rule);border-bottom-width:2px;border-radius:var(--radius-sm);background:var(--paper-raised);color:var(--ink-soft);}
/* The front door in three groups rather than one list of ten. Six suites,
   two applications and two things belonging to this device are three
   different kinds of thing, and a flat list gave a suite of a hundred and
   three tools exactly the weight of the link to Settings.

   The caption is deliberately small and quiet. It is there to be read once,
   on the first visit, and then skipped forever after - which is what a group
   caption is for, and why making it loud would be a mistake. */
/* The front door is a board, not a list.
 *
 * It was a list, and a list of ten destinations is a thing you read top to
 * bottom before you can choose - which is the wrong shape for a menu you
 * will use hundreds of times and which, at ten rows with a line of prose
 * each, did not fit on a screen. A grid is chosen from rather than read.
 * Six suites first, at full size, because that is what almost everybody
 * came for; then four smaller tiles for the things that are not a suite.
 *
 * The page is wider here than anywhere else on the site. Everything else
 * is 640px because it is prose or a form and a long measure is hard to
 * read; this is neither, and three columns of tile need the room. */

.boot-groups{width:100%;display:flex;flex-direction:column;gap:22px;}
.boot-sect{width:100%;display:flex;flex-direction:column;gap:10px;text-align:left;}
.boot-sect-head{display:flex;flex-direction:column;gap:1px;margin:0;padding:0 2px;
  font-family:var(--font-sans,system-ui,sans-serif);font-size:var(--text-2xs);font-weight:650;
  line-height:1.3;letter-spacing:.055em;text-transform:uppercase;color:var(--ink-soft);}
.boot-sect-sub{font-size:var(--text-xs);font-weight:400;line-height:1.4;letter-spacing:-.004em;
  text-transform:none;color:var(--ink-faint);}

.boot-grid{display:grid;gap:12px;grid-template-columns:repeat(3,1fr);}
.boot-grid-sm{grid-template-columns:repeat(4,1fr);}

/* A tile. The mark, the name, one line of what is behind it, and the count
   in the corner. Nothing else fits at this size and nothing else is needed
   to choose between ten things. */
.boot-tile{position:relative;display:flex;flex-direction:column;align-items:flex-start;
  gap:7px;box-sizing:border-box;min-height:132px;padding:15px 15px 14px;border:0;
  border-radius:var(--radius-md);background:var(--paper-raised);color:var(--ink);
  text-align:left;text-decoration:none;font:inherit;cursor:pointer;
  box-shadow:var(--elev-1);}
.boot-tile-sm{min-height:104px;padding:13px 13px 12px;gap:6px;}
/* Hover and press are with the row's, under "responding". */
.boot-tile:focus-visible{outline:2px solid var(--accent);outline-offset:3px;}

/* The mark. Each place has a hue of its own (--ico-h, from
   group-icons.mjs), drawn as a line in that hue over a light plane of it,
   on a square of the tile's paper tinted with it and lit a little from
   above. Not a block of saturated colour with a white glyph in it, which
   is what these were until 28 September 2026 and is the most recognisable
   template on the web: the colour is in the drawing, and the square is
   only the drawing's light. The lightness, chroma and strengths are the
   tokens by --on-accent, per mode; Monochrome takes the chroma away.
   Settings is .is-neutral, a place that is not work. A browser without
   OKLCH or color-mix keeps the accent on its tint. */
.boot-tile-ico{display:grid;place-items:center;flex:none;width:34px;height:34px;
  border-radius:var(--radius-md);}
:is(.boot-tile-ico, span.boot-continue-ico){background:var(--accent-soft);color:var(--accent-strong);}
@supports (color: oklch(0.5 0.1 200)) and (color: color-mix(in oklab, red 10%, blue)) {
  :is(.boot-tile-ico, span.boot-continue-ico){
    --ico-ink:oklch(var(--ico-l) var(--ico-c) var(--ico-h, 258));
    color:var(--ico-ink);
    background:linear-gradient(color-mix(in oklab, var(--ico-ink) var(--ico-tint-top), var(--paper-raised)),
      color-mix(in oklab, var(--ico-ink) var(--ico-tint), var(--paper-raised)));
    box-shadow:inset 0 0 0 1px color-mix(in oklab, var(--ico-ink) 16%, transparent);}
  .boot-tile-ico.is-neutral{--ico-c:0;}
  /* Gentle (a device asking for less motion): an icon does not act out its
     picture under the pointer; its square deepens a shade instead. */
  :root[data-motion-gentle] :is(.boot-tile, .os-row):is(:hover, :focus-visible) :is(.boot-tile-ico, .os-row-ico){
    background:color-mix(in oklab, currentColor 26%, var(--paper-raised));}
}
.boot-tile-ico svg{width:19px;height:19px;stroke-width:var(--icon-stroke);}
:is(.boot-tile-ico, .boot-continue-ico) .ico-plane{fill-opacity:var(--ico-plane);}
.boot-tile-sm .boot-tile-ico{width:28px;height:28px;border-radius:var(--radius-md);}
.boot-tile-sm .boot-tile-ico svg{width:16px;height:16px;}

/* The name of a place, in the interface face at 600: a tile is a card, and
   the serif is kept for the headline of a page. It was the serif, and on a
   system without Source Serif 4 it fell through to a second one. */
.boot-tile-name{font-family:var(--font-ui);font-size:var(--text-lg);font-weight:600;
  line-height:1.3;letter-spacing:-.008em;}
.boot-tile-sm .boot-tile-name{font-size:var(--text-md);}
/* Two lines at most. Without the clamp a tile whose line happens to wrap
   three times made its whole row of three taller, so one long sentence set
   the height of two tiles that did not need it. */
.boot-tile-sub{font-size:var(--text-xs);line-height:1.35;letter-spacing:var(--track-body);
  color:var(--ink-soft);display:-webkit-box;-webkit-box-orient:vertical;
  -webkit-line-clamp:2;line-clamp:2;overflow:hidden;}
.boot-tile-meta{position:absolute;top:14px;right:14px;font:500 var(--text-2xs) var(--font-mono,ui-monospace,monospace);
  color:var(--ink-faint);letter-spacing:.01em;}
.boot-tile-sm .boot-tile-meta{top:12px;right:12px;font-size:var(--text-2xs);}

/* ------------------------------------------------- questions on a page */

/* These lived inside faq.html, which was the right place while /faq was
   the only page that had questions on it. Every tool page carries its own
   now - three hundred and thirty-seven of them - and a rule copied onto
   three hundred and thirty-seven pages is the thing check.mjs exists to
   catch. Moved, not rewritten: /faq looks exactly as it did. */
.faq-list{max-width:760px;}
.faq-item{border-bottom:1px solid var(--rule);}
.faq-item summary{list-style:none;cursor:pointer;padding:18px 2px;display:flex;align-items:center;justify-content:space-between;gap:16px;font:600 var(--text-md)/1.4 var(--font-headline);color:var(--ink);}
.faq-item summary::-webkit-details-marker{display:none;}
/* The glyph stays a plus and turns, rather than being swapped for a
   dash. This rule already asked for a transform transition and never
   changed a transform, so the marker snapped between two characters and
   the transition did nothing at all. A plus turned an eighth of a turn
   is a cross, which is a movement the eye can follow; two characters
   replacing each other is not. */
.faq-item summary::after{content:"+";font-family:var(--font-mono,monospace);font-size:var(--text-xl);color:var(--accent-strong);flex:none;transition:transform var(--dur-3) var(--ease-standard);}
.faq-item[open] summary::after{transform:rotate(45deg);}
.faq-item summary:hover{color:var(--accent-strong);}
.faq-a{padding:0 2px 18px;font-size:var(--text-sm);line-height:1.7;color:var(--ink-soft);max-width:680px;}
.faq-a a{color:var(--accent-strong);}
.faq-a p{margin:0 0 10px;}
.faq-a code{font-family:var(--font-mono,ui-monospace,monospace);font-size:var(--text-xs);
  background:var(--paper-inset);border-radius:var(--radius-sm);padding:1px 5px;}


/* ---------------------------------------------------- a suite's tools */

/* A suite page is the front door one level down, so it is made of the same
   tiles. What it has that the front door does not is fifteen groups and a
   hundred and three tools, and the two differences follow from that.

   The mark moves from the tile to the heading. Ten tiles in a group all
   carry the same group mark, so ten of them in a grid is a pattern rather
   than a signal; one at the top of the ten says the same thing once and
   leaves the tiles to say what is actually different about them, which is
   their name and what they do. */
.boot-sect-marked{flex-direction:row;align-items:center;gap:9px;padding:0 2px 2px;}
.boot-sect-ico{display:grid;place-items:center;flex:none;width:26px;height:26px;
  border-radius:var(--radius-sm);background:var(--accent-soft);color:var(--accent-strong);}
.boot-sect-ico svg{width:15px;height:15px;}
.boot-sect-title{font-size:var(--text-xs);font-weight:650;letter-spacing:.05em;text-transform:uppercase;}
.boot-sect-marked .boot-sect-sub{margin-left:auto;font-size:var(--text-2xs);
  font-family:var(--font-mono,ui-monospace,monospace);letter-spacing:.01em;
  text-transform:none;white-space:nowrap;}

/* Three across, and the same tile the front door is made of: the mark, the
   name, a sentence. It was four across with no mark and the codename in
   mono, which was tidy and read as a table of contents rather than as
   somewhere to go. The front door does not look like a table of contents,
   which is the whole reason it works.

   The mark is the group's, so ten tools in one group wear one colour and
   the next group wears another. Within a group that is a repeat; across a
   page of fifteen it is the thing that tells you where you are without
   reading a heading.

   Both classes are named, every time, so this is two classes against the
   one that .boot-grid is. The front door narrows .boot-grid to two columns
   further down the sheet, and on a tie that rule wins simply by being
   below this one - which it was, and which is why a tablet got two very
   wide columns of tool tiles until it was measured. */
.boot-grid.boot-grid-tools{grid-template-columns:repeat(3,1fr);gap:12px;}
.boot-tile-tool{min-height:118px;gap:7px;padding:15px 15px 14px;}
/* The codename is still a codename - it is what you type into the palette
   and what you will remember - so it keeps the mono face, at the size the
   front door sets a name in. */
.boot-tile-tool .boot-tile-name{font-family:var(--font-mono,ui-monospace,monospace);
  font-size:var(--text-sm);font-weight:600;letter-spacing:.01em;text-transform:uppercase;}
/* Three lines rather than the front door's two. Every tile in a row of the
   grid is the height of the tallest one whatever this says, so a third line
   costs nothing anybody can see and buys the sentences that need it. */
.boot-tile-tool .boot-tile-sub{font-size:var(--text-xs);-webkit-line-clamp:3;line-clamp:3;}
/* An application rather than a tool: Docs, Sheets, Slides. These are
   products with names, set the way the front door sets a suite's name, and
   not codenames to be shouted in a monospaced face. */
.boot-tile-app .boot-tile-name{font-family:var(--font-ui);font-size:var(--text-lg);
  font-weight:600;line-height:1.3;letter-spacing:-.008em;text-transform:none;}

/* A page rather than a tool. The mono face is for a codename you will type
   into the palette; a guide's title is a sentence and reads as one. */
.boot-tile-flat .boot-tile-name{font-family:var(--font-sans,system-ui,sans-serif);
  font-size:var(--text-sm);font-weight:620;letter-spacing:-.004em;text-transform:none;line-height:1.3;}
/* A page tile is as tall as what it says. Held to a tool tile's 118px, a
   tile with one line in it was two thirds empty, and Help was a wall of
   half-empty boxes. */
.boot-tile-tool.boot-tile-flat{min-height:44px;}

/* The jump row. Fifteen headings used to be fifteen closed rows you had to
   scroll past; this is the same table of contents in one band, and it does
   not cost a click to read. It scrolls sideways on a narrow screen rather
   than wrapping to five lines above the page it is meant to introduce. */
.boot-jump{display:flex;flex-wrap:wrap;gap:6px;width:100%;min-width:0;max-width:100%;box-sizing:border-box;margin:0 0 20px;}
.boot-jump-chip{display:inline-flex;align-items:center;gap:6px;
  padding:5px 10px;border-radius:var(--radius-pill);border:1px solid var(--rule);
  background:var(--paper-raised);color:var(--ink-soft);text-decoration:none;
  font-size:var(--text-2xs);line-height:1.3;letter-spacing:-.004em;white-space:nowrap;
  transition:color var(--dur-2) var(--ease-standard),
             border-color var(--dur-2) var(--ease-standard),
             background-color var(--dur-2) var(--ease-standard);}
.boot-jump-chip span{font-family:var(--font-mono,ui-monospace,monospace);
  font-size:var(--text-2xs);color:var(--ink-faint);}
.boot-jump-chip:hover{color:var(--ink);border-color:var(--accent);background:var(--paper);}
.boot-jump-chip:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
/* A chip is a link, and a link a finger has to hit is forty-four pixels
   high wherever it appears. The same rule the footer links carry, for the
   same reason and at the same two triggers: a narrow screen, or any screen
   being driven by something blunter than a mouse. */
@media (max-width:560px), (pointer:coarse){
  .boot-jump-chip{min-height:44px;padding-top:0;padding-bottom:0;}
}

/* The Related block at the foot of three hundred and twenty-eight tool
   pages is a <ul>, and it stays one: it is a list of four things and a
   screen reader should be told so. So the <li> becomes the grid cell and
   the tile fills it, rather than the list items being taken out of the
   tree with display:contents - which does remove them from it, and takes
   the list with them.

   The alternative was to stop it being a list. A list of four related
   tools is a list; the markup should say what the thing is and the
   stylesheet should say what it looks like, which is the whole bargain. */
.boot-grid > li{display:flex;}
.boot-grid > li > .boot-tile{flex:1 1 auto;}

/* Three across, not four. The column count everywhere else is written for
   a container about a thousand pixels wide; this one lives in a tool page's
   main column, which is seven hundred and twenty, and four tiles in seven
   hundred and twenty pixels is a hundred and seventy-two pixels each. A
   codename and four words survive that. A guide called "Can Your Writing
   Style Identify You? What Stylometry Sees" becomes five lines of two
   words, and the four tiles beside it inherit its height and sit in a
   field of white. */
.os-rel .boot-grid.boot-grid-tools{grid-template-columns:repeat(3,1fr);}
@media (max-width:640px){
  .os-rel .boot-grid.boot-grid-tools{grid-template-columns:repeat(2,1fr);}
}
/* And one at 320. Half of what the gutters leave is 140 pixels, and a row's
   padding and chevron take 58 of them, which is less than ENVELOPE or
   RECONCILE in bold: a grid column cannot be narrower than the longest word
   in it, so the second column was pushed past the edge of the screen, where
   nothing could scroll to it. A list inside a panel (/sitemap, /help) has
   the panel's padding to lose as well. One tile a line is what fits. */
@media (max-width:360px){
  .os-rel .boot-grid.boot-grid-tools,
  .panel .os-groups .boot-grid.boot-grid-tools{grid-template-columns:minmax(0,1fr);}
}

/* A break between two groups has to be clearly larger than the gap between
   two tiles inside one, or fifteen groups read as one grid of a hundred. */
.os-tool-groups > .boot-groups{gap:30px;}

/* A heading jumped to must not end up under the bar at the top. */
.boot-sect[id], .boot-sect-head[id]{scroll-margin-top:76px;}

/* Where a tablet stops being a tablet. An iPad in portrait is 768 to 834
   CSS pixels wide and an iPad mini is 744, so a breakpoint at 860 handed
   every one of them the phone's two columns: two tool tiles four hundred
   pixels wide, a name of one word in each, and a page four times longer
   than it needed to be. Measured on an iPad at 820 it was two columns; on
   an iPad Pro at 1024 it was three, and the three looked like the site.

   700 is below the mini and above a phone in landscape, which is the one
   width where two is genuinely right - a 700-pixel column of three tiles
   is 226 pixels each, and /sovereign-suite's longest blurb still fits it,
   which is the browser's answer rather than mine. */
@media (max-width:699px){
  .boot-grid.boot-grid-tools{grid-template-columns:repeat(2,1fr);}
}
@media (max-width:640px){
  .boot-grid.boot-grid-tools{grid-template-columns:repeat(2,1fr);gap:8px;}
  /* 44px, not 0. A tile with one short line of text and this padding is
     40.55px tall, which is under the floor a finger needs; the point of
     dropping the min-height here was to stop a tile being taller than its
     text, not to let it be smaller than a tap. */
  .boot-tile-tool{min-height:44px;padding:12px 12px 11px;gap:5px;}
  /* Half the width is twice the lines. At a hundred and seventy pixels a
     word like "correspondence" is most of a line on its own, so a fifty-nine
     character sentence can need five of them - and a row of tiles is as tall
     as its tallest whatever this says, so the extra lines cost nothing. */
  .boot-tile-tool .boot-tile-sub{-webkit-line-clamp:5;line-clamp:5;}
  .boot-jump{flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-x:contain;
    scrollbar-width:none;padding-bottom:2px;}
  .boot-jump-chip{flex:none;}
  .boot-jump::-webkit-scrollbar{display:none;}
}

/* The suites, at the same tablet break as the tools above: three across on
   an iPad, two on a phone. The four small tiles below them stay at two on
   a tablet rather than going to three, because there are four of them and
   three leaves one alone on a second row - a 2x2 is the shape four things
   make. */
@media (max-width:699px){
  .boot-grid{grid-template-columns:repeat(2,1fr);}
}
@media (max-width:860px){
  .boot-grid-sm{grid-template-columns:repeat(2,1fr);}
}
/* A tile left alone on the last row (ten apps in threes, four suites in
   threes, an odd number in twos) takes the whole row, and the last of two
   on a row of three takes two places, so the grid ends on a full line
   rather than on a hole. */
@media (min-width:700px){
  .boot-grid:not(.boot-grid-sm) > :last-child:nth-child(3n+1){grid-column:1 / -1;}
  .boot-grid:not(.boot-grid-sm) > :last-child:nth-child(3n+2){grid-column:span 2;}
}
@media (min-width:861px){
  .boot-grid-sm > :last-child:nth-child(4n+1){grid-column:1 / -1;}
}
@media (max-width:699px){
  .boot-grid > :last-child:nth-child(2n+1){grid-column:1 / -1;}
}
@media (min-width:700px) and (max-width:860px){
  .boot-grid-sm > :last-child:nth-child(2n+1){grid-column:1 / -1;}
}
/* On a phone all six suites already sit above the fold. The four tiles
   below them are one short flick away, which is how every phone interface
   has ever worked, and is a better trade than deleting the line that says
   what a suite is for from the one place somebody meeting it for the first
   time will read it. So: tighter, not emptier. */
@media (max-width:420px){
  .boot-grid{gap:9px;}
  .boot-tile{min-height:44px;padding:12px 12px 11px;gap:5px;}
  .boot-tile-sm{padding:11px;gap:4px;}
  .boot-tile-name{font-size:var(--text-md);}
  .boot-tile-sm .boot-tile-name{font-size:var(--text-sm);}
  /* Three lines here, two everywhere else. The column is half as wide on a
     phone, so the same sentence needs the extra line, and two left most of
     them cut off mid-word. The six suites still clear the fold. */
  .boot-tile-sub{font-size:var(--text-2xs);-webkit-line-clamp:3;line-clamp:3;}
  .boot-tile-ico{width:30px;height:30px;}
  .boot-tile-sm .boot-tile-ico{width:26px;height:26px;}
}

.boot-recent{width:100%;display:flex;flex-direction:column;gap:8px;text-align:left;}
.boot-recent-head{display:flex;align-items:center;justify-content:space-between;padding:0 2px;}
/* Set as the groups above it are headed, so the page has one kind of
   section label and one left edge rather than two of each. */
.boot-recent-head .boot-count{margin:0;font:650 var(--text-2xs)/1.3 var(--font-ui);letter-spacing:.055em;color:var(--ink-soft);}
/* The link is a control of its own on the row, not a word in a sentence,
   so it gets the 44px a finger needs; the row centres on it. */
.boot-recent-head a{display:inline-flex;align-items:center;min-height:44px;box-sizing:border-box;font-size:var(--text-xs);color:var(--accent-strong);text-decoration:none;}
.boot-recent-head a:hover{text-decoration:underline;text-underline-offset:2px;}
/* In the dark the cards' shadow has nothing to fall on, so a hairline
   border does the lifting instead. */
:root[data-theme="dark"] .boot-recent .os-list.tight,
:root[data-theme="dark"] .boot-sect .os-list.tight{border:1px solid var(--rule);}
/* Continue: the documents last worked on, as small tiles that deal in
   after the groups above (--sect is when the last of those has arrived,
   which home-about.mjs writes onto .boot as --boot-end). Four across,
   two on a phone; the picture, the opening lines or the corner of a sheet
   on top, then the app's own icon, the name and when. */
.boot-continue{--sect:150ms;position:relative;}
/* Above the tiles, and the same height whether it is holding places or
   showing documents: the name and the line under it are one line each,
   and the places index.html holds open are the same boxes, empty. */
.boot-continue-tile .boot-tile-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;}
.boot-continue-tile .boot-tile-sub{-webkit-line-clamp:1;line-clamp:1;}
.boot-continue-tile.is-held{pointer-events:none;}
.boot-continue:not(.is-all) .boot-continue-tile.is-more{display:none;}
.boot-continue-acts{display:inline-flex;gap:14px;}
.boot-continue-tile.is-held .boot-tile-name, .boot-continue-tile.is-held .boot-tile-sub{border-radius:var(--radius-sm);background:var(--paper-inset);color:transparent;}
/* Dealt as the groups' tiles are, a --stagger apart, but it is not one of
   the groups: it comes and goes with what this browser has worked on. */
.boot-continue .boot-grid > *{--os-fade:var(--dur-fade);animation:os-rise-soft var(--dur-3) var(--ease-standard) backwards;animation-delay:var(--sect);}
.boot-continue .boot-grid > *:nth-child(2){animation-delay:calc(var(--sect) + var(--stagger));}
.boot-continue .boot-grid > *:nth-child(3){animation-delay:calc(var(--sect) + var(--stagger) * 2);}
.boot-continue .boot-grid > *:nth-child(4){animation-delay:calc(var(--sect) + var(--stagger) * 3);}
.boot-continue .boot-grid > *:nth-child(n+5){animation-delay:calc(var(--sect) + var(--stagger) * 4);}
.boot-continue > .boot-recent-head{--os-fade:var(--dur-fade);animation:os-fade-in var(--dur-3) var(--ease-standard) backwards;animation-delay:var(--sect, 0ms);}
.boot-continue-clear{display:inline-flex;align-items:center;min-height:44px;padding:0 2px;border:0;background:none;
  font:inherit;font-size:var(--text-xs);color:var(--accent-strong);cursor:pointer;}
.boot-continue-clear:hover{text-decoration:underline;text-underline-offset:2px;}
.boot-continue-clear:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--radius-sm);}
.boot-continue-note{margin:-6px 0 0;padding:0 2px;font-size:var(--text-2xs);line-height:1.4;color:var(--ink-faint);}
.boot-grid.boot-continue-grid{grid-template-columns:repeat(4,minmax(0,1fr));}
.boot-grid.boot-continue-grid > :nth-child(n):last-child{grid-column:auto;}
.boot-continue-tile{min-height:0;gap:6px;}
.boot-continue-peek{align-self:stretch;display:block;box-sizing:border-box;aspect-ratio:16 / 10;min-height:0;overflow:hidden;
  position:relative;border-radius:var(--radius-sm);background:var(--paper-inset);}
/* The edge is drawn over the picture, so a white page still has one. */
.boot-continue-peek::after{content:"";position:absolute;inset:0;border-radius:inherit;box-shadow:inset 0 0 0 1px var(--rule);pointer-events:none;}
.boot-continue-peek img{display:block;width:100%;height:100%;object-fit:cover;object-position:top;}
.boot-continue-peek.is-empty{display:grid;place-items:center;}
.boot-continue-peek.is-empty img{width:40px;height:40px;opacity:.85;}
.boot-continue-peek.is-lines{display:flex;flex-direction:column;gap:3px;padding:9px 10px;
  font-size:var(--text-2xs);line-height:1.35;color:var(--ink-soft);}
.boot-continue-peek.is-lines span{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}
.boot-continue-peek.is-lines span:first-child{font-weight:600;color:var(--ink);}
.boot-continue-peek.is-cells{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:1fr;
  font:var(--text-2xs)/1.2 var(--font-mono,ui-monospace,monospace);color:var(--ink-soft);}
.boot-continue-peek.is-cells span{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;padding:2px 4px;
  border-right:1px solid var(--rule);border-bottom:1px solid var(--rule);}
.boot-continue-name{display:flex;align-items:center;gap:7px;min-width:0;max-width:100%;}
.boot-continue-ico{flex:none;width:18px;height:18px;border-radius:var(--radius-sm);}
/* The app's mark, as its tile wears it (the colour rules are the tile's,
   under .boot-tile-ico): small beside the name, larger where a document
   has no preview to show. */
span.boot-continue-ico{display:grid;place-items:center;width:20px;height:20px;}
span.boot-continue-ico svg{width:14px;height:14px;stroke-width:var(--icon-stroke);}
.boot-continue-peek.is-empty span.boot-continue-ico{width:44px;height:44px;border-radius:var(--radius-md);}
.boot-continue-peek.is-empty span.boot-continue-ico svg{width:26px;height:26px;}
.boot-continue-name .boot-tile-name{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}
@media (max-width:699px){
  .boot-grid.boot-continue-grid{grid-template-columns:repeat(2,minmax(0,1fr));}
}
/* Settings, Available offline: one line for each of what is kept. */
.offline-line{display:block;}
.offline-line + .offline-line{margin-top:6px;}
/* Open in, under a tool's result: the tools the result can go on to. */
.ob-open-in{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 14px;}

.boot-foot{display:flex;flex-wrap:wrap;justify-content:flex-start;align-items:center;gap:0 18px;font-size:var(--text-xs);letter-spacing:-.003em;color:var(--ink-faint);}
.boot-foot a{color:var(--ink-soft);text-decoration:none;font-weight:400;display:inline-flex;align-items:center;min-height:24px;}
.boot-foot a:hover{color:var(--ink);text-decoration:underline;text-underline-offset:2px;}
.boot-foot .sep{display:none;}
/* A finger needs 44px; the small links are given it without looking it. */
@media (max-width:560px), (pointer:coarse){ .boot-foot a, .boot-foot button{display:inline-flex;align-items:center;min-height:44px;} }
@media (max-width:520px){
  .boot{padding:44px 20px 34px;gap:28px;}
  .boot-slogan{font-size:var(--text-md);}
  .boot-search{min-height:54px;padding:10px 16px;font-size:var(--text-md);}
  .boot-keys{display:none;}
}
/* A suite page: the brand becomes a small link home, the page's own name
   takes the h1, and the list starts at the top rather than centred in the
   viewport, because it is longer than one. */
/* Six pages, and all six are now a grid of a hundred tiles rather than the
   column of rows they were built as. 640px was right for a column and is
   half a tile too narrow for four tiles across: at that width four fifths
   of the tool blurbs were cut off mid-sentence, so every tile said what it
   was called and not what it did. The head and the line under it keep a
   readable measure of their own, because a sentence set across a thousand
   pixels is no easier to read here than anywhere else. */
.boot-page{justify-content:flex-start;padding-top:36px;max-width:1060px;}
.boot-page .boot-head,
.boot-page > .boot-count{max-width:720px;}
.boot-title{margin:10px 0 0;font-family:var(--font-headline);font-weight:700;font-size:var(--text-3xl);line-height:1.05;letter-spacing:var(--track-display);color:var(--ink);}
.boot-count{margin:-14px 0 0;font:500 var(--text-2xs) var(--font-mono,ui-monospace,monospace);letter-spacing:.09em;text-transform:uppercase;color:var(--ink-faint);}
/* A count that is a heading (/apps, /other-suites) met the site's ":root h2"
   rule, which outranks one class and set it in the heading face with the
   headings' tight tracking: EIGHTAPPLICATIONS. The page's own count takes
   its face and its tracking back. */
.boot-page > h2.boot-count{font-family:var(--font-mono,ui-monospace,monospace);letter-spacing:.09em;}
/* The front door's group headings met the same rule: YOUROWN VAULT. */
.boot-sect > h2.boot-sect-head{font-family:var(--font-sans,system-ui,sans-serif);letter-spacing:.055em;}
/* A group is a row that opens. The chevron points down while it is shut
   and up while it is open, and the tools inside are the tight list every
   other index on the site uses. */
.boot-group{width:100%;}
.boot-group>summary{list-style:none;cursor:pointer;-webkit-user-select:none;user-select:none;}
.boot-group>summary::-webkit-details-marker{display:none;}
summary.os-row::after{content:"";flex:none;box-sizing:border-box;width:7px;height:7px;margin:-3px 3px 0 2px;border-right:1.9px solid currentColor;border-bottom:1.9px solid currentColor;border-radius:0;transform:rotate(45deg);color:var(--ink-faint);opacity:.7;transition:transform var(--dur-2) var(--ease-standard);}
.boot-group[open]>summary.os-row::after{transform:rotate(225deg);margin-top:3px;}
.boot-group[open]>summary.os-row{box-shadow:var(--elev-2);}
.boot-group>.os-list.tight{margin:10px 0 4px;text-align:left;}
.boot-page .os-about{width:100%;text-align:left;box-sizing:border-box;}
.boot-page .os-about h2{font-size:var(--text-lg);margin:22px 0 8px;}
.boot-page .os-about a{color:var(--accent-strong);}
.boot-page .os-about p{font-size:var(--text-sm);line-height:1.65;color:var(--ink-soft);}
.boot-foot button{background:none;border:0;padding:0;margin:0;font:inherit;color:var(--accent-strong);font-weight:500;cursor:pointer;}
.boot-foot button:hover{text-decoration:underline;text-underline-offset:2px;}
@media (max-width:520px){
  .boot-title{font-size:var(--text-2xl);}
  .boot-page{padding-top:28px;}
}

/* ---------------------------------------------------------------
   The front door's additions: three tiles across under "Everything
   else", and the waitlist above the foot. The mark beside the title
   turns while it is pointed at; home-mark.js draws that.
   --------------------------------------------------------------- */


/* The waitlist: one card above the foot, centred like the head of the
   page, and the only place on it that asks for anything. */
.home-wait{width:100%;box-sizing:border-box;padding:28px 28px 22px;border-radius:var(--radius-md);
  background:var(--paper-raised);box-shadow:var(--elev-1);text-align:center;}
.home-wait h2{margin:0;font-family:var(--font-headline);font-size:var(--text-xl);font-weight:700;letter-spacing:var(--track-head);color:var(--ink);}
.home-wait > p{margin:8px auto 0;max-width:52ch;font-size:var(--text-sm);line-height:1.55;color:var(--ink-soft);}
.home-wait > p a{color:var(--accent-strong);text-underline-offset:3px;white-space:nowrap;}
.home-wait-form{margin:18px auto 0;max-width:520px;}
.home-wait-row{display:flex;gap:10px;}
.home-wait-row input{flex:1 1 auto;min-width:0;min-height:48px;box-sizing:border-box;padding:0 16px;border:0;border-radius:var(--radius-md);
  background:var(--paper);box-shadow:var(--elev-sunk);color:var(--ink);font:inherit;font-size:var(--text-md);}
.home-wait-row input:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--accent);}
/* Filled in the accent, with the paper colour for its label so it reads in
   the dark as well, where the accent is light. The front door carries no
   tool.css, which is where a button.btn-primary is otherwise filled. */
.home-wait-row .btn-primary{flex:none;min-height:48px;padding:0 22px;border:0;border-radius:var(--radius-md);
  background:var(--accent-strong);color:var(--paper-raised);font:600 var(--text-md) var(--font-sans);white-space:nowrap;cursor:pointer;
  transition:transform var(--dur-2) var(--ease-standard),filter var(--dur-2) var(--ease-standard);}
.home-wait-row .btn-primary:hover{filter:brightness(1.08);}
.home-wait-row .btn-primary:active{transform:scale(.97);}
.home-wait-row .btn-primary:focus-visible{outline:2px solid var(--accent);outline-offset:3px;}
.home-wait-row .btn-primary:disabled{cursor:default;filter:none;transform:none;}
.home-wait-form.is-done .btn-primary{background:var(--good);}
.home-wait-note{margin:10px 2px 0;min-height:1.5em;font-size:var(--text-xs);line-height:1.5;color:var(--ink-faint);}
.home-wait-note[data-state="ok"]{color:var(--good);}
.home-wait-note[data-state="error"]{color:var(--danger);}
@media (max-width:560px){ .home-wait-row{flex-direction:column;} .home-wait{padding:24px 18px 18px;} }

/* The slim bar the five full-window apps carry: the same parts, 38px tall,
   edge to edge, because the app's own toolbars sit directly under it. The
   height is a token so an app column can be sized against it. */
:root { --os-bar-h: 38px; }
.os-topbar.slim {
  max-width: none;
  /* A floor, not a height: between about 820 and 1000px the tabs wrap onto
     a second row, and a fixed 38px let that row lie across the app's own
     header. The apps measure the bar rather than assume it. */
  height: auto;
  min-height: var(--os-bar-h);
  padding: 0 14px;
  margin-bottom: 0;
  border-left: 0;
  border-right: 0;
  border-radius: 0;
  box-shadow: none;
}
/* The wordmark keeps its size on the slim bar: the bar is slimmer by its
   padding and its controls, not by making the name harder to read. */
.os-topbar.slim .brand-title { font-size: var(--text-md); }
.os-topbar.slim .os-mark { width: 18px; height: 18px; }
.os-topbar.slim .os-nav-tabs { padding: 2px 4px; }
.os-topbar.slim .nav-tab-btn { padding: 3px 10px; font-size: var(--text-xs); }
.os-topbar.slim .btn-search, .os-topbar.slim .btn-settings { padding: 3px 9px; font-size: var(--text-2xs); min-height: 26px; }
@media (pointer: coarse) {
  .os-topbar.slim { height: auto; min-height: var(--os-bar-h); }
  .os-topbar.slim .btn-search, .os-topbar.slim .btn-settings { min-height: 44px; }
}

/* The wordmark and the tagline (chrome.mjs): the line that says whose page
   this is, at the left of the footer's one line. */
.os-foot-brand { display: flex; flex-wrap: wrap; align-items: center; gap: 0 12px; margin: 0; min-width: 0; }
.os-foot-brand .brand-title { display: inline-flex; align-items: center; gap: 7px; min-height: 44px; font-size: var(--text-sm); text-decoration: none; }
.os-foot-brand .os-mark { width: 15px; height: 15px; }
/* The bar's mark is the one named for a navigation to carry across; a name
   used twice animates neither. */
.os-foot-brand .os-mark-wrap { view-transition-name: none; }
.os-foot-tag { font-size: var(--text-xs); color: var(--ink-soft); letter-spacing: var(--track-body); }

/* The footer, one shape on every page it is on. It used to be laid out by
   each page's own stylesheet and an inline style on its inner row, which is
   how the same footer came to be three footers. */
.os-footer {
  width: 100%;
  max-width: 1040px;
  box-sizing: border-box;
  margin: 56px auto 0;
  padding: 20px 22px 48px;
  border-top: 1px solid var(--rule);
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  font-size: var(--text-xs);
  color: var(--ink-faint);
}
.os-footer strong { font-family: var(--font-ui); font-size: var(--text-sm); color: var(--ink); }
.os-footer > div { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; }
.os-footer a { color: var(--ink-soft); text-decoration: none; }
.os-footer a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }

/* One line under the tool that points at its questions on the FAQ page. */
.os-faq-link { max-width: 944px; width: 100%; box-sizing: border-box; margin: 32px auto 0; padding: 0 22px; font: var(--text-sm)/1.5 var(--font-sans); color: var(--ink-soft); }
.os-faq-link a { color: var(--accent-strong); text-decoration: underline; text-underline-offset: 3px; }
@media (pointer: coarse) { .os-faq-link a { display: inline-flex; align-items: center; min-height: 44px; } }
/* The footer: one quiet line on every page. The wordmark and the tagline
   at the left, the few links at the right, in the quieter ink and a size
   down, so the foot of a page is somewhere to leave from rather than
   something more to read. No dots between the links: the gap separates
   them. */
/* Two rows at every width, the wordmark line and then the links: as one
   row the links wrapped under it only once privacyproof.js had added
   "Sent from this page" beside the tagline, and the footer jumped. */
.os-footer.os-foot-row { flex-direction: column; justify-content: flex-start; align-items: flex-start; gap: 0 24px; padding: 18px 22px 40px; font-size: var(--text-xs); letter-spacing: -.003em; color: var(--ink-faint); }
.os-footer.os-foot-row .os-foot-links { display: flex; flex-wrap: wrap; align-items: center; gap: 0 18px; }
.os-footer.os-foot-row .sep { display: none; }
/* At least 24 pixels tall at every width (WCAG 2.5.8): the links sit in
   rows with no gap between them, so the row's height is all the room a
   pointer has. Phones and touch screens raise it to 44 below. */
.os-footer.os-foot-row a { color: var(--ink-soft); font-weight: 400; display: inline-flex; align-items: center; min-height: 24px; }
.os-footer.os-foot-row a:hover { color: var(--ink); }
.os-footer.os-foot-row .os-foot-brand a { color: var(--ink); font-weight: 700; }
.os-footer.os-foot-row .os-foot-brand a span { color: var(--accent-strong); }
@media (max-width: 560px), (pointer: coarse) { .os-footer.os-foot-row a { display: inline-flex; align-items: center; min-height: 44px; } }
/* The phone rule further up stacks every footer into a column, with
   !important to reach inline styles. This row is its links themselves, not
   blocks holding links, so stacked it put every link and every dot on a line
   of its own: 560px of footer on a 390px screen. It stays a wrapping row. */
/* On a phone the wordmark's line sits over the links, both from the left,
   and the links wrap; each is 44px tall there. */
@media (max-width: 760px) {
  .os-footer.os-foot-row { flex-direction: column !important; align-items: flex-start !important; gap: 0 !important; padding: 14px 16px 32px; }
  .os-footer.os-foot-row .os-foot-links { gap: 0 20px !important; }
}

/* ---------------------------------------------------------------
   Page chrome, lifted out of the pages themselves.

   Each of these was written out identically on ten or more pages - and
   the same rule in forty-four files is a rule that gets changed in forty
   of them. Generated by .github/scripts/lift-css.mjs; a rule whose body
   differed between pages was left where it was.
   --------------------------------------------------------------- */
.gd p { font-size: var(--text-sm); line-height: 1.72; margin: 0 0 13px; }
.gd li { font-size: var(--text-sm); line-height: 1.72; margin: 0 0 9px; }
.gd h2 { font-size: var(--text-xl); margin: 0 0 12px; }

/* ---------------------------------------------------------------
   Page chrome, lifted out of the pages themselves.

   Each of these was written out identically on ten or more pages - and
   the same rule in forty-four files is a rule that gets changed in forty
   of them. Generated by .github/scripts/lift-css.mjs; a rule whose body
   differed between pages was left where it was.
   --------------------------------------------------------------- */
.gd-wrap { overflow-x: auto; margin: 4px 0 14px; }
.gd-sum { width: 100%; border-collapse: collapse; font-size: var(--text-sm); min-width: 460px; }
.gd-sum th, .gd-sum td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--rule); vertical-align: top; }
.gd-sum th { font: 600 var(--text-2xs) var(--font-mono); letter-spacing: .05em; text-transform: uppercase; color: var(--ink-soft); }

/* ---------------------------------------------------------------
   Page chrome, lifted out of the pages themselves.

   Each of these was written out identically on ten or more pages - and
   the same rule in forty-four files is a rule that gets changed in forty
   of them. Generated by .github/scripts/lift-css.mjs; a rule whose body
   differed between pages was left where it was.
   --------------------------------------------------------------- */
.gd code { font: var(--text-xs)/1.4 var(--font-mono,monospace); background: var(--paper-inset); border: 1px solid var(--rule); border-radius: var(--radius-sm); padding: 1px 5px; }
.gd ul, .gd ol { font-size: var(--text-md); line-height: 1.7; margin: 0 0 13px; padding-left: 20px; }
.gd pre { font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1.5; overflow: auto; margin: 4px 0 14px; padding: 12px 14px; background: var(--paper-inset); border: 1px solid var(--rule); border-radius: var(--radius-sm); }
.gd-answer { font-size: var(--text-md); line-height: 1.65; padding: 14px 18px; border-left: 3px solid var(--accent-strong); background: var(--paper-inset); border-radius: 0 var(--radius-md) var(--radius-md) 0; margin: 0 0 18px; }
.gd-box { display: grid; grid-template-columns: 96px minmax(0,1fr); gap: 6px 14px; font-size: var(--text-sm); line-height: 1.55; margin: 0 0 14px; }
.gd-box b { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--accent-strong); }
.gd-sum td.yes { color: var(--ok, var(--ink)); font-weight: 600; }
.gd-sum td.no { color: var(--ink-faint); }

/* A guide is read, not used, so its prose sits on the page rather than in
   a white card: a card 1000px wide with a 500px column of text in it left
   the right half of every section empty. A guide is the page with a date
   under its heading (.os-dated). The text is set at the size of a book's,
   to one measure, and the lede above it keeps the same measure, so the
   left and right edges of the reading column run straight down the page. */
main:has(> .page-header .os-dated) > .page-header > :is(.page-sub, .os-dated) { max-width: var(--measure); }
main:has(> .page-header .os-dated) > .page-header > .page-sub { font-size: var(--text-md); line-height: var(--leading-prose); }
main:has(> .page-header .os-dated) > .panel.gd {
  max-width: var(--measure);
  margin: 0 0 var(--space-7);
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  font-size: var(--text-md);
}
main:has(> .page-header .os-dated) > .panel.gd > :is(p, ul, ol) { max-width: none; }
main:has(> .page-header .os-dated) > .panel.gd :is(p, li) { font-size: var(--text-md); line-height: var(--leading-prose); }
main:has(> .page-header .os-dated) > .panel.gd h2 { font-size: var(--text-xl); line-height: 1.3; margin: 0 0 var(--space-3); }
main:has(> .page-header .os-dated) > .panel.gd h2:not(:first-child) { margin-top: var(--space-6); }
main:has(> .page-header .os-dated) > .panel.gd .gd-answer { font-size: var(--text-md); }
main:has(> .page-header .os-dated) > .page-header { margin-bottom: var(--space-6); }

/* ==================================================================
   MOTION, PART TWO: ARRIVING, REVEALING AND RESPONDING

   The block further up gives the site its hovers and presses. This one
   lets things arrive, and carries the frame of the site from one page to
   the next.

   Continuity. The bar at the top of every page is given a transition
   name, so a cross-document view transition treats the bar on the page
   you are leaving and the bar on the page you are arriving at as the
   same object. It stays put while the content changes underneath it.
   Opening an application does the same with its icon: the tile's icon
   on the page you left becomes the icon in the application's bar.

   Arrival. The title block and the first panels rise 8px into place, one
   step after another; the rows of a list 4px, 25ms apart and never more
   than eight steps. The distances are small on purpose: a long slide
   reads as a template, a short one as the page settling.

   Reveal. Sections further down do the same as they come into view,
   driven by a scroll timeline rather than by script. They fade from 0.85
   rather than from nothing: text held at zero opacity while it waits to
   be scrolled to is text a contrast checker reads as failing.

   Response. A row, a tile or a card answers the pointer with a tint and
   a firmer edge, and lifts by a pixel. A tile or a plan also carries a
   soft light under the pointer, and the four suites on the front door
   lean towards it by three degrees at most (motion.js writes where the
   pointer is). A tile's icon acts out its picture, once, when the tile is
   pointed at or focused; the mark turns while it is pointed at; and a
   primary button catches one highlight across its face. Each is a reply
   to the hand, over in --dur-5 or less, and none of them runs on its own.

   Switching it off. All of it is governed by data-motion on <html>; with
   motion switched down, every entrance here becomes a fade of 100ms
   (--os-fade, below each one) and nothing moves.
   ================================================================== */


/* ---------------------------------------------------- continuity */

/* One bar per page, every page, so the name is unique wherever it is
   used and the browser can match the two. Pages without a bar (the
   front door is its own thing) simply fade as before. */
/* (No names: see the view transitions above, now off.) */

/* The mark and the tab you are on carry their names across a navigation
   too. The mark is fixed in place, so naming it simply stops it
   flickering; the active tab is in a different place on each page, so
   naming it makes the highlight slide from the old tab to the new one.

   Only the active tab is named. A name has to be unique on the page,
   and naming every tab would give the browser a dozen elements calling
   themselves the same thing, at which point it gives up and animates
   none of them - including the one that mattered. */



/* Opening an application. motion.js names the icon of the tile that was
   pressed, and only that one, as the page is left; the application's own
   icon in its bar carries the same name, so the browser moves the one
   into the other while the pages cross-fade. An application page marks
   its icon with data-app-icon; the marks the Workspace applications
   already draw are named here so they need no change. A page with no
   such icon simply lets the tile's icon fade with the rest. */
[data-app-icon],
.docs-appmark, .sh-mark, .sl-logo, .pn-mark, .sv-mark { view-transition-name: app-icon; }
::view-transition-group(app-icon) {
  animation-duration: var(--dur-4);
  animation-timing-function: var(--ease-emph);
}
/* The two pictures of the icon are different drawings, so they cross-
   fade inside the moving box rather than one stretching into the other. */
::view-transition-old(app-icon),
::view-transition-new(app-icon) { height: 100%; object-fit: contain; }

/* A dialog arrives rather than appearing: it fades, rises 8px and comes
   up from 99% over --dur-3, and leaves in two thirds of that. ::backdrop
   is transitioned separately because it is not a descendant of the
   dialog and inherits nothing from it. Both need allow-discrete: a
   dialog goes from display:none to display:block in one step, and
   without it there is nothing to animate between. @starting-style is
   what provides the "before" that a discrete change otherwise lacks. */
@supports (transition-behavior: allow-discrete) {
  dialog {
    opacity: 0;
    transform: translateY(var(--os-rise)) scale(0.99);
    transition: opacity var(--dur-3-out) var(--ease-exit),
                transform var(--dur-3-out) var(--ease-exit),
                overlay var(--dur-3-out) allow-discrete,
                display var(--dur-3-out) allow-discrete;
  }
  dialog[open] {
    opacity: 1;
    transform: none;
    transition-duration: var(--dur-3);
    transition-timing-function: var(--ease-standard);
  }
  @starting-style {
    dialog[open] { opacity: 0; transform: translateY(var(--os-rise)) scale(0.99); }
  }
  dialog::backdrop {
    background: rgb(0 0 0 / 0);
    transition: background-color var(--dur-3) var(--ease-standard),
                overlay var(--dur-3) allow-discrete,
                display var(--dur-3) allow-discrete;
  }
  dialog[open]::backdrop { background: rgb(0 0 0 / 0.42); }
  @starting-style {
    dialog[open]::backdrop { background: rgb(0 0 0 / 0); }
  }
}

/* ---------------------------------------------------- arriving */

/* Both of these are written with a from and no to, and are filled
   backwards rather than both ways. That is not a stylistic choice, it
   is the whole thing working at all.

   An animation that fills forwards keeps its last keyframe applied for
   as long as the animation exists, and an animation with no duration
   limit on the element exists for ever. A `to { transform: none }` on
   a row therefore does not settle the row, it pins it: every hover on
   every row would stop working, silently, because a filled keyframe
   beats a matching rule no matter how specific the rule is.

   With no `to`, the animation runs towards whatever the element's own
   rules say, and with `backwards` it lets go the instant it is done.

   The distance is a custom property so that the same keyframes become a
   plain fade with motion switched down, where it is nought. */
@keyframes os-rise {
  from { opacity: 0; transform: translate3d(0, var(--os-rise), 0); }
}

@keyframes os-rise-soft {
  from { opacity: 0; transform: translate3d(0, var(--os-rise-sm), 0); }
}

/* Every element that arrives says so with --os-fade, which is what lets
   it keep a 100ms fade with motion switched down (section MOTION,
   SWITCHED DOWN); anything that does not ask ends at once there. */
.page-header, .app-wrapper > main > .panel, .os-list > .os-row, .os-list > li > .os-row,
.boot-search, .boot-foot,
.boot-sect .boot-grid > *, .boot-groups > .boot-grid > *, .boot-sect > .boot-sect-head,
.boot-title, .boot-slogan, .boot-count, .os-tool-groups > .boot-group,
.eyebrow, .page-title, .lede, main.wrap > *, .wrap > main > section, .os-jump,
main.app-wrapper > .installer, main.app-wrapper > section.block,
.os-arriving, .status-line, .panel img, [data-os-save] [data-os-tick] {
  --os-fade: var(--dur-fade);
}

/* The title block, then the panels under it, a step apart. Four steps
   at most: past that the eye stops reading one movement with a
   direction and starts reading a queue. */
.page-header { animation: os-rise var(--dur-5) var(--ease-emph) backwards; }

/* Counted with the sibling combinator rather than with :nth-of-type,
   because :nth-of-type counts elements of the same tag and every one of
   these is a div - so on a page whose title block is also a div the
   first panel is the second div, and the delays all landed one place
   out. `.panel ~ .panel` counts panels, which is what was meant. */
.app-wrapper > main > .panel {
  animation: os-rise var(--dur-4) var(--ease-emph) var(--os-step) backwards;
}
.app-wrapper > main > .panel ~ .panel { animation-delay: calc(var(--os-step) * 2); }
.app-wrapper > main > .panel ~ .panel ~ .panel { animation-delay: calc(var(--os-step) * 3); }

/* A list of rows deals itself out: 4px, --dur-3 apiece, --stagger apart,
   and eight steps then flat. The delays are written here rather than
   measured by a script on purpose: a class added after the page has
   painted would send rows that are already on screen back to the start
   of their entrance, which is worse than no stagger at all. The sitemap
   has well over a hundred rows, and with the cap the last of them has
   started 200ms after the first. */
.os-list > .os-row,
.os-list > li > .os-row {
  animation: os-rise-soft var(--dur-3) var(--ease-standard) backwards;
}
.os-list > .os-row:nth-child(2), .os-list > li:nth-child(2) > .os-row { animation-delay: var(--stagger); }
.os-list > .os-row:nth-child(3), .os-list > li:nth-child(3) > .os-row { animation-delay: calc(var(--stagger) * 2); }
.os-list > .os-row:nth-child(4), .os-list > li:nth-child(4) > .os-row { animation-delay: calc(var(--stagger) * 3); }
.os-list > .os-row:nth-child(5), .os-list > li:nth-child(5) > .os-row { animation-delay: calc(var(--stagger) * 4); }
.os-list > .os-row:nth-child(6), .os-list > li:nth-child(6) > .os-row { animation-delay: calc(var(--stagger) * 5); }
.os-list > .os-row:nth-child(7), .os-list > li:nth-child(7) > .os-row { animation-delay: calc(var(--stagger) * 6); }
.os-list > .os-row:nth-child(8), .os-list > li:nth-child(8) > .os-row { animation-delay: calc(var(--stagger) * 7); }
.os-list > .os-row:nth-child(n+9), .os-list > li:nth-child(n+9) > .os-row { animation-delay: calc(var(--stagger) * 8); }

/* A paid application's start screen deals its cards out, left to right:
   start something, open a file, the demonstration. Each application names
   its row of cards in its own sheet; the arrival is the same in all of
   them, so it is written once here. */
:is(.kp-cards, .ec-cards, .bk-start, .rt-cards, .rs-start-grid, .pr-cards, .wr-cards, .au-cards, .sd-cards, .vx-cards, .er-start, .um-cards) > * {
  --os-fade: var(--dur-fade);
  animation: os-rise var(--dur-4) var(--ease-emph) backwards;
}
:is(.kp-cards, .ec-cards, .bk-start, .rt-cards, .rs-start-grid, .pr-cards, .wr-cards, .au-cards, .sd-cards, .vx-cards, .er-start, .um-cards) > :nth-child(2) { animation-delay: calc(var(--os-step) * 2); }
:is(.kp-cards, .ec-cards, .bk-start, .rt-cards, .rs-start-grid, .pr-cards, .wr-cards, .au-cards, .sd-cards, .vx-cards, .er-start, .um-cards) > :nth-child(3) { animation-delay: calc(var(--os-step) * 4); }
:is(.kp-cards, .ec-cards, .bk-start, .rt-cards, .rs-start-grid, .pr-cards, .wr-cards, .au-cards, .sd-cards, .vx-cards, .er-start, .um-cards) > :nth-child(n+4) { animation-delay: calc(var(--os-step) * 6); }

/* ---------------------------------------------------- revealing */

/* Scroll-driven, so it costs nothing and needs no script. A section is
   settled by the time 60% of it has come into view, which is what stops
   a long page feeling as though it is fighting the scroll. Anything
   already on the first screen when the page opens is past that range
   and simply there. */
@supports (animation-timeline: view()) {
  @keyframes os-reveal {
    from { opacity: 0.4; transform: translate3d(0, var(--os-rise-lg), 0); }
    to   { opacity: 1; transform: none; }
  }

  .app-wrapper > main > .panel ~ .panel ~ .panel ~ .panel,
  .app-wrapper > main > .os-rel,
  .os-guides {
    animation: os-reveal linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 60%;
  }
}

/* ------------------------------------ the other two page shapes

   A suite page - /sovereign-suite, /record, /workspace - is
   .boot.boot-page: a .boot-head, then a list, then a column of groups.
   A prose page - /why, /privacy, /about, /whats-new and the rest - is a
   .wrap holding an .eyebrow, an h1.page-title and a p.lede, with no
   panel and no header block. Both are given the same steps as a tool. */

/* The search row is the primary action on the front door and takes the
   step after the title block. The footer waits for the last group:
   home-about.mjs works out when that is and writes it onto .boot. */
.boot-search { animation: os-rise-soft var(--dur-3) var(--ease-standard) calc(var(--os-step) * 3) backwards; }
/* The first visit's steps come in a step after the search row they sit
   under, and before the first group deals its tiles. */
.boot-foot   { animation: os-rise-soft var(--dur-3) var(--ease-standard) backwards;
  animation-delay: var(--boot-end, var(--dur-5)); }

/* The waitlist is below the fold on most screens, so it arrives as it is
   scrolled to, and finishes once it is wholly on screen: it sits right
   above the foot, where a range that ends part way up the viewport could
   never be reached. */
@supports (animation-timeline: view()) {
  .home-wait {
    animation: os-reveal linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 100%;
  }
}

/* Each group deals its own tiles out, starting from the moment the group
   carries as --sect. home-about.mjs computes that from the tiles above
   it, so moving a tile between groups cannot leave the timing behind.

   The tiles are offset rather than the section being faded in over them.
   A container animating with backwards fill hides its children while
   they run, so their entrance happens behind an invisible box. */
.boot-sect .boot-grid > *,
.boot-groups > .boot-grid > *               { animation: os-rise-soft var(--dur-3) var(--ease-standard) backwards;
                                                 animation-delay: var(--sect, 0ms); }
.boot-sect .boot-grid > *:nth-child(2),
.boot-groups > .boot-grid > *:nth-child(2)  { animation-delay: calc(var(--sect, 0ms) + var(--stagger)); }
.boot-sect .boot-grid > *:nth-child(3),
.boot-groups > .boot-grid > *:nth-child(3)  { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 2); }
.boot-sect .boot-grid > *:nth-child(4),
.boot-groups > .boot-grid > *:nth-child(4)  { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 3); }
.boot-sect .boot-grid > *:nth-child(5),
.boot-groups > .boot-grid > *:nth-child(5)  { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 4); }
.boot-sect .boot-grid > *:nth-child(6),
.boot-groups > .boot-grid > *:nth-child(6)  { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 5); }
.boot-sect .boot-grid > *:nth-child(7),
.boot-groups > .boot-grid > *:nth-child(7)  { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 6); }
.boot-sect .boot-grid > *:nth-child(8),
.boot-groups > .boot-grid > *:nth-child(8)  { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 7); }
.boot-sect .boot-grid > *:nth-child(9),
.boot-groups > .boot-grid > *:nth-child(9)  { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 8); }
.boot-sect .boot-grid > *:nth-child(10),
.boot-groups > .boot-grid > *:nth-child(10) { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 9); }
.boot-sect .boot-grid > *:nth-child(11),
.boot-groups > .boot-grid > *:nth-child(11) { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 10); }
.boot-sect .boot-grid > *:nth-child(12),
.boot-groups > .boot-grid > *:nth-child(12) { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 11); }
.boot-sect .boot-grid > *:nth-child(13),
.boot-groups > .boot-grid > *:nth-child(13) { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 12); }
/* Fourteen places a group at most, so far: the applications came in one at
   a time and each new tile was arriving with the one before it. */
.boot-sect .boot-grid > *:nth-child(n+14),
.boot-groups > .boot-grid > *:nth-child(n+14) { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 13); }
/* The Workspace is one group, so its grid is not inside a section and
   carries no --sect of its own; it deals out after the title block. */
.boot-groups > .boot-grid { --sect: calc(var(--os-step) * 3); }

.boot-sect .os-list > .os-row                  { animation-delay: var(--sect, 0ms); }
.boot-sect .os-list > .os-row:nth-child(2)     { animation-delay: calc(var(--sect, 0ms) + var(--stagger)); }
.boot-sect .os-list > .os-row:nth-child(3)     { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 2); }
.boot-sect .os-list > .os-row:nth-child(4)     { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 3); }
.boot-sect .os-list > .os-row:nth-child(5)     { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 4); }
.boot-sect .os-list > .os-row:nth-child(6)     { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 5); }
.boot-sect .os-list > .os-row:nth-child(7)     { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 6); }
.boot-sect .os-list > .os-row:nth-child(n+8)   { animation-delay: calc(var(--sect, 0ms) + var(--stagger) * 7); }

/* The caption arrives with the first tile it names. */
.boot-sect > .boot-sect-head { animation: os-fade-in var(--dur-3) var(--ease-standard) backwards;
  animation-delay: var(--sect, 0ms); }

/* The title block of a suite page, and of the front door. The wrapper
   is deliberately not animated, only its children: a parent that moves
   while its children also move reads as two entrances rather than one. */
.boot-title  { animation: os-rise      var(--dur-4) var(--ease-emph)                          backwards; }
.boot-slogan { animation: os-rise-soft var(--dur-3) var(--ease-standard) var(--os-step)       backwards; }
.boot-count  { animation: os-rise-soft var(--dur-3) var(--ease-standard) calc(var(--os-step) * 2) backwards; }

/* The groups on a suite page deal themselves out like any other list,
   after the title block. */
.os-tool-groups > .boot-group { animation: os-rise-soft var(--dur-3) var(--ease-standard) backwards;
  animation-delay: calc(var(--os-step) * 3); }
.os-tool-groups > .boot-group:nth-child(2)  { animation-delay: calc(var(--os-step) * 3 + var(--stagger)); }
.os-tool-groups > .boot-group:nth-child(3)  { animation-delay: calc(var(--os-step) * 3 + var(--stagger) * 2); }
.os-tool-groups > .boot-group:nth-child(4)  { animation-delay: calc(var(--os-step) * 3 + var(--stagger) * 3); }
.os-tool-groups > .boot-group:nth-child(5)  { animation-delay: calc(var(--os-step) * 3 + var(--stagger) * 4); }
.os-tool-groups > .boot-group:nth-child(6)  { animation-delay: calc(var(--os-step) * 3 + var(--stagger) * 5); }
.os-tool-groups > .boot-group:nth-child(7)  { animation-delay: calc(var(--os-step) * 3 + var(--stagger) * 6); }
.os-tool-groups > .boot-group:nth-child(n+8) { animation-delay: calc(var(--os-step) * 3 + var(--stagger) * 7); }

/* A prose page. These are plain class selectors on purpose, so that the
   .page-header rule further up still governs a tool page and this one
   only reaches the pages that have no header block at all. */
.eyebrow    { animation: os-rise-soft var(--dur-3) var(--ease-standard)                backwards; }
.page-title { animation: os-rise      var(--dur-4) var(--ease-emph)                    backwards; }
.lede       { animation: os-rise      var(--dur-4) var(--ease-emph)     var(--os-step) backwards; }

/* Forty-two pages put their content straight into <main class="wrap">
   rather than into panels. The working parts of them have no class they
   share - .fields, .controls, .seedline, .exports, .grid2 and thirty
   more - so this asks for the children and names the exceptions instead.

   One delay for all of them, not a stagger. The body of a tool is a
   label, a box, a row of buttons and a result: read as a block, arriving
   as a block. The stagger is for lists.

   Every argument of a single :not() counts once towards specificity, and
   the most specific one of these is a class, so the whole selector counts
   as one class and one element. That is deliberate: it has to beat
   .os-arriving, which is one class, so that a result shown on one of
   these pages keeps one animation-name for its whole life. The note
   beside .os-arriving further down says what happens when it does not. */
main.wrap > *:not(.page-title, .sub, .lede, .eyebrow, .badge-lic, .panel,
    .os-faq-link, .boot-foot, .entry, .road, .obs-file-a11y,
    h2, hr, script, style, template, link, noscript) {
  animation: os-rise var(--dur-4) var(--ease-emph) calc(var(--os-step) * 2) backwards;
}

/* The sentence under the title. Half these pages call it .lede and half
   call it .sub, which is the same line doing the same job under two
   names. .sub is also the small grey caption under a drop zone, under a
   field and under a chart, several hundred times over; this is only the
   one directly after the title. */
main.wrap > .page-title + .sub,
.wrap > main > .page-title + .sub {
  animation: os-rise var(--dur-4) var(--ease-emph) var(--os-step) backwards;
}

/* The line pointing at the questions, where a page still has one: the
   last step of the title block. Named with its element so it beats
   .os-arriving rather than tying with it. */
p.os-faq-link { animation: os-rise-soft var(--dur-3) var(--ease-standard) calc(var(--os-step) * 3) backwards; }

/* A page that splits the wrapper from the main element and puts its
   whole body in one section: the section arrives as one block, and the
   h2s in it reveal as they are scrolled to. */
.wrap > main > section { animation: os-rise var(--dur-4) var(--ease-emph) calc(var(--os-step) * 2) backwards; }

@supports (animation-timeline: view()) {
  /* The body of a prose page, and the hundred entries on /whats-new,
     reveal as they are scrolled to rather than all at once. */
  .wrap > main > h2,
  .wrap > main > .panel,
  main.wrap > h2,
  main.wrap > .panel,
  main.wrap > .entry,
  main.wrap > .road,
  .boot-page .os-about h2 {
    animation: os-reveal linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 60%;
  }
}

/* Everything else on a prose page, and the blocks that end every page.
   The range ends when the element is wholly on the screen rather than
   part way up it: a footer, or the last paragraph before one, can never
   be scrolled far up the viewport, and a reveal that cannot finish leaves
   its element half transparent. */
@supports (animation-timeline: view()) {
  .wrap > main > .lede ~ :is(p, ul, ol, dl, figure, blockquote, table, div, section),
  .os-footer,
  .os-groups > .os-group,
  main.app-wrapper > section.block ~ section.block {
    animation: os-reveal linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 100%;
  }
}

/* The first blocks of /desktop and /teams arrive with the title, as a
   tool's first panel does; the rest reveal above. */
main.app-wrapper > .installer,
main.app-wrapper > section.block:first-of-type { animation: os-rise var(--dur-4) var(--ease-emph) var(--os-step) backwards; }

/* The row of section links at the top of /settings. */
.os-jump { animation: os-rise-soft var(--dur-3) var(--ease-standard) var(--os-step) backwards; }

/* A link inside running text answers the pointer by thickening its rule
   rather than by changing colour, which is the quieter of the two and
   the one that does not move the words around it. */
.wrap > main :is(p, li, dd) a {
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: text-decoration-thickness var(--dur-2) var(--ease-standard),
              text-underline-offset var(--dur-2) var(--ease-standard);
}
.wrap > main :is(p, li, dd) a:hover { text-decoration-thickness: 2px; text-underline-offset: 4px; }

/* Changing the colourway, or light for dark, is one cross-fade of the
   whole page over --dur-2 rather than every element easing its own
   colours - which is several thousand transitions on /sitemap, and still
   left gradients and shadows to snap. theme.js adds the class for the
   length of the swap. It used to open as a circle from the point that
   was pressed, which is an effect somebody notices every time; a change
   of colour should be noticed once. */
html.theme-swap::view-transition-old(root) { animation: var(--dur-2) var(--ease-standard) both os-fade-out; }
html.theme-swap::view-transition-new(root) { animation: var(--dur-2) var(--ease-standard) both os-fade-in; }

/* The bar gains a shadow once the page has moved under it, so it reads
   as a layer above the content rather than as part of it, and on a page
   that is read rather than used - a guide, the policies, the changelog,
   anything that is not a tool - it carries a hairline that fills as the
   page is read. Both are driven by the document's own scroll position,
   so there is no scroll handler anywhere on this site, and the line is
   a transform. A tool page has no line: it is a thing to work in, not a
   distance to cover. A page too short to scroll leaves its timeline
   inactive, and the line stays empty. With motion switched down the line
   is not drawn at all, because a scroll timeline sent back to the
   document's clock would end on a full line. */
@supports (animation-timeline: scroll()) {
  @keyframes os-topbar-lift {
    from { box-shadow: none; }
    to   { box-shadow: 0 10px 24px -18px rgb(0 0 0 / 0.55); }
  }
  .os-topbar {
    animation: os-topbar-lift linear both;
    animation-timeline: scroll(root);
    animation-range: 0 90px;
  }

  @keyframes os-progress { to { transform: scaleX(1); } }
  body:not([data-kind="tool"]) .os-topbar::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: -1px;
    height: 2px;
    transform: scaleX(0);
    transform-origin: 0 50%;
    background: var(--accent-strong);
    pointer-events: none;
    animation: os-progress linear both;
    animation-timeline: scroll(root);
  }
  :root[data-motion="none"] .os-topbar::after { display: none; }
}

/* The lift is driven by scrolling, and with motion off the rule above puts
   every animation back on the document's clock, which ends it at once: the
   bar sat under a drop shadow from the moment the page opened. With motion
   off it has no lift at all, only its hairline. */
:root[data-motion="none"] .os-topbar { animation: none !important; }

/* On a page in a column (a tool, a guide, /apps, /pro) the bar is as wide as
   the column, 1040px at most, and its hairline stopped short of the window on
   both sides, a strip floating in the middle of a wide screen where the
   front door and the applications run theirs edge to edge. The hairline is
   drawn here instead, one pixel tall and carried to both edges by a spread
   shadow clipped to that pixel: a shadow is never scrollable overflow, so
   nothing can scroll sideways, and it needs no gradient. The lift, which was
   as narrow as the column, is the same line growing heavier as the page
   scrolls under it. */
:root body > .os-topbar { box-shadow: none; animation: none; }
/* Where the stacked bar scrolls away (601 to 1040px) it is not pinned, but
   it still has to be the box its hairline is measured from. */
@media (min-width: 601px) and (max-width: 1040px) {
  :root body > .os-topbar { position: relative; }
}
:root body > .os-topbar::before {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--hairline);
  box-shadow: 0 0 0 100vmax var(--hairline);
  clip-path: inset(0 -100vmax);
  pointer-events: none;
}
@supports (animation-timeline: scroll()) {
  @keyframes os-topbar-rule { to { background: var(--hairline-strong); box-shadow: 0 0 0 100vmax var(--hairline-strong); } }
  :root body > .os-topbar::before {
    animation: os-topbar-rule linear both;
    animation-timeline: scroll(root);
    animation-range: 0 90px;
  }
  :root[data-motion="none"] body > .os-topbar::before { animation: none !important; }
}

/* ---------------------------------------------------- responding */

/* A row, a tile or a card answers the pointer the same way: the surface
   takes a tint towards the inset paper, its edge firms to the strong
   rule, and it lifts by one pixel. --dur-2, both ways. The chevron on a
   row goes on ahead of it by 3px, so the movement has a direction and
   reads as "this goes somewhere"; the chevron is a rotated corner, so
   the slide has to come before the rotation or it travels diagonally. */
a.os-row:hover, a.os-row:focus-visible,
button.os-row:hover, button.os-row:focus-visible,
.boot-tile:hover, .boot-tile:focus-visible {
  transform: translateY(-1px);
  background-color: color-mix(in srgb, var(--paper-raised) 55%, var(--paper-inset));
  box-shadow: 0 0 0 1px var(--rule-strong);
}
/* Pressed, it goes down to 99%: a tile is wider than a button, and the
   same ratio moves its edges further. */
a.os-row:active, button.os-row:active, .boot-tile:active {
  transform: scale(0.99);
  transition-duration: var(--dur-1);
}
.os-row, .boot-tile {
  transition: transform var(--dur-2) var(--ease-standard),
              background-color var(--dur-2) var(--ease-standard),
              box-shadow var(--dur-2) var(--ease-standard);
}
a.os-row::after {
  transition: transform var(--dur-2) var(--ease-standard),
              opacity var(--dur-2) var(--ease-standard);
}
a.os-row:hover::after, a.os-row:focus-visible::after {
  transform: translateX(3px) rotate(45deg);
  opacity: 1;
}

/* The light under the pointer. A tile or a plan carries a circle of the
   accent the size of a hand, at 9 per cent at its centre, which sits
   under the pointer while it is over the surface: about as far as it can
   go before it stops reading as light on a surface and starts reading as
   a coloured box. motion.js writes where the pointer is (--os-gx, --os-gy)
   and the circle is moved there by transform; it comes and goes by
   opacity over --dur-3. It is under the surface's content (z-index -1 in
   the surface's own stacking context, so above its background) and it
   catches nothing. Only with a real pointer, and not at all with motion
   switched down. A plan's clip is let out by 3px, because the light
   kit.js runs once round a plan's edge (OBSCURA.edgeLight) is drawn on
   its border and would otherwise be cut in half. */
.boot-tile, .pp-plan { isolation: isolate; overflow: hidden; overflow: clip; }
.pp-plan { overflow-clip-margin: 3px; }
.boot-tile::before, .pp-plan::before {
  content: "";
  position: absolute;
  left: -160px;
  top: -160px;
  z-index: -1;
  width: 320px;
  height: 320px;
  border-radius: 50%;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--accent-strong) 9%, transparent), transparent);
  opacity: 0;
  pointer-events: none;
  transform: translate3d(var(--os-gx, 0px), var(--os-gy, 0px), 0);
  transition: opacity var(--dur-3) var(--ease-standard);
}
@media (hover: hover) and (pointer: fine) {
  .boot-tile:hover::before, .pp-plan:hover::before { opacity: 1; }
}
:root[data-motion="none"] .boot-tile::before,
:root[data-motion="none"] .pp-plan::before { display: none; }


/* A highlight crosses a primary button once as the pointer arrives, in
   --dur-5. Once rather than a loop, because a looping shine on a page
   full of buttons would be unbearable. The light is under the label, in
   the button's own stacking context, so the words are never dimmed by
   it. The button's position and clipping are set at no weight at all
   (:where), so any page that places a primary button itself still does. */
:where(.btn-primary) { position: relative; overflow: hidden; isolation: isolate; }
.btn-primary::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(105deg, transparent 35%, rgb(255 255 255 / 0.22) 50%, transparent 65%);
  transform: translateX(-120%);
}
@media (hover: hover) {
  .btn-primary:hover::after { animation: os-sheen var(--dur-5) var(--ease-standard); }
}
@keyframes os-sheen { to { transform: translateX(120%); } }
:root[data-motion="none"] .btn-primary::after { display: none; }

/* Cards and panels settle rather than snap when their edge changes. */
.panel, .tile {
  transition: box-shadow var(--dur-2) var(--ease-standard),
              border-color var(--dur-2) var(--ease-standard);
}

/* A details block opens to its height instead of snapping to it.
   interpolate-size is what makes a keyword like auto animatable at
   all; without it there is no height to transition between. */
@supports (interpolate-size: allow-keywords) {
  :root { interpolate-size: allow-keywords; }
  details::details-content {
    block-size: 0;
    overflow: clip;
    transition: block-size var(--dur-3) var(--ease-standard),
                content-visibility var(--dur-3) allow-discrete;
  }
  details[open]::details-content { block-size: auto; }
}

/* There is deliberately no animation on the focus ring.

   command-palette.js injects a `*:focus-visible` rule with the outline
   and its offset both marked important, on every page, so that one ring
   is the ring wherever you are and no page's own stylesheet can quietly
   make its own worse. A ring that is identical everywhere is an
   accessibility guarantee; a ring that grows is a flourish. */

/* A drop target answers the pointer with colour only. */
.dropzone {
  transition: border-color var(--dur-2) var(--ease-standard),
              background-color var(--dur-2) var(--ease-standard);
}

/* Thirteen prose pages name "IBM Plex Mono" for their eyebrow and
   "Spectral" for their headings in their own style blocks, from when the
   site loaded both from Google. Neither is loaded now, so the eyebrow fell
   through to the browser's default serif - Times, in capitals, above every
   one of those pages. The headings are the headline face above; the
   eyebrow is the mono it was always meant to be. */
:root .eyebrow{font-family:var(--font-mono);}

/* ---------------------------------------------------------------
   The suite pages, brought into line with the front door.

   A tool tile led with its codename - CLOAK, EXIF, VEIL, BASTION - in
   capitals, which is what you type into the palette and tells a first
   visitor nothing. It now leads with what the tool does, in the reading
   face, and the codename sits under it as a label. The markup is
   unchanged (name, then line), so the order is the stylesheet's: a
   screen reader still hears the name first, which is the right way
   round for somebody who is listening rather than scanning.

   The head is left-aligned under a small wordmark, as the front door's
   is, and the row of group names stays at the top of the screen while
   the page scrolls under it, in one line that scrolls sideways rather
   than three that wrap.
   --------------------------------------------------------------- */
/* A page tile (.boot-tile-flat, on Help and the site map) is a title and
   a line under it, the right way round: it is not a tool with a codename. */
.boot-tile-tool:not(.boot-tile-app):not(.boot-tile-flat){gap:10px;padding:16px 16px 14px;}
.boot-tile-tool:not(.boot-tile-app):not(.boot-tile-flat) .boot-tile-sub{order:1;font-size:var(--text-sm);font-weight:600;line-height:1.38;
  letter-spacing:-.006em;color:var(--ink);-webkit-line-clamp:3;line-clamp:3;}
.boot-tile-tool:not(.boot-tile-app):not(.boot-tile-flat) .boot-tile-name{order:2;margin-top:auto;font-size:var(--text-2xs);font-weight:600;
  letter-spacing:.07em;color:var(--ink-faint);}
.boot-tile-tool:not(.boot-tile-app):not(.boot-tile-flat):hover .boot-tile-name{color:var(--accent-strong);}
/* A narrower tile needs more lines for the same sentence, not a shorter
   sentence: three at a laptop's width, four on a tablet, six on a phone. */
@media (max-width:1023px){ .boot-tile-tool:not(.boot-tile-app):not(.boot-tile-flat) .boot-tile-sub{font-size:var(--text-sm);-webkit-line-clamp:4;line-clamp:4;} }
@media (max-width:640px){ .boot-tile-tool:not(.boot-tile-app):not(.boot-tile-flat) .boot-tile-sub{font-size:var(--text-xs);-webkit-line-clamp:6;line-clamp:6;} }

.boot.boot-page{align-items:stretch;text-align:left;padding-top:20px;gap:22px;}
.boot-page .boot-head{align-items:flex-start;max-width:760px;gap:12px;}
.boot-page .boot-title{margin:0;font-size:clamp(var(--text-3xl),4.4vw,var(--text-4xl));line-height:1.04;}
.boot-page .suite-lead{font-size:var(--text-lg);line-height:1.55;color:var(--ink-soft);text-wrap:pretty;}
.boot-page > .boot-count{margin:14px 0 -8px;text-align:left;}

.boot-page .boot-jump{position:sticky;top:0;z-index:5;flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-x:contain;
  scrollbar-width:none;margin:0 -24px 22px;padding:10px 24px;width:auto;max-width:none;
  background:var(--paper);
  border-bottom:1px solid var(--rule);
  -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 48px),transparent);
          mask-image:linear-gradient(90deg,#000 calc(100% - 48px),transparent);}
.boot-page .boot-jump::-webkit-scrollbar{display:none;}
/* The row runs off the right edge on purpose, so it fades there rather
   than cutting a chip through the middle of a word: that is what says it
   scrolls. */
.boot-page .boot-jump{-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 48px),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 48px),transparent);}
.boot-page .boot-jump-chip{flex:none;}
.boot-page .boot-sect[id], .boot-page .boot-sect-head[id]{scroll-margin-top:72px;}
@media (max-width:520px){
  .boot-page .boot-jump{margin:0 -20px 18px;padding:8px 20px;}
  .boot-page .suite-lead{font-size:var(--text-md);}
}

/* The controls the browser draws for itself. A native select is a grey
   bevelled box on most systems and a file input is a grey button with
   "No file chosen" beside it, and both sat inside panels whose own fields
   are white, rounded and inset - so every tool with a dropdown looked as
   though half of it had not been styled, because it had not. Scoped to
   a tool's panels: the applications draw compact selects of their own in
   their toolbars, and those are right as they are. The chevron is a mid grey that reads on both grounds. */
.app-wrapper .panel select:not([multiple]):not([size]) {
  -webkit-appearance: none !important; appearance: none !important;
  min-height: 38px; padding: 7px 34px 7px 12px;
  /* important because a good many tools wrote a border and a grey fill
     inline on their selects, reaching for this same look by hand. */
  border: 0 !important; border-radius: var(--radius-md) !important;
  padding-right: 34px !important;
  background-color: var(--paper-raised) !important;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%23888894' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important;
  background-position: calc(100% - 13px) 50% !important;
  background-size: 12px 8px !important;
  background-repeat: no-repeat !important;
  box-shadow: var(--elev-sunk) !important;
  color: var(--ink); font: inherit; font-size: var(--text-sm); line-height: 1.3;
  cursor: pointer;
}
.app-wrapper .panel select:not([multiple]):not([size]):focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent) !important; }
.app-wrapper .panel select:disabled { opacity: .55; cursor: default; }
.app-wrapper .panel input[type="file"]::file-selector-button {
  margin-right: 12px; padding: 8px 14px; border: 0; border-radius: var(--radius-md);
  background: var(--paper-raised); color: var(--ink);
  box-shadow: 0 0 0 0.5px var(--hairline-strong), 0 1px 2px rgba(16, 16, 24, 0.05);
  font: 600 var(--text-xs) var(--font-sans); cursor: pointer;
}
.app-wrapper .panel input[type="file"]::file-selector-button:hover { box-shadow: 0 0 0 1px var(--accent-strong); }
@media (max-width:640px), (pointer:coarse) {
  .app-wrapper .panel select:not([multiple]):not([size]) { min-height: 44px; font-size: var(--text-md); }
}

/* A panel that explains rather than does: a heading and paragraphs of
   prose. The paragraphs had no space between them, so three of them read
   as one block, and the next heading sat hard against the last line; and
   they ran the full width of the panel, a hundred and ten characters to
   the line, where a reading measure is about seventy. */
.app-wrapper .panel > p { max-width: 72ch; }
:root .app-wrapper .panel > p + p { margin-top: 12px; }
:root .app-wrapper .panel > p + h2,
:root .app-wrapper .panel > p + h3,
:root .app-wrapper .panel > ul + h3,
:root .app-wrapper .panel > ol + h3 { margin-top: 26px; }

/* A tool's empty state: one generous place to put a file, what happens to
   it, and a way to try the tool without one. Written for CLOAK first and
   named for any tool, so the next page to want it does not grow its own. */
.tool-drop{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;
  margin-top:18px;min-height:250px;padding:34px 24px;box-sizing:border-box;text-align:center;}
.tool-drop-ico{width:40px;height:40px;margin-bottom:6px;color:var(--accent-strong);}
.tool-drop-title{margin:0;font-family:var(--font-ui);font-size:var(--text-lg);font-weight:600;letter-spacing:-.006em;color:var(--ink);}
.tool-drop-sub{margin:0;font-size:var(--text-sm);line-height:1.5;color:var(--ink-soft);}
.tool-drop-actions{display:flex;flex-wrap:wrap;justify-content:center;gap:10px;margin-top:16px;}
.tool-drop-actions button{min-height:44px;}
.tool-drop.is-over{outline-color:var(--accent-strong);background:color-mix(in srgb,var(--accent-strong) 12%,var(--paper-raised));}
@media (max-width:560px){ .tool-drop{min-height:220px;padding:28px 16px;} .tool-drop-actions{flex-direction:column;align-self:stretch;} }

/* An application with nothing in it yet: a quiet mark, what goes here,
   and the two or three ways to begin as real buttons. One component for
   every application's empty view, so a new address book, an empty drive
   and an empty list read alike; each fills it with its own buttons. */
.app-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;
  max-width:440px;margin:0 auto;padding:64px 24px;box-sizing:border-box;text-align:center;}
.app-empty[hidden]{display:none;}
.app-empty-ico{display:grid;place-items:center;width:48px;height:48px;margin-bottom:8px;border:1px solid var(--rule);
  border-radius:var(--radius-md,10px);background:var(--paper-inset);color:var(--ink-soft);}
.app-empty-ico svg{width:24px;height:24px;}
.app-empty-title{display:block;margin:0;font-family:var(--font-ui);font-size:var(--text-lg);font-weight:600;letter-spacing:-.006em;color:var(--ink);}
.app-empty-sub{margin:0;font-size:var(--text-sm);line-height:1.5;color:var(--ink-soft);}
.app-empty-actions{display:flex;flex-wrap:wrap;justify-content:center;gap:8px;margin-top:16px;}
.app-empty-actions > *{min-height:40px;}
@media (pointer:coarse){ .app-empty-actions > *{min-height:44px;} }
@media (max-width:560px){ .app-empty{padding:48px 16px;} }

/* Under an application's start cards, the ways in that only some people
   need ("Coming from Scrivener?") as one quiet line of links rather than
   a further card. */
.app-start-also{display:flex;flex-wrap:wrap;align-items:center;gap:0 16px;margin:12px 0 0;font-size:var(--text-sm);color:var(--ink-soft);}
.app-start-also .app-start-link{display:inline-flex;align-items:center;min-height:36px;padding:0;border:0;background:none;color:var(--accent-strong);font:inherit;font-weight:600;cursor:pointer;text-decoration:none;}
.app-start-also .app-start-link:hover{text-decoration:underline;text-underline-offset:3px;}
.app-start-also .app-start-link:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--radius-sm);}
@media (pointer:coarse), (max-width:560px){ .app-start-also .app-start-link{min-height:44px;min-width:44px;justify-content:center;} }

/* Something that has just been worked out should say so by arriving
   rather than by being there. */
.status-line:not(:empty) { animation: os-rise-soft var(--dur-3) var(--ease-standard) backwards; }

/* An answer arriving. Every tool shows its result by taking `hidden`
   off an element or removing `hide` from it, and both of those change
   display, which is a discrete step with nothing in between for a
   stylesheet to animate through. motion.js notices the change and adds
   this class; the shape of the arrival is decided here, in the sheet,
   along with everything else. The same 8px over --dur-4 as a panel
   arriving with its page: an answer is a panel arriving late. */
@keyframes os-arrive {
  from { opacity: 0; transform: translate3d(0, var(--os-rise), 0); }
}
.os-arriving { animation: os-arrive var(--dur-4) var(--ease-standard) backwards; }

/* One class, and it loses. `.app-wrapper > main > .panel` is two classes
   and an element, so on the two hundred and eighty-four pages where a
   result is a panel this rule does not win and the panel re-runs its own
   entrance instead. That was measured, not assumed, and it is left alone
   on purpose.

   It is left alone because raising it would break the thing it looks like
   it would fix. An element that is shown carries this class for the
   length of one animation and then has it taken off again. If winning
   changed the computed animation-name, taking the class off would change
   it back - and a changed animation-name starts a new animation, so every
   result on the site would arrive twice, once as os-arrive and once more
   as its entrance. Where this rule loses, the name never changes at all
   and the arrival is carried by the element going from display:none to
   displayed, which restarts the entrance on its own.

   So: this governs everything that has no entrance rule of its own, which
   is nearly everything, and everything that does keeps the one it has.
   Any new entrance rule must therefore beat this rather than tie with it,
   or the two will swap on every showing. */

/* A result is usually a list or a table, and a block of twenty rows
   arriving as one rectangle reads as a panel being swapped rather than
   as an answer being worked out. The rows follow the container a step
   behind, in order, on the same list stagger as everywhere else: 4px,
   --stagger apart, eight steps and then flat. */
.os-arriving > tbody > tr,
.os-arriving > table > tbody > tr,
.os-arriving > ul > li,
.os-arriving > ol > li,
.os-arriving > .os-list > .os-row {
  --os-fade: var(--dur-fade);
  animation: os-rise-soft var(--dur-3) var(--ease-standard) var(--os-step) backwards;
}
.os-arriving > tbody > tr:nth-child(2),
.os-arriving > table > tbody > tr:nth-child(2),
.os-arriving > ul > li:nth-child(2),
.os-arriving > ol > li:nth-child(2),
.os-arriving > .os-list > .os-row:nth-child(2) { animation-delay: calc(var(--os-step) + var(--stagger)); }
.os-arriving > tbody > tr:nth-child(3),
.os-arriving > table > tbody > tr:nth-child(3),
.os-arriving > ul > li:nth-child(3),
.os-arriving > ol > li:nth-child(3),
.os-arriving > .os-list > .os-row:nth-child(3) { animation-delay: calc(var(--os-step) + var(--stagger) * 2); }
.os-arriving > tbody > tr:nth-child(4),
.os-arriving > table > tbody > tr:nth-child(4),
.os-arriving > ul > li:nth-child(4),
.os-arriving > ol > li:nth-child(4),
.os-arriving > .os-list > .os-row:nth-child(4) { animation-delay: calc(var(--os-step) + var(--stagger) * 3); }
.os-arriving > tbody > tr:nth-child(5),
.os-arriving > table > tbody > tr:nth-child(5),
.os-arriving > ul > li:nth-child(5),
.os-arriving > ol > li:nth-child(5),
.os-arriving > .os-list > .os-row:nth-child(5) { animation-delay: calc(var(--os-step) + var(--stagger) * 4); }
.os-arriving > tbody > tr:nth-child(6),
.os-arriving > table > tbody > tr:nth-child(6),
.os-arriving > ul > li:nth-child(6),
.os-arriving > ol > li:nth-child(6),
.os-arriving > .os-list > .os-row:nth-child(6) { animation-delay: calc(var(--os-step) + var(--stagger) * 5); }
.os-arriving > tbody > tr:nth-child(7),
.os-arriving > table > tbody > tr:nth-child(7),
.os-arriving > ul > li:nth-child(7),
.os-arriving > ol > li:nth-child(7),
.os-arriving > .os-list > .os-row:nth-child(7) { animation-delay: calc(var(--os-step) + var(--stagger) * 6); }
.os-arriving > tbody > tr:nth-child(n+8),
.os-arriving > table > tbody > tr:nth-child(n+8),
.os-arriving > ul > li:nth-child(n+8),
.os-arriving > ol > li:nth-child(n+8),
.os-arriving > .os-list > .os-row:nth-child(n+8) { animation-delay: calc(var(--os-step) + var(--stagger) * 7); }

/* A notice (OBSCURA.notice, and the note a save leaves) rises 12px from
   the foot of the window over --dur-3 on the one curve with a hair of
   overshoot, which is what makes it read as having landed. It is created
   by script and appended, so @starting-style is where its "before" comes
   from. Leaving, it only fades, in two thirds of the time. */
@supports (transition-behavior: allow-discrete) {
  #obscuraNotice, #obscura-save-notice {
    --os-fade: var(--dur-fade);
    transition: opacity var(--dur-3-out) var(--ease-exit),
                transform var(--dur-3) var(--ease-spring);
  }
  @starting-style {
    #obscuraNotice, #obscura-save-notice {
      opacity: 0;
      transform: translateY(var(--os-rise-lg));
    }
  }
}

/* A control that has just done something says so by settling, not by
   its label changing out from under the pointer. kit.js sets this
   while a copy button is showing "Copied"; it is taken off again when
   the label goes back. */
@keyframes os-confirm {
  from { transform: scale(0.97); }
}
.is-confirmed { animation: os-confirm var(--dur-3) var(--ease-standard); }

/* A picture fades in as it decodes rather than snapping in a line at a
   time. Only inside a panel: the chrome's own marks are inline SVG and
   have nothing to wait for. */
.panel img { animation: os-fade-in var(--dur-4) var(--ease-standard) backwards; }

/* ---------------------------------------------------------------
   Waiting. A loading placeholder, .os-skel, for anything that takes
   long enough to be noticed: the Pro lock deciding whether to open, a
   Sovereign service switching, the Drive list, the Engineering Suite's
   first build.

   It is invisible for its first 300ms, because most of what it stands in
   for is ready before then, and a placeholder that flashes for a frame
   reads as a flicker rather than as waiting. After that it fades in as a
   tint with a light passing across it once every 1.2s. With motion
   switched down it is the tint alone: nothing loops.

   Use it on the element that stands in for the content, sized as the
   content will be:  <div class="os-skel" style="height:120px"></div>
   A line of text is .os-skel.is-line; it takes the font size it sits in.
   --------------------------------------------------------------- */
.os-skel {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--paper-inset);
  opacity: 0;
  animation: os-skel-in var(--dur-3) var(--ease-standard) var(--dur-wait) forwards;
  --os-fade: var(--dur-fade);
}
.os-skel.is-line { height: 1em; border-radius: var(--radius-sm); }
.os-skel.is-line + .os-skel.is-line { margin-top: 0.6em; }
.os-skel::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--paper-raised) 70%, transparent), transparent);
  transform: translateX(-100%);
  animation: os-skel-sweep var(--dur-sweep) linear var(--dur-wait) infinite;
}
@keyframes os-skel-in { to { opacity: 1; } }
@keyframes os-skel-sweep { to { transform: translateX(100%); } }
/* Switched down: the tint, arriving on the same delay, and no sweep. The
   delay has to be restated with !important, because the switch sets every
   delay to nought and a placeholder that is there at once is the flicker
   this exists to avoid. */
:root[data-motion="none"] .os-skel { animation-delay: var(--dur-wait) !important; }
/* Lines of text waiting together are one paragraph waiting, so the light
   runs down them as a wave, each a few steps behind the one above,
   rather than crossing them all at once like a barcode. */
.os-skel.is-line + .os-skel.is-line::after { animation-delay: calc(var(--dur-wait) + var(--stagger) * 3); }
.os-skel.is-line + .os-skel.is-line + .os-skel.is-line::after { animation-delay: calc(var(--dur-wait) + var(--stagger) * 6); }
.os-skel.is-line + .os-skel.is-line + .os-skel.is-line + .os-skel.is-line::after { animation-delay: calc(var(--dur-wait) + var(--stagger) * 9); }
:root[data-motion="none"] .os-skel::after { display: none; }

/* The one-time offer to somebody whose device has asked for reduced
   motion. motion.js decides whether to show it; this is what it looks
   like. Deliberately quiet and deliberately at the foot, because it is
   an offer and not a warning, and because anybody who genuinely wants
   the setting should be able to ignore it without reading it twice.

   It does not animate in. A bar sliding up to tell you the site is not
   moving would be answering its own question. */
.os-motion-offer {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: 12px;
  z-index: 2147482000;
  max-width: 520px;
  margin: 0 auto;
  padding: 14px 16px;
  border-radius: var(--radius-lg);
  background: var(--paper-raised);
  color: var(--ink);
  border: 1px solid var(--rule-strong);
  box-shadow: 0 10px 34px rgb(0 0 0 / 0.22);
}
.os-motion-offer p {
  margin: 0 0 12px;
  font: var(--text-sm)/1.55 var(--font-sans);
  color: var(--ink-soft);
}
.os-motion-offer-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.os-motion-offer-row .btn { min-height: 44px; }


/* The data-motion="none" block near the top of this file already zeroes
   every animation and transition on the site with !important, so
   everything above is switched off there too, in one place, rather than
   each rule here having to remember to switch itself off. This note is
   only here because a reader who found that block and not this one would
   reasonably wonder. */


/* ==================================================================
   THE BAR AT THE TOP, LAID OUT

   These rules were injected by os-nav.js, which is deferred. Until it
   ran, the bar was laid out by the rules further up this file alone,
   and on a wide screen its three parts stacked: 152 pixels tall rather
   than 54. Ninety milliseconds later the script landed and the whole
   page jumped ninety-eight pixels upward.

   That happened on every page carrying the bar, which is most of the
   site, and measured as a layout shift of 0.05 to 0.06 apiece. It is
   the single most visible reason the site read as cheap: you open a
   page, start reading the first line, and everything moves.

   They live here because this file is render-blocking, so the bar is
   its final shape the first time it is painted. They are at the end of
   the file for the same reason they worked from a script: they have to
   win against the rules above, and among selectors of the same weight
   the later one does.
   ================================================================== */
.os-mark-wrap { display: inline-flex; flex: none; color: var(--accent-strong); line-height: 0; }
.os-mark { width: 20px; height: 20px; display: block; }
/* The wordmark as one drawing (wordmark.svg), for a bar that is not this
   one: the Sovereign, Engineering Suite and Photo Editor bars, and the
   front door. It is the bar's own brand set in outline, a 20px mark
   beside 18px capitals, so the two are the same at the same size.
     <svg class="os-wordmark" role="img" aria-label="OBSCURA OS"><use href="/wordmark.svg#wordmark"/></svg> */
.os-wordmark {
  display: inline-block;
  flex: none;
  height: 20px;
  aspect-ratio: 153.9 / 24;
  vertical-align: middle;
  color: var(--ink);
  --wordmark-mark: var(--accent-strong);
  --wordmark-os: var(--accent-strong);
}
.os-topbar .brand-title { display: inline-flex; align-items: center; gap: 8px; }

/* Five suite tabs plus the brand and the two controls need about
   1000px, and the standard page wrapper gives the bar 996px, so
   slightly tighter spacing brings the row in with room to spare. It
   was four tabs when this was written; Standard and Yard made it five,
   and the breakpoint below is what actually keeps the row honest on a
   narrow window, so that is the number to check when a sixth arrives. */
.os-topbar { gap: 10px; padding: 10px 14px; }
.os-topbar .os-nav-tabs .nav-tab-btn { padding: 6px 11px; }
.os-topbar .os-controls { flex: 0 0 auto; flex-wrap: wrap; justify-content: flex-end; }
.os-topbar .os-brand-group { flex: 0 0 auto; align-items: center; }
.os-topbar .os-brand-col { gap: 0; }

@media (max-width: 520px) {
  .os-topbar .btn-search .search-kbd { display: none; }
}

/* Below 760px the tabs take a row of their own. os-nav.js applies the
   same shape with .is-stacked when the bar's own container is too narrow
   even though the viewport is not, which a media query cannot see; that
   rule stays in the script because it depends on a measurement.
   It was 1040px when the bar carried seven tabs. With four, the wordmark,
   the tabs and the controls need about 700px, and between 760 and 1040 the
   bar was put on two rows here and then forced back to one by os-nav.js,
   with the tabs left last and stretched, after Sign in and Settings (21
   October 2026). */
@media (max-width: 760px) {
  .os-topbar { flex-wrap: wrap; row-gap: 10px; }
  .os-topbar .os-brand-group { order: 1; }
  .os-topbar .os-controls { order: 2; margin-left: auto; }
  .os-topbar .os-nav-tabs {
    order: 3; width: 100%; justify-content: flex-start;
    flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .os-topbar .os-nav-tabs::-webkit-scrollbar { display: none; }
  .os-topbar .os-nav-tabs .nav-tab-btn { flex: 0 0 auto; }
}
/* ---------------------------------------------------------------
   Page chrome, lifted out of the pages themselves.

   Each of these was written out identically on ten or more pages - and
   the same rule in forty-four files is a rule that gets changed in forty
   of them. Generated by .github/scripts/lift-css.mjs; a rule whose body
   differed between pages was left where it was.
   --------------------------------------------------------------- */
.gd-sum td.num { text-align: right; font-family: var(--font-mono,monospace); font-size: var(--text-xs); white-space: nowrap; }
.gd h3 { font-size: var(--text-md); margin: 20px 0 8px; }

/* ---------------------------------------------------------------
   Page chrome, lifted out of the pages themselves.

   Each of these was written out identically on ten or more pages - and
   the same rule in forty-four files is a rule that gets changed in forty
   of them. Generated by .github/scripts/lift-css.mjs; a rule whose body
   differed between pages was left where it was.
   --------------------------------------------------------------- */
.gd p:last-child { margin-bottom:0; }

/* ------------------------------------------------- save and open

   The two buttons localsave.js puts in a Workspace app's header. The rules
   were injected by the script itself, so the bar arrived unstyled and was
   restyled a frame later — a layout shift on top of the one caused by
   inserting it at all. They are here, in the sheet the page waits for, so
   the bar is the size it is going to be the instant it exists. */
.ls-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: 600 var(--text-xs)/1 ui-sans-serif, system-ui, sans-serif;
  padding: 7px 11px;
  border-radius: var(--radius-md);
  border: 1px solid var(--rule-strong, #5a4d45);
  background: var(--paper-inset, #2a2320);
  color: var(--ink, #eee);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color var(--dur-2) var(--ease-standard), background-color var(--dur-2) var(--ease-standard);
}
.ls-btn:hover { border-color: var(--accent-strong, #c2603f); }
.ls-btn.primary {
  background: var(--accent-strong, #c2603f);
  color: var(--on-accent, #fff);
  border-color: var(--accent-strong, #c2603f);
}
.ls-btn svg { width: 14px; height: 14px; flex: none; }
#lsBar { display: inline-flex; align-items: center; gap: 8px; }
#lsStatus {
  font: 500 var(--text-2xs)/1 ui-sans-serif, system-ui, sans-serif;
  color: var(--ink-soft, #b8aaa0);
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 640px) {
  .ls-btn span { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
  .ls-btn { padding: 7px 9px; }
  #lsStatus { display: none; }
  #lsBar { gap: 6px; }
}
/* A finger needs forty-four points, not forty; the buttons were thirty tall
   on a phone and then forty, which is still short. */
@media (pointer: coarse), (max-width: 640px) {
  .ls-btn { min-height: 44px; min-width: 44px; justify-content: center; }
}

/* ---------------------------------------------------------------
   Page chrome, lifted out of the pages themselves.

   Each of these was written out identically on ten or more pages - and
   the same rule in forty-four files is a rule that gets changed in forty
   of them. Generated by .github/scripts/lift-css.mjs; a rule whose body
   differed between pages was left where it was.
   --------------------------------------------------------------- */
.gd li:last-child { margin-bottom:0; }

/* ---------------------------------------------------------------
   Page chrome, lifted out of the pages themselves.

   Each of these was written out identically on ten or more pages - and
   the same rule in forty-four files is a rule that gets changed in forty
   of them. Generated by .github/scripts/lift-css.mjs; a rule whose body
   differed between pages was left where it was.
   --------------------------------------------------------------- */
.gd-sum tr:last-child td { border-bottom:none; }

/* ---------------------------------------------------------------
   Page chrome, lifted out of the pages themselves.

   Each of these was written out identically on ten or more pages - and
   the same rule in forty-four files is a rule that gets changed in forty
   of them. Generated by .github/scripts/lift-css.mjs; a rule whose body
   differed between pages was left where it was.
   --------------------------------------------------------------- */
.gd pre code { border:none; background:none; padding:0; font-size:var(--text-xs); line-height:1.65; white-space:pre; }


/* ---------------------------------------------------------------
   Small moves that answer the person.

   Each of these is a reply to something somebody did: pointing at a
   tile, joining the list, checking a box, opening a menu, jumping to a
   section, holding a file over a drop zone and letting it go, saving
   one, getting a field wrong. None of them runs on its own. Each moves
   by transform and opacity only, over --dur-5 or less, and all of them
   are switched down with everything else when motion is, by the rule
   near the top of this sheet: a move that is not an entrance asks for
   no --os-fade, so there it ends before it has begun.

   Two that were once here are not: a heading flashing its background
   when its chip was pressed, and the waitlist card rising to meet its
   tick. The first painted a colour rather than moving anything, and the
   second moved a card that the tick in front of it already answers for.
   --------------------------------------------------------------- */

/* A tile's icon arrives a beat after its tile, growing from --os-pop on
   the spring, so a grid dealing itself out lands rather than slides. The
   beat is the tile's own place in the deal: --os-n is its place in the
   grid, counted as the tiles' delays are, thirteen steps and then flat. A
   group dealt out late, as it is scrolled to, deals its icons out again
   under a new name, because an animation whose name has not changed does
   not run a second time. The icon is where the arrival lives, not its
   drawing, so the drawing is free to act out its picture under the
   pointer at the same time. */
.boot-grid > :nth-child(2) { --os-n: 1; }
.boot-grid > :nth-child(3) { --os-n: 2; }
.boot-grid > :nth-child(4) { --os-n: 3; }
.boot-grid > :nth-child(5) { --os-n: 4; }
.boot-grid > :nth-child(6) { --os-n: 5; }
.boot-grid > :nth-child(7) { --os-n: 6; }
.boot-grid > :nth-child(8) { --os-n: 7; }
.boot-grid > :nth-child(9) { --os-n: 8; }
.boot-grid > :nth-child(10) { --os-n: 9; }
.boot-grid > :nth-child(11) { --os-n: 10; }
.boot-grid > :nth-child(12) { --os-n: 11; }
.boot-grid > :nth-child(13) { --os-n: 12; }
.boot-grid > :nth-child(n+14) { --os-n: 13; }
.boot-grid .boot-tile-ico {
  --os-fade: var(--dur-fade);
  animation: os-ico-in var(--dur-4) var(--ease-spring) backwards;
  animation-delay: calc(var(--sect, 0ms) + var(--stagger) * var(--os-n, 0) + var(--dur-2));
}
.boot-sect[data-os-deal="go"] .boot-grid .boot-tile-ico { animation-name: os-ico-deal; }
@keyframes os-ico-in { from { opacity: 0; scale: var(--os-pop); } }
@keyframes os-ico-deal { from { opacity: 0.6; scale: var(--os-pop); } }

/* Each icon does the thing it is a picture of, once, when its tile or row
   is pointed at or reached from the keyboard: the Workspace's page is
   leafed up off the sheet behind it and laid back, the shield pulses, the
   caliper is lifted and set down on the work, the records box is lifted
   by one corner, Umbra's eclipse drifts across and back, the ledger tips
   open on its spine and closes, Keep's case is pressed shut on its clasp,
   Roots' tree grows a little and settles, Research's speech bubble lifts and
   settles, Practice's briefcase is picked up by its handle, Write's script is lifted by
   its brads, Authorship's nib writes a stroke and lifts, Sound's clip swells
   once, as a note played through it would, and settles, Vector's curve
   is bent by its handle and springs back, Erase's letter draws back as a
   line is struck from it, the layers of the other suites
   spread and settle, the camera's shutter fires, PDF Studio's page tips
   towards its cursor, the folder opens and the gear turns an eighth. On /other-suites the clipboard turns over, the
   rule slides along its marks, the desk rocks and settles and the glass
   over the trace looks closer. Anything without a move of its own hops.
   Every one is transform and opacity only, and ends on a drawing
   identical to the one it started from - the gear has eight teeth so
   that an eighth of a turn does - so nothing snaps when it is over. Pro's
   tile wears the mark, which motion.js turns as it turns the mark in the
   bar, so it has no move here. The drawing moves inside its tinted
   square, which keeps still. */
.boot-tile:is(:hover, :focus-visible) .boot-tile-ico svg,
.os-row:is(:hover, :focus-visible) .os-row-ico svg,
:is(.hx-app, .hx-item):is(:hover, :focus-visible) .hx-ico svg,
.os-app-home:is(:hover, :focus-visible) :is(svg, img) { animation: os-ico-hop var(--dur-5) var(--ease-standard); }
:is(.boot-tile, .hx-app, .hx-item)[href="/workspace"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-leaf; transform-origin: 30% 85%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/sovereign-suite"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-pulse; }
:is(.boot-tile, .hx-app, .hx-item)[href="/engineering-suite"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-set; transform-origin: 50% 90%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/case"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-lid; transform-origin: 50% 100%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/umbra"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-dusk; }
:is(.boot-tile, .hx-app, .hx-item)[href="/books"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-tally; transform-origin: 25% 90%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/keep"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-clasp; transform-origin: 50% 70%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/roots"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-sprout; transform-origin: 50% 92%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/research"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-speak; transform-origin: 30% 90%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/practice"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-carry; transform-origin: 50% 20%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/write"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-sheaf; transform-origin: 30% 20%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/authorship"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-scribe; transform-origin: 50% 65%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/sound"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-swell; transform-origin: 50% 50%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/vector"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-curve; transform-origin: 23% 75%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/erase"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-strike; transform-origin: 70% 75%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/converter"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-swap; }
:is(.boot-tile, .hx-app, .hx-item)[href="/other-suites"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-fan; transform-origin: 50% 100%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/photo-editor"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-flash; }
:is(.boot-tile, .hx-app, .hx-item)[href="/pdf-studio"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-write; transform-origin: 70% 80%; }
:is(#homeFolder, :is(.boot-tile, .hx-app, .hx-item)[href="/drive"]):is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-open; transform-origin: 15% 85%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/settings"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-turn; }
/* The Workspace's applications, on the front door and in its sidebar, with
   the moves their pictures share: Docs' page is leafed up, Notes' sheets
   lifted by their top, Slides' screen set down on its stand, Tasks ticked
   off, Calendar's page turned, Plan's bars slid along and the PDF written
   on. The others hop. */
:is(.hx-app, .hx-item)[href="/docs"]:is(:hover, :focus-visible) .hx-ico svg { animation-name: os-ico-leaf; transform-origin: 30% 85%; }
:is(.hx-app, .hx-item)[href="/noteline"]:is(:hover, :focus-visible) .hx-ico svg { animation-name: os-ico-sheaf; transform-origin: 30% 20%; }
:is(.hx-app, .hx-item)[href="/slides"]:is(:hover, :focus-visible) .hx-ico svg { animation-name: os-ico-set; transform-origin: 50% 90%; }
:is(.hx-app, .hx-item)[href="/tasks"]:is(:hover, :focus-visible) .hx-ico svg { animation-name: os-ico-tally; transform-origin: 25% 90%; }
:is(.hx-app, .hx-item)[href="/calendar"]:is(:hover, :focus-visible) .hx-ico svg { animation-name: os-ico-flip; }
:is(.hx-app, .hx-item)[href="/plan"]:is(:hover, :focus-visible) .hx-ico svg { animation-name: os-ico-slide; }
:is(.hx-app, .hx-item)[href="/pdf"]:is(:hover, :focus-visible) .hx-ico svg { animation-name: os-ico-write; transform-origin: 70% 80%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/pro"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation: none; }
:is(.boot-tile, .hx-app, .hx-item)[href="/record"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-flip; }
:is(.boot-tile, .hx-app, .hx-item)[href="/standard"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-slide; }
:is(.boot-tile, .hx-app, .hx-item)[href="/desk"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-settle; transform-origin: 50% 100%; }
:is(.boot-tile, .hx-app, .hx-item)[href="/trace"]:is(:hover, :focus-visible) :is(.boot-tile-ico, .hx-ico) svg { animation-name: os-ico-look; }
@keyframes os-ico-hop {
  35% { transform: translateY(-3px); }
  70% { transform: translateY(0.5px); }
}
@keyframes os-ico-leaf {
  35% { transform: translate(-1px, -2px) rotate(-8deg); }
  70% { transform: translateY(0.5px) rotate(1.5deg); }
}
@keyframes os-ico-pulse {
  30% { transform: scale(1.18); }
  60% { transform: scale(0.95); }
  80% { transform: scale(1.04); }
}
@keyframes os-ico-set {
  35% { transform: translateY(-3px); }
  70% { transform: translateY(0.5px) scale(1.06, 0.92); }
}
@keyframes os-ico-lid {
  30% { transform: translateY(-2.5px) rotate(-4deg); }
  65% { transform: translateY(0.5px) rotate(1deg); }
}
/* Keep's case is pressed shut on its clasp and settles. */
@keyframes os-ico-clasp {
  35% { transform: scale(.93); }
  70% { transform: scale(1.02); }
}
/* Research's speech bubble lifts, as a voice is raised, and settles. */
@keyframes os-ico-speak {
  35% { transform: translateY(-1.5px) rotate(-5deg); }
  70% { transform: translateY(0.5px) rotate(1deg); }
}
/* Practice's briefcase is picked up by its handle and set down. */
@keyframes os-ico-carry {
  35% { transform: translateY(-2.5px) rotate(4deg); }
  70% { transform: translateY(0.5px) rotate(-1deg); }
}
/* Umbra's eclipse drifts across and back, as the Moon crosses the Sun. */
@keyframes os-ico-dusk {
  35% { transform: translateX(1.5px) rotate(-7deg); }
  70% { transform: translateX(-0.5px) rotate(1.5deg); }
}
/* The ledger tips open on its spine and closes, as a book is opened to a
   page and shut. */
@keyframes os-ico-tally {
  35% { transform: rotate(-7deg) translateY(-1.5px); }
  70% { transform: rotate(1.5deg); }
}
/* Roots' tree grows up from its root and settles back. */
@keyframes os-ico-sprout {
  40% { transform: scaleY(1.09); }
  75% { transform: scaleY(0.97); }
}
/* Write's script is lifted by its brads, as a page is turned over, and
   laid down again. */
@keyframes os-ico-sheaf {
  35% { transform: translateY(-1.5px) rotate(-6deg); }
  70% { transform: translateY(0.5px) rotate(1.5deg); }
}
/* Authorship's nib writes a stroke and lifts. */
@keyframes os-ico-scribe {
  35% { transform: translate(1px, 1px) rotate(-8deg); }
  70% { transform: translate(-0.5px, -0.5px) rotate(2deg); }
}
/* Sound's clip swells as a note goes through it, and settles. */
@keyframes os-ico-swell {
  40% { transform: scale(1.04, 1.12); }
  75% { transform: scale(0.99, 0.97); }
}
/* Vector's curve is bent by its handle, as the pen drags one out, and
   springs back. */
@keyframes os-ico-curve {
  35% { transform: rotate(-6deg) scale(1.03); }
  70% { transform: rotate(1.5deg); }
}
/* Erase's letter is drawn back a little, as a line is taken out of it, and
   settles. */
@keyframes os-ico-strike {
  40% { transform: translateX(-1.5px) scale(.96); }
  75% { transform: translateX(.5px); }
}
/* The Converter's two files turn about each other, a part of a turn and
   back, as one becomes the other. */
@keyframes os-ico-swap {
  40% { transform: rotate(-14deg) scale(.96); }
  75% { transform: rotate(4deg); }
}
@keyframes os-ico-fan {
  40% { transform: translateY(-1px) scaleY(1.16); }
  75% { transform: scaleY(0.94); }
}
@keyframes os-ico-flash {
  20% { transform: scale(0.84); opacity: 0.45; }
  45% { transform: scale(1.1); opacity: 1; }
}
/* The page tips towards the cursor, as a sheet does under a pen, and
   settles. */
@keyframes os-ico-write {
  35% { transform: translate(1px, -1px) rotate(6deg); }
  70% { transform: rotate(-1.5deg); }
}
@keyframes os-ico-open {
  40% { transform: translateY(-1px) rotate(-9deg); }
  75% { transform: rotate(2deg); }
}
/* The gear has eight teeth, so an eighth of a turn ends on a drawing
   that is the same as the one it started from. */
@keyframes os-ico-turn { to { transform: rotate(45deg); } }
@keyframes os-ico-flip {
  from { transform: perspective(60px) rotateY(0); }
  to { transform: perspective(60px) rotateY(360deg); }
}
@keyframes os-ico-slide {
  30% { transform: translateX(3px); }
  65% { transform: translateX(-2px); }
}
@keyframes os-ico-settle {
  30% { transform: translateY(-2px) rotate(-6deg); }
  60% { transform: rotate(3deg); }
}
@keyframes os-ico-look {
  40% { transform: translate(1px, -1px) scale(1.12) rotate(-6deg); }
}

/* The search and settings buttons in the bar at the top: the glass tips
   as if to look, the gear turns an eighth. A transition rather than an
   animation, so it goes back the way it came when the pointer leaves. */
.btn-search svg, .btn-settings svg { transition: transform var(--dur-4) var(--ease-spring); }
.btn-search:is(:hover, :focus-visible) svg { transform: rotate(-14deg) scale(1.08); }
.btn-settings:is(:hover, :focus-visible) svg { transform: rotate(45deg); }

/* Joining the list: the tick draws itself in front of the words. */
.home-wait-row .btn-primary svg {
  width: 18px;
  height: 18px;
  margin-right: 8px;
  vertical-align: -4px;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke);
  stroke-linecap: round;
  stroke-linejoin: round;
}
.home-wait-row .btn-primary svg path {
  stroke-dasharray: 22;
  --os-fade: var(--dur-fade);
  animation: os-tick-draw var(--dur-4) var(--ease-standard) backwards;
}
@keyframes os-tick-draw { from { stroke-dashoffset: 22; } }
/* A tick lands as well as drawing itself: it grows from --os-pop on the
   spring while its stroke is drawn, here and on a button that has just
   saved a file. */
.home-wait-row .btn-primary svg,
[data-os-tick] svg {
  --os-fade: var(--dur-fade);
  animation: os-pop var(--dur-4) var(--ease-spring) backwards;
}
@keyframes os-pop { from { opacity: 0; scale: var(--os-pop); } }

/* A box being checked, or a choice being made, answers the press: it
   grows from --os-pop to its size on the spring. Only once it has focus,
   which a click or a key gives it, so a box that was already checked
   when the page opened does not jump; unchecked and checked again, it
   answers again. `scale` rather than `transform`, so a box a page has
   placed with a transform keeps its place. */
input:is([type="checkbox"], [type="radio"]):checked:focus {
  animation: os-check var(--dur-3) var(--ease-spring);
}
@keyframes os-check { from { scale: var(--os-pop); } }

/* A menu opens rather than appearing: the panel under a menu bar's name
   (the PDF editor's, the photo editor's) fades in and comes down the
   last 4px from its name, over --dur-3. */
:is(.pdf-menu, .pe-menu)[open] > div {
  --os-fade: var(--dur-fade);
  animation: os-menu-in var(--dur-3) var(--ease-standard);
}
@keyframes os-menu-in { from { opacity: 0; transform: translate3d(0, calc(var(--os-rise-sm) * -1), 0); } }

/* The light kit.js runs once round a card's edge (OBSCURA.edgeLight).
   The front door no longer asks for it; the sign-in on /pro still does,
   and until it stops this is what it looks like. kit.js places and sizes
   it; here it is the accent. It catches no clicks. */
[data-ring] {
  position: absolute;
  overflow: visible;
  pointer-events: none;
}
[data-ring] rect {
  fill: none;
  stroke: var(--accent);
  stroke-width: var(--icon-stroke);
  stroke-linecap: round;
}
[data-ring] [data-part="tail"] { stroke-opacity: 0.32; }

/* A chip on a suite page glides to its group rather than jumping. The
   glide is the document's own smooth scrolling, only where there are
   chips, and not for somebody who has switched motion down. */
:root:not([data-motion="none"]):has(.boot-jump) { scroll-behavior: smooth; }

/* A group below the fold is dealt out again as it is scrolled to, with
   the same steps the rest of the page used when it loaded (motion.js marks
   it). A new name rather than the old one, because an animation whose name
   has not changed does not run again. Nothing is hidden while it waits:
   it used to be held at nought until it was scrolled to, so a group that
   never met the scroll - a full-page capture, a reader that jumps, a tab
   that was printed - stayed blank. The entrance starts from most of the
   way there, so a group that is dealt while already in view only settles
   into place; motion here is decoration, never what makes a thing seen. */
.boot-sect[data-os-deal="go"] { --sect: 0ms; }
.boot-sect[data-os-deal="go"] .boot-grid > *,
.boot-sect[data-os-deal="go"] .os-list > .os-row { animation-name: os-deal; }
@keyframes os-deal { from { opacity: 0.6; transform: translate3d(0, var(--os-rise-sm), 0); } }

/* A file held over a drop zone: a ring round it that is its own and not
   the zone's border, which half the tools style already, and the zone
   breathes - out by a hundredth and back, every two --dur-5 - for as
   long as the file is held there, so the zone is plainly the thing that
   will take it. It is the one loop here, it runs only while somebody is
   holding a file, and with motion switched down it is the ring alone.
   Let go, the zone settles under the file, a hundredth of its size,
   once, and the file's name drops from the pointer into it (motion.js
   makes the label). `scale` rather than `transform`, so a zone placed
   with a transform keeps its place. */
[data-os-drag] {
  outline: 2px solid color-mix(in srgb, var(--accent) 60%, transparent);
  outline-offset: 3px;
  animation: os-breathe var(--dur-5) var(--ease-standard) infinite alternate;
}
@keyframes os-breathe { to { scale: 1.01; } }
/* With motion switched down, gentle or not, the ring alone: a zone that
   also takes part in an entrance asks for a fade, which would otherwise
   have let it breathe once. */
:root[data-motion="none"] [data-os-drag] { animation: none; }
[data-os-landed] { animation: os-landed var(--dur-3) var(--ease-standard); }
@keyframes os-landed { from { scale: 0.99; } }
[data-os-ghost] {
  position: fixed;
  z-index: 2147482600;
  pointer-events: none;
  box-sizing: border-box;
  max-width: min(260px, 70vw);
  padding: 7px 12px 7px 30px;
  border-radius: var(--radius-sm);
  background: var(--paper-raised);
  box-shadow: var(--elev-2);
  color: var(--ink);
  font: 500 var(--text-xs)/1.3 var(--font-mono, ui-monospace, monospace);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  animation: os-ghost var(--dur-5) var(--ease-standard);
}
[data-os-ghost]::before {
  content: "";
  position: absolute;
  left: 11px;
  top: 50%;
  width: 10px;
  height: 13px;
  margin-top: -7px;
  border: 1.5px solid var(--accent-strong);
  border-radius: var(--radius-sm);
}
@keyframes os-ghost {
  0% { opacity: 0; transform: translate(-50%, -140%) scale(1.06); }
  25% { opacity: 1; transform: translate(-50%, -95%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, 15%) scale(0.72); }
}

/* Saving: the words under the button give way to a tick that draws
   itself, and come back a moment later. motion.js sets data-os-save to
   "in" when the download starts and to "out" when the tick has been
   seen, and takes it off after that; the hold between the two is the
   script's, so no animation here runs longer than --dur-4. The words
   are hidden by their fill rather than their colour, because a primary
   button's colour is held with !important against the dark themes, and
   neither a transition nor an animation can move an !important value. */
[data-os-save] { transition: -webkit-text-fill-color var(--dur-2) var(--ease-standard); }
[data-os-save="in"] { -webkit-text-fill-color: transparent; }
[data-os-tick] {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
  -webkit-text-fill-color: currentColor;
  transition: opacity var(--dur-3-out) var(--ease-exit);
}
@starting-style { [data-os-tick] { opacity: 0; } }
[data-os-save="out"] [data-os-tick] { opacity: 0; }
[data-os-tick] svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke);
  stroke-linecap: round;
  stroke-linejoin: round;
}
[data-os-tick] path { stroke-dasharray: 22; animation: os-tick-draw var(--dur-4) var(--ease-standard) backwards; }

/* The rule near the top that switches motion down reaches every element
   but not these: a view transition's pieces are pseudo-elements of the
   root. Nothing is named with motion down, so only the page itself takes
   part, and it cross-fades in --dur-fade rather than rising. */
:root[data-motion="none"]::view-transition-group(*) { animation: none !important; }
:root[data-motion="none"]::view-transition-old(root) { animation: var(--dur-fade) linear both os-fade-out !important; }
:root[data-motion="none"]::view-transition-new(root) { animation: var(--dur-fade) linear both os-fade-in !important; }

/* ---------------------------------------------------------------
   The second set of small moves.
   --------------------------------------------------------------- */

/* A field given something it cannot take shakes its head (kit.js). The
   shake is a movement, so with motion switched down it is not there at
   all; the message under the field says the same thing. */
[data-os-shake] { animation: os-shake var(--dur-4) var(--ease-standard); }
@keyframes os-shake {
  15% { translate: -6px 0; }
  35% { translate: 5px 0; }
  55% { translate: -3px 0; }
  75% { translate: 2px 0; }
}

/* Three dots after "Adding you", in turn, for as long as the list is
   being asked. The one loop on the site besides the loading light, and
   like it gone with motion switched down. */
[data-dots] { display: inline-flex; gap: 3px; margin-left: 4px; vertical-align: 1px; }
[data-dots] i {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  animation: os-dot calc(var(--dur-4) * 3) var(--ease-standard) infinite;
}
[data-dots] i:nth-child(2) { animation-delay: calc(var(--dur-4) * 0.4); }
[data-dots] i:nth-child(3) { animation-delay: calc(var(--dur-4) * 0.8); }
@keyframes os-dot {
  0%, 60%, 100% { opacity: 0.35; }
  30% { opacity: 1; }
}

/* A footer link draws its rule in from the left rather than having one
   appear under it. */
.boot-foot a,
.os-footer a {
  text-decoration: none;
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 calc(100% - 0.1em);
  background-size: 0% 1px;
  transition: background-size var(--dur-3) var(--ease-standard), color var(--dur-2) var(--ease-standard);
}
.boot-foot a:hover,
.os-footer a:hover { text-decoration: none; background-size: 100% 1px; }

/* A heading in a guide carries a short rule above it, which draws itself
   out as the heading comes up the screen. The rule is there, finished,
   for anybody whose browser cannot tie an animation to the scroll or who
   has switched motion down. */
:root .wrap #obscuraMainContent > h2::before {
  content: "";
  display: block;
  width: 28px;
  height: 2px;
  margin-bottom: 12px;
  border-radius: var(--radius-sm);
  background: var(--accent-strong);
  transform-origin: left center;
}
@supports (animation-timeline: view()) {
  :root .wrap #obscuraMainContent > h2 { view-timeline: --os-h2 block; }
  :root .wrap #obscuraMainContent > h2::before {
    animation: os-rule linear both;
    animation-timeline: --os-h2;
    animation-range: entry 10% entry 100%;
  }
}
@keyframes os-rule { from { transform: scaleX(0); } }

/* A question at the foot of a page opens by growing to its answer's
   height and closes the same way, where the browser can animate to a
   height it has to work out. Elsewhere it opens at once, as before. The
   plus turns into a cross in both kinds of question list. */
@supports (interpolate-size: allow-keywords) {
  .os-faq details, details.os-faq-fold { interpolate-size: allow-keywords; }
  .os-faq details::details-content,
  details.os-faq-fold::details-content {
    block-size: 0;
    overflow: clip;
    opacity: 0;
    transition: block-size var(--dur-3) var(--ease-standard), opacity var(--dur-3) var(--ease-standard),
                content-visibility var(--dur-3) allow-discrete;
  }
  .os-faq details[open]::details-content,
  details.os-faq-fold[open]::details-content { block-size: auto; opacity: 1; }
}
.os-faq summary::after { transition: transform var(--dur-3) var(--ease-standard); }
.os-faq details[open] > summary::after { transform: rotate(45deg); }

/* The switches in Settings move their knob across on the standard
   curve. */
.sw::after { transition: left var(--dur-2) var(--ease-standard) !important; }

/* ---------------------------------------------------------------
   The apps under a finger.

   Docs, Sheets, Slides, PDF, Plan, Tasks, Calendar, Drive and the Photo
   Editor are laid out as their desktop namesakes are, with ribbons of
   30px buttons and 26px fields, which is right for a mouse and a thing
   nobody can hit on a phone. On a touch screen every control in them is
   brought up to the 44px floor the rest of the site keeps. The floor is
   !important because each app sizes its own controls, some with an id,
   and a floor that loses to any one of them is not a floor; it only ever
   raises a size, never lowers one. The box is border-box so the floor is
   the size on the screen and not 44px plus padding.

   A checklist in a document is ticked by its whole line, so the line is
   what is raised, by as much more as the page is zoomed out: Docs says
   how far in --docs-zoom. */
@media (pointer: coarse) {
  body:is(.pdf-app, .slides-app, .plan-app, .tasks-app, .pe-app, .sheets-app, .docs-app, .calendar-app, .drive-app)
    :is(button, select, summary, [role="button"], a[href]:not(:is(p, li, td, dd) a)) {
    min-height: 44px !important;
    min-width: 44px !important;
    box-sizing: border-box !important;
  }
  body:is(.pdf-app, .slides-app, .plan-app, .tasks-app, .pe-app, .sheets-app, .docs-app, .calendar-app, .drive-app) #ppBadge {
    min-height: 44px !important;
  }
  body.docs-app label:has(> input[type="checkbox"]) {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
    min-height: calc(44px / var(--docs-zoom, 1));
    min-width: calc(44px / var(--docs-zoom, 1));
  }
  body.tasks-app input[type="search"] { min-width: 44px; }
}

/* Slides and Tasks on a phone: the work under one bar (phone-chrome-test.mjs).
   These come before each page's own <style>, so they name the body to win.

   Slides: the row of tabs is the ribbon. A tab opens its panel over the
   slide until a command is run or the slide is touched, as Docs does
   (slides-app.js); under the tabs the panel took a hundred pixels more and,
   with the bar and the thumbnails, the slide began a third of the way down.
   The badge stands above the status bar on a touch screen (slides.html), so
   the bar no longer keeps a badge's room at its end, which pushed the zoom
   off a narrow phone. */
@media (max-width: 599px) {
  body.slides-app .sl-ribbon { position: relative; }
  body.slides-app .sl-ribbon:not(.is-peek) .sl-panels { display: none; }
  body.slides-app .sl-ribbon.is-peek .sl-panels {
    position: absolute; left: 0; right: 0; top: 100%; z-index: 60;
    background: var(--sl-bar); border-bottom: 1px solid var(--sl-line);
    box-shadow: 0 8px 20px rgba(0, 0, 0, .18);
  }
}
@media (max-width: 760px) {
  body.slides-app .sl-status { padding-right: 8px; }
}
/* Tasks: the board's name, its views and its buttons are one row that
   scrolls sideways, as a ribbon does here. Wrapped, they were three rows and
   a hundred and fifty pixels of the screen before the first list. */
@media (max-width: 700px) {
  body.tasks-app .tk-board-head { flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; padding: 3px 8px; }
  body.tasks-app .tk-board-head::-webkit-scrollbar { display: none; }
  body.tasks-app .tk-board-head > *, body.tasks-app .tk-views > * { flex: none; }
  body.tasks-app .tk-views { display: flex; gap: 4px; flex: none; }
  body.tasks-app .tk-board-name { max-width: 50vw; }
}

/* The colourway picker in Docs's header is not part of this site's way of
   choosing a colourway — Settings is — so it is not shown. The rules were
   injected by os-nav.js, which is deferred, so the button was laid out at
   first paint and removed a frame later: a hundred and thirty pixels out
   of the middle of the header, and everything to its right moved with it.
   Here, the page waits for them and it is never laid out at all. */
#btnColorwayPicker,
[title="Switch Theme Colorway"] { display: none !important; }

/* ---------------------------------------------------------------
   Page chrome, lifted out of the pages themselves.

   Each of these was written out identically on ten or more pages - and
   the same rule in forty-four files is a rule that gets changed in forty
   of them. Generated by .github/scripts/lift-css.mjs; a rule whose body
   differed between pages was left where it was.
   --------------------------------------------------------------- */
.gd-sum td.num, .gd-sum th.num { font-family:var(--font-mono,monospace); font-size:var(--text-xs); white-space:nowrap; }

/* ---------------------------------------------------------------
   Page chrome, lifted out of the pages themselves.

   Each of these was written out identically on ten or more pages - and
   the same rule in forty-four files is a rule that gets changed in forty
   of them. Generated by .github/scripts/lift-css.mjs; a rule whose body
   differed between pages was left where it was.
   --------------------------------------------------------------- */
.scroller { overflow-x:auto; }

/* The Sovereign suite's lead card: the Console, above the suite index.
   It was a dark banner in every theme, the one inverted block on a light
   page; it is now a card like the others on the landing pages, set apart
   by its border and its place at the top rather than by colour. Inside
   the suite's own start screen (.sv-home-row) sovereign-app.css dresses it
   for that screen, so the button's size and border are this page's only. */
.os-suite-hero{display:block;text-decoration:none;margin:0 0 var(--space-5);padding:var(--space-5) var(--space-5) var(--space-4);border:1px solid var(--rule-strong);border-radius:var(--radius-md);background:var(--paper-raised);color:var(--ink);transition:border-color var(--dur-2) var(--ease-standard)}
.os-suite-hero:hover{border-color:var(--accent-border)}
.os-suite-hero:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.os-suite-hero-eyebrow{display:flex;align-items:center;gap:var(--space-2);font:600 var(--text-2xs)/1 var(--font-mono);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-soft);margin-bottom:var(--space-3)}
.os-suite-hero-mark{flex:none;width:8px;height:8px;border-radius:var(--radius-sm);background:var(--good)}
.os-suite-hero-name{display:block;font:600 var(--text-lg)/1.3 var(--font-ui);color:var(--ink);margin-bottom:var(--space-1)}
.os-suite-hero-sub{display:block;font-size:var(--text-md);line-height:1.55;color:var(--ink-soft);max-width:var(--measure);margin-bottom:var(--space-4)}
.os-suite-hero-cta{display:inline-block;font-weight:600;font-size:var(--text-sm);line-height:1;color:var(--ink)}
.os-tool-groups > .os-suite-hero .os-suite-hero-cta{display:inline-flex;align-items:center;min-height:44px;box-sizing:border-box;padding:0 var(--space-4);border:1px solid var(--rule-strong);border-radius:var(--radius-sm);background:var(--paper)}
.os-tool-groups > .os-suite-hero:hover .os-suite-hero-cta{border-color:var(--accent-border);color:var(--accent-strong)}

/* ---------------------------------------------------------------
   Pro in the bar. Subscribe is the one control in the bar that is about
   money, so it is the one that is filled; Sign in stands beside it,
   outlined. For a subscriber both give way to one Pro control, with a
   tick, and the wordmark reads OBSCURA OS PRO. data-pro on <html> is
   written by theme.js before the first paint and kept in step by pro.js
   once the key's signature has been checked. It changes words, not what
   works: the Pro code is served only to a paid key.
   --------------------------------------------------------------- */
.os-pro-word, .btn-pro-short { display: none; }
:root[data-pro] .os-pro-word {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 6px;
  font: 700 0.6em/1.2 var(--font-mono);
  letter-spacing: 0.08em;
  vertical-align: 0.2em;
  color: var(--on-accent);
  background: var(--accent-strong);
  border-radius: var(--radius-sm);
}
:root .btn-pro {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding: 7px 14px;
  border-radius: var(--radius-pill);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 650;
  letter-spacing: var(--track-body);
  white-space: nowrap;
  text-decoration: none;
  color: var(--on-accent);
  background: var(--accent-strong);
  box-shadow: 0 1px 2px rgba(16, 16, 24, 0.22);
  transition: background-color var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard);
}
:root .btn-pro:hover { box-shadow: 0 0 0 2px var(--accent-soft), 0 1px 2px rgba(16, 16, 24, 0.22); }
:root[data-pro] .btn-pro {
  font-weight: 600;
  color: var(--ink-soft);
  background: var(--paper-raised);
  box-shadow: 0 0 0 0.5px var(--hairline-strong);
}
:root[data-pro] .btn-pro:hover { color: var(--ink); box-shadow: 0 0 0 0.5px var(--rule-strong); }
.os-topbar.slim .btn-pro { padding: 3px 11px; font-size: var(--text-2xs); min-height: 26px; }
@media (pointer: coarse) {
  .btn-pro, .os-topbar.slim .btn-pro { min-height: 44px; }
}
/* Sign in, beside Subscribe, for somebody who has paid on another device
   or in another browser. It used to be a grey word, and below 700px it was
   not in the bar at all, only at the foot of the page, so a subscriber on a
   new phone met Subscribe and nothing else. It is a button now, outlined
   where Subscribe is filled, on every page and at every width, with a key
   for its icon: the key is what signs a browser in, and there is no
   account. Pressed, it opens the sign-in dialog over the page (signin.js,
   loaded by theme.js); as a link it goes to /pro#signin.
   For a subscriber, Sign in and Subscribe give way to one control, Pro
   with a tick, which opens a menu of the plan: its date, the plan page,
   using it on another device, and signing this device out.
   Each is 34px to sit in the row with Search and Settings, and presses as
   44: the ::before reaches past the pill, above and below, so a slightly
   missed click still lands. On a touch screen the pill itself is 44. */
.btn-signin, .btn-account, .pro-lock-signin {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  box-sizing: border-box; min-height: 34px; padding: 0 13px;
  border: 1px solid var(--rule-strong); border-radius: var(--radius-pill);
  background: var(--paper-raised); color: var(--ink);
  font: 650 var(--text-xs)/1 var(--font-sans); letter-spacing: var(--track-body);
  white-space: nowrap; text-decoration: none; cursor: pointer;
  transition: border-color var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard), background-color var(--dur-2) var(--ease-standard);
}
.btn-signin::before, .btn-account::before {
  content: ""; position: absolute; left: 0; right: 0; top: 50%;
  height: max(100%, 44px); transform: translateY(-50%);
}
.btn-signin svg, .btn-account svg, .pro-lock-signin svg { flex: none; width: 15px; height: 15px; }
.btn-account svg { color: var(--good); }
.btn-signin:hover, .btn-account:hover, .btn-account[aria-expanded="true"], .pro-lock-signin:hover { border-color: var(--accent); color: var(--accent-strong); background: var(--paper); }
.btn-signin:focus-visible, .btn-account:focus-visible, .pro-lock-signin:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn-account { display: none; }
:root[data-pro] .btn-account { display: inline-flex; }
:root[data-pro] .btn-signin, :root[data-pro] .btn-pro, :root[data-pro] .os-foot-signin { display: none !important; }
.os-account { display: flex; align-items: center; gap: 8px; }
.os-topbar.slim .btn-signin, .os-topbar.slim .btn-account { min-height: 26px; padding: 0 10px; font-size: var(--text-2xs); gap: 5px; }
.os-topbar.slim .btn-signin svg, .os-topbar.slim .btn-account svg { width: 13px; height: 13px; }
@media (pointer: coarse) {
  .btn-signin, .btn-account, .os-topbar.slim .btn-signin, .os-topbar.slim .btn-account { min-height: 44px; }
}
/* Words that differ for a subscriber, anywhere else on a page. */
.when-pro { display: none; }
:root[data-pro] .when-pro { display: inline; }
:root[data-pro] .when-free { display: none; }
/* Below 700px Search and Settings are icons (their words stay for screen
   readers), which leaves the first row room for the brand, Sign in and
   Subscribe beside them down to about 480px. */
@media (max-width: 699px) {
  .os-topbar .btn-search, .os-topbar .btn-settings { position: relative; gap: 0; padding: 8px 9px; }
  .os-topbar .btn-search > span:not(.search-kbd), .os-topbar .btn-settings > span {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  .os-topbar .btn-search .search-kbd { display: none; }
  :root .btn-pro { padding: 8px 12px; }
}
/* On a phone the brand, Search and Settings keep the first row, and Sign
   in and Subscribe share the second, half each, so neither is squeezed to
   an icon and neither can be missed. A subscriber has one control where
   those two were, and it fits on the first row. The tabs go under both. */
/* Orders 1 to 3 are the ones the stacked bar already gives the brand and
   the tabs (above, and .is-stacked in os-nav.js); the controls slot in by
   order and then by their place in the markup. */
@media (max-width: 479px) {
  .os-topbar .os-controls { display: contents; }
  .os-topbar .btn-search { order: 1; margin-left: auto; }
  .os-topbar .btn-settings { order: 1; }
  .os-topbar .os-account { order: 2; flex: 1 0 100%; }
  .os-topbar .os-account > a { flex: 1 1 0; min-height: 44px; }
  :root[data-pro] .os-topbar .os-account { order: 1; flex: 0 0 auto; margin-left: auto; }
  :root[data-pro] .os-topbar .btn-search { margin-left: 0; }
  :root[data-pro] .os-topbar .os-account > a { flex: 0 0 auto; }
  /* The Pro control says Pro; the mark beside the name would say it twice
     and push Settings off the row. */
  :root[data-pro] .os-topbar .os-pro-word { display: none; }
}
@media (max-width: 359px) {
  .os-topbar { gap: 6px; padding-left: 10px; padding-right: 10px; }
  :root[data-pro] .os-topbar .btn-account { gap: 4px; padding: 0 9px; }
}

/* ---------------------------------------------------------------
   The bar on a phone, in two rows of 44.

   It was four: the brand with Search and Settings, Sign in and Subscribe
   on a row of their own, and the six tabs wrapped onto two more. 228px of
   an 844px screen, a quarter of it, before the page began, and a seventh
   tab would have made it five. Now:

     row one   the wordmark, and Sign in and Subscribe beside it
     row two   the tabs, one row that scrolls sideways under a fade at
               whichever edge has more, the current one scrolled into
               view (os-nav.js), then Search and Settings as icons

   106px at 390 wide, every target 44. The tabs cost no more height
   however many there are, which is the point: the row that grew with
   each application is the row that scrolls. The slim bar the
   full-window apps carry is the same two rows, since on a phone there
   is no room to make it slimmer without taking a target under 44.
   :root raises these over the slim bar's own sizes and the stacked
   bar's rules above, which they replace at this width.
   --------------------------------------------------------------- */
@media (max-width: 560px) {
  :root .os-topbar, :root .os-topbar.slim {
    position: static; flex-wrap: wrap; align-items: center;
    padding: 6px 10px; gap: 6px; margin-bottom: 16px; min-height: 0;
  }
  :root .os-topbar.slim { margin-bottom: 0; }
  :root .os-topbar .os-controls { display: contents; }
  :root .os-topbar .os-brand-group { order: 1; flex: 0 1 auto; min-width: 0; }
  :root .os-topbar .brand-title { font-size: var(--text-lg); min-height: 44px; }
  :root .os-topbar .os-account { order: 2; flex: 0 0 auto; margin-left: auto; gap: 6px; }
  :root .os-topbar .os-account > a { flex: 0 0 auto; min-height: 44px; padding: 0 12px; font-size: var(--text-xs); }
  :root .os-topbar .btn-pro { padding: 0 14px; }
  :root .os-topbar .os-nav-tabs {
    order: 3; flex: 1 1 180px; width: auto; min-width: 0; margin: 0; padding: 0; gap: 2px;
    flex-wrap: nowrap; justify-content: flex-start; overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;
    border-radius: var(--radius-pill); position: relative; scroll-padding-inline: 24px;
  }
  :root .os-topbar .os-nav-tabs .nav-tab-btn {
    flex: 0 0 auto; min-height: 44px; margin: 0; padding: 0 12px;
    font-size: var(--text-xs); letter-spacing: var(--track-body);
  }
  :root .os-topbar .btn-search { order: 4; margin: 0; }
  :root .os-topbar .btn-settings { order: 5; }
  :root .os-topbar :is(.btn-search, .btn-settings) { min-width: 44px; min-height: 44px; padding: 0; justify-content: center; }
  :root[data-pro] .os-topbar .os-account { order: 2; }
  /* The fade says there is more past the edge; os-nav.js says which edge. */
  :root .os-nav-tabs[data-more~="end"] { -webkit-mask-image: linear-gradient(to right, black calc(100% - 28px), transparent); mask-image: linear-gradient(to right, black calc(100% - 28px), transparent); }
  :root .os-nav-tabs[data-more~="start"] { -webkit-mask-image: linear-gradient(to left, black calc(100% - 28px), transparent); mask-image: linear-gradient(to left, black calc(100% - 28px), transparent); }
  :root .os-nav-tabs[data-more~="start"][data-more~="end"] { -webkit-mask-image: linear-gradient(to right, transparent, black 28px, black calc(100% - 28px), transparent); mask-image: linear-gradient(to right, transparent, black 28px, black calc(100% - 28px), transparent); }
}
/* Four tabs, since the bar came down to Workspace, Keep, Apps and Other,
   do not need a row that scrolls: with Search and Settings beside them the
   last tab was cut off at 360 wide, and the tabs sat under Sign in and
   Subscribe as if they belonged to them. So the controls share the first
   row with the wordmark, and the tabs have the second to themselves, each
   an equal share of the width, nothing hidden past an edge. Below 440 the
   first row has no room for Settings as well; it is a tile on the front door
   and a result in Search. */
@media (max-width: 560px) {
  :root .os-topbar .btn-search { order: 2; margin-left: auto; }
  :root .os-topbar .os-account, :root[data-pro] .os-topbar .os-account { order: 3; margin-left: 0; }
  :root .os-topbar .btn-settings { order: 4; }
  :root .os-topbar .os-nav-tabs { order: 5; flex: 1 1 100%; justify-content: space-between; overflow: visible; -webkit-mask-image: none; mask-image: none; }
  :root .os-topbar .os-nav-tabs .nav-tab-btn { flex: 1 1 0; justify-content: center; text-align: center; padding: 0 6px; }
}
/* On a wider screen the bar is one row again, and has to stay one with a
   seventh tab in it: the wordmark, the tabs, Sign in and Subscribe, and
   Search and Settings as the icons they are on a phone (both keep their
   words for screen readers and as a tooltip). With both spelt out, a visitor without Pro saw the
   bar break onto two rows at 1280 wide, 98px where 55 would do. */
@media (min-width: 561px) {
  :root .os-topbar .os-nav-tabs .nav-tab-btn { padding-left: 9px; padding-right: 9px; }
  :root .os-topbar :is(.btn-search, .btn-settings) { position: relative; gap: 6px; padding: 7px 10px; }
  :root .os-topbar .btn-search > span:not(.search-kbd), :root .os-topbar .btn-settings > span {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  /* Ctrl K is in the magnifier's tooltip and on the front door's search
     row; in the bar it was the 56px that, with seven tabs, left a row with
     no room for the webfont arriving wider than its fallback, so the bar
     sometimes broke onto two rows after the first paint. */
  :root .os-topbar .btn-search .search-kbd { display: none; }
}
/* Phones: the first row holds the wordmark, Search, Go Pro and Settings,
   and nothing else. With Sign in as well it held five controls and pushed
   Settings off the edge of a 390px screen. Sign in is still in every
   page's footer and on /pro, where Go Pro leads. */
@media (max-width: 560px) {
  :root .os-topbar .btn-signin { display: none; }
  :root .os-topbar .btn-pro-off { display: none; }
  :root .os-topbar .btn-pro-short { display: inline; }
  :root .os-topbar .os-controls, :root .os-topbar .os-account { gap: 6px; }
}
/* Narrower than most phones: a smaller wordmark, so the row still fits. */
@media (max-width: 374px) {
  :root .os-topbar .btn-signin svg { display: none; }
  :root .os-topbar .btn-pro-off { display: none; }
  :root .os-topbar .btn-pro-short { display: inline; }
  :root .os-topbar .brand-title { font-size: var(--text-md); gap: 6px; }
}

/* ---------------------------------------------------------------
   An application that is all Pro code (pro-load.js): its lock screen.
   pro-load.js writes data-pro-code on <html> while the page is still
   being read, so the right one of the two is on screen from the first
   paint: the application once its code is coming, this with no key,
   or with a key the server would not take.
   --------------------------------------------------------------- */
.pro-lock { display: none; }
html[data-pro-code="locked"] .pro-lock,
html[data-pro-code="refused"] .pro-lock { display: block; }
html[data-pro-code="locked"] [data-pro-app],
html[data-pro-code="refused"] [data-pro-app] { display: none !important; }
/* An application's page without a key (pro-locks.mjs), laid out as one
   product family: the icon, the name, one line, Start and Sign in, the
   price; the picture, wide; six features to scan; the two plans; the
   application at work; and everything else folded into one disclosure.
   Left-aligned in a column wide enough for the picture to be read. */
.pro-lock { width: 100%; max-width: 1080px; box-sizing: border-box; margin: 0 auto; padding: 56px 22px 72px; color: var(--ink); }
.pro-lock-ico { display: block; flex: none; width: 56px; height: 56px; margin: 0 0 18px; }
.pro-lock h1 { margin: 0 0 10px; font: 700 clamp(2.25rem, 6vw, 3.5rem)/1.04 var(--font-headline); letter-spacing: -.02em; text-wrap: balance; }
.pro-lock-lead { margin: 0 0 26px; max-width: 40ch; font-size: var(--text-xl); line-height: 1.4; color: var(--ink-soft); text-wrap: pretty; }
/* The two choices, each straight to its checkout (pro.js lockPlans): the
   application on its own and everything, each with its price and the free
   days under its name. Sign in is the bar's own button and opens the same
   dialog. Not offered to a key that has ended, which is told so instead. */
.pro-lock-in { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 12px; margin: 0 0 14px; }
.pro-lock-in .pro-lock-buy { display: inline-flex; flex-direction: column; justify-content: center; align-items: flex-start; gap: 1px; min-height: 52px; box-sizing: border-box; padding: 7px 20px; border-radius: var(--radius-md); font: 600 var(--text-sm)/1.3 var(--font-sans); text-decoration: none; }
.pro-lock-in .pro-lock-buy small { font: 500 var(--text-xs)/1.3 var(--font-sans); font-variant-numeric: tabular-nums; text-wrap: balance; opacity: 0.92; }
.pro-lock-in .pro-lock-buy:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pro-lock .pro-lock-signin, .pro-lock-in .pro-lock-signin { min-height: 44px; padding: 0 18px; font-size: var(--text-sm); }
html[data-pro-code="refused"] .pro-lock-in .pro-lock-buy { display: none; }
@media (max-width: 560px) {
  /* Side by side, half the column each, with Sign in on the row below. */
  .pro-lock-in .pro-lock-buy { flex: 1 1 140px; padding: 7px 14px; }
}
.pro-lock-refused { display: none; margin: 0 0 18px; max-width: 62ch; padding: 14px 16px; border-radius: var(--radius-md); background: var(--warn-soft); font-size: var(--text-md); line-height: 1.5; }
html[data-pro-code="refused"] .pro-lock-refused { display: block; }
/* The price, once, from the one place prices are kept. */
.pro-lock-price { margin: 0 0 44px; max-width: 62ch; font-size: var(--text-sm); line-height: 1.55; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.pro-lock-shot { margin: 0 0 40px; border: 1px solid var(--rule); border-radius: var(--radius-lg); overflow: hidden; background: var(--paper-inset); }
.pro-lock-shot img { display: block; width: 100%; height: auto; }
/* Six things it does, a few words each: a list to scan, in columns, with
   the hairlines the site's lists use and no icons. */
.pro-lock-gets { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: 28px; list-style: none; margin: 0 0 56px; padding: 0; }
.pro-lock-gets li { padding: 13px 0; border-top: 1px solid var(--rule); font-size: var(--text-md); line-height: 1.4; color: var(--ink); }
.pro-lock-gets a { color: var(--accent-strong); text-decoration: underline; text-underline-offset: 3px; }
.pro-lock-gets a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
/* The panel fills the column rather than sitting 640px wide against its
   left edge. */
.pro-lock-wall { scroll-margin-top: 72px; }
.pro-lock-wall .pro-wall { margin: 0; max-width: none; }
/* Everything else: one quiet line that opens on the rest. */
.pro-lock-all { margin: 56px 0 0; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
.pro-lock-all > summary { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 56px; cursor: pointer; list-style: none; font: 600 var(--text-md)/1.3 var(--font-ui); color: var(--ink); }
.pro-lock-all > summary::-webkit-details-marker { display: none; }
.pro-lock-all > summary::after { content: ""; flex: none; width: 8px; height: 8px; margin-right: 4px; border-right: 1.5px solid var(--ink-soft); border-bottom: 1.5px solid var(--ink-soft); transform: translateY(-2px) rotate(45deg); transition: transform var(--dur-2) var(--ease-standard); }
.pro-lock-all[open] > summary::after { transform: translateY(2px) rotate(-135deg); }
.pro-lock-all > summary:hover { color: var(--accent-strong); }
.pro-lock-all > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
.pro-lock-all[open] { padding-bottom: 40px; }
.pro-lock-all > .ld-sec:first-of-type { margin-top: var(--space-6); }
/* Inside it, what was the detail: the paragraph, the one limit most likely
   to decide it, and the comparisons. */
.pro-lock-more { margin: 8px 0 18px; max-width: 62ch; font-size: var(--text-md); line-height: 1.6; color: var(--ink-soft); }
.pro-lock-limit a, .pro-lock-compare a { color: var(--accent-strong); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }
.pro-lock-limit a:focus-visible, .pro-lock-compare a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
.pro-lock-limit { margin: 0 0 10px; max-width: 70ch; padding: 10px 14px; border-left: 2px solid var(--rule-strong); background: var(--paper-inset); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; font-size: var(--text-sm); line-height: 1.55; color: var(--ink-soft); }
.pro-lock-limit b { color: var(--ink); font-weight: 600; }
.pro-lock-limit a { margin-left: 4px; }
.pro-lock-compare { margin: 0 0 18px; font-size: var(--text-sm); line-height: 1.55; color: var(--ink-soft); }
@media (max-width: 860px) {
  .pro-lock-gets { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .pro-lock { padding-top: 24px; padding-bottom: 56px; }
  /* Inside the page's own column (the Photo Editor's), which has its own
     room under the bar already: the two together began the page 160px down. */
  .app-wrapper .pro-lock { padding-top: var(--space-2); }
  .pro-lock-ico { width: 48px; height: 48px; margin-bottom: 14px; }
  .pro-lock-lead { font-size: var(--text-lg); margin-bottom: 20px; }
  .pro-lock-price { margin-bottom: 28px; }
  .pro-lock-shot { margin-bottom: 28px; }
  .pro-lock-gets { grid-template-columns: minmax(0, 1fr); margin-bottom: 40px; }
  .pro-lock-gets li { padding: 11px 0; }
}
/* The site's bar over a lock screen (pro-locks.mjs): shown only while the
   page is locked, so the application, once its code has come, keeps the
   bar that is its own. */
html:not([data-pro-code="locked"]):not([data-pro-code="refused"]) .pro-lock-bar { display: none !important; }
/* An application's page leaves the body's margin to its own layout, which
   is Pro code; with the lock screen showing, the bar runs edge to edge as
   it does on every other page. */
html[data-pro-code="locked"] body:has(> .pro-lock-bar), html[data-pro-code="refused"] body:has(> .pro-lock-bar) { margin: 0; }
.pro-lock-sub { margin: 48px 0 8px; font: 700 var(--text-xl)/1.2 var(--font-headline); }
.pro-lock-copy .sv-lib-head { display: none; }
/* The page arrives as a tool's title block does: the name, the line under
   it and the picture rise a --os-step apart, and the features deal
   themselves out on the list stagger. It is shown by an attribute rather
   than on load, so each part runs its entrance the moment the lock is
   decided. */
.pro-lock > :is(.pro-lock-ico, h1, .pro-lock-lead, .pro-lock-in, .pro-lock-price, .pro-lock-shot, .pro-lock-wall) {
  --os-fade: var(--dur-fade);
  animation: os-rise var(--dur-4) var(--ease-emph) calc(var(--os-step) * var(--os-n, 0)) backwards;
}
.pro-lock > h1 { --os-n: 1; }
.pro-lock > .pro-lock-lead, .pro-lock > .pro-lock-in, .pro-lock > .pro-lock-price { --os-n: 2; }
.pro-lock > .pro-lock-shot { --os-n: 3; }
.pro-lock > .pro-lock-wall { --os-n: 4; }
.pro-lock-gets > li {
  --os-fade: var(--dur-fade);
  animation: os-rise-soft var(--dur-3) var(--ease-standard) backwards;
  animation-delay: calc(var(--os-step) * 3 + var(--stagger) * var(--os-n, 0));
}
.pro-lock-gets > li:nth-child(2) { --os-n: 1; }
.pro-lock-gets > li:nth-child(3) { --os-n: 2; }
.pro-lock-gets > li:nth-child(4) { --os-n: 3; }
.pro-lock-gets > li:nth-child(5) { --os-n: 4; }
.pro-lock-gets > li:nth-child(6) { --os-n: 5; }

/* ---------------------------------------------------------------
   Page chrome, lifted out of the pages themselves.

   Each of these was written out identically on ten or more pages - and
   the same rule in forty-four files is a rule that gets changed in forty
   of them. Generated by .github/scripts/lift-css.mjs; a rule whose body
   differed between pages was left where it was.
   --------------------------------------------------------------- */
.page-header .os-intro { max-width: 780px; margin: 12px 0 0; font-size: var(--text-sm); line-height: 1.7; color: var(--ink-soft); text-wrap: pretty; }

/* ---------------------------------------------------------------
   The parts of a suite that is more than one application: the
   Engineering Suite's 3D parts and 2D drawings. The same switch sits
   in each application's own bar, the part you are in pressed.
   --------------------------------------------------------------- */
.suite-parts{display:inline-flex;align-items:center;gap:2px;flex:none;padding:2px;border:1px solid var(--rule);border-radius:var(--radius-pill);background:var(--paper-inset);}
.suite-parts a{display:inline-flex;align-items:center;min-height:28px;padding:0 11px;border-radius:var(--radius-pill);color:var(--ink-soft);font-size:var(--text-xs);font-weight:600;line-height:1;text-decoration:none;white-space:nowrap;transition:background-color var(--dur-2) var(--ease-standard),color var(--dur-2) var(--ease-standard);}
.suite-parts a:hover{color:var(--ink);}
.suite-parts a[aria-current="page"]{background:var(--paper-raised);color:var(--ink);box-shadow:0 0 0 1px var(--rule),0 1px 2px rgba(0,0,0,.08);}
.suite-parts a:focus-visible{outline:2px solid var(--accent);outline-offset:1px;}
/* On a touch screen the links are a finger's 44px and the frame is drawn inside them, so the switch is 44px and not 46px: it sits in an application's title bar and must not make that row taller than the home button beside it. */
@media (pointer:coarse){.suite-parts{padding:0;border:0;box-shadow:inset 0 0 0 1px var(--rule);}.suite-parts a{min-height:44px;padding:0 14px;}}

/* ---------------------------------------------------------------
   Page chrome, lifted out of the pages themselves.

   Each of these was written out identically on ten or more pages - and
   the same rule in forty-four files is a rule that gets changed in forty
   of them. Generated by .github/scripts/lift-css.mjs; a rule whose body
   differed between pages was left where it was.
   --------------------------------------------------------------- */
.page-header .page-sub { text-wrap: pretty; }

/* ---------------------------------------------------------------
   Loading, in the brand's own motion.

   An application that takes a moment to open (Case reading its store,
   the Engineering Suite's geometry kernel) says so with the mark turning,
   the product's name and what it is doing, and a thin bar that says the
   wait is live. It was a line of grey text in a corner of an empty
   window. The mark turns as it does under the pointer (motion.js turns
   every .os-loading-mark for as long as it is on screen); with motion off
   it stands still and the bar is a plain rule, and the words say it all.
   --------------------------------------------------------------- */
.os-loading { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; min-height: 40vh; padding: 40px 16px; box-sizing: border-box; text-align: center; color: var(--ink-soft); }
.os-loading[hidden] { display: none; }
.os-loading-mark { display: inline-flex; color: var(--accent-strong); line-height: 0; }
.os-loading-mark svg { width: 44px; height: 44px; }
.os-loading-name { font: 700 var(--text-md)/1.2 var(--font-ui); letter-spacing: -.005em; color: var(--ink); }
.os-loading-msg { font-size: var(--text-sm); line-height: 1.5; }
.os-loading-bar { position: relative; display: block; width: 140px; height: 3px; margin-top: 4px; overflow: hidden; border-radius: var(--radius-pill); background: var(--rule); }
/* The placeholder's own sweep (os-skel-sweep), at the pace the mark turns. */
.os-loading-bar::after { content: ""; position: absolute; inset: 0; border-radius: inherit; background: linear-gradient(90deg, transparent, var(--accent), transparent); transform: translateX(-100%); animation: os-skel-sweep var(--dur-turn) var(--ease-standard) infinite; }
:root[data-motion="none"] .os-loading-bar::after { animation: none; transform: none; background: var(--accent); opacity: .35; }

/* The way home from an application's own bar (brand-heads.mjs): the
   product's family icon, the same drawing as its tab icon and its icon on
   a home screen, at one size in every application. The page's own class
   beside it places it; this sizes it. A finger gets 44. */
.os-app-home { display: inline-flex; align-items: center; justify-content: center; flex: none; box-sizing: border-box; width: 36px; height: 36px; padding: 0; border-radius: var(--radius-md); line-height: 0; text-decoration: none; }
.os-app-home img { display: block; width: 24px; height: 24px; }
.os-app-home:hover { background: color-mix(in srgb, currentColor 12%, transparent); }
.os-app-home:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (pointer: coarse) { .os-app-home { width: 44px; height: 44px; } }

/* /press-kit#brand (brand-page.mjs): the logo files on the grounds they are
   for, the colours as swatches, the product names with their icons. */
.bk-logos { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; margin: 0 0 14px; }
.bk-logo { margin: 0; padding: 18px 16px 12px; border: 1px solid var(--rule); border-radius: var(--radius-md); background: var(--paper-raised); display: flex; flex-direction: column; gap: 12px; align-items: flex-start; }
.bk-logo img { display: block; max-width: 100%; height: 40px; width: auto; }
.bk-logo figcaption { font-size: var(--text-xs); line-height: 1.5; }
/* The two grounds are the colourways' own paper, light and dark, written
   onto each figure by brand-page.mjs from theme.css's tokens. */
.bk-logo figcaption a { color: inherit; }
.bk-swatch { display: inline-block; width: 18px; height: 18px; margin-right: 8px; vertical-align: -4px; border-radius: var(--radius-sm); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .15); }
.bk-names img { display: block; }
.bk-pro { display: inline-block; margin-left: 6px; padding: 1px 6px; border-radius: var(--radius-sm); font: 700 var(--text-2xs)/1.4 var(--font-mono); letter-spacing: .06em; color: var(--on-accent); background: var(--accent-strong); }
.bk h3 { margin: 22px 0 8px; font: 650 var(--text-md)/1.3 var(--font-ui); }

/* In a window of the desktop (/os). theme.js marks <html data-in-os> when a
   page is opened inside one of the desktop's windows, or with ?in=os. The
   window has its own title bar with the application's name and mark, its
   own close and its own way back to the desktop, so the site's bar and
   footer, an application's own way home, and the lock screen's wordmark
   would each be a second copy of something already on screen. The install
   offer belongs to the desktop, which is what gets installed. */
html[data-in-os] .os-topbar,
html[data-in-os] .os-footer,
html[data-in-os] .os-app-home,
html[data-in-os] .pro-lock-home,
html[data-in-os] #pfBar { display: none !important; }
/* The same for an application's "Back to OBSCURA OS" in its File or Help
   menu: in a window, the desktop is already there behind it. */
html[data-in-os] [data-cmd="leaveApp"],
html[data-in-os] [data-cmd="leave"],
html[data-in-os] [data-help="leave"],
html[data-in-os] [data-os-leave] { display: none !important; }
/* An application's own picture beside its name: the window's title bar
   already carries the picture. The name stays, as the page's heading. */
html[data-in-os] .sv-mark,
html[data-in-os] .ca-brand > svg,
html[data-in-os] .ct-brand > svg,
html[data-in-os] .dr-brand > img { display: none !important; }

/* The page's own question in a window, from os-bridge.js: whether a file
   comes from the desktop's Files or from the computer, and whether a save
   goes to Files or to the downloads folder. A dialog of the page's, so it
   stays inside the window it belongs to. */
.osb-dialog { width: min(440px, calc(100vw - 32px)); max-height: calc(100vh - 32px); max-height: calc(100dvh - 32px); margin: auto; padding: 0; border: 1px solid var(--rule-strong); border-radius: var(--radius-lg); background: var(--paper-raised); color: var(--ink); font: 400 var(--text-sm)/1.5 var(--font-ui); overflow: auto; }
.osb-dialog::backdrop { background: rgba(10, 10, 14, .4); }
.osb-in { padding: 8px 20px 20px; }
.osb-head { display: flex; align-items: center; gap: 12px; margin-right: -12px; }
.osb-head h2 { flex: 1; min-width: 0; margin: 0; font: 600 var(--text-md)/1.35 var(--font-ui); overflow-wrap: anywhere; }
.osb-close { flex: none; width: 44px; height: 44px; border: 0; border-radius: var(--radius-sm); background: none; color: var(--ink-soft); font-size: var(--text-xl); line-height: 1; cursor: pointer; }
.osb-close:hover { background: var(--paper-inset); color: var(--ink); }
.osb-dialog p.osb-sub { margin: 4px 0 16px; color: var(--ink-soft); font-size: var(--text-sm); }
.osb-acts { display: flex; flex-wrap: wrap; gap: 8px; }
.osb-acts > button { min-height: 44px; padding: 0 var(--space-4); border-radius: var(--radius-md); font: 600 var(--text-sm)/1.2 var(--font-ui); cursor: pointer; }
/* Its own colours, so the question reads the same in every application,
   whatever that application's sheet does to a button. */
.osb-acts > .btn-primary { background: var(--accent-strong); color: var(--on-accent); }
.osb-acts > .btn-secondary { background: var(--paper-raised); color: var(--ink); box-shadow: inset 0 0 0 1px var(--rule-strong); }
.osb-acts > .btn-secondary:hover { background: var(--paper-inset); }

/* A single tool in a window of the desktop is a utility window, not a web
   page: the window's title bar names it, so the code above the heading,
   the star that adds it to the front door, the longer introduction and
   the links to related tools and questions give way to the work, and the
   page uses the window's width with tighter spacing. In a tab nothing
   here applies. */
html[data-in-os] body[data-kind="tool"] .app-wrapper { max-width: none; padding: var(--space-4) var(--space-5) var(--space-6); }
html[data-in-os] body[data-kind="tool"] .page-header { margin-bottom: var(--space-4); }
html[data-in-os] body[data-kind="tool"] .page-title { font-size: var(--text-xl); letter-spacing: -0.01em; margin-bottom: var(--space-1); }
html[data-in-os] body[data-kind="tool"] .page-header .h1-code,
html[data-in-os] body[data-kind="tool"] .page-header .ob-star,
html[data-in-os] body[data-kind="tool"] .page-header .os-intro,
html[data-in-os] body[data-kind="tool"] main .os-bridge,
html[data-in-os] body[data-kind="tool"] main .os-rel,
html[data-in-os] body[data-kind="tool"] main .os-faq-link { display: none; }
html[data-in-os] body[data-kind="tool"] .panel { padding: var(--space-4) var(--space-5); margin-bottom: var(--space-4); }

/* --------------------------------------------------------------------------
   The Help panel (help-panel.js), opened with F1 or Help in any
   application's Help menu: a column down the right, beside the work rather
   than over it, with a search box and short answers. On a phone it is the
   whole width.
   -------------------------------------------------------------------------- */
.ohp { position: fixed; top: 0; right: 0; bottom: 0; z-index: 99990; display: flex; flex-direction: column; width: min(380px, 100vw); background: var(--paper-raised); color: var(--ink); border-left: 1px solid var(--rule-strong); font-family: var(--font-ui); font-size: var(--text-sm); line-height: var(--leading-ui); }
.ohp[hidden] { display: none; }
.ohp-head { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4); border-bottom: 1px solid var(--rule); }
.ohp-title { flex: 1; min-width: 0; margin: 0; font-size: var(--text-md); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ohp-title:focus { outline: none; }
.ohp-x { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; padding: 0; border: 0; border-radius: var(--radius-sm); background: none; color: var(--ink-soft); cursor: pointer; }
.ohp-x:hover { background: var(--paper-inset); color: var(--ink); }
.ohp-x svg { width: 18px; height: 18px; }
.ohp-find { position: relative; margin: var(--space-3) var(--space-4) 0; }
.ohp-find svg { position: absolute; left: var(--space-3); top: 50%; width: 16px; height: 16px; transform: translateY(-50%); color: var(--ink-faint); pointer-events: none; }
.ohp-input { box-sizing: border-box; width: 100%; min-height: 44px; padding: 0 var(--space-3) 0 36px; border: 1px solid var(--rule-strong); border-radius: var(--radius-md); background: var(--paper); color: var(--ink); font: inherit; font-size: var(--text-md); }
.ohp-input:focus { outline: 2px solid var(--focus); outline-offset: 1px; border-color: var(--focus); }
.ohp-say { min-height: 0; margin: var(--space-1) var(--space-4) 0; font-size: var(--text-xs); color: var(--ink-soft); }
.ohp-say:empty { display: none; }
.ohp-list { flex: 1; min-height: 0; overflow: auto; padding: var(--space-2) var(--space-4) var(--space-4); }
.ohp-a { padding: var(--space-3) 0; border-bottom: 1px solid var(--rule); }
.ohp-a:last-child { border-bottom: 0; }
.ohp-a h3 { margin: 0 0 var(--space-1); font-size: var(--text-sm); font-weight: 600; }
.ohp-a p { margin: 0; color: var(--ink-soft); line-height: var(--leading-prose); }
.ohp-a p + p { margin-top: var(--space-2); }
.ohp-a a, .ohp-foot a, .ohp-none a { color: var(--accent-strong); }
.ohp-a kbd { display: inline-block; min-width: 1.6em; padding: 0 var(--space-1); border: 1px solid var(--rule-strong); border-radius: var(--radius-sm); background: var(--paper); color: var(--ink); font-family: var(--font-mono); font-size: var(--text-2xs); line-height: 1.6; text-align: center; }
.ohp-sub { margin: var(--space-4) 0 0; padding-top: var(--space-3); border-top: 1px solid var(--rule-strong); font-size: var(--text-xs); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); }
.ohp-none { margin: var(--space-4) 0; color: var(--ink-soft); line-height: var(--leading-prose); }
.ohp-seg { display: flex; width: 100%; margin: var(--space-2) 0; border: 1px solid var(--rule-strong); border-radius: var(--radius-md); overflow: hidden; }
.ohp-seg button { flex: 1; min-height: 44px; padding: 0 var(--space-2); border: 0; border-left: 1px solid var(--rule); background: var(--paper); color: var(--ink-soft); font: inherit; font-weight: 600; cursor: pointer; }
.ohp-seg button:first-child { border-left: 0; }
.ohp-seg button[aria-checked="true"] { background: var(--accent-soft); color: var(--ink); }
.ohp-seg button:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.ohp-link { display: inline-flex; align-items: center; min-height: 44px; padding: 0; border: 0; background: none; color: var(--accent-strong); font: inherit; text-decoration: underline; cursor: pointer; }
.ohp-foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); padding: var(--space-3) var(--space-4); border-top: 1px solid var(--rule); font-size: var(--text-xs); color: var(--ink-faint); }
.ohp-foot a { display: inline-flex; align-items: center; min-height: 44px; }
.ohp-foot .ohp-sent { display: inline-flex; align-items: center; min-height: 44px; padding: 0; border: 0; background: none; font: inherit; color: var(--accent-strong); cursor: pointer; }
.ohp-foot .ohp-sent:hover { text-decoration: underline; text-underline-offset: 2px; }

/* A ribbon's tabs, grouped for a screen reader as a tab list without the
   File button beside them (which opens the backstage, not a panel), and
   laid out as if the group were not there. */
.os-tablist { display: contents; }

/* Letters that draw what a ribbon button does (ab struck through, x
   squared): a picture made with CSS from data-g, so the button's name is
   its label alone and not the letters. */
.rb-glyph[data-g]::before, .rb-glyph [data-g]::before, .glyph[data-g]::before, .glyph [data-g]::before { content: attr(data-g); }

/* ---------------------------------------------------------------
   The not-found page (404.html): one raised panel, then the Workspace,
   the applications and the suites, as the front door's sidebar lists
   them. */
.wrap.nf-page{ max-width: 880px; margin: 0 auto; padding: var(--space-7) var(--space-4) var(--space-8); }
.nf-page .nf-panel{ background:var(--paper-raised); border:1px solid var(--rule); border-radius:var(--radius-md); padding:var(--space-6); }
.nf-page .nf-head{ display:flex; align-items:flex-start; gap:var(--space-4); }
.nf-page .mark{ flex:none; display:inline-flex; color:var(--accent-strong); }
.nf-page .mark svg{ width:44px; height:44px; }
.nf-page .code{ font-family:var(--font-mono); font-size:var(--text-2xs); letter-spacing:.14em; text-transform:uppercase; color:var(--ink-faint); }
.nf-page h1{ font-family:var(--font-headline); font-size: clamp(var(--text-xl), 4vw, var(--text-2xl)); font-weight:600; letter-spacing:-.01em; margin-top:var(--space-1); text-wrap:balance; }
.nf-page .sub{ color:var(--ink-soft); margin:var(--space-2) 0 0; line-height:var(--leading-prose); max-width:40em; text-wrap:pretty; }
.nf-page .nf-path{ font-family:var(--font-mono); font-size:var(--text-xs); color:var(--ink); background:var(--paper-inset); border:1px solid var(--rule); border-radius:var(--radius-sm); padding:1px 6px; overflow-wrap:anywhere; }
.nf-page .nf-actions{ display:flex; flex-wrap:wrap; gap:var(--space-2); margin-top:var(--space-5); }
.nf-page .search-btn{ display:flex; align-items:center; gap:8px; flex:1 1 260px; max-width:360px; min-height:44px; padding:0 var(--space-3); background:var(--paper); border:1px solid var(--rule-strong); border-radius:var(--radius-md); color:var(--ink-soft); font:inherit; font-size:var(--text-sm); cursor:pointer; }
.nf-page .search-btn:hover{ border-color:var(--accent); color:var(--ink); }
.nf-page .search-btn kbd{ margin-left:auto; font-family:var(--font-mono); font-size:var(--text-2xs); border:1px solid var(--rule-strong); border-radius:var(--radius-sm); padding:1px 5px; }
.nf-page .home-link{ display:inline-flex; align-items:center; min-height:44px; padding:0 var(--space-4); border-radius:var(--radius-md); background:var(--accent-strong); color:var(--paper-raised); text-decoration:none; font-size:var(--text-sm); font-weight:600; }
.nf-page .home-link:hover{ filter:brightness(1.08); }
.nf-page .nf-group{ margin-top:var(--space-6); }
.nf-page .nf-label{ display:flex; align-items:baseline; justify-content:space-between; gap:var(--space-3); padding-bottom:var(--space-2); border-bottom:1px solid var(--rule); font-family:var(--font-mono); font-size:var(--text-2xs); font-weight:600; letter-spacing:.14em; text-transform:uppercase; color:var(--ink-soft); }
.nf-page .nf-label span{ font-weight:400; letter-spacing:.06em; color:var(--ink-faint); text-transform:none; }
.nf-page .nf-grid{ display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:var(--space-2); margin-top:var(--space-3); }
.nf-page .nf-grid.nf-one{ grid-template-columns:minmax(0,1fr); }
.nf-page .nf-grid a, .nf-page .nf-apps a{ display:flex; flex-direction:column; gap:2px; min-height:44px; padding:var(--space-3); border:1px solid var(--rule); border-radius:var(--radius-md); background:var(--paper-raised); color:var(--ink); text-decoration:none; transition:border-color var(--dur-2) var(--ease); }
.nf-page .nf-grid a:hover, .nf-page .nf-apps a:hover{ border-color:var(--accent); }
.nf-page .nf-name{ font-size:var(--text-sm); font-weight:600; }
.nf-page .nf-desc{ font-size:var(--text-xs); line-height:var(--leading-ui); color:var(--ink-faint); }
.nf-page .nf-apps{ display:grid; grid-template-columns:repeat(4, minmax(0,1fr)); gap:var(--space-2); margin-top:var(--space-3); }
.nf-page .nf-apps a{ flex-direction:row; align-items:center; justify-content:space-between; padding:0 var(--space-3); }
.nf-page .nf-apps .hx-lock{ display:inline-flex; color:var(--ink-faint); }
.nf-page .nf-apps .hx-lock svg{ width:12px; height:12px; }
.nf-page #nfNear .nf-grid{ grid-template-columns:repeat(3, minmax(0,1fr)); }
/* The closest tools are worked out twice, the second time once the search
   words have come (404.html), and the second three can have longer lines:
   each line is held to two lines' room, so the list below does not move. */
.nf-page #nfNear .nf-desc{ display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2; overflow:hidden; height:calc(2em * var(--leading-ui)); }
.nf-page > .boot-foot{ margin-top:var(--space-7); }
@media (max-width:760px){ .nf-page .nf-apps{ grid-template-columns:repeat(2, minmax(0,1fr)); }  .nf-page #nfNear .nf-grid{ grid-template-columns:minmax(0,1fr); } }
@media (max-width:520px){ .nf-page .nf-panel{ padding:var(--space-4); }  .nf-page .nf-grid{ grid-template-columns:minmax(0,1fr); }  .nf-page .search-btn{ max-width:none; }  .nf-page .home-link{ flex:1 1 100%; justify-content:center; } }

/* The four suites of single tools (Desk, Record, Standard, Trace) as a
   list rather than a wall of cards (suite-index.mjs, .boot-groups-list):
   each tool is a row with its name and its line, ruled off from the next,
   in as many columns as fit. No raised paper, no shadow, no lift; the
   name takes the accent under the pointer. */
.boot-groups-list .boot-grid.boot-grid-tools { grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr)); gap: 0 28px; margin-top: var(--space-2); }
.boot-groups-list .boot-grid.boot-grid-tools > .boot-tile.boot-tile-tool { grid-column: auto; }
.boot-groups-list .boot-tile-tool:not(.boot-tile-app):not(.boot-tile-flat) {
  min-height: 44px;
  gap: 2px;
  padding: 11px 2px 12px;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  background: none;
  box-shadow: none;
}
.boot-groups-list .boot-tile-tool:not(.boot-tile-app):not(.boot-tile-flat) .boot-tile-name {
  order: 1; margin-top: 0; font-size: var(--text-xs); font-weight: 600; letter-spacing: .04em; color: var(--ink);
}
.boot-groups-list .boot-tile-tool:not(.boot-tile-app):not(.boot-tile-flat) .boot-tile-sub {
  order: 2; font-size: var(--text-sm); font-weight: 400; line-height: 1.4; color: var(--ink-soft); -webkit-line-clamp: 2; line-clamp: 2;
}
.boot-groups-list .boot-tile.boot-tile-tool:hover, .boot-groups-list .boot-tile.boot-tile-tool:focus-visible { transform: none; background: none; box-shadow: none; }
.boot-groups-list .boot-tile.boot-tile-tool:hover .boot-tile-name { color: var(--accent-strong); }
.boot-groups-list .boot-tile.boot-tile-tool:active { transform: none; }
.boot-groups-list .boot-tile.boot-tile-tool::before { display: none; }
.boot-groups-list .boot-sect + .boot-sect { margin-top: var(--space-6); }

/* A guide's top: the dates and Share on one quiet line under the lead,
   rather than a line and then a bordered button of their own. */
.page-header > .os-dated:has(+ .os-share) { display: inline-block; margin-right: var(--space-3); vertical-align: middle; }
.page-header > .os-dated + .os-share { display: inline-flex; vertical-align: middle; margin-top: var(--space-1); }
.page-header > .os-dated + .os-share .os-share-btn { border-color: transparent; padding: 0 var(--space-1); text-decoration: underline; text-decoration-color: var(--rule-strong); text-underline-offset: 3px; }
.page-header > .os-dated + .os-share .os-share-btn:hover, .page-header > .os-dated + .os-share .os-share-btn:focus-visible { border-color: transparent; text-decoration-color: currentColor; }

/* A row in an application that scrolls sideways says so (details.js puts
   the marks in): a chevron on the row's own colour, held at the edge there
   is more beyond, with a hairline between it and what scrolls under it.
   Solid, not a fade. Shown only while there is more that way. */
.row-cue { display: none; position: sticky; z-index: 3; flex: none; align-self: stretch; align-items: center; justify-content: center; width: 28px; min-height: 24px; padding: 0; color: var(--ink-soft); background: var(--paper); cursor: pointer; -webkit-user-select: none; user-select: none; }
.row-cue svg { width: 14px; height: 14px; flex: none; }
.row-cue:hover { color: var(--ink); }
.row-cue-r { order: 9999; right: 0; margin-left: -28px; border-left: 1px solid var(--rule); }
.row-cue-l { order: -9999; left: 0; margin-right: -28px; border-right: 1px solid var(--rule); }
.row-cue-l svg { transform: scaleX(-1); }
[data-scroll-more~="r"] > .row-cue-r, [data-scroll-more~="l"] > .row-cue-l { display: inline-flex; }
@media print { .row-cue { display: none !important; } }
