* {
  box-sizing: border-box;
}

/* NATIVE FORM-CONTROL CHROME, FOR BOTH THEMES (HR-0499 point 4).

   The only `color-scheme` in this file was the print rule further down, so
   every native popup a `<select>` opens anywhere in the app - the dropdown
   list itself, its scrollbar - was told nothing about the theme and rendered
   with Chromium's default ("light"). In the dark theme that meant an
   `<option>`'s own `color` (`--color-heading`, near-white) painted onto that
   default light popup rather than the dark one `.select`'s own
   `background-color` suggests, which is Darren's "hard to read the light
   text" on the Sent tab's Message filter: light text, light popup.

   `[data-theme="dark"]` is set on the shell's own `.app` div, not on `<html>`
   (`HRGoatApp.tsx`), and `color-scheme` inherits, so setting it there reaches
   every `<select>` under it exactly as a `:root` default would reach every
   one above it. Belt-and-suspenders: `.select option`/`.select optgroup`
   still get explicit token colours too (CommunicationsAdmin.module.css),
   because `color-scheme` alone decides the POPUP's chrome, not what an
   author's own CSS paints inside it, and a theme switch is also a page
   reload candidate on some embeds where `color-scheme` can lag a paint. */
:root { color-scheme: light; }
[data-theme="dark"] { color-scheme: dark; }

body {
  margin: 0;
  padding: 0;
  font-family: var(--font-ui, 'IBM Plex Sans', system-ui, sans-serif);
  background: var(--color-bg);
  color: var(--color-text);
  font-size: var(--fs-base, 16px);
  line-height: var(--lh-normal, 1.6);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ============================================================
   UNIFORM PAGE LAYOUT - single source of truth for every module.
   Every screen wraps its top-level container with the `hgPage`
   class, or better, uses HgPageShell, which is this class plus
   the header and the tab row. Do not redefine padding, max-width,
   gap, or the header size/spacing in per-component CSS.

   The four numbers come from --page-* in tokens.css, so the whole
   product's rhythm moves together and a screen cannot quietly
   pick its own. The phone override is on the tokens, not here.
   ============================================================ */
.hgPage {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--page-gap);
  padding: var(--page-pad-block) var(--page-pad-inline);
  box-sizing: border-box;
  width: 100%;
}
/* HR-0551: `:not([data-hg-overlay])` IS THE FIX, NOT A TWEAK TO IT.

   HgPageShell (and plenty of screens that write `<div className="hgPage">`
   by hand) spread a screen's whole return value straight in here with
   nothing in between. A one-off dialog built as two sibling elements at the
   top of that return - a `position: fixed; inset: 0` scrim and its panel,
   the AddPersonModal/CompanyKnowledge/SectionHealthScore shape - is just
   another direct child as far as this rule is concerned, so it got capped to
   --page-max and centred exactly like a card: the scrim stopped at the
   content column and the rail and second-level nav were left bright and
   fully clickable underneath it (HR-0249 round 4 hit this for PolicyCenter;
   HR-0551 is the same bug in AddPersonModal, CompanyKnowledge's document
   reader and its acknowledgement record).

   HgModal already has the other correct answer - portal the dialog to
   document.body, which leaves `.hgPage` entirely (see its own comment) - and
   that is still the better choice for a NEW dialog. This attribute is the
   shared escape hatch for the inline scrim+panel pattern that already exists
   all over this codebase and was not worth rewriting wholesale: put
   `data-hg-overlay` on every element that is BOTH a direct child of this
   screen's return AND `position: fixed` (the scrim, and the panel too if it
   is a sibling of the scrim rather than nested inside it), and this rule
   stops touching it - no max-width, no centring, nothing to undo.

   A future overlay written the obvious way (two siblings, no attribute, no
   portal) is exactly the AddPersonModal mistake again: `verify-modal-
   backdrop.mjs` opens every overlay on this audit's list and elementFromPoint
   over the rail and the nav panel heading, at 1440 and 390, so that mistake
   fails the check it already has to pass rather than shipping quietly. */
.hgPage > *:not([data-hg-overlay]) {
  max-width: var(--page-max);
  margin-left: auto;
  margin-right: auto;
  width: 100%;
}
.hgPageInner {
  display: flex;
  flex-direction: column;
  gap: var(--page-gap);
  width: 100%;
}

/* HEADINGS TAKE THE DISPLAY FACE, wherever the screen has not already said so.

   This is an element selector, so any component whose own module sets a family
   on the class still wins - the 309 places that already say var(--font-display)
   are unaffected. What it catches is the heading that says nothing, which was
   invisible while --font-ui and --font-display were both Manrope and became a
   heading set in the body face the moment they stopped being the same. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
}

/* WHAT SOMEBODY CAN CHANGE THE TYPE TO.

   The list, the reasons, and the on-demand loading of every face below are in
   src/styles/fontChoice.ts; this file is only where each one takes effect. The
   attribute is written by applyFontChoice() on <html>, and read back at boot
   before React renders so a chosen face does not arrive as a repaint.

   Nothing here is loaded in index.html except the default pair. A rule naming
   a family the browser has not fetched falls through to the next entry in its
   own stack, which is why every one of them ends in a real system fallback. */

/* The brand face throughout, for somebody who wants the character everywhere
   and does not read long stretches on this product. */
[data-font-family="space-only"] {
  --font-ui: 'Space Grotesk', system-ui, sans-serif !important;
  --font-display: 'Space Grotesk', system-ui, sans-serif !important;
}

[data-font-family="rounded"] {
  --font-ui: 'Rubik', system-ui, sans-serif !important;
  --font-display: 'Rubik', system-ui, sans-serif !important;
}

/* Lora rather than Georgia, which was the previous value here: Georgia is a
   system font on macOS and Windows and simply absent on Linux and most
   Androids, so "Editorial Serif" silently rendered as Times for a large share
   of the people who picked it. Georgia stays as the first fallback. */
[data-font-family="serif"] {
  --font-ui: 'Lora', Georgia, 'Times New Roman', serif !important;
  --font-display: 'Lora', Georgia, 'Times New Roman', serif !important;
}

[data-font-family="dyslexia"] {
  --font-ui: 'Atkinson Hyperlegible', 'Trebuchet MS', 'OpenDyslexic', sans-serif !important;
  --font-display: 'Atkinson Hyperlegible', 'Trebuchet MS', 'OpenDyslexic', sans-serif !important;
  letter-spacing: 0.035em !important;
  word-spacing: 0.06em !important;
}

[data-font-family="monospace"] {
  --font-ui: 'Roboto Mono', 'Cascadia Code', 'Courier New', monospace !important;
  --font-display: 'Roboto Mono', 'Cascadia Code', 'Courier New', monospace !important;
}

[data-font-family="system"] {
  --font-ui: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
  --font-display: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
}

/* Size, not face: this one keeps whatever the default pair is and raises every
   step of the scale. It is why nothing in this product may hardcode a px size
   - a literal is a line this setting can never reach. */
[data-font-family="large-reader"] {
  --fs-xs: 14px !important;
  --fs-sm: 16px !important;
  --fs-base: 18px !important;
  --fs-md: 20px !important;
  --fs-lg: 24px !important;
  --fs-xl: 28px !important;
  --lh-normal: 1.7 !important;
}

/* HR-0358: the chat text size, set in My AI settings and read by
   styles/chatTextSize.ts. Deliberately picks WHICH `--fs-*` token `--chat-fs`
   points at rather than setting a size directly, so it composes with "large
   reader" above instead of fighting it: that setting decides what each token
   IS WORTH, this one decides which token the chat uses. No `!important`
   needed — `--chat-fs` is only ever set at :root, so the attribute rule wins
   the ordinary cascade, and whatever large-reader has done to `--fs-base` /
   `--fs-md` / `--fs-lg` is what these `var()`s resolve to either way. */
[data-chat-text-size="small"] { --chat-fs: var(--fs-base); }
[data-chat-text-size="large"] { --chat-fs: var(--fs-lg); --chat-lh: 1.65; }

/* Custom Smooth Scrollbar */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: var(--color-border-strong);
  border-radius: 9999px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--color-violet);
}

/* Interactive Elements Base Micro-Animations */
button, input, select, textarea {
  font-family: inherit;
}

button {
  transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}

button:active {
  transform: scale(0.98);
}

/* Eliminate focus outlines in inputs, textareas, and chatboxes */
*:focus {
  outline: none !important;
}

*:focus-visible {
  outline: none !important;
}

input:focus, textarea:focus, select:focus,
input:focus-visible, textarea:focus-visible, select:focus-visible {
  outline: none !important;
  box-shadow: none !important;
}

button:focus-visible {
  outline: 2px solid var(--color-violet) !important;
  outline-offset: 2px !important;
}

/* ============================================================
   EVERY TABLE'S HEADER ROW STAYS PUT WHILE ITS ROWS SCROLL
   (HR-0441). Darren: "all tables headers should always freeze on
   scroll."

   Here and not in each screen's module because there are forty
   tables in thirty files, and a per-screen rule is forty rules and
   a forty-first table without one. `thead th` rather than `thead`:
   sticky on the cell works in every engine, sticky on the row
   group does not in older Safari.

   THE BACKGROUND IS WHAT MAKES IT WORK AT ALL. A header cell is
   transparent by default, so a sticky one draws its label on top
   of whichever row is passing underneath and reads as a collision.
   `--color-surface` because nearly every table sits on a card; a
   screen whose table sits on something else sets
   `--hg-table-head-bg` on its wrapper. A module that already paints
   its header a colour of its own keeps it: its rule is more
   specific than this one.

   THE LINE UNDER THE HEADER IS A SHADOW, NOT THE BORDER. With
   `border-collapse: collapse` the border belongs to the table
   grid, not to the cell, and Chromium leaves it behind at the
   header's resting place while the cell itself slides down. The
   inset shadow is painted by the cell, so it travels with it.

   WHERE IT STICKS. `top: 0` of the nearest box that scrolls. For
   most tables that is their own `overflow-x: auto` wrapper, which
   each screen gives `max-height: var(--hg-table-max-h)` so it
   scrolls, and so has a top edge to stick to (lib/scrollport.ts
   has why the height is measured rather than guessed). A table
   with no wrapper sticks to `main`, and nothing pinned lives
   inside `main` any more: the save bar and the top bar are both
   outside it (`.pageChrome`), so `top: 0` is already underneath
   them.
   ============================================================ */
:root {
  --hg-table-max-h: max(240px, calc(var(--hg-scrollport-h, 70dvh) - var(--space-6)));
}
thead th,
thead td {
  position: sticky;
  /* 0 almost everywhere. Two exceptions, both set from code:
     `--hg-table-head-top`, for a table wider than its box while the page is
     scrolled past its top (lib/tableFit.ts says how far, and why), and
     `--hg-sticky-above`, where a screen freezes something of its own at the
     top of the page and the header has to sit under it (the policy reader's
     row of clause links). */
  top: var(--hg-table-head-top, var(--hg-sticky-above, 0px));
  z-index: 3;
  background-color: var(--hg-table-head-bg, var(--color-surface));
  box-shadow: inset 0 -1px 0 var(--color-border);
}
/* A row reached with Tab lands BELOW the frozen header, not under
   it (WCAG 2.2, 2.4.11 Focus Not Obscured). Only a box that scrolls
   reads scroll-padding, so this is inert on a wrapper that does not. */
:where(:has(> table > thead)) { scroll-padding-top: calc(var(--fs-sm) * 3.5); }

/* A wrapper whose table fits across it stops being a scrolling box, so its
   header freezes to the PAGE while the page scrolls (lib/tableFit.ts sets
   the mark and says why). `clip` keeps the rounded corners; the height is
   released because nothing inside needs to scroll. Same specificity as the
   screens' own wrapper classes, and this sheet is last in the cascade
   (vite.config.ts), so it wins without `!important`. */
[data-hg-table-fits] {
  overflow: clip;
  max-height: none;
}

/* ============================================================
   EVERY DROP-DOWN HAS THE SAME ARROW, WITH ROOM AROUND IT
   (HR-0445). Darren, on Roles and job profiles: the "All
   departments" and "All levels" arrows were squashed against the
   border. Each screen styled its own <select> and left the arrow
   to the browser, which draws it inside the padding box: a screen
   with `padding: 0 12px` got the arrow at 4px from a round border.
   A few screens drew their own SVG arrow at their own distance,
   so four arrows were in use across 146 drop-downs.

   One arrow, here, for all of them. The browser's own is switched
   off and a chevron is painted in the select's own text colour
   (`currentColor` inside two gradients, because a background image
   cannot read a token and a data-URI SVG cannot follow dark mode),
   16px in from the edge, with the text stopping 36px from it: the
   arrow keeps the same distance from the right edge as the text
   keeps from the left one, which is what makes it look placed
   rather than pushed.

   The selector outranks a screen's one-class rule on purpose, so a
   module's `background:` shorthand cannot wipe the arrow and its
   `padding: 0 12px` cannot pull the text over it. Colour, border
   and radius stay the screen's. A list box (`multiple`, `size`)
   draws no arrow and is left alone. */
select:not([multiple]):not([size]) {
  -webkit-appearance: none;
  appearance: none;
  padding-right: calc(var(--space-8) + var(--space-1));
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position:
    right calc(var(--space-4) + 5px) center,
    right var(--space-4) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
/* In Windows high contrast the page's backgrounds are dropped, arrow
   and all, so the browser's own arrow comes back there. */
@media (forced-colors: active) {
  select:not([multiple]):not([size]) { appearance: auto; background-image: none; }
}

/* ============================================================
   ON PAPER. LAST IN THE FILE, and that is the point: at equal
   specificity the later rule wins, so a print override written
   above the rule it overrides is a silent no-op.

   What this block owns is the SHELL, and nothing else. A screen
   that is printed decides for itself which of its own parts go
   on paper (see the print block at the end of MoneyOut's
   module); what no screen can reach from its own CSS module is
   the rail, the nav panel, the header and the assistant dock,
   because those are hashed class names in another file.

   THE SCROLLPORT HAS TO BE UNDONE OR ONLY THE FIRST PAGE
   PRINTS. `main` is the scrolling box in this shell, and a
   print of an `overflow: auto` element is a print of the part
   you can see. Height auto and overflow visible, all the way up
   from `main` to `html`, is what puts the rest of the report on
   page two.
   ============================================================ */
@media print {
  html, body {
    height: auto !important;
    overflow: visible !important;
    background: var(--color-surface) !important;
  }

  /* The rail, the nav panel, the workspace header and the assistant.
     Addressed by their landmark roles and labels rather than by class,
     because the class names are hashed by the module system. */
  aside[aria-label="Primary"],
  aside[aria-label="hr goat assistant"],
  [role="navigation"],
  [data-shell="col"] > header,
  [data-print-hide] {
    display: none !important;
  }

  [data-shell="col"],
  main {
    height: auto !important;
    max-height: none !important;
    overflow: visible !important;
    background: var(--color-surface) !important;
  }

  main { padding: 0 !important; }

  /* A table wrapper holds its rows to one screen so its header can
     freeze (HR-0441). On paper that prints one screen of rows and
     cuts the rest, so the height goes. */
  :root { --hg-table-max-h: none; }

  /* One save bar, and it is a control. Nothing anybody presses goes on
     paper, and a sticky bar prints on top of the first page of a report. */
  [role="status"][data-state] { display: none !important; }

  /* HR-0328 (Kelsey, printing a 1:1). THE SHELL HELD EVERY PRINT TO ONE
     SCREEN. Its root (`.app`, the element carrying data-active-cat) is
     `height: 100dvh; overflow: hidden` so the app never scrolls as a page,
     and on paper that clipped everything below the first screen: "not every
     KPI prints". Its --color-bg ground also printed as a grey band down the
     side the rail used to occupy. Neither is the screen's business, so the
     root lets go of both here, for every screen. The corner band and the
     dock after `main`, and the "Back to chat" button and the save-bar band
     above it, are controls or nudges and never belong on paper. */
  [data-active-cat] {
    display: block !important;
    height: auto !important;
    overflow: visible !important;
    background: var(--color-surface) !important;
  }
  [data-shell="col"] > main ~ *,
  [data-page-chrome],
  [data-back-to-chat] { display: none !important; }

  /* A screen that draws its own paper version marks it `data-print-sheet`
     (the 1:1 is the first). Then the page is plain black on white whatever
     theme the screen was in, via `color-scheme: light` and the system
     colours, and the sheet is the only thing in `main`: no breadcrumb, and
     no page header, since the sheet carries its own title. */
  html:has([data-print-sheet]) { color-scheme: light; background: Canvas !important; }
  html:has([data-print-sheet]) :is(body, [data-active-cat], [data-shell="col"], main) { background: Canvas !important; }
  main:has([data-print-sheet]) > :not(.hgPage),
  .hgPage:has([data-print-sheet]) > :first-child { display: none !important; }
  .hgPage:has([data-print-sheet]) > * { max-width: none !important; margin: 0 !important; }
}

/* ============================================================
   TAP TARGETS ON A PHONE — one rule, at the end of the file.

   Measured 2026-09-02 at 390px across nineteen admin screens:
   info triggers 16x16, a clear-search 14x14, a breadcrumb 44x18,
   "Save now" 93x31, a modal close 28x28, a sort button 84x27, the
   assistant's own header buttons 23x27. A finger is about 9mm
   across; below 44px it lands on whatever is beside the thing it
   was aimed at, which on a settings screen is somebody else's
   answer.

   It is HERE and not on each screen because there were nineteen
   screens and one shell. A per-screen fix is nineteen fixes and a
   twentieth screen still wrong.

   AT THE END, deliberately. A media query above the rule it
   overrides is a silent no-op at equal specificity, and this file
   has cost a whole round to that before.

   WHY min-height/min-width AND NOT A PSEUDO-ELEMENT HIT BOX for
   everything: ::before and ::after are already carrying arrows,
   rules and badges all over this product, and a global rule that
   claims one of them silently deletes whatever was drawn there.
   Growing the box is visible and testable. The few controls that
   genuinely sit INSIDE a line of text - an info dot beside a
   heading - would break the line if the box grew, so those opt
   out with data-hit="inline" and carry their own pseudo hit box.

   The desk is untouched: nothing here applies above 720px.
   ============================================================ */
@media (max-width: 720px) {
  button,
  summary,
  select,
  [role="button"],
  [role="tab"],
  a[href] {
    min-height: 44px;
    min-width: 44px;
  }

  /* a[href] is in that list and is safe there, which is not obvious:
     min-height and min-width DO NOT APPLY to a non-replaced inline
     element, so a link inside a sentence is untouched and a link that
     has been made a block, a flex row or an inline-block - a link
     acting as a button, which is what the rule is for - is not. The
     browser draws the distinction the stylesheet cannot express. */

  /* A checkbox and a radio are the one control the browser draws
     itself: growing the box grows the tick, so a 44px checkbox is a
     44px checkbox rather than a small one with room around it, and
     they cannot carry a pseudo-element hit box because they have no
     content box to hang one on. 24px is what can honestly be had here
     - roughly double what Chrome draws - and it is short of 44. Said
     out loud rather than quietly asserted away. */
  input[type="checkbox"],
  input[type="radio"] {
    min-height: 24px;
    min-width: 24px;
  }

  /* A TEXT FIELD IS THE SAME HEIGHT AS THE SELECT BESIDE IT.

     `select` is in the list above and `input` was not, so a select came out at
     44px and a text field at 40, and two fields sharing a row were 2px out of
     line with each other - measured on My AI, "What should hr goat call you?"
     against "Pronouns". It is also a tap target in its own right: a finger
     lands in a text field to type in it.

     Tick boxes and radios keep their own rule above; a file input is usually
     the visually hidden half of a button and must not be given a height that
     takes it back into the layout. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]) {
    min-height: 44px;
  }

  /* INSIDE A ZOOMABLE CANVAS, THE BOX IS NOT THE SIZE ANYWAY.

     The org chart and the skills tree draw their nodes on a surface
     they pan and scale with an inline transform, and the controls on a
     node are positioned absolutely against its corners - "..." at
     top: -4px; right: -4px, the collapse pill centred on the bottom
     edge. Growing those boxes does not give a finger more room, it
     moves the control: measured at 390 the quick-view button went from
     22px to 44px anchored at its top right, so it grew down and left
     across the face of the person it belongs to. And whatever CSS size
     they are given, what a finger meets is that size times the current
     zoom, so 44 is not 44 here in the first place.

     Matched on the inline transform because that IS the condition -
     "this subtree is scaled" - rather than on a class name from one
     screen. Both canvases are named above so the next one is easy to
     find. These controls are still under the floor and that is real;
     it needs the chart's own layout to change, not a size on a button.

     Selector note: the exclusion has to be more specific than the rule
     above, and a descendant selector already is. */
  [style*="scale("] button,
  [style*="scale("] [role="button"],
  [style*="scale("] summary {
    min-height: 0;
    min-width: 0;
  }

  /* Inside a line of text. Growing the box would set the line height
     to 44px and push the sentence apart, so the box stays and an
     absolutely positioned hit area reaches past it. The element must
     be positioned for this to land, and it must not already be
     drawing something in ::after. */
  [data-hit="inline"] {
    min-height: 0;
    min-width: 0;
    position: relative;
  }
  /* calc(50% - 22px) is a NEGATIVE inset on anything under 44px, and
     percentages here resolve against the trigger's own box, so a 16px
     dot and a 20px one both end up with exactly 44px of reach without
     either of them being told its own size. Written as insets rather
     than as width + translate so the number is readable straight out
     of getComputedStyle: a hit box a test cannot measure is a hit box
     nobody will notice losing. */
  [data-hit="inline"]::after {
    content: '';
    position: absolute;
    inset: calc(50% - 22px);
  }
}

/* THE BOX A LINK WAS ABOUT, MARKED FOR A MOMENT. lib/fieldFocus.ts sets this
   on the one input a Needs you card, an assistant button or a `?field=` link
   named, and takes it off again after a few seconds. An attribute rather than
   a class so it reaches a box on any screen without that screen's CSS module
   knowing about it. Violet because it is hr goat pointing at something, and a
   ring rather than a fill so the value inside stays exactly as legible as it
   was. */
/* !important, AND MORE SPECIFIC THAN THE RULE ABOVE THAT REMOVES EVERY INPUT
   OUTLINE. `input:focus { outline: none !important }` earlier in this file is
   (0,1,1), which beats a bare attribute even with !important on both, so the
   first build set the attribute and drew nothing (measured: outline-style
   none, on My tax and My profile, 2026-09-15). The element is focused on
   purpose here, so the selector says so. It lasts 2.6 seconds and is removed
   by lib/fieldFocus.ts. */
[data-field-focus="on"],
:is(input, textarea, select)[data-field-focus="on"]:focus,
:is(input, textarea, select)[data-field-focus="on"]:focus-visible {
  outline: 3px solid var(--color-violet) !important;
  outline-offset: 3px !important;
  box-shadow: 0 0 0 8px color-mix(in srgb, var(--color-violet) 18%, transparent) !important;
  transition: box-shadow 0.4s ease;
}
@media (prefers-reduced-motion: reduce) {
  [data-field-focus="on"] { transition: none; }
}

/* THE FADE BETWEEN YOU AND ADMIN (HR-0248), started by lib/modeFade.ts.
   Darren: "can you fade it rather than a sudden jolt?" The whole page
   cross-fades over 280ms, a little slower than the browser's default 250 so
   it reads as a fade rather than a flicker, and eased out so the new menu
   settles rather than stops.

   The switch is its own layer (`view-transition-name: hg-mode-switch` in
   ModeSwitch.module.css) and is shown LIVE: the old picture of it is dropped
   and the new one is not faded, so what a person sees is the thumb sliding to
   the side they pressed, not two thumbs dissolving into each other.

   Nothing moves for somebody who asked for less motion; modeFade.ts does not
   start a transition at all then, and this is the belt to that. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 280ms;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
}
::view-transition-old(hg-mode-switch) { display: none; }
::view-transition-new(hg-mode-switch) { animation: none; }
::view-transition-group(hg-mode-switch) { animation-duration: 0s; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
