/* ============================================================================
   StratiLogger — Design Tokens + Shared Component Styles
   docs/planning/03-design-system.md

   DESIGN: FLEXY — chosen by the owner from studio/ (the five-layout gallery).
   Light, not dark. Every surface is a white card on a cool grey ground, joined
   by soft shadows rather than borders. Tokens are read straight out of
   flexy-bootstrap-lite's own stylesheet, not eyeballed from a screenshot:

     --bs-primary        #1e4db7      --bs-secondary   #1a97f5
     --bs-heading-color  #11142d      --bs-body-color  #67757c
     --bs-border-color   #ecf0f2      --bs-light       #f6f8fb
     --bs-primary-bg-subtle #ddebff
     --bs-border-radius  7px  (sm 5 · lg 12) · body-wrapper 20px
     shadow  rgba(145,158,171,.2) 0 0 2px, rgba(145,158,171,.12) 0 12px 24px -4px
     sidebar-link  padding 10 · radius 7 · icon 21

   SHARED between landscape.html and portrait.html.
   Contains NO layout. Layout lives in shells/landscape.css and shells/portrait.css.
   ========================================================================== */

:root {
  /* ---- Flexy palette ---------------------------------------------------- */
  --primary:   #1E4DB7;
  --primary-d: #1A4099;
  --secondary: #1A97F5;
  --tint:      #DDEBFF;
  --heading:   #11142D;
  --body:      #67757C;
  --muted:     #99A1AA;
  --border:    #ECF0F2;
  --border-2:  #F3F6F8;
  --ground:    #F6F8FB;
  --card:      #FFFFFF;
  --ok:        #13DEB9;
  --warn:      #FFAE1F;
  --danger:    #FA896B;

  /* ---- the same ramp under the names the rest of the core already uses ---
     shared/ui/*.js writes a handful of these inline (save state, pen pressure,
     panel hints) and CL.tokens.sync() reads them for the canvas, so the two
     vocabularies must stay in step. One palette, two spellings. */
  --c-primary-900: #0E2A63;
  --c-primary-700: #1A4099;
  --c-primary-600: #1E4DB7;
  --c-primary-500: #2F63D4;
  --c-primary-300: #8FB0EE;
  --c-primary-100: #DDEBFF;
  --c-primary-050: #F1F6FF;

  --c-ink:        #11142D;
  --c-ink-2:      #67757C;
  --c-ink-3:      #99A1AA;
  --c-line:       #ECF0F2;
  --c-line-soft:  #F3F6F8;
  --c-surface:    #FFFFFF;
  --c-surface-2:  #F6F8FB;
  --c-surface-3:  #F6F8FB;

  --c-danger:  #E23A3A;      /* text-legible; --danger is the Flexy fill */
  --c-warning: #B76E00;
  --c-success: #08A88A;
  --c-focus:   #1A97F5;
  --c-selection: #1E4DB7;

  /* ---- paper (the log canvas) ------------------------------------------
     The canvas is PAPER, always: a geologist judging rock colour must see it
     against white. Chrome is free to change; these five are not. */
  --c-paper:       #FFFFFF;
  --c-paper-line:  #D5DCE2;
  --c-paper-grid:  #F0F3F6;
  --c-paper-ink:   #11142D;
  --c-paper-ink-2: #3D4750;

  /* ---- type ------------------------------------------------------------ */
  --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", "Roboto Mono", Consolas, monospace;

  --t-display: 700 24px/30px var(--font-ui);
  --t-title:   600 18px/24px var(--font-ui);
  --t-section: 600 15px/20px var(--font-ui);
  --t-body:    400 15px/22px var(--font-ui);
  --t-label:   500 13px/18px var(--font-ui);
  --t-caption: 400 12.5px/17px var(--font-ui);

  /* ---- spacing --------------------------------------------------------- */
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px;
  --s-5: 24px; --s-6: 32px; --s-7: 48px; --s-8: 64px;

  /* Flexy radii. --radius-3 is the body-wrapper radius every card carries. */
  --radius-0: 5px;
  --radius-1: 7px;
  --radius-2: 12px;
  --radius-3: 20px;
  --hairline: 1px;
  --rule: 1px;

  /* ---- fixed dimensions ------------------------------------------------ */
  --gap:       14px;    /* the space between cards — Flexy's only "border" */
  --topbar-h:  62px;
  --rail-w:    64px;
  --rail-h:    68px;
  /* Two tracks, since the rail carries fifteen tools (owner, 2 Aug 2026).
     Kept as their own tokens so the one-track sizes stay available for the
     print chrome and for anything that wants a single strip. */
  --rail-w2:  108px;
  --rail-h2:  112px;
  /* Narrower by request (owner, 2 Aug 2026): 330 was wider than any panel's
     content needed and the log paid for it on every screen. The widest thing
     in here is a labelled slider row, which fits 268 comfortably. Portrait
     caps this against the viewport as well — see shells/portrait.css. */
  /* Wider since 2 Aug 2026 — 268 px left the lithology swatches too small to
     read on a desktop. Overridden per project by the drag handle; this is the
     value a project that has never been resized gets. */
  --panel-w:   320px;
  --panel-w-min: 220px;
  --panel-w-max: 560px;
  --ruler-w:   48px;
  --colhead-h: 38px;
  --meta-h:    58px;

  /* ---- elevation — Flexy's own shadow, at three strengths -------------- */
  --e1: rgba(145,158,171,.16) 0 1px 2px 0, rgba(145,158,171,.10) 0 6px 14px -4px;
  --e2: rgba(145,158,171,.20) 0 0 2px 0, rgba(145,158,171,.12) 0 12px 24px -4px;
  --e3: rgba(145,158,171,.24) 0 0 3px 0, rgba(145,158,171,.20) 0 24px 48px -8px;

  /* ---- motion ---------------------------------------------------------- */
  --dur-fast: 120ms;
  --dur-base: 180ms;
  --ease: cubic-bezier(0.2, 0, 0, 1);

  /* ---- safe area -------------------------------------------------------
     How much of each edge belongs to somebody else. ZERO EVERYWHERE BY
     DEFAULT, which is what the web client sees for ever: no browser sets
     these, so every `calc()` below collapses to the plain value it had
     before and the desktop layout is unchanged to the pixel.

     Android sets them. Targeting API 36 makes edge-to-edge non-optional, so
     the WebView is handed the whole window — status bar, navigation bar and
     display cutout included — and `WorkspaceActivity` forwards the window
     insets in through `StratiLoggerWebView.applySafeArea`, in CSS pixels.

     WHY THIS RATHER THAN `env(safe-area-inset-*)`: that reports display
     cutouts only. It is silent about the Android navigation bar, which is
     the edge that actually eats a footer.

     The rule this buys is one rule, not two — the same stylesheet lays the
     chrome out inside the safe area on both clients, with different numbers
     in the variables (CLAUDE.md §2.5). The GROUND still reaches the physical
     edge on Android: only the cards move. A log that stopped short of the
     screen edge would look broken on a tablet. */
  --inset-top: 0px;
  --inset-right: 0px;
  --inset-bottom: 0px;
  --inset-left: 0px;
}

/* ============================================================================
   RESET
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; }
body {
  margin: 0;
  font: var(--t-body);
  color: var(--body);
  background: var(--ground);
  -webkit-text-size-adjust: 100%;
  overscroll-behavior: none;
  touch-action: manipulation;
}
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; padding: 0; }
h1, h2, h3, h4, h5, h6 { color: var(--heading); }
:focus-visible { outline: 2px solid var(--secondary); outline-offset: 2px; }
:focus:not(:focus-visible) { outline: none; }
.num { font-variant-numeric: tabular-nums; }
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: #DFE5EA; border-radius: 4px; }
::-webkit-scrollbar-track { background: transparent; }

/* ============================================================================
   CARDS
   The one structural idea in this design: each region of the app is a white
   card on the grey ground. The shells decide where the cards sit and how big
   they are; the treatment itself is shared, so it is here.
   ========================================================================== */
.topbar, .rail, .fxa-log, .ws__panel, .landing__top, .landing__lists, .landing__bar {
  background: var(--card);
  border-radius: var(--radius-3);
  box-shadow: var(--e2);
}

/* ============================================================================
   BUTTONS
   ========================================================================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: 38px; padding: 0 var(--s-4);
  border-radius: var(--radius-1);
  font: var(--t-label); font-size: 14px;
  border: var(--hairline) solid transparent;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
  white-space: nowrap;
}
.btn--filled  { background: var(--primary); color: #fff; border-color: var(--primary); }
.btn--filled:hover  { background: var(--primary-d); border-color: var(--primary-d); }
.btn--filled:active { background: #16357F; }
.btn--outline { border-color: var(--border); color: var(--body); background: var(--card); }
.btn--outline:hover { background: var(--ground); color: var(--primary); border-color: var(--tint); }
.btn--ghost   { color: var(--body); }
.btn--ghost:hover { background: var(--ground); color: var(--primary); }
.btn--danger  { color: var(--c-danger); }
.btn--danger:hover { background: #FDECEA; }
.btn[disabled] { opacity: .4; pointer-events: none; }
.btn--sm { min-height: 30px; padding: 0 var(--s-3); font-size: 12.5px; }
.btn--block { width: 100%; }

.iconbtn {
  width: 38px; height: 38px; flex: none;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-1); color: var(--body);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.iconbtn:hover { background: var(--ground); color: var(--primary); }
.iconbtn--danger { color: var(--c-danger); }
.iconbtn--danger:hover { background: #FDECEA; }
.iconbtn--muted { color: var(--muted); }
.iconbtn svg { width: 18px; height: 18px; }
.iconbtn.is-hidden { visibility: hidden; }

/* ============================================================================
   FORM CONTROLS
   ========================================================================== */
.field { display: flex; flex-direction: column; gap: 6px; }
.field__label { font: var(--t-label); color: var(--heading); }
.input {
  height: 40px; padding: 0 var(--s-3); width: 100%;
  background: var(--card); color: var(--heading);
  border: var(--hairline) solid var(--border);
  border-radius: var(--radius-1);
}
.input::placeholder { color: var(--muted); }
.input:focus { border-color: var(--secondary); outline: none; }
textarea.input { height: auto; padding: 10px var(--s-3); resize: vertical; line-height: 1.55; }
select.input { cursor: pointer; }

/* checkbox / radio rows — 18px visual, 40px hit area */
.checkrow, .radiorow {
  display: flex; align-items: center; gap: 10px;
  min-height: 40px; padding: var(--s-1) 0; cursor: pointer;
  font-size: 14px; color: var(--body);
}
.checkrow input, .radiorow input {
  width: 18px; height: 18px; margin: 0; flex: none;
  accent-color: var(--primary); cursor: pointer;
}
.checkrow:hover, .radiorow:hover { color: var(--primary); }
.checkrow__hint { margin-left: auto; font: var(--t-caption); font-family: var(--font-mono); color: var(--muted); }

/* A long list of checkboxes in two columns (owner, 4 Aug 2026: "make object
   checkbox list more compact").

   TWO COLUMNS RATHER THAN SHORTER ROWS. The eraser's target list is thirteen
   kinds; at 40 px each it ran past the height of a phone panel. Halving the row
   height would have got there too and would have broken the 40 px touch target
   the whole design system is built on — this is a control used with a finger,
   on a tablet, in a core shed. Two columns halve the height and leave every
   target the size it was. */
.checkgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--s-3); }
.checkgrid .checkrow { min-width: 0; }
.checkgrid .checkrow span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* One column when there is not room for two — a truncated label is worse than
   a longer list. */
@media (max-width: 420px) { .checkgrid { grid-template-columns: 1fr; } }

/* The row of bulk actions above such a list. */
.checkgrid__bulk { display: flex; gap: var(--s-2); margin-bottom: var(--s-1); }

/* segmented control — Flexy separates the segments rather than welding them */
.seg {
  display: inline-flex; gap: var(--s-1);
  max-width: 100%; overflow-x: auto; scrollbar-width: none;
}
.seg::-webkit-scrollbar { display: none; }
.seg button {
  flex: none; min-height: 34px; padding: 0 var(--s-3); font-size: 13px;
  border: var(--hairline) solid var(--border); border-radius: var(--radius-1);
  background: var(--card); color: var(--body);
}
.seg button:hover { border-color: var(--tint); background: var(--ground); }
.seg button[aria-pressed="true"] {
  background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 500;
}

/* slider */
.slider { display: flex; flex-direction: column; gap: var(--s-2); }
.slider__head { display: flex; align-items: baseline; gap: var(--s-2); }
.slider__title { font: var(--t-label); color: var(--heading); }
.slider__val { margin-left: auto; font-family: var(--font-mono); font-size: 13px; font-weight: 600; color: var(--primary); }
.slider__chips { display: flex; gap: 5px; flex-wrap: wrap; }
.slider__chip {
  min-width: 34px; height: 26px; padding: 0 var(--s-2);
  font: var(--t-caption); font-family: var(--font-mono);
  border: var(--hairline) solid var(--border); border-radius: var(--radius-1);
  background: var(--card); color: var(--muted);
}
.slider__chip[aria-pressed="true"] {
  background: var(--tint); border-color: var(--tint); color: var(--primary); font-weight: 600;
}
.slider input[type=range] { width: 100%; accent-color: var(--primary); height: 26px; }
input[type=range] { accent-color: var(--primary); }

/* grain-size chip row — full Wentworth wraps to as many rows as it needs */
.chiprow { display: flex; gap: 5px; flex-wrap: wrap; }
.chiprow .btn { min-width: 46px; padding: 0 10px; font-family: var(--font-mono); }

/* colour swatch field — the whole row opens the picker, so it is a 44px target */
.swatchfield {
  display: flex; align-items: center; gap: 10px; min-height: 44px;
  cursor: pointer; border-radius: var(--radius-1);
}
.swatchfield__label { font-size: 14px; color: var(--body); }
.swatchfield__box {
  margin-left: auto; width: 56px; height: 36px;
  border: var(--hairline) solid var(--border); border-radius: var(--radius-1);
  cursor: pointer; flex: none;
}
.swatchfield:hover .swatchfield__box { border-color: var(--primary); }
.swatchfield__hex {
  font: var(--t-caption); font-family: var(--font-mono);
  color: var(--muted); width: 68px; text-align: right; text-transform: uppercase;
}

/* ============================================================================
   COLOUR POPOVER + HSV PICKER   (shared/ui/components.js ui.colorPicker)

   One shared widget on every platform — deliberately NOT <input type="color">,
   which is a different OS dialog on Windows, on GTK and in an Android WebView
   and would breach the identical-interactivity rule (CLAUDE.md §2.5).

   The SV field and the hue bar are painted with CSS gradients rather than a
   <canvas>: two gradients ARE the HSV square, exactly, and there is then no
   backing store to size against the device pixel ratio and no unpainted
   300x150 default to catch stretching before the first frame.
   ========================================================================== */
.cpop {
  position: fixed; z-index: 500;
  background: var(--card); border-radius: var(--radius-2); box-shadow: var(--e3);
  border: var(--hairline) solid var(--border);
  padding: 12px; width: 300px;
  max-width: calc(100vw - 16px); max-height: calc(100vh - 16px); overflow-y: auto;
}
.cpick { display: flex; flex-direction: column; gap: 10px; }

/* Presets. 38px cell + 6px gap = a 44px pitch: a fingertip that overshoots
   lands in the gap rather than on the neighbouring colour. */
.cpick__presets {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(38px, 1fr));
  gap: 6px; max-height: 140px; overflow-y: auto;
}
.cpick__pre {
  height: 38px; display: flex; align-items: center; justify-content: center;
  border: var(--hairline) solid var(--border); border-radius: var(--radius-0);
}
.cpick__pre[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--primary); }
.cpick__pre svg { width: 15px; height: 15px; }

/* The saturation / value field. Press anywhere to jump, then drag — the target
   is the whole 150px surface, never the dot. */
.cpick__sv {
  position: relative; height: 150px; border-radius: var(--radius-0);
  box-shadow: inset 0 0 0 1px rgba(17, 20, 45, .14);
  touch-action: none; cursor: crosshair;
  background-image:
    linear-gradient(to top, #000000, rgba(0, 0, 0, 0)),
    linear-gradient(to right, #FFFFFF, #FF0000);
}
.cpick__dot {
  position: absolute; width: 22px; height: 22px; margin: -11px 0 0 -11px;
  border-radius: 50%; border: 2px solid #fff; pointer-events: none;
  box-shadow: 0 0 0 1px rgba(17, 20, 45, .55), 0 1px 3px rgba(17, 20, 45, .35);
}

/* The hue bar is 18px of colour inside a 44px press row — the gap above and
   below it is dead space that still hits the bar. */
.cpick__hue {
  position: relative; height: 44px; padding: 13px 0;
  background-clip: content-box; border-radius: var(--radius-0);
  touch-action: none; cursor: ew-resize;
  background-image: linear-gradient(to right,
    #FF0000 0%, #FFFF00 16.667%, #00FF00 33.333%, #00FFFF 50%,
    #0000FF 66.667%, #FF00FF 83.333%, #FF0000 100%);
}
.cpick__hdot {
  position: absolute; top: 50%; width: 18px; height: 30px; margin: -15px 0 0 -9px;
  border-radius: var(--radius-0); border: 2px solid #fff; pointer-events: none;
  box-shadow: 0 0 0 1px rgba(17, 20, 45, .55), 0 1px 3px rgba(17, 20, 45, .35);
}

.cpick__row { display: flex; align-items: center; gap: 8px; }
.cpick__chip {
  width: 44px; height: 40px; flex: none; border-radius: var(--radius-0);
  border: var(--hairline) solid var(--border);
}
.cpick__hex {
  flex: 1 1 auto; min-width: 0; height: 40px; padding: 0 10px;
  font-family: var(--font-mono); font-size: 14px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--heading);
  background: var(--card); border: var(--hairline) solid var(--border);
  border-radius: var(--radius-1);
}
.cpick__hex:focus { border-color: var(--secondary); outline: none; }
.cpick__done { min-height: 40px; flex: none; padding: 0 var(--s-3); }

/* swatch grid — FIXED cell footprints. The cells never stretch to fill the
   container; a wider panel gets more columns instead (justify-content keeps
   the grid packed left). The old `1fr` split made a colour chip the width of
   a playing card inside a tablet bottom sheet. */
.swatchgrid { display: grid; gap: 6px; justify-content: start; }
.swatchgrid--color   { grid-template-columns: repeat(auto-fill, 40px); }
.swatchgrid--color   button { width: 40px;  height: 32px; }
.swatchgrid--pattern { grid-template-columns: repeat(auto-fill, 88px); }
.swatchgrid--pattern button { width: 88px;  height: 56px; }
/* Smaller lithology swatches (owner, 2 Aug 2026). Still a legible texture at
   58x36, and two more fit per row on a tablet panel. */
.swatchgrid--pattern-sm { grid-template-columns: repeat(auto-fill, 58px); }
.swatchgrid--pattern-sm button { width: 58px;  height: 36px; }
.swatchgrid button {
  border: var(--hairline) solid var(--border); border-radius: var(--radius-1);
  position: relative; transition: transform var(--dur-fast) var(--ease);
}
.swatchgrid button:hover { transform: scale(1.06); }
.swatchgrid button[aria-pressed="true"] {
  box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--primary); z-index: 1;
}
.swatchgrid button[aria-pressed="true"]::after {
  content: ""; position: absolute; inset: 0;
  background: no-repeat center/12px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='4'%3E%3Cpath d='M4 12l6 6L20 6'/%3E%3C/svg%3E");
  filter: drop-shadow(0 0 1px rgba(0,0,0,.9));
}
.swatchgrid .add {
  background: var(--ground); color: var(--primary); font-size: 18px;
  display: flex; align-items: center; justify-content: center; border-style: dashed;
}

/* style / preview picker rows */
.stylelist { display: flex; flex-direction: column; gap: var(--s-1); }
.stylelist button {
  height: 36px; display: flex; align-items: center; padding: 0 10px;
  border: var(--hairline) solid transparent; border-radius: var(--radius-1); color: var(--body);
}
.stylelist button:hover { background: var(--ground); }
.stylelist button[aria-pressed="true"] { border-color: var(--primary); background: var(--tint); color: var(--primary); }
.stylelist svg { width: 100%; height: 18px; }

/* ============================================================================
   PANEL SECTIONS  (shared by right dock and bottom sheet)
   ========================================================================== */
.panel { display: flex; flex-direction: column; height: 100%; background: var(--card); }
.panel__head {
  display: flex; align-items: center; gap: 10px; flex: none;
  padding: var(--s-4) var(--s-5);
  border-bottom: var(--hairline) solid var(--border);
}
.panel__title { font: var(--t-section); font-size: 16px; color: var(--heading); }
/* Compact by default — the panels were mostly air (owner feedback
   1 Aug 2026). Sections keep their grouping; the padding stops pretending
   a phone sheet is a settings web page. */
.panel__body {
  flex: 1; overflow-y: auto; padding: 12px var(--s-4) var(--s-4);
  display: flex; flex-direction: column; gap: 14px;
}
.panel__section { display: flex; flex-direction: column; gap: 10px; }
.panel__section > h4 { margin: 0; font: var(--t-label); font-size: 13px; font-weight: 600; color: var(--heading); }
.panel__rule { height: var(--hairline); background: var(--border); }
.panel__empty { color: var(--muted); font-size: 13px; text-align: center; padding: 12px var(--s-3); }

/* ============================================================================
   LISTS  (project list, column list, layer tree)
   ========================================================================== */
.list { display: flex; flex-direction: column; gap: 2px; }

/* A LIST BOX — owner, 8 Aug 2026: the panel lists "list like 'list box' in
   pyqt5". A Qt QListWidget is a framed, scrolling well of plain rows: the frame
   is what says "these are the contents of one thing", and the fixed height is
   what stops nine entries from pushing the rest of the panel off screen. The
   earlier build put a Remove button on every row, which is the shape this
   replaces — the buttons belong OUTSIDE the box, acting on the selection.

   Rows inside it are ordinary `.listrow`s, so selection, hover and the
   keyboard behave exactly as they do in the project list and the layer tree. */
.listbox {
  display: flex; flex-direction: column; gap: 1px;
  border: var(--hairline) solid var(--border); border-radius: var(--radius-1);
  background: var(--surface);
  max-height: 168px;            /* ~4 rows, then it scrolls */
  overflow-y: auto; overflow-x: hidden;
  padding: 2px;
}
/* Denser than a full-page list: this one sits inside a tool panel, and 44 px
   rows would show two entries in the space four belong in. Still 32 px, which
   is a comfortable pointer target for a row whose job is selection. */
.listbox .listrow { min-height: 32px; padding: var(--s-1) var(--s-2); border-radius: 3px; }
.listbox .listrow__meta {
  font: var(--t-caption); font-family: var(--font-mono);
  color: var(--muted); margin-left: auto; flex: none; padding-left: var(--s-2);
}
.listbox .listrow__dot {
  width: 10px; height: 10px; border-radius: 50%; flex: none;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.18);
}
.listbox__empty { color: var(--muted); font-size: 12.5px; padding: 10px var(--s-3); text-align: center; }

.listrow {
  display: flex; align-items: center; gap: var(--s-3);
  min-height: 44px; padding: var(--s-2) var(--s-3);
  border: var(--hairline) solid transparent; border-radius: var(--radius-1);
  cursor: pointer; text-align: left; width: 100%; color: var(--body);
}
.listrow:hover { background: var(--ground); }
.listrow[aria-selected="true"] { background: var(--tint); border-color: var(--tint); color: var(--primary); }
.listrow__idx { font: var(--t-caption); font-family: var(--font-mono); color: var(--muted); width: 18px; flex: none; }
.listrow__name {
  flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500;
}
.listrow__actions { display: flex; align-items: center; gap: 2px; margin-left: auto; flex: none; }
/* Show/hide a column from the list (owner, 4 Aug 2026). Sized like every other
   checkbox in the product so the row reads as one control set rather than as a
   list with something bolted on. `flex: none` because the name beside it is
   what should absorb a narrow panel, not the switch. */
.listrow__show {
  width: 18px; height: 18px; margin: 0; flex: none;
  accent-color: var(--primary); cursor: pointer;
}
/* A hidden column reads as hidden at a glance, without a second badge: the row
   is still selectable and still says what it is, just quietly. */
.listrow:has(.listrow__show:not(:checked)) .listrow__name { opacity: .55; }
.listrow:has(.listrow__show:not(:checked)) .listrow__idx { opacity: .55; }

/* badges */
.badge {
  font: 600 10.5px/1 var(--font-ui);
  padding: 4px 8px; border-radius: var(--radius-0);
  background: var(--ground); color: var(--muted);
}
.badge--core    { background: var(--tint); color: var(--primary); }
.badge--outcrop { background: #FFF4E0; color: #B76E00; }

/* ============================================================================
   DIALOG / SHEET
   ========================================================================== */
.scrim {
  position: fixed; inset: 0; background: rgba(17,20,45,.36);
  display: flex; align-items: center; justify-content: center; z-index: 100;
  animation: fade var(--dur-base) var(--ease);
}
@keyframes fade { from { opacity: 0 } }
.dialog {
  background: var(--card); border-radius: var(--radius-3); box-shadow: var(--e3);
  display: flex; flex-direction: column; overflow: hidden;
  max-width: 96vw;
  /* `vh` first, then `dvh`: an engine that does not know `dvh` keeps the
     first and one that does takes the second. It matters on Android, where
     `vh` is fixed to the LARGE viewport and does not shrink when the soft
     keyboard opens — so a `100vh` dialog keeps its full height behind the
     keyboard and pushes its own footer out of reach. `dvh` tracks the visible
     viewport, which is what a form with a text field in it needs. */
  max-height: 92vh;
  max-height: 92dvh;
}
.dialog__head {
  display: flex; align-items: center; gap: var(--s-3);
  padding: 18px 22px; border-bottom: var(--hairline) solid var(--border); flex: none;
}
.dialog__title { font: var(--t-title); color: var(--heading); }
.dialog__sub { font: var(--t-caption); color: var(--muted); }
.dialog__body { flex: 1; overflow: auto; }
.dialog__foot {
  display: flex; align-items: center; gap: var(--s-2);
  padding: var(--s-3) 22px; border-top: var(--hairline) solid var(--border); flex: none;
}
.dialog__foot .spacer { flex: 1; }

/* ============================================================================
   OVERFLOW MENU  (portrait top bar)
   ========================================================================== */
.menuwrap { position: fixed; inset: 0; z-index: 200; }
.menu {
  position: absolute; right: 18px; top: 76px; min-width: 210px; padding: 6px;
  background: var(--card); border-radius: var(--radius-2); box-shadow: var(--e3);
}
.menu .listrow { gap: var(--s-3); }

/* ============================================================================
   TOAST
   ========================================================================== */
.toast {
  /* Clear of the navigation bar. A toast is the one piece of chrome that is
     both fixed to an edge and short-lived, so a toast half under the bar is
     read as a rendering fault rather than as a layout one. */
  position: fixed; left: 50%; bottom: calc(26px + var(--inset-bottom)); transform: translateX(-50%);
  background: var(--heading); color: #fff; font: var(--t-label); font-size: 13.5px;
  padding: 11px 18px; border-radius: var(--radius-1); box-shadow: var(--e3);
  z-index: 300; animation: rise var(--dur-base) var(--ease);
  max-width: 90vw; text-align: center;
}
@keyframes rise { from { opacity: 0; transform: translate(-50%, 8px) } }

/* ============================================================================
   TOOL RAIL BUTTONS (shared look; the shell decides direction)

   Icon-only, 48x48 — the field-use touch minimum from
   docs/planning/03-design-system.md §5.1. Flexy's sidebar-link treatment: 7px
   radius, filled solid when active. The stacked 9px labels the earlier design
   carried cost the log a third of the rail's width for text you read once;
   the active tool is still named, in the panel header.
   ========================================================================== */
.tool {
  display: flex; align-items: center; justify-content: center;
  width: 48px; height: 48px; flex: none;
  border-radius: var(--radius-1); color: var(--body);
  position: relative;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.tool svg { width: 21px; height: 21px; }
.tool span { display: none; }
.tool:hover { background: var(--ground); color: var(--primary); }
.tool[aria-pressed="true"] { background: var(--primary); color: #fff; box-shadow: var(--e1); }
.tool--toggle[aria-pressed="true"] { background: var(--secondary); color: #fff; }
.tool[disabled] { opacity: .35; pointer-events: none; }
.rail__swatch { border: var(--hairline) solid var(--border); border-radius: var(--radius-0); }

/* ============================================================================
   TOP BAR (shared look; the shell positions the card)
   ========================================================================== */
/* `height`, not `min-height`. The bar used to grow a second row whenever a
   status readout got longer — the live-input chip going from "touch" to
   "PEN 0.50 flat" was enough — and every pixel it grew pushed the log down,
   so putting the stylus down visibly moved the drawing you were aiming at
   (owner report, 2 Aug 2026). A fixed height cannot do that; the readouts are
   individually clipped instead. */
.topbar {
  display: flex; align-items: center; gap: var(--s-2);
  height: var(--topbar-h); min-height: var(--topbar-h); padding: 10px var(--s-4);
  flex: none; flex-wrap: nowrap; overflow: hidden;
}
.topbar__grp { display: flex; align-items: center; gap: 6px; }
.topbar__grp .btn { border-radius: var(--radius-1); border: var(--hairline) solid var(--border); }
.topbar__scale {
  display: flex; align-items: center; gap: 6px; height: 38px; padding: 0 var(--s-4);
  border: var(--hairline) solid var(--border); border-radius: var(--radius-1);
  background: var(--card); color: var(--heading);
  font-family: var(--font-mono); font-weight: 600;
}
.topbar__scale:hover { border-color: var(--tint); background: var(--ground); }
.topbar__status {
  margin-left: auto; display: flex; align-items: center; gap: var(--s-3);
  flex: none; min-width: 0;
}
.topbar__save { font-size: 13px; font-weight: 500; color: var(--c-success); flex: none; }
.topbar__depth {
  font-family: var(--font-mono); font-size: 13px; color: var(--muted);
  width: 78px; flex: none; text-align: right;
  white-space: nowrap; overflow: hidden;
}
/* THE SIZE OF THIS CHIP IS FIXED, in both axes, whatever it says.
   It reports live pointer state, so its text changes on every stroke — and a
   chip that resized with the text dragged the whole bar, and the log under it,
   with it. `width` + `nowrap` + `hidden` means the worst case is a clipped
   word, never a moved log. The dot carries the state that matters (pressure /
   no pressure), so clipping the tail costs nothing. */
.topbar__input {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  font-family: var(--font-mono); font-size: 12px; color: var(--muted);
  background: var(--ground); border: 0; border-radius: var(--radius-1);
  padding: 0 8px; width: 94px; height: 26px; flex: none;
  white-space: nowrap; overflow: hidden;
}
.topbar__inputdot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: currentColor; opacity: .55;
}
.topbar__inputtext { overflow: hidden; text-overflow: ellipsis; }

/* ============================================================================
   APP IDENTITY (region A0) — one thin row above the top bar

   Shared, not per-shell. These rules lived in shells/landscape.css, so the
   portrait shell rendered the row with no styling at all: the home button, the
   document name and the SedStrat mark ran together into one unreadable line
   ("StratiLogger123SedStrat" — owner screenshot, 2 Aug 2026). Look belongs here;
   only placement belongs to a shell.

   Separate from the top bar on purpose: the top bar belongs to the LOG (scale,
   undo, save) and this belongs to the APP. Mixing the two is what buried the
   Columns and Layers buttons before. Height is deliberately small — every
   pixel here is one the log does not get.
   ========================================================================== */
.identity {
  display: flex; align-items: center; gap: var(--s-3);
  height: 26px; flex: none;
  padding: 0 var(--s-3);
  font: var(--t-caption);
  color: var(--c-ink-2);
  border-bottom: var(--hairline) solid var(--border);
}
.identity__home {
  display: inline-flex; align-items: center; gap: 4px;
  background: none; border: 0; padding: 2px 4px; cursor: pointer;
  color: inherit; font: inherit; font-weight: 600; border-radius: 3px;
  flex: none;
}
.identity__home:hover { background: var(--c-hover, rgba(0,0,0,.05)); color: var(--c-ink); }
.identity__doc {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  opacity: .7;
}
.identity__mark {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  background: none; border: 0; cursor: pointer; padding: 2px 6px;
  border-radius: 3px; color: inherit; font: inherit;
  letter-spacing: .04em; opacity: .65;
}
.identity__mark:hover { opacity: 1; background: var(--c-hover, rgba(0,0,0,.05)); }
.identity__marktext { font-weight: 700; }

/* The bundled SedStrat artwork (shared/theme/brand.js). `block` so the inline
   box does not add a descender gap that would push the 26 px row over. */
.brandmark { display: inline-flex; flex: none; line-height: 0; }
.brandmark svg { display: block; }

/* ============================================================================
   METADATA HEADER (region C) — the log card's own header row
   ========================================================================== */
.meta {
  display: flex; align-items: center; gap: 22px;
  padding: var(--s-4) var(--s-5);
  background: var(--card); border-bottom: var(--hairline) solid var(--border);
  flex: none;
}
.meta__name { font: var(--t-title); color: var(--heading); letter-spacing: -.02em; }
.meta__cluster { display: flex; gap: 26px; margin-left: auto; }
.meta__pair { display: flex; flex-direction: column; text-align: right; }
.meta__k { font: 400 11px/14px var(--font-ui); color: var(--muted); }
.meta__v { font: 600 14px/18px var(--font-ui); color: var(--heading); }

/* ============================================================================
   LAYER TREE
   ========================================================================== */
.layergrp { border-bottom: var(--hairline) solid var(--border); }
.layergrp__head {
  display: flex; align-items: center; gap: 10px;
  width: 100%; min-height: 44px; padding: 10px var(--s-4);
  font: var(--t-section); font-size: 14px; color: var(--heading);
}
.layergrp__head:hover { background: var(--ground); }
.layergrp__count { margin-left: auto; font: var(--t-caption); color: var(--muted); }
.layerrow {
  display: flex; align-items: center; gap: 10px;
  min-height: 42px; padding: var(--s-1) var(--s-4) var(--s-1) 30px;
}
.layerrow:hover { background: var(--ground); }
.layerrow input[type=checkbox] { width: 17px; height: 17px; accent-color: var(--primary); flex: none; }
.layerrow__name { font-size: 14px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.layerrow__kind {
  font: 600 9.5px/1 var(--font-mono); padding: 3px 5px; border-radius: var(--radius-0);
  background: var(--ground); color: var(--muted);
}
.grip { color: var(--muted); cursor: grab; width: 16px; text-align: center; flex: none; }

/* ============================================================================
   MEASUREMENT PANEL  (shared/ui/panels.js P.measurement)
   Five swatches, each drawing the glyph it will place on the log — the row
   reads as marks rather than as five words.
   ========================================================================== */
.chiprow--measure { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.measurechip {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 7px 4px 5px; height: auto; line-height: 1.1;
}
.measurechip canvas { pointer-events: none; }
.measurechip__label { font: 500 10.5px/1.2 var(--font-ui); }
.measurechip[aria-pressed="true"] canvas { filter: brightness(0) invert(1); }
.measurefields { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.measurefields .field:only-child { grid-column: 1 / -1; }

/* ============================================================================
   LAYER MANAGER, in the side panel  (shared/ui/layers.js layerPanelSpec)

   Compact by request (owner, 2 Aug 2026). One line per row at a 32 px touch
   target, no per-row card, and the greyed rows are the actions a column allows
   but nothing has been drawn into yet — present so you can see what the column
   CAN hold, obviously empty so you do not go looking for data that is not
   there.
   ========================================================================== */
.lyrgrp { border-bottom: var(--hairline) solid var(--border); padding-bottom: 4px; margin-bottom: 4px; }
.lyrgrp__head {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 2px 3px;
  font: 600 12px/1.2 var(--font-ui); color: var(--heading);
}
.lyrgrp__n { margin-left: auto; font: var(--t-caption); color: var(--muted); }
.lyr {
  display: flex; align-items: center; gap: 6px;
  min-height: 32px; padding: 0 2px;
}
.lyr input[type=checkbox] { width: 15px; height: 15px; accent-color: var(--primary); flex: none; }
.lyr__kind {
  flex: none; width: 30px; text-align: center;
  font: 600 9px/1 var(--font-mono); color: var(--muted);
  background: var(--ground); border-radius: var(--radius-0); padding: 3px 2px;
}
.lyr__name {
  flex: 1; min-width: 0; font: 400 12.5px/1.2 var(--font-ui); color: var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lyr__count { flex: none; font: 600 11px/1 var(--font-mono); color: var(--muted); min-width: 22px; text-align: right; }
.lyr--empty { opacity: .5; padding-left: 21px; }
/* "+ Add data" — the one row in this list that changes what the column can
   hold. Full-width so it reads as the end of the group rather than a layer. */
.lyr--add {
  width: 100%; border: none; background: none; cursor: pointer;
  color: var(--primary); padding-left: 4px; text-align: left;
}
.lyr--add .lyr__name { color: inherit; font-weight: 500; }
.lyr--add svg { flex: none; }
.lyr--add:hover { background: var(--ground); border-radius: var(--radius-1); }

/* An allowed action the column did not ask for — it came with another one.
   Ticked, and visibly not yours to untick here. */
.checkrow--locked { opacity: .62; cursor: default; }
.checkrow--locked input { cursor: default; }
.iconbtn--sm { width: 26px; height: 26px; }
.iconbtn--sm svg { width: 15px; height: 15px; }

/* The column manager, in the panel rather than a dialog: it has to fit. */
.ws__panel .cm { grid-template-columns: 1fr; }
.ws__panel .cm__list { border-right: none; }
.ws__panel .cm__detailwrap { display: none; }
.ws__panel .cm--detail .cm__list { display: none; }
.ws__panel .cm--detail .cm__detailwrap { display: flex; }
.ws__panel .cm__back { display: inline-flex; margin: var(--s-2); align-self: flex-start; }

/* ============================================================================
   THE LITHOLOGY SWATCH ROW  (owner, 2 Aug 2026: "only populate 17 (4x4, + 1)")

   Four across, sixteen textures and the [+] that opens the library. Small on
   purpose: the texture is recognised, not studied, and a row you can take in at
   a glance beats four big pictures and a scroll.
   ========================================================================== */
/* FOUR ACROSS IS A FLOOR, NOT A FIXED COUNT (owner, 2 Aug 2026: the swatches
   are cramped on desktop).
   The panel is resizable — drag its inner edge, CL.App.installPanelGrip — but a
   hard `repeat(4, 1fr)` spent every pixel of the extra width on making four
   tiles wider, which is the one thing that does not help: a texture is
   recognised at a glance and then you want to see MORE of them at once.
   `auto-fill` with a 64px floor keeps four across at the shipped 320px and adds
   a fifth, sixth and seventh column as the panel is widened, so the library
   fills the space instead of stretching into it. */
.lithogrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
  gap: 4px;
}
.lithoswatch {
  display: flex; flex-direction: column; gap: 2px; align-items: stretch;
  padding: 3px; height: auto; text-align: center; cursor: pointer;
  border: var(--hairline) solid var(--border); border-radius: var(--radius-1);
  background: var(--card);
  /* min-width:0 or the nowrap name below sets the track's floor and the fourth
     column pushes out past the panel. */
  min-width: 0;
}
.lithoswatch[aria-pressed="true"] { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
.lithoswatch canvas { pointer-events: none; }
.lithoswatch__name {
  font: 500 8.5px/1.15 var(--font-ui); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lithoswatch--add {
  align-items: center; justify-content: center; min-height: 40px;
  border-style: dashed; color: var(--primary);
}
.lithoswatch__plus { font: 300 20px/1 var(--font-ui); }

/* ============================================================================
   THE SYMBOL SWATCH ROW  (owner, 4 Aug 2026: "present it just like lithology
   swatch - show first 16 or so, add [+] button")

   The lithology row's geometry, one size up: a texture is recognised from a
   22 px band of ornament, a glyph needs its own height before a burrow stops
   looking like a ripple. Everything else — the auto-fill floor, the caption,
   the pressed ring, the dashed [+] — is deliberately identical, because the
   two palettes are the same control holding different things.
   ========================================================================== */
.symgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(68px, 1fr));
  gap: 4px;
}
.symswatch {
  display: flex; flex-direction: column; gap: 2px; align-items: stretch;
  padding: 3px; height: auto; text-align: center; cursor: pointer;
  border: var(--hairline) solid var(--border); border-radius: var(--radius-1);
  background: var(--card); min-width: 0;
}
.symswatch[aria-pressed="true"] { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
.symswatch canvas { pointer-events: none; border-radius: 2px; }
.symswatch__name {
  font: 500 8.5px/1.15 var(--font-ui); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.symswatch--add {
  align-items: center; justify-content: center; min-height: 46px;
  border-style: dashed; color: var(--primary);
}

/* The library dialog: every FGDC texture, grouped as the catalog groups them. */
.dialog--lib { width: min(720px, 94vw); }
/* The two library dialogs are the only ones whose body is a bare grid rather
   than a laid-out form, so they carry their own gutter: `.dialog__body` has no
   padding of its own, which put the first and last swatch of every row hard
   against the dialog's rounded edge. */
.dialog--lib .dialog__body { padding: 0 18px 14px; }
/* The symbol catalogue is the same dialog with taller cells and a filter box.
   Sticky, because with ~180 entries the search field must stay reachable from
   the bottom of the ninth group. */
.libsearch {
  position: sticky; top: 0; z-index: 2; background: var(--card);
  padding: 2px 0 6px; display: flex; align-items: center; gap: 10px;
}
.libsearch .input { flex: 1; min-width: 0; }
/* Group headings are sticky too — park them below the filter box rather than
   underneath it. Scoped by the sibling selector, so the lithology library,
   which has no filter box, keeps its headings at the top. */
.libsearch ~ .libgroup .libgroup__head { top: 40px; }
.libgrid--sym { grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); }
.libswatch--sym .symswatch__name { color: var(--body); font-size: 9px; }
.libgroup { padding: 6px 0; }
.libgroup__head {
  font: 600 11px/1.2 var(--font-ui); color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em; padding: 6px 2px 4px;
  position: sticky; top: 0; background: var(--card); z-index: 1;
}
.libgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 5px; }
.libswatch {
  display: flex; flex-direction: column; gap: 2px; align-items: stretch;
  padding: 3px; height: auto; text-align: center; cursor: pointer;
  border: var(--hairline) solid var(--border); border-radius: var(--radius-1);
  background: var(--card); opacity: .55; min-width: 0;
}
.libswatch[aria-pressed="true"] {
  opacity: 1; border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary);
}
.libswatch canvas { pointer-events: none; }
.libswatch__name {
  font: 500 9px/1.15 var(--font-ui); color: var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The delete column in the project browser — present on every row, quiet until
   you look for it. */
.prow__act { display: flex; justify-content: flex-end; }

/* ============================================================================
   STRATIGRAPHY SWATCHES  (contact · surface · sequence)
   Each swatch draws what it will put on the log rather than naming it — a
   dashed contact and a solid one are one word apart and one glance apart.
   ========================================================================== */
/* Two across at the shipped width, more as the panel is widened — the same
   reason as .lithogrid. The floor is larger because a surface swatch draws a
   line AND its label, and below ~120px the label is all ellipsis. */
.stratgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 6px;
}
.stratgrid--3 { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
.stratswatch {
  display: flex; flex-direction: column; gap: 3px; align-items: stretch;
  padding: 4px; height: auto; text-align: center;
  border: var(--hairline) solid var(--border); border-radius: var(--radius-1);
  background: var(--card); cursor: pointer;
}
.stratswatch[aria-pressed="true"] { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
.stratswatch canvas { pointer-events: none; border-radius: 2px; }
.stratswatch__name {
  font: 500 10.5px/1.2 var(--font-ui); color: var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ============================================================================
   SELECTED SWATCH  (shared/ui/components.js ui.selected)
   The strip above a swatch grid saying what is currently chosen. It sits ABOVE
   the grid rather than inside it because it answers a different question — the
   pressed cell says which one, this says what it IS — and because a grid that
   scrolls takes the pressed cell off screen with it.
   ========================================================================== */
.selsw {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 6px; padding: 5px 7px;
  border: var(--hairline) solid var(--border); border-radius: var(--radius-1);
  background: var(--card); min-width: 0;
}
.selsw > canvas, .selsw__dot {
  width: 46px; height: 24px; flex: none; border-radius: 2px;
  /* Its own hairline: a pale swatch on a pale card has no edge otherwise, and
     that is exactly the one you most need to see. */
  border: var(--hairline) solid var(--border);
}
.selsw__txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.selsw__name {
  font: 600 11.5px/1.2 var(--font-ui); color: var(--heading);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.selsw__sub {
  font: 500 10px/1.2 var(--font-ui); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ============================================================================
   GTS CHIP  (shared/ui/panels.js P.surface, Age style)
   The chart colour of the selected time unit, shown beside its name — because
   the colour is half of what an age surface draws and a <select> cannot show
   it. The dot is the same swatch that will end up on the log.
   ========================================================================== */
.gtschip {
  display: flex; align-items: center; gap: 6px;
  margin-top: 6px; padding: 4px 6px;
  border: var(--hairline) solid var(--border); border-radius: var(--radius-1);
  background: var(--card);
}
.gtschip__dot {
  width: 22px; height: 12px; flex: none; border-radius: 2px;
  /* Its own hairline: the palest Pliocene yellow is invisible on a white card
     without one, and that is exactly the unit you most need to see. */
  border: var(--hairline) solid var(--border);
}
.gtschip__name { font: 600 11.5px/1.2 var(--font-ui); color: var(--body); }

/* ============================================================================
   FACIES SWATCHES  (shared/ui/panels.js P.facies)
   Each swatch draws its own texture and colours, so the row reads as the beds
   it will paint rather than as a list of names.
   ========================================================================== */
/* Three across, not two: the facies legend grew past the point where a
   two-column grid of picture-sized swatches fitted the panel. */
.faciesgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; }
.faciesswatch {
  display: flex; flex-direction: column; gap: 3px; align-items: stretch;
  padding: 5px; height: auto; text-align: left;
  border: var(--hairline) solid var(--border); border-radius: var(--radius-1);
  background: var(--card); cursor: pointer; position: relative;
}
.faciesswatch[aria-pressed="true"] { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
.faciesswatch__name {
  font: 600 11px/1.25 var(--font-ui); color: var(--heading);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.faciesswatch__gs {
  position: absolute; right: 7px; top: 7px;
  font: 600 9px/1 var(--font-mono); color: var(--heading);
  background: rgba(255,255,255,.82); border-radius: 2px; padding: 2px 3px;
}
.faciesswatch--add {
  align-items: center; justify-content: center; min-height: 62px;
  border-style: dashed; color: var(--muted);
}
.faciesswatch__plus { font: 300 26px/1 var(--font-ui); }

/* ============================================================================
   LIVE MEASUREMENT PREVIEW  (shared/ui/livemeasure.js)
   The glyph rotates with the device over the value it currently reads, so the
   tablet reads as an instrument rather than as a button that emits a number.
   ========================================================================== */
.livem { display: flex; align-items: center; gap: var(--s-5); padding: 4px 0 2px; }
.livem__dial { flex: none; display: block; }
.livem__panel { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.livem__value {
  font: 700 26px/1.15 var(--font-mono); color: var(--heading);
  letter-spacing: -.01em; word-break: break-word;
}
.livem__sub { font: var(--t-caption); color: var(--muted); }
.livem__steady {
  align-self: flex-start; margin-top: 4px;
  font: 600 11px/1 var(--font-ui); letter-spacing: .02em;
  padding: 5px 9px; border-radius: var(--radius-1);
}
.livem__steady--wait { background: var(--ground); color: var(--muted); }
.livem__steady--good { background: rgba(54,179,126,.14); color: var(--c-success); }
.livem__steady--bad  { background: rgba(255,86,48,.12); color: var(--c-danger); }

/* ============================================================================
   CROP DIALOG  (shared/ui/crop.js)
   ========================================================================== */
.dialog--crop { width: min(760px, 94vw); }
/* A picker is as wide as its longest filename and no wider — the default
   dialog stretches a three-row list across the tablet. */
.dialog--pick { width: min(400px, 92vw); }
.crop__stage {
  display: flex; align-items: center; justify-content: center;
  background: var(--ground); border-radius: var(--radius-1);
  padding: 10px; min-height: 220px;
}
/* touch-action: none for the same reason the log canvas sets it — the drag
   handles are pointer-driven, and without it the WebView claims the gesture
   as a scroll before a second event arrives. */
.crop__canvas { display: block; touch-action: none; border-radius: var(--radius-0); }
.crop__bar { display: flex; align-items: center; gap: var(--s-3); padding: 8px 2px 0; }
.crop__readout { font-size: 12px; color: var(--muted); }

/* ============================================================================
   CANVAS HOST
   ========================================================================== */
/* `touch-action: none` on the WRAPPER as well as the canvas. The pointer
   handlers are bound to the wrapper (els.canvasHost), so a gesture that begins
   on any part of it — not only over the canvas box — must reach them instead
   of being consumed as a scroll or a page pinch. This is what makes two-finger
   pan and pinch work in the Android WebView, where the default touch handling
   would otherwise claim them before the DOM sees a second pointer. */
.canvaswrap {
  position: relative; overflow: hidden; background: var(--c-paper);
  flex: 1; min-height: 0; touch-action: none;
}
.canvaswrap canvas { display: block; touch-action: none; }
.canvas-empty {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 4px; pointer-events: none;
  color: var(--muted); text-align: center;
}
.canvas-empty b { font: var(--t-title); color: var(--body); }

/* ============================================================================
   THE SIGN-IN CARD — shared with the web client's own /login page.

   Owner, 5 Aug 2026: *"i do not like current login interface, i'd preffer the
   style as we have in the web. can you sync it?"*

   These rules were written inline in `web/templates/auth/_base.html`, which is
   why Android could not have them: a Flask template is not something the APK
   ships. Lifted here so the two are literally the same stylesheet — the field
   tablet's sign-in and the desk's are one design, not two that resemble each
   other until somebody edits one.
   ========================================================================== */
.authcard {
  width: 100%; max-width: 380px; margin: 8vh auto; padding: var(--s-5);
  background: var(--card); border-radius: var(--radius-3); box-shadow: var(--e2);
}
.authcard h1 { font: var(--t-title); margin: 0 0 4px; color: var(--heading); }
.authcard .sub { color: var(--body); margin: 0 0 20px; font-size: 14px; }
.authcard label { display: block; margin: 12px 0 4px; font-size: 13px; font-weight: 600; }
.authcard input[type=text], .authcard input[type=email], .authcard input[type=password] {
  width: 100%; padding: 9px 10px; font: inherit; box-sizing: border-box;
  border: var(--hairline) solid var(--border); border-radius: var(--radius-0);
  background: var(--card); color: var(--heading);
}
.authcard .authcard__submit {
  width: 100%; margin-top: 18px; padding: 10px; font: inherit; font-weight: 600;
  border: 0; border-radius: var(--radius-1); cursor: pointer;
  background: var(--primary); color: #fff;
}
.authcard .authcard__submit:disabled { opacity: .55; cursor: default; }
.authcard .alt { margin-top: 16px; font-size: 13px; text-align: center; color: var(--body); }
.authcard .alt a, .authcard .alt button {
  color: var(--primary); background: none; border: 0; cursor: pointer; font: inherit;
}
.authcard .row { display: flex; align-items: center; gap: 8px; margin-top: 14px; font-size: 13px; }
.authcard .hint { font-size: 12px; color: var(--muted); margin-top: 4px; }
.authcard .flash { padding: 9px 11px; border-radius: var(--radius-0); margin-bottom: 14px; font-size: 13px; }
.authcard .flash.error { background: #FDEAEA; color: #8B1E1E; }

/* The password field and its Show button — the same arrangement the web uses:
   the button sits INSIDE the field's box so the input keeps the full width every
   other field has. */
.authcard .pw { position: relative; display: flex; align-items: center; }
.authcard .pw input { padding-right: 62px; }
.authcard .pw__toggle {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: auto; margin: 0; padding: 5px 9px; font: inherit; font-size: 12px;
  font-weight: 600; background: transparent; color: var(--body);
  border: 0; border-radius: var(--radius-0); cursor: pointer;
}
.authcard .pw__toggle:hover { background: var(--border); }
