/*
// © Copyright 1990-2026 Toptronic® Ltd, all rights reserved; no part of
// this document may be reproduced, modified or stored in a retrieval system,
// or transmitted in any form or by any means, electronic, mechanical,
// photocopying, recording or otherwise, without the prior written permission
// of Toptronic Limited. For copyright inquiries: jacques@toptronic.com
 */
/*
 * PLWS :: Plazdiez Website - Session 0105 layer (glossary page presentation v3)
 * Project: Paul Lambourn Web Site (PLWS) - www.plazdiez.co.za
 * Generated/Updated on: 2026-09-17T16:10:25+10:00
 * Audience: Jacques (hardware/C bg), Ryan (14), Ava (9), Students (15-23),
 *           and the Master's-level reader who wants the "why".
 *
 * WHAT THIS FILE IS: the THIRD presentation layer of the Glossary page and
 * nothing else. It is linked on glossary.html AFTER css/styles_011.css, with
 * ?v=0105. It adds a new capability, so it is a NEW numbered file: /css/* is
 * served "immutable" for a year and a returning visitor keeps the old bytes.
 *
 * WHAT IS NEW IN 0105: an answer block above the table (.gl-about), an A-Z
 * jump strip (.gl-index), a second and lighter filter for the Customers Talks
 * table (.gl-talk-tools), the row that filter hides (.gl-talk-row-hidden),
 * its empty state (.gl-no-hits), the "Link copied" toast (.gl-toast), a
 * quieter pill for the two new categories, and three pieces of table polish.
 *
 * COLOURS: a colour is only valid for the surface it was chosen for.
 * --clr-steel (#8a94a6) is for DARK surfaces (3.06:1 on white fails AA);
 * --gl-muted (#5f6774, 5.71:1 on white) is for LIGHT surfaces;
 * --clr-brand (#e8a020) is 2.22:1 on white, so it may never carry text on a
 * light surface - on ink it is 8.18:1 and is used freely.
 * --clr-brand-deep (#8f5d00) is the light-surface brass: 5.62:1 on white.
 * Every pair below is checked against the surface it is painted on and the
 * ratio is written beside the rule.
 * -- Jacques: choose the colour for the surface, like a material spec.
 * -- Ava: dark words on light paper, light words on dark ink.
 * -- Ryan: contrast ratio = how far apart two colours are in brightness;
 *    4.5:1 is the minimum for ordinary text.
 *
 * HOUSE RULES OBEYED HERE: no @import, no external URL, no !important
 * outside the print block, every selector starts with .gl- (or is the
 * data-cat pill pair the contract names), no v2 rule re-declared without a
 * reason written next to it, and the file stays under 470 lines.
 */

/* ============ SECTION 0: TWO NEW LOCAL VALUES ============
   styles_010.css owns --gl-muted and --gl-rule; styles_011.css added five
   more. This layer adds only what it truly needs.
   --gl-head-shade is declared here AND registered with @property in
   SECTION 6: the plain declaration is what keeps the sticky header's
   box-shadow valid in an engine that ignores @property (an unknown var()
   with no fallback would drop the whole declaration, amber underline
   included). */
:root {
  --gl-tint-quiet: rgba(20, 22, 26, 0.04);  /* disabled letter face: ink on it 16.76:1 */
  --gl-head-shade: rgba(20, 22, 26, 0);     /* starts invisible: no shadow at rest */
}

/* ============ SECTION 1: THE ANSWER BLOCK (section.gl-about) ============
   The short plain answer to "what is this page?" sits above the table, where
   a reader - and a machine that extracts answers - looks first. It is a quiet
   white card with ONE amber rule down its left side, the same signature the
   .gl-nojs box already uses, so it reads as a note beside the glossary and
   never as a second hero.
   Contrast on white: the heading is --clr-ink (#14161a) 18.11:1, the text is
   --gl-ink-soft (#2b2f36) 13.44:1, and the caption grey is --gl-muted
   (#5f6774) 5.71:1. The 4px left rule is amber on white - 2.22:1, which is
   fine for this because it is a decorative edge and carries no information
   of its own: the heading beside it says what the block is (WCAG 1.4.11
   covers graphics that convey meaning; this one does not).
   -- Ava: a small white card that answers one question in one paragraph. */
.gl-about {
  max-width: var(--max-width, 1200px);
  margin: 0 auto var(--sp-lg);
  padding: var(--sp-md) var(--sp-lg);
  background: #fff;
  border: 1px solid var(--gl-rule);
  border-left: 4px solid var(--clr-brand);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}
/* The heading is deliberately small. An h2 the size of the hero title would
   make the page look twice-launched, which is exactly what the brief forbids.
   1.05rem is a single step above the body copy and no more. */
.gl-about h2 {
  margin: 0 0 var(--sp-xs);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--clr-ink);            /* 18.11:1 on white */
}
/* 70ch is the widest a comfortable line may run (the brief allows <= 70ch),
   so the block can never turn into a wall of text; 1.02rem makes this the
   lead line of the whole page's prose - slightly larger than the 1rem body
   copy around it, slightly smaller than the hero lead. */
.gl-about-text {
  max-width: 70ch;
  margin: 0;
  font-size: 1.02rem;
  line-height: 1.6;
  color: var(--gl-ink-soft);        /* 13.44:1 on white */
}

/* ============ SECTION 2: THE A-Z JUMP STRIP (div.gl-index) ============
   A row of small round letter buttons that jump to the first term beginning
   with that letter. It is deliberately NOT sticky: the terms toolbar already
   pins itself on a wide screen (styles_011.css SECTION 2), and a second
   pinned bar would eat the reader's viewport for no gain.
   -- Jacques: an index tab on the edge of the file, like a card index.
   -- Ryan: one round button per letter. A letter with nothing behind it is
      greyed out and cannot be pressed.
   MARKUP CONTRACT: js/glossary_004.js builds the buttons as
   <button class="gl-index-key" type="button"> inside span.gl-index-letters,
   sets aria-pressed="true" on the letter last jumped to, and sets disabled +
   aria-disabled="true" on a letter with no term behind it. This file only
   decides how those states look. No width, margin or padding is set on
   .gl-index itself: it takes the frame of whatever wrapper the markup gives
   it, so it cannot fight the page frame. */
.gl-index {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-sm);
  margin: 0 0 var(--sp-md);
}
.gl-index-label {
  flex: 0 0 auto;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gl-muted);           /* 5.71:1 on white, 5.34:1 on paper */
}
.gl-index-letters {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  min-width: 0;                     /* lets a long row shrink, never push the page wider */
}
/* 28x28px round keys: above the 24x24px WCAG 2.5.8 minimum, and a comfortable
   thumb target. Round on purpose - a square would read as another chip of the
   toolbar, and this strip is a different job. */
.gl-index-key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  min-width: 28px;
  height: 28px;
  padding: 0;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1;
  color: var(--clr-ink);            /* 18.11:1 on white */
  background: #fff;
  border: 1px solid rgba(20, 22, 26, 0.14);
  border-radius: 50%;
  cursor: pointer;
  transition: background-color var(--trans-fast), color var(--trans-fast),
              border-color var(--trans-fast);
}
/* Hover is gated behind (hover: hover), the same as the table rows in
   styles_011.css, so a touch screen does not keep a stuck highlight.
   Ink text on the amber wash is 16.85:1 (measured in styles_011.css). */
@media (hover: hover) {
  .gl-index-key:hover:not([disabled]) {
    background: var(--gl-tint-hover);
    border-color: var(--clr-brand-deep, #8f5d00);
  }
}
/* The keyboard ring is ink: amber on white is 2.22:1, which is not a visible
   focus indicator on a light surface. */
.gl-index-key:focus-visible { outline: 2px solid var(--clr-ink); outline-offset: 2px; }
/* The letter the reader just jumped to stays filled: paper on ink, 16.95:1.
   The short list of names accepts the attribute the contract promises plus
   the class names this codebase already uses elsewhere (see the .gl-top rule
   in styles_011.css), so a small difference in the script cannot leave the
   strip looking dead. */
.gl-index-key[aria-pressed="true"],
.gl-index-key.is-active,
.gl-index-key.is-on {
  color: var(--clr-paper);          /* 16.95:1 on ink */
  background: var(--clr-ink);
  border-color: var(--clr-ink);
}
/* A letter with no term behind it: dimmed AND inert. The colour is
   --gl-muted (5.71:1 on white) before the fade is applied; WCAG 1.4.3
   exempts disabled controls from the contrast minimum, and the button is
   truly disabled, so nothing on the page depends on reading it. */
.gl-index-key[disabled] {
  color: var(--gl-muted);
  background: var(--gl-tint-quiet);
  border-color: transparent;
  opacity: 0.55;
  cursor: default;
}

/* ============ SECTION 3: THE TALKS FILTER (div.gl-talk-tools) ============
   The same visual language as the terms toolbar (.gl-bar): white card,
   hairline, the same 44px controls. It is deliberately LIGHTER - no shadow,
   smaller padding - because it filters the second table only, and it must
   never look like the page's main control panel. It must also NEVER be
   sticky: the terms toolbar already owns that job on a wide screen.
   -- Students: one component, two weights; the weight difference IS the
      information hierarchy, not decoration. */
.gl-talk-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-sm) var(--sp-md);
  margin: 0 0 var(--sp-md);
  padding: var(--sp-sm) var(--sp-sm) var(--sp-sm) var(--sp-md);
  background: #fff;
  border: 1px solid var(--gl-rule);
  border-radius: var(--radius-md);
}
/* DELIBERATE OVERRIDE of one styles_011.css rule, and the only one in this
   file: .gl-search reserves 42px of left padding for the magnifier that
   .gl-search-row draws with ::before/::after. The talks field has no
   .gl-search-row around it, so nothing is drawn there and the gap would be a
   hole in front of the typed words - especially in a language whose
   placeholder is long. The field keeps every other v2 property. */
.gl-talk-tools .gl-search { padding-left: 14px; }
/* Adding a state to the v2 search/clear rules, not replacing them: the same
   ink ring the terms toolbar uses, so both filters feel like one family. */
.gl-talk-tools .gl-search:focus,
.gl-talk-tools .gl-search:focus-visible,
.gl-talk-tools .gl-clear:focus-visible {
  outline: 2px solid var(--clr-ink);
  outline-offset: 2px;
}

/* ============ SECTION 4: THE HIDDEN ROW AND THE EMPTY STATE ============
   A talks row the filter hides carries the class gl-talk-row-hidden. Two
   things matter here:
   1. The selector names .gl-talks and .gl-table as well as the class,
      because styles_010.css sets "display: block" on every .gl-table tr
      inside its phone block - one class plus one element outranks a single
      class, and without the extra weight a hidden row would reappear on a
      phone. Written this way there is no need for !important, which is not
      allowed outside the print block.
   2. The row is never removed from the DOM; the script toggles the class
      only. So the terms table is untouched by this rule, and Print
      (SECTION 9) has to keep the row hidden on paper. */
.gl-talks .gl-table tr.gl-talk-row-hidden { display: none; }
/* The empty state: one line inside section.gl-talks, holding the dictionary
   word for "none", created once and removed again by the script. The amber
   left rule repeats the page's signature for a remark and the words are
   --gl-muted (5.71:1 on white). */
.gl-no-hits {
  max-width: 70ch;
  margin: var(--sp-md) 0 0;
  padding: var(--sp-sm) var(--sp-md);
  border-left: 3px solid var(--clr-brand);
  color: var(--gl-muted);           /* 5.71:1 on white */
  font-size: 0.95rem;
}

/* ============ SECTION 5: THE COPY TOAST (div.gl-toast) ============
   The small "Link copied" confirmation js/glossary_004.js shows after the
   copy-link button under a term is pressed. The rules it has to obey:
   * fixed, bottom-centre, and it can never block the back-to-top button:
     z-index 15 sits UNDER that button (z-index 20 in styles_011.css), and
     pointer-events: none means a tap passes straight through it even if the
     two ever overlap.
   * it respects the phone's home-bar area through env(safe-area-inset-*),
     with the plain 16px line first so an engine without env()/max() keeps
     the simple value - the same trick styles_011.css uses for .gl-top.
   * amber on ink: #e8a020 on #14161a = 8.18:1, the pairing the hero eyebrow
     already uses, so the toast looks like a piece of this page.
   * the fade lasts about as long as the script keeps the element (roughly
     1.8s); the script removes it, and this file only makes the leave calm.
   -- Ava: a little golden envelope that says "Link copied" and then goes
      away by itself. */
.gl-toast {
  position: fixed;
  left: 50%;
  bottom: 16px;
  bottom: max(16px, env(safe-area-inset-bottom));
  z-index: 15;                      /* below .gl-top (20): the button stays clickable */
  max-width: calc(100vw - 96px);    /* keeps clear of the back-to-top button */
  padding: 10px 16px;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-align: center;
  color: var(--clr-brand);          /* 8.18:1 on ink */
  background: var(--clr-ink);
  border: 1px solid var(--clr-brand);
  border-radius: 999px;
  box-shadow: var(--shadow-md);
  transform: translateX(-50%);
  pointer-events: none;
  animation: gl-toast-fade 1.8s ease both;
}
/* translate(-50%) is carried inside every frame as well as on the rule, so
   the toast can never jump to one side while the fade runs. */
@keyframes gl-toast-fade {
  0%   { opacity: 0; transform: translate(-50%, 10px); }
  10%  { opacity: 1; transform: translate(-50%, 0); }
  75%  { opacity: 1; transform: translate(-50%, 0); }
  100% { opacity: 0; transform: translate(-50%, 0); }
}

/* ============ SECTION 6: THE TABLE POLISH v2 MISSED ============
   Three small things, all inside the terms table. */

/* (a) A hanging term. The first line keeps the 16px it had, and a SECOND line
   of a long term indents to 32px, so a wrapped term reads as one block and
   the amber hover marker on the left edge never cuts through a letter.
   padding-left 32px minus text-indent 16px = the original 16px on line one.
   -- Ryan: like a paragraph in a book - the first line sticks out, the rest
      lines up behind it. */
.gl-table .gl-term { padding-left: 32px; text-indent: -16px; }

/* (b) The meaning column measured to 68ch. styles_011.css asked for 62ch:
   this is a DELIBERATE override of that one value (the brief asks for 68ch),
   because the two new categories carry longer definitions and 68ch still sits
   inside the comfortable 45-75 character band for body text. */
.gl-table .gl-mean { max-width: 68ch; }

/* (c) The sticky header gets a hairline shadow ONLY while it rides over the
   rows. position: sticky comes from styles_010.css; what was missing is the
   depth cue - a header with an amber underline and no shadow looks pasted on
   once rows slide underneath it.
   CSS cannot ask "is this element stuck?". It CAN ask where the table is on
   screen: a view() scroll timeline turns that into 0-100% progress, and its
   "exit" phase begins the moment the table's top edge reaches the top of the
   viewport - within a hair of the moment the header starts to stick. So the
   shade fades in over the next 60px of scrolling, inside the browser, with no
   JavaScript and no scroll listener.
   The animated value lives on the TABLE and is inherited by the header cells
   as a custom property, because a view timeline measured on a sticky element
   is pinned to the viewport and would never advance.
   Fallback: an engine without @property or animation-timeline simply never
   sees the shade - the shadow list resolves with the transparent :root value
   and the header keeps the 2px amber underline from styles_011.css. */
@property --gl-head-shade {
  syntax: "<color>";
  inherits: true;
  initial-value: rgba(20, 22, 26, 0);
}
@supports (animation-timeline: view()) {
  .gl-table {
    animation: gl-head-shade linear both;
    animation-timeline: view(block);
    animation-range: exit 0% exit 60px;
  }
  @keyframes gl-head-shade {
    from { --gl-head-shade: rgba(20, 22, 26, 0); }
    to   { --gl-head-shade: rgba(20, 22, 26, 0.35); }
  }
  .gl-table thead th {
    box-shadow: inset 0 -2px 0 var(--clr-brand),
                0 6px 8px -6px var(--gl-head-shade);
  }
}

/* ============ SECTION 7: THE TWO NEW CATEGORIES ============
   tr[data-cat="tool"] and tr[data-cat="finish"] are the categories added in
   session 0105. Their pill keeps the shape and the OPAQUE background of every
   other pill; only the ink becomes deep brass (--clr-brand-deep, #8f5d00) and
   the hairline follows it. A reader then sees at a glance which part of the
   glossary is new, without a second badge or a shouting colour.
   Contrast: #8f5d00 on the pill background #f4f5f5 = 5.15:1, above the 4.5:1
   AA minimum. Colour is NOT the only cue - the cell's own words name the
   category - so a reader who cannot separate the two hues loses nothing
   (WCAG 1.4.1). */
tr[data-cat="tool"] .gl-cat-pill,
tr[data-cat="finish"] .gl-cat-pill {
  color: var(--clr-brand-deep, #8f5d00);   /* 5.15:1 on the pill */
  border-color: rgba(143, 93, 0, 0.35);
}

/* ============ SECTION 8: PHONES AND NARROW SCREENS (820px and below) ============
   The card layout from styles_010.css stays exactly as it is; this block only
   fits the three new pieces onto a phone. Nothing here can widen the page, so
   no sideways scrollbar appears.
   -- Ava: on a phone the letters roll sideways under your finger, the white
      card loses its coloured edge, and the search box stands above its
      button instead of beside it. */
@media screen and (max-width: 820px) {
  /* The A-Z strip: the label takes a line of its own, then the letters share
     one scrolling row. The letters keep their order and the page keeps its
     width - the overflow is taken by the strip, never by the document. */
  .gl-index { flex-wrap: wrap; }
  .gl-index-label { flex: 1 1 100%; }
  .gl-index-letters {
    flex: 1 1 100%;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 4px;            /* room for the scrollbar on a desktop browser */
  }
  .gl-index-key { flex: 0 0 auto; }
  /* A card on a phone: the 4px amber rule would cost 4px of a 320px screen
     and buy nothing, so the card keeps its plain hairline instead. */
  .gl-about { border-left: 1px solid var(--gl-rule); padding: var(--sp-md); }
  /* One column. flex: 0 0 auto is essential: in a column flex container the
     basis would otherwise be applied to the HEIGHT, and the v2 field asks for
     260px, which would open a 260px-tall box. */
  .gl-talk-tools { flex-direction: column; align-items: stretch; padding: var(--sp-sm); }
  .gl-talk-tools .gl-search,
  .gl-talk-tools .gl-clear { flex: 0 0 auto; width: 100%; }
  /* The term is its own full-width line in the card layout, so the hanging
     indent of SECTION 6 goes back to the plain 16px it has everywhere else. */
  .gl-table .gl-term { padding-left: 16px; text-indent: 0; }
}

/* ============ SECTION 9: PRINT - the handout ============
   The print block is the ONLY place in this file allowed to use !important,
   because a printed sheet has to beat the screen rules. It does four things:
   * the jump strip, the talks filter, the toast and the back-to-top button
     are controls: they do not exist on paper;
   * the answer block prints as a short paragraph - black on white, no card,
     no coloured rule (a printer usually drops background colour, and text on
     a dropped background prints as nothing at all);
   * a row the talks filter hid MUST stay hidden on paper. This is the one
     place where the talks filter differs from the terms filter: styles_010.cs
     shows the terms rows again because those rows are the page's content,
     while these are the reader's own choice and the sheet is the reader's;
   * the header shadow is a screen hint, so paper gets the hairline only. */
@media print {
  .gl-index, .gl-talk-tools, .gl-toast, .gl-top { display: none !important; }
  .gl-about {
    max-width: none;
    margin: 0 0 6mm;
    padding: 0;
    background: none !important;
    border: none !important;
    border-radius: 0;
    box-shadow: none !important;
  }
  .gl-about h2, .gl-about-text { color: #000 !important; max-width: none; }
  .gl-talks .gl-table tr.gl-talk-row-hidden { display: none !important; }
  .gl-table { animation: none !important; }
  .gl-table thead th { box-shadow: none !important; }
  .gl-no-hits { color: #000 !important; border-left-color: #000; }
  tr[data-cat="tool"] .gl-cat-pill,
  tr[data-cat="finish"] .gl-cat-pill {
    color: #000 !important;
    background: none !important;
    border-color: #999 !important;
  }
}

/* ============ SECTION 10: CALM - reduced motion ============
   A visitor who asked for less movement gets a toast that simply appears and
   then goes, no fade, and no transitions on the new controls. The header
   shade is a slow fade tied to scrolling, so it is switched off as well; the
   header keeps its amber underline, which carries the same "this is a header"
   message without moving - the same reasoning styles_011.css uses for leaving
   the sticky toolbar in place.
   -- Ryan: "prefers-reduced-motion" is the visitor telling the browser once,
      "please stop things moving about"; we simply obey. */
@media (prefers-reduced-motion: reduce) {
  .gl-toast { animation: none; }
  .gl-table { animation: none; }
  .gl-index-key { transition: none; }
  .gl-talk-tools .gl-search,
  .gl-talk-tools .gl-clear { transition: none; }
}

/* Content continues in css/styles_013.css if this file ever reaches 480 lines. */
