/*
  Gestalt admin visual language — shared values (draft 0.43, 2026-10-29)

  One set of colors, type, spacing, corners, shadows and focus for every Gestalt admin and front end, and the
  plugins that run inside them. Publisher's and design-system's public front ends are themeable and follow their own themes. Projects keep their own components; they only point their own variables at these.

  Every name starts with --g- so a host app's own variables (--text, --brand, ...) can never overwrite these,
  and these never overwrite a host's. Plugins still read plain host names: a host that runs the Gestalt
  plugins also includes gestalt-plugin-bridge.css.

  Modes: follow the system setting by default. The person's own choice wins: data-admin-theme="light" or "dark"
  (Publisher and design-system admins), or data-theme="light" or "dark", on the root element. With no attribute and
  no system preference, dark applies.

  Loading: the --g-skeleton and --g-spinner values, plus the small .g-skeleton, .g-spinner and .g-loading classes at the
  end of this file, are the one shared pattern every project uses while content arrives.

  Scrollbars: the .g-slim-scroll class at the end of this file gives scrolling areas inside a page (sidebars, lists,
  panels) a slim, quiet scrollbar. The page's own scrollbar keeps the browser default.

  Spec and open decisions: "Gestalt Admin Visual Language" doc in the Gestalt Visual Language project.
*/

:root {
  color-scheme: dark;

  /* Surfaces */
  --g-bg: #0f1115;
  --g-surface: #171a21;
  --g-surface-2: #2c303a;

  /* Lines */
  --g-line: #262a33;
  --g-line-strong: #626875;  /* 3.1:1 on surface: WCAG minimum for control outlines */
  --g-control-border: rgba(255, 255, 255, 0.35);
  --g-input-border: rgba(255, 255, 255, 0.4);

  /* Text */
  --g-text: #e8e8ea;
  --g-muted: #a7adba;
  --g-faint: #6b7280;

  /* Accent */
  --g-brand: #ff4d1c;
  --g-brand-fill: #d63a0c;
  --g-brand-hover: #b8310a;
  --g-brand-contrast: #ffffff;
  --g-brand-link: #ff8a66;
  --g-brand-tint: rgba(255, 77, 28, 0.16);

  /* Status */
  --g-danger: #ff7a7a;
  --g-danger-tint: rgba(255, 122, 122, 0.16);
  --g-danger-fill: #be123c;   /* filled danger button, crimson to stand apart from the orange: 6.3:1 under white */
  --g-danger-hover: #9f1239;
  --g-danger-contrast: #ffffff;
  --g-warning: #ffc24b;
  --g-warning-tint: rgba(255, 194, 75, 0.16);
  --g-success: #6fcf97;
  --g-success-tint: rgba(111, 207, 151, 0.16);
  --g-info: #8ba3f0;
  --g-info-tint: rgba(139, 163, 240, 0.16);

  /* Badges: small solid labels (a count, "Public", "Beta"). Status pills use the tints above instead. */
  --g-badge-success-bg: #163a2b;  --g-badge-success-text: #5fd38a;
  --g-badge-warning-bg: #4a3a05;  --g-badge-warning-text: #ffd24d;
  --g-badge-danger-bg: #4a1c1c;   --g-badge-danger-text: #ff9a9a;
  --g-badge-info-bg: #14305c;     --g-badge-info-text: #7fb2ff;
  --g-badge-neutral-bg: #2c3238;  --g-badge-neutral-text: #c4ccd4;
  --g-badge-brand-bg: var(--g-brand-fill); --g-badge-brand-text: var(--g-brand-contrast);

  /* Interaction */
  --g-hover-tint: rgba(255, 255, 255, 0.06);
  --g-focus: var(--g-text);

  /* Loading: grey pulsing placeholders shaped like the content; an orange ring for small in-place waits */
  --g-skeleton: #2c303a;
  --g-loading-delay: 300ms;   /* nothing shows for fast loads */
  --g-pulse-duration: 1.6s;
  --g-spinner-s: 16px;        /* inside a button */
  --g-spinner-m: 24px;        /* a panel or section */
  --g-spinner-stroke: 2px;

  /* Slim scrollbars (inside the page only) */
  --g-scrollbar-thumb: var(--g-line-strong);   /* 3:1 on surface, so it is still found at rest */
  --g-scrollbar-thumb-hover: var(--g-muted);   /* pointer over the scrolling area */

  /* Type */
  --g-font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* Headings. Inter by default; the Portal (gestalt-system-project) sets Space Grotesk here for its headings. */
  --g-font-display: var(--g-font);
  --g-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --g-text-xs: 11px;  --g-lh-xs: 16px;  /* badges, overlines */
  --g-text-s: 12px;   --g-lh-s: 16px;   /* buttons, helper text */
  --g-text-m: 14px;   --g-lh-m: 20px;   /* default body text */
  --g-text-l: 16px;   --g-lh-l: 24px;   /* card and section titles */
  --g-text-xl: 18px;  --g-lh-xl: 26px;  /* panel headings */
  --g-text-2xl: 20px; --g-lh-2xl: 28px; /* dialog titles */
  --g-text-3xl: 24px; --g-lh-3xl: 32px; /* page title */
  /* Display: front-end pages only (landing and sign-in heroes), in --g-font-display at --g-weight-title. */
  --g-text-4xl: 32px; --g-lh-4xl: 40px; /* section headline */
  --g-text-5xl: 48px; --g-lh-5xl: 56px; /* hero headline */
  --g-weight-regular: 400;
  --g-weight-strong: 600;
  --g-weight-title: 700;

  /* Spacing (4px grid) */
  --g-space-1: 4px;
  --g-space-2: 8px;
  --g-space-3: 12px;
  --g-space-4: 16px;
  --g-space-5: 20px;
  --g-space-6: 24px;
  --g-space-8: 32px;
  --g-space-10: 40px;

  /* Control heights */
  --g-control-s: 28px;
  --g-control-m: 32px;
  --g-control-l: 40px;
  --g-control-xl: 44px;  /* touch: phone dialogs and sign-in forms */

  /* Corners */
  --g-radius-s: 4px;
  --g-radius-m: 8px;
  --g-radius-l: 12px;
  --g-radius-pill: 999px;
  --g-btn-radius: var(--g-radius-m);

  /* Elevation */
  --g-shadow-1: 0 8px 24px rgba(0, 0, 0, 0.45);
  --g-shadow-2: 0 16px 48px rgba(0, 0, 0, 0.55);
}

:root[data-admin-theme="light"],
:root[data-theme="light"] {
  color-scheme: light;

  --g-bg: #f3f4f7;
  --g-surface: #ffffff;
  --g-surface-2: #e6e9ef;

  --g-line: #dfe2e8;
  --g-line-strong: #868b96;  /* 3.4:1 on surface */
  --g-control-border: #949494;
  --g-input-border: #8a8a8a;

  --g-text: #16181d;
  --g-muted: #565d6b;
  --g-faint: #646b79;

  --g-brand: #d63a0c;
  --g-brand-fill: #d63a0c;
  --g-brand-hover: #b8310a;
  --g-brand-contrast: #ffffff;
  --g-brand-link: #c93509;
  --g-brand-tint: rgba(201, 53, 9, 0.1);

  --g-danger: #c62828;
  --g-danger-tint: rgba(198, 40, 40, 0.1);
  --g-danger-fill: #be123c;   /* filled danger button, crimson to stand apart from the orange: 6.3:1 under white */
  --g-danger-hover: #9f1239;
  --g-danger-contrast: #ffffff;
  --g-warning: #8a5a00;
  --g-warning-tint: rgba(230, 145, 10, 0.14);
  --g-success: #1b7a43;
  --g-success-tint: rgba(27, 122, 67, 0.1);
  --g-info: #2447c4;
  --g-info-tint: rgba(36, 71, 196, 0.1);

  --g-badge-success-bg: #dff3e8;  --g-badge-success-text: #17683a;
  --g-badge-warning-bg: #ffe9a8;  --g-badge-warning-text: #6b4a00;
  --g-badge-danger-bg: #fde2e2;   --g-badge-danger-text: #a3201f;
  --g-badge-info-bg: #dce9ff;     --g-badge-info-text: #1a4fb5;
  --g-badge-neutral-bg: #e3e7eb;  --g-badge-neutral-text: #3f4852;

  --g-hover-tint: rgba(22, 24, 29, 0.05);

  --g-skeleton: #e3e6ec;

  --g-shadow-1: 0 8px 24px rgba(20, 24, 35, 0.14);
  --g-shadow-2: 0 16px 48px rgba(20, 24, 35, 0.18);
}

/* No choice made: follow the system. A data-admin-theme or data-theme of "dark" keeps dark. */
@media (prefers-color-scheme: light) {
  :root:not([data-admin-theme="dark"]):not([data-theme="dark"]) {
    color-scheme: light;

    --g-bg: #f3f4f7;
    --g-surface: #ffffff;
    --g-surface-2: #e6e9ef;

    --g-line: #dfe2e8;
    --g-line-strong: #868b96;  /* 3.4:1 on surface */
    --g-control-border: #949494;
    --g-input-border: #8a8a8a;

    --g-text: #16181d;
    --g-muted: #565d6b;
    --g-faint: #646b79;

    --g-brand: #d63a0c;
    --g-brand-fill: #d63a0c;
    --g-brand-hover: #b8310a;
    --g-brand-contrast: #ffffff;
    --g-brand-link: #c93509;
    --g-brand-tint: rgba(201, 53, 9, 0.1);

    --g-danger: #c62828;
    --g-danger-tint: rgba(198, 40, 40, 0.1);
    --g-danger-fill: #be123c;   /* filled danger button, crimson to stand apart from the orange: 6.3:1 under white */
    --g-danger-hover: #9f1239;
    --g-danger-contrast: #ffffff;
    --g-warning: #8a5a00;
    --g-warning-tint: rgba(230, 145, 10, 0.14);
    --g-success: #1b7a43;
    --g-success-tint: rgba(27, 122, 67, 0.1);
    --g-info: #2447c4;
    --g-info-tint: rgba(36, 71, 196, 0.1);

    --g-badge-success-bg: #dff3e8;  --g-badge-success-text: #17683a;
    --g-badge-warning-bg: #ffe9a8;  --g-badge-warning-text: #6b4a00;
    --g-badge-danger-bg: #fde2e2;   --g-badge-danger-text: #a3201f;
    --g-badge-info-bg: #dce9ff;     --g-badge-info-text: #1a4fb5;
    --g-badge-neutral-bg: #e3e7eb;  --g-badge-neutral-text: #3f4852;

    --g-hover-tint: rgba(22, 24, 29, 0.05);

    --g-skeleton: #e3e6ec;

    --g-shadow-1: 0 8px 24px rgba(20, 24, 35, 0.14);
    --g-shadow-2: 0 16px 48px rgba(20, 24, 35, 0.18);
  }
}

/*
  Loading pattern. Use a skeleton when the shape of what is coming is known (a page, list, table or card); use the spinner
  only for a small wait in place (a button saving, a panel refreshing). Both stay invisible for --g-loading-delay, so a
  fast load never flashes. Wrap them in .g-loading with role="status" and a .g-visually-hidden "Loading" so screen
  readers hear it once, and set aria-busy="true" on the region being filled.

    <div class="g-loading" role="status"><span class="g-visually-hidden">Loading</span>
      <span class="g-skeleton g-skeleton--title"></span>
      <span class="g-skeleton g-skeleton--line"></span>
      <span class="g-skeleton g-skeleton--line"></span>
    </div>
    <span class="g-spinner g-spinner--s" aria-hidden="true"></span>
*/
@keyframes g-loading-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes g-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
@keyframes g-spin { to { transform: rotate(360deg); } }

.g-loading { display: grid; gap: var(--g-space-3); }

.g-skeleton {
  display: block;
  min-height: var(--g-space-3);
  border-radius: var(--g-radius-s);
  background: var(--g-skeleton);
  animation:
    g-loading-in 0s linear var(--g-loading-delay) both,
    g-pulse var(--g-pulse-duration) ease-in-out var(--g-loading-delay) infinite;
}
.g-skeleton--line { height: var(--g-space-3); }
.g-skeleton--line:last-child:not(:first-child) { width: 60%; }
.g-skeleton--title { height: var(--g-space-5); width: 40%; }
.g-skeleton--circle { width: var(--g-control-m); height: var(--g-control-m); border-radius: var(--g-radius-pill); }
.g-skeleton--block { height: 120px; border-radius: var(--g-radius-m); }

.g-spinner {
  display: inline-block;
  flex: none;
  vertical-align: middle;
  width: var(--g-spinner-m);
  height: var(--g-spinner-m);
  border: var(--g-spinner-stroke) solid var(--g-brand-tint);
  border-top-color: var(--g-brand);
  border-radius: var(--g-radius-pill);
  animation:
    g-loading-in 0s linear var(--g-loading-delay) both,
    g-spin 0.8s linear var(--g-loading-delay) infinite;
}
.g-spinner--s { width: var(--g-spinner-s); height: var(--g-spinner-s); }
/* On an orange filled button the ring takes the button's text color instead. */
.g-spinner--on-fill { border-color: rgba(255, 255, 255, 0.35); border-top-color: var(--g-brand-contrast); }

.g-visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Reduce motion: skeletons hold still; the spinner keeps turning, slowly, because it is the only sign of the wait. */
@media (prefers-reduced-motion: reduce) {
  .g-skeleton { animation: g-loading-in 0s linear var(--g-loading-delay) both; }
  .g-spinner { animation: g-loading-in 0s linear var(--g-loading-delay) both, g-spin 2.4s linear var(--g-loading-delay) infinite; }
}

/*
  Slim scrollbars. Scrolling areas inside a page (a sidebar, a list, a panel, a dialog body) get a thin scrollbar: a
  rounded grey thumb on no track, which darkens while the pointer is over the area. The page's own scrollbar (the
  window, or the one element that scrolls the whole page) keeps the browser default.

  Put .g-slim-scroll on a wrapper (the app's root, or body): it and every scrolling area inside it go slim. Put
  .g-page-scroll on an element that scrolls the whole page so it keeps the default. Selectors are :where() so an app's
  own rule always wins. Chrome, Edge and Safari draw a 4px rounded thumb in an 8px gutter with no arrows
  (::-webkit-scrollbar); Firefox uses scrollbar-width: thin and scrollbar-color.

    <body class="g-slim-scroll"> ... <main class="g-page-scroll"> ... <ul class="list"> ...
*/
:where(.g-slim-scroll, .g-slim-scroll *):not(html, body, .g-page-scroll)::-webkit-scrollbar { width: 8px; height: 8px; }
:where(.g-slim-scroll, .g-slim-scroll *):not(html, body, .g-page-scroll)::-webkit-scrollbar-track,
:where(.g-slim-scroll, .g-slim-scroll *):not(html, body, .g-page-scroll)::-webkit-scrollbar-corner { background: transparent; }
:where(.g-slim-scroll, .g-slim-scroll *):not(html, body, .g-page-scroll)::-webkit-scrollbar-thumb {
  background: var(--g-scrollbar-thumb) padding-box;
  border: 2px solid transparent;
  border-radius: var(--g-radius-pill);
}
:where(.g-slim-scroll, .g-slim-scroll *):not(html, body, .g-page-scroll):hover::-webkit-scrollbar-thumb {
  background-color: var(--g-scrollbar-thumb-hover);
}
/* Firefox has no ::-webkit-scrollbar. (Chrome would ignore the rules above if these were set for it too.) */
@supports not selector(::-webkit-scrollbar) {
  :where(.g-slim-scroll, .g-slim-scroll *):not(html, body, .g-page-scroll) {
    scrollbar-width: thin;
    scrollbar-color: var(--g-scrollbar-thumb) transparent;
  }
  :where(.g-slim-scroll, .g-slim-scroll *):not(html, body, .g-page-scroll):hover {
    scrollbar-color: var(--g-scrollbar-thumb-hover) transparent;
  }
}

/*
  Update available. When a new version of the app has gone live while a tab is open, a small card in the bottom-left
  corner says so, with Refresh and Later. It never reloads by itself, never blocks the page, and waits for unsaved work
  to save before Refresh reloads. The app checks a small version file or endpoint (never cached) every 5 minutes while
  the tab is showing, when the person comes back to the tab, and when the connection comes back; it shows the card when
  the version it was built as differs. One card at a time; Later hides it until a newer version comes out.

  Lift it above a bar along the bottom of an app with --g-update-notice-bottom; on a phone it spans the width. Every
  part has its own flat .g- class (no element or nested selectors), so apps that copy only .g- classes can take it.

    <div class="g-update-notice" role="status" aria-live="polite">
      <span class="g-update-notice-icon" aria-hidden="true">(Material Symbols "browser_updated", 18px)</span>
      <div class="g-update-notice-text">
        <strong class="g-update-notice-title">Gestalt System PM has an update</strong>
        <span class="g-update-notice-line">Refresh to get it. It only takes a moment.</span>
      </div>
      <div class="g-update-notice-actions">
        <button type="button" class="g-update-notice-btn g-update-notice-later">Later</button>
        <button type="button" class="g-update-notice-btn g-update-notice-refresh">Refresh</button>
      </div>
    </div>
*/
@keyframes g-update-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

.g-update-notice {
  position: fixed;
  left: var(--g-space-4);
  bottom: calc(var(--g-update-notice-bottom, var(--g-space-4)) + env(safe-area-inset-bottom, 0px));
  z-index: 2147483000;  /* above an app's own panels, docks and toasts */
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--g-space-2) var(--g-space-3);
  width: min(360px, calc(100vw - 2 * var(--g-space-4)));
  box-sizing: border-box;
  padding: var(--g-space-3) var(--g-space-3) var(--g-space-3) var(--g-space-4);
  background: var(--g-surface);
  color: var(--g-text);
  border: 1px solid var(--g-line);
  border-radius: var(--g-radius-l);
  box-shadow: var(--g-shadow-2);
  font-family: var(--g-font);
  font-size: var(--g-text-s);
  line-height: var(--g-lh-s);
  text-align: left;
  animation: g-update-in 200ms ease-out both;
}
.g-update-notice-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-top: 1px;
  color: var(--g-brand);
  font-size: 18px !important;  /* a Material Symbols span: an app's own icon-font size must not win */
  line-height: 1;
}
.g-update-notice-text { display: grid; gap: 2px; min-width: 0; }
.g-update-notice-title { font-size: var(--g-text-m); line-height: var(--g-lh-m); font-weight: var(--g-weight-strong); }
.g-update-notice-line { color: var(--g-muted); }
.g-update-notice-actions {
  grid-column: 2;
  display: flex;
  justify-content: flex-end;
  gap: var(--g-space-2);
}
.g-update-notice-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--g-space-2);
  height: var(--g-control-s);
  padding: 0 var(--g-space-3);
  border: 1px solid transparent;
  border-radius: var(--g-btn-radius);
  font: inherit;
  font-weight: var(--g-weight-strong);
  cursor: pointer;
  white-space: nowrap;
}
.g-update-notice-btn:focus-visible { outline: 2px solid var(--g-focus); outline-offset: 2px; }
.g-update-notice-later { background: transparent; color: var(--g-text); }
.g-update-notice-later:hover { background: var(--g-hover-tint); }
.g-update-notice-refresh { background: var(--g-brand-fill); color: var(--g-brand-contrast); }
.g-update-notice-refresh:hover { background: var(--g-brand-hover); }

@media (max-width: 480px) {
  .g-update-notice { right: var(--g-space-4); width: auto; }
  .g-update-notice-btn { height: var(--g-control-l); }  /* a bigger target for a thumb */
}
@media (prefers-reduced-motion: reduce) {
  .g-update-notice { animation: none; }
}
