/* Service Stream corporate identity for the LISA Enterprise GIS Platform
   gallery. Palette sampled from servicestream-hires.png and contrast
   verified — see .superpowers/sdd/2026-08-03-lisa-apps-directory/
   design-brief-servicestream.md. Values are not to be substituted.
   The client specified a white page throughout, so there is deliberately
   no prefers-color-scheme: dark block. */
:root {
  --page-max: 1200px;
  --measure: 62ch;
  --radius: 0;

  /* Brand — non-text only. Never used as a text colour; see the rule below. */
  --ss-cyan: #25ADC1;
  --ss-teal: #40B1B1;
  --ss-green: #54B5A1;

  /* Text-safe brand colours. */
  --ss-interactive: #197683;
  --ss-interactive-hover: #176B78;

  --text-primary: #111010;
  --text-secondary: #605E5C;
  --hairline: #E1E1E1;
  --surface: #FFFFFF;
  --canvas: #FFFFFF;
  /* Fix round 1 (coordinator review): the plate now rests at --surface
     (white), matching the reference page where a logo sits directly on the
     card rather than inside a visibly tinted box. --plate-tint-hover is the
     only remaining tint — a very slight deepening on hover/focus, per the
     brief's "no elevation, no lift" restraint. The base --plate-tint value
     is retired; nothing references it any more. */
  --plate-tint-hover: #EDF3F4;

  --focus-ring: var(--ss-interactive);
}

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  padding: 0 0 4rem;
  background: var(--canvas);
  color: var(--text-primary);
  font-family: "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 1rem;
  line-height: 1.55;
}

.page { max-width: 1200px; margin-inline: auto; padding-inline: 1rem; }

.skip-link {
  position: absolute; left: -9999px; top: 0;
  background: var(--surface); color: var(--text-primary);
  padding: 0.75rem 1rem; z-index: 10; border: 1px solid var(--hairline);
}
.skip-link:focus { left: 1rem; top: 1rem; }

/* ------------------------------------------------------------- page chrome */

.site-header { background: var(--surface); }
.site-header__bar {
  max-width: 1200px; margin-inline: auto; padding: 0.85rem 1rem;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  border-bottom: 1px solid var(--hairline);
}
.site-header__logo { display: block; height: 28px; width: auto; }
.site-header__right { display: flex; align-items: center; gap: 1rem; }
.site-header__product {
  font-size: 0.9375rem; font-weight: 600; letter-spacing: 0.08em;
  color: var(--text-secondary); text-transform: uppercase;
}
.site-header__rule { height: 3px; background: var(--ss-cyan); }

/* Help control — task-20-help-brief.md. Client-selected outlined treatment
   with a question-mark icon, deliberately not a filled/primary button
   style — this page has no single primary call-to-action for that filled
   treatment to be reserved for. Built and inserted by public.js's
   buildHelpButton only when helpUrl is a valid non-empty value — never
   present in the static markup, so there is no disabled/dead control to
   hide. */
.help-button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;            /* real hit target, not just visual height */
  padding: 8px 16px;
  border: 1px solid var(--hairline);   /* #E1E1E1 */
  border-radius: 2px;                  /* square-cornered, per the identity */
  background: var(--surface);
  color: var(--ss-interactive);        /* #197683 — 5.30:1 on white */
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
}
.help-button:hover {
  border-color: var(--ss-cyan);        /* #25ADC1 */
  background: #F2FAFB;                 /* pale brand tint */
  text-decoration: underline;
}
.help-button__icon { width: 16px; height: 16px; flex-shrink: 0; }

.platform-title {
  font-size: 1.875rem;  /* 30px */
  font-weight: 300;
  color: var(--text-primary);
  margin: 1.75rem 0 1.25rem;
}

/* Platform-home introduction, directly under the h1 — plain body text, not
   a bordered callout. This page is a welcome/landing surface, not an alert
   needing visual separation, so it deliberately does not reuse the
   left-accent card treatment .help-block-note uses for actual callouts.
   The 2.5rem bottom margin is carried over from that earlier callout's own
   bottom margin, so the gap above "Applications" reads as deliberate. */
.intro {
  max-width: var(--measure);
  color: var(--text-secondary);
  font-size: 1rem;
  margin: 0 0 2.5rem;
}

/* -------------------------------------------------------------- directory */

/* Fix round 1 (coordinator review): "Select an application to continue."
   (directory__description) now sits directly under the "Applications"
   heading it belongs to, and the result count sits directly under the
   search field instead of floating top-right, vertically detached from it. */
.directory__header {
  display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between;
  gap: 0.75rem 1.5rem;
  margin: 0 0 1.25rem;
}
.directory__intro { min-width: 0; }
.directory__header h2 {
  margin: 0; font-size: 1.25rem; font-weight: 600; color: var(--text-primary);
}
.directory__description { color: var(--text-secondary); font-size: 0.875rem; max-width: var(--measure); margin: 0.3rem 0 0; }
.directory__controls { display: flex; flex-direction: column; align-items: flex-start; gap: 0.4rem; }

/* :not([hidden]) rather than a bare .controls { display: flex } — see the
   matching bug trace in admin.css above .admin__identity. #controls starts
   `hidden` in index.html until public.js decides whether to show search
   and/or category filters; an unscoped `display: flex` here has the same
   specificity as the UA's `[hidden] { display: none }` and, as an author
   rule, would win regardless of source order, defeating the hidden state. */
.controls:not([hidden]) { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; }
.controls__label { display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.8125rem; color: var(--text-primary); }
.controls fieldset { border: 0; margin: 0; padding: 0; }
.controls__categories { margin-bottom: 1.25rem; }
#search {
  min-width: min(280px, 100%); padding: 0.55rem 0.75rem;
  border: 1px solid var(--hairline); border-radius: var(--radius);
  background: var(--surface); color: var(--text-primary); font: inherit; font-size: 0.875rem;
}
#search:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }

.chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.chip {
  padding: 0.4rem 0.85rem; border: 1px solid var(--hairline);
  border-radius: var(--radius); background: var(--surface); color: var(--text-primary);
  font: inherit; font-size: 0.8125rem; cursor: pointer;
  transition: border-color 150ms ease, background-color 150ms ease, color 150ms ease;
}
.chip:hover { border-color: var(--ss-interactive); }
.chip[aria-pressed="true"] { background: var(--ss-interactive); color: #FFFFFF; border-color: var(--ss-interactive); }

.result-count { color: var(--text-secondary); font-size: 0.8125rem; margin: 0; white-space: nowrap; }

/* --------------------------------------------------------------- app grid */

.grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 1.25rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  /* Without this, Grid only stretches a row's height to match its tallest
     sibling *within that row*. When the tile count isn't a multiple of the
     column count, the final row has no siblings to stretch against and
     renders at its own (shorter) intrinsic height — visible here as the
     ninth tile at 225px against 252px for the rest. grid-auto-rows: 1fr
     makes every row an equal fraction of the grid's own block size instead,
     so row three matches rows one and two regardless of how many tiles it
     holds. This is a size for the *row track*, not the card: a card with a
     much longer description still just wraps inside that row height (the
     title/description column already handles overflow via .card__description's
     line-clamp), it does not force the row taller. */
  grid-auto-rows: 1fr;
}

/* The plate normalises wildly different source-logo sizes into one fixed
   presentation. No elevation, no hover lift — the reference page has no
   shadows. On hover/focus only the border tints cyan and the plate deepens
   slightly; that restraint is the identity. */
.card {
  display: flex; flex-direction: column; height: 100%;
  text-decoration: none; color: inherit;
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius);
  transition: border-color 150ms ease;
}
.card:hover, .card:focus-visible { border-color: var(--ss-cyan); }
.card:hover .card__plate, .card:focus-visible .card__plate { background: var(--plate-tint-hover); }

/* Fix round 1 (coordinator review): plate height raised 104px -> 132px and
   the image is now constrained primarily by WIDTH (max-width: 90%) with a
   generous height ceiling (104px, the plate's old total height), so a wide
   mark like Connect Sydney (726x432) actually fills most of the card
   instead of being squeezed down to whatever a tight height ceiling allows. */
.card__plate {
  height: 132px;
  display: grid;
  place-items: center;
  padding: 14px 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--hairline);
  transition: background-color 150ms ease;
}
.card__plate img {
  /* BUG (found while verifying screenshots for the original 104px plate,
     not present in unit tests): the design brief's own literal snippet used
     a percentage `max-height: 100%` here. .card__plate uses `place-items:
     center`, i.e. align-items: center on the grid item. A non-stretch
     align-self leaves the item's block size "auto" rather than stretching
     to the (definite) row height, and a percentage max-height cannot
     resolve against an auto/indefinite containing block — per spec it
     computes as none. The browser then falls through to plain width-based
     intrinsic scaling, ignoring the height cap entirely; the square
     Defence crest (542x542) and NSW LAHC (290x200) overflowed worst,
     spilling into the title text below. A percentage max-width is
     unaffected by this because .card__plate's own inline size is definite
     by ordinary block layout, not grid-area percentage resolution. The
     fix carries forward here: max-height below is an absolute px length,
     not a percentage, so it needs no containing-block resolution at all
     and holds regardless of place-items. Verified against both the widest
     source (Connect Sydney, 726x432) and the two most overflow-prone
     sources (Defence 542x542 square, Perth Metro 522x342) — none clip. */
  max-width: 90%;
  max-height: 104px;
  width: auto;
  height: auto;
  object-fit: contain; /* never crop or distort a client logo */
}
.card__plate .card__icon { width: 2.25rem; height: 2.25rem; color: var(--text-secondary); }

.card__body { display: flex; flex-direction: column; flex: 1; gap: 0.4rem; padding: 1rem 1.1rem 1.1rem; }
.card__title { margin: 0; font-size: 1rem; font-weight: 600; color: var(--text-primary); }
.card__description {
  margin: 0; color: var(--text-secondary); font-size: 0.8125rem;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.card__meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem;
  margin-top: auto; padding-top: 0.6rem;
}
.card__tag {
  font-size: 0.6875rem;   /* 11px */
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.card__tag + .card__tag::before { content: '\2022'; margin-right: 0.4em; color: var(--hairline); }
.card__external { margin-left: auto; color: var(--ss-interactive); font-size: 0.9375rem; line-height: 1; }

.state { padding: 1.25rem; background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--radius); color: var(--text-secondary); }

/* ------------------------------------------------------------- help page */

.back-link { display: inline-block; margin: 1.5rem 0 0.5rem; color: var(--ss-interactive); font-size: 0.875rem; }
.back-link:hover { color: var(--ss-interactive-hover); }

/* Help blocks (task 21 — admin-editable help page). Styled on each block's
   own class rather than nested under a page-specific ancestor selector, so
   the same rules render identically wherever help-render.js's
   renderHelpBlocks() is used: the public /help/ page and the admin GUI's
   live preview, which does not share /help/'s surrounding markup. This
   replaces the former h2.help-part-heading / h3 / h4 tiers: the block
   model has exactly one heading level (`heading` -> <h2>), so every
   heading block renders at this one weight — see the conversion notes at
   the top of shared/seed-help.mjs for why. */
.help-block-heading {
  font-size: 1.25rem; font-weight: 600; color: var(--text-primary);
  margin: 2rem 0 0.6rem;
}
.help-block-heading:first-child { margin-top: 0; }

/* Text content keeps the readable 62ch measure; figures (below) are
   deliberately excluded from this so instructional screenshots — dense
   with small ribbon labels — can render closer to their natural size. */
.help-block-text, .help-block-list {
  max-width: var(--measure); color: var(--text-secondary); font-size: 0.9375rem;
  margin: 0 0 0.75rem;
}
.help-block-list { padding-left: 1.25rem; }
.help-block-list li { margin-bottom: 0.35rem; }

/* `note` block — a callout box, distinguished from a plain paragraph by a
   left accent border. */
.help-block-note {
  max-width: var(--measure); background: var(--surface); border: 1px solid var(--hairline);
  border-left: 4px solid var(--ss-interactive); padding: 1rem 1.25rem; margin: 0 0 1.25rem;
}
.help-block-note p { margin: 0; color: var(--text-secondary); font-size: 0.9375rem; }

/* Screenshots from the LISA 2.0 User Guide. No width:100% here — the
   img keeps its authored (intrinsic) size up to the column width, so a
   655px-wide capture is not stretched/blurred to fill a wider column;
   max-width + height:auto only ever shrinks it down for narrow viewports.
   The `image` block schema has no caption field, so there is no
   <figcaption> here any more — the alt text is the only description an
   administrator can attach to a guide image. */
.help-figure { margin: 0 0 1.5rem; max-width: 100%; }
.help-figure img { display: block; max-width: 100%; height: auto; border: 1px solid var(--hairline); }

/* In-page contents list — task-20-help-brief follow-up ("the page is
   long now"), generated by help-render.js's buildToc() from the heading
   blocks. A single flat <ol> now (task 21): the block model has one
   heading level, so there is no Part -> topic nesting left to mirror. */
.help-toc { border: 1px solid var(--hairline); background: var(--surface); padding: 1rem 1.25rem; margin: 0 0 2rem; max-width: var(--measure); }
.help-toc p { margin: 0 0 0.5rem; font-weight: 600; color: var(--text-primary); font-size: 0.875rem; }
.help-toc ol { list-style: decimal; margin: 0; padding-left: 1.25rem; font-size: 0.875rem; }
.help-toc li { margin-bottom: 0.35rem; }
.help-toc a { color: var(--ss-interactive); }
.help-toc a:hover { color: var(--ss-interactive-hover); }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important; scroll-behavior: auto !important;
  }
}
