/* ============================================================
   TRUE FLOW POOL SOLUTIONS — brand/components.css
   CANONICAL presentation components shared by EVERY surface:
   website visit report, visit summary email (inlined), PoolCrew,
   PoolBooks/PoolDock, PoolInspector.

   Companion to tokens.css. tokens.css owns VALUES; this file owns
   the SHAPES those values are assembled into. Never hardcode a hex
   here — every colour resolves to a token.

   The /deploy skill copies the whole brand/ folder into each app,
   so a component can only ever be changed HERE.

   Provenance: .tf-reading-* is lifted near-verbatim from the live
   website visit report's .chem-card (True Flow Website/visit/
   index.html), which is the surface everyone already approved, so
   its structure and spacing are inherited rather than reinvented.
   ONE deliberate departure: in-range tiles are tinted green here,
   where that page tints them aqua (see .tf-reading.is-ok below).
   Adopting this file will therefore change that page's appearance.
   That is intended, decided by Meesha 2026-09-01, and is the only
   such difference — everything else is a visual no-op.

   ---- LOCAL LAYER ----------------------------------------------
   Everything below paints from a --tfc-* local layer declared once
   on :root, NOT from brand tokens directly. That indirection is
   what makes a dark block possible without a dark brand palette:
   dark redefines ~20 locals, not 200 call sites. When brand tokens
   gain a real dark ramp, this local layer is what gets repointed,
   and no component rule changes.

   ---- EMAIL ----------------------------------------------------
   HTML email cannot load an external stylesheet, so the summary
   email inlines equivalent declarations generated from the same
   token values. Nothing here relies on a custom property at paint
   time, on :has(), or on a container query, so every rule can be
   flattened to an inline style. Keep it that way.
   ============================================================ */

:root {
  /* Surfaces */
  --tfc-card-bg:        var(--tf-white);
  --tfc-card-border:    var(--border, rgba(6,40,64,0.10));
  --tfc-neutral-bg:     var(--tf-ink-050, #f2fafc);
  --tfc-neutral-border: var(--border, rgba(6,40,64,0.10));
  /* TRUE neutral — for "no status applies". Distinct from the tinted neutral
     above, which is still ocean-family and reads as pale blue next to an info
     chip. See --app-text-neutral in tokens.css. */
  --tfc-grey-bg:        var(--app-neutral-bg, #f2f0ec);
  --tfc-grey-border:    var(--app-neutral-border, #e0dcd4);
  --tfc-grey-text:      var(--app-text-neutral, #4a5761);
  --tfc-accent-bg:      var(--tf-aqua-050, #e4f7fc);
  --tfc-accent-border:  var(--border-blue, rgba(26,159,196,0.22));

  /* Text */
  --tfc-text-strong:    var(--text-strong, #062840);
  --tfc-text-body:      var(--text-body, #1a4a62);
  --tfc-text-muted:     var(--text-muted, #456f83);
  --tfc-accent-ink:     var(--tf-ocean-800, #0e6f8f);

  /* Status — every component reads these, never --status-* directly,
     so dark has exactly one place to intervene. */
  --tfc-ok-bg:       var(--status-success-soft);
  --tfc-ok-border:   var(--status-success-border);
  --tfc-ok-text:     var(--status-success-text);
  --tfc-warn-bg:     var(--status-warning-soft);
  --tfc-warn-border: var(--status-warning-border);
  --tfc-warn-text:   var(--status-warning-text);
  --tfc-bad-bg:      var(--status-danger-soft);
  --tfc-bad-border:  var(--status-danger-border);
  --tfc-bad-text:    var(--status-danger-text);
  --tfc-info-bg:     var(--status-info-soft);
  --tfc-info-border: var(--status-info-border);
  --tfc-info-text:   var(--status-info-text);
}

/* ---- Dark ---------------------------------------------------------------
   NARROW BY DESIGN. brand/tokens.css is a light-only palette; this block
   keeps these components legible on a dark ground without committing the
   brand to a full dark system. Values are local to this file and are the
   thing to delete once real dark tokens land.

   Three selectors on purpose: bare :root is the light default, the media
   query covers a viewer whose OS is dark and who has made no explicit
   choice (guarded so an explicit light choice still wins), and the
   [data-theme="dark"] block lets an in-app toggle win in both directions. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --tfc-card-bg:        #0f2434;
    --tfc-card-border:    rgba(168,200,216,0.18);
    --tfc-neutral-bg:     #16303f;
    --tfc-neutral-border: rgba(168,200,216,0.16);
    --tfc-grey-bg:        #1b2a33;
    --tfc-grey-border:    rgba(190,200,208,0.20);
    --tfc-grey-text:      #b2bfc9;
    --tfc-accent-bg:      #10394a;
    --tfc-accent-border:  rgba(125,212,234,0.28);

    --tfc-text-strong:    #eaf6fb;
    --tfc-text-body:      #cfe6ef;
    --tfc-text-muted:     #9ab8c7;
    --tfc-accent-ink:     #7dd4ea;

    --tfc-ok-bg:       #12332a;  --tfc-ok-border:   #2f6b4d;  --tfc-ok-text:   #79d3a3;
    --tfc-warn-bg:     #3a2a12;  --tfc-warn-border: #7d5c22;  --tfc-warn-text: #f0be6a;
    --tfc-bad-bg:      #3a1c1c;  --tfc-bad-border:  #7d3535;  --tfc-bad-text:  #f19a9a;
    --tfc-info-bg:     #123441;  --tfc-info-border: #2c6478;  --tfc-info-text: #86cfe4;
  }
}
:root[data-theme="dark"] {
  --tfc-card-bg:        #0f2434;
  --tfc-card-border:    rgba(168,200,216,0.18);
  --tfc-neutral-bg:     #16303f;
  --tfc-neutral-border: rgba(168,200,216,0.16);
  --tfc-grey-bg:        #1b2a33;
  --tfc-grey-border:    rgba(190,200,208,0.20);
  --tfc-grey-text:      #b2bfc9;
  --tfc-accent-bg:      #10394a;
  --tfc-accent-border:  rgba(125,212,234,0.28);

  --tfc-text-strong:    #eaf6fb;
  --tfc-text-body:      #cfe6ef;
  --tfc-text-muted:     #9ab8c7;
  --tfc-accent-ink:     #7dd4ea;

  --tfc-ok-bg:       #12332a;  --tfc-ok-border:   #2f6b4d;  --tfc-ok-text:   #79d3a3;
  --tfc-warn-bg:     #3a2a12;  --tfc-warn-border: #7d5c22;  --tfc-warn-text: #f0be6a;
  --tfc-bad-bg:      #3a1c1c;  --tfc-bad-border:  #7d3535;  --tfc-bad-text:  #f19a9a;
  --tfc-info-bg:     #123441;  --tfc-info-border: #2c6478;  --tfc-info-text: #86cfe4;
}

/* ============================================================
   1. READING CARD — a single water-chemistry reading
   ------------------------------------------------------------
   <div class="tf-reading-grid">
     <div class="tf-reading is-ok">          (or .is-off / .is-untargeted)
       (.is-untargeted omits the tf-reading-target line entirely)
       <p class="tf-reading-label">Free chlorine</p>
       <p class="tf-reading-value">2.4 ppm</p>
       <p class="tf-reading-target">Target 2 - 4 ppm</p>
       <div class="tf-reading-status"><svg class="tf-ico">..</svg><span>In range</span></div>
     </div>
   </div>

   THREE states, not two. .is-untargeted is for a reading taken where
   no target band is set (the bands are still pending approval, so the
   inspection report has values and no verdict). It must read as
   deliberate and calm — a recorded fact — never as broken or alarming,
   which is why it takes the neutral surface and NOT an amber one.
   ============================================================ */
.tf-reading-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--space-3, 12px);
  margin: 0;
}
.tf-reading {
  background: var(--tfc-neutral-bg);
  border: 1px solid var(--tfc-neutral-border);
  border-radius: var(--radius-md, 10px);
  padding: 0.85rem 0.95rem;
}
/* In range is tinted GREEN, matching the amber of out-of-range, so the tint
   itself carries the verdict and the grid can be read without looking at the
   status line. This REPLACES the earlier aqua wash the visit report used, and
   is therefore the one place adopting this file will visibly change that page.
   Decided by Meesha 2026-09-01. */
.tf-reading.is-ok {
  background: var(--tfc-ok-bg);
  border-color: var(--tfc-ok-border);
}
.tf-reading.is-off {
  background: var(--tfc-warn-bg);
  border-color: var(--tfc-warn-border);
}
/* NOT text-transform: uppercase — that renders "pH" as "PH", which is simply
   the wrong word. Labels arrive already cased correctly from the server.
   Sized at --fs-sm, not --fs-eyebrow: the eyebrow member is documented in
   tokens.css as being for UPPERCASE tracked labels only, where the caps make up
   the lost x-height, and these labels are deliberately not uppercase. At 0.72rem
   in mixed case the label read as small print rather than as the name of the
   thing being measured. */
.tf-reading-label {
  font-size: var(--fs-sm, 0.875rem);
  font-weight: 700;
  color: var(--tfc-text-muted);
  margin: 0 0 0.35rem;
}
.tf-reading-value {
  font-family: var(--font-display, Georgia, serif);
  font-size: 1.45rem;
  font-weight: 700;
  line-height: 1.1;
  color: var(--tfc-text-strong);
  margin: 0;
}
.tf-reading-target {
  font-size: var(--fs-xs, 0.8125rem);
  color: var(--tfc-text-muted);
  margin: 0.15rem 0 0.45rem;
}
.tf-reading-status {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-size: var(--fs-xs, 0.8125rem);
  font-weight: 700;
  line-height: 1.3;
}
.tf-reading-status .tf-ico { width: var(--icon-sm, 16px); height: var(--icon-sm, 16px); flex: none; }
/* An untargeted reading renders NO target line at all. "No target set" told the
   reader nothing they could act on and drew the eye to an absence; the tile now
   simply states the value it recorded. This margin replaces the spacing that
   line used to occupy so untargeted tiles stay the same height as their
   neighbours in the grid. */
.tf-reading.is-untargeted .tf-reading-value { margin-bottom: 0.6rem; }
.tf-reading.is-ok          .tf-reading-status { color: var(--tfc-ok-text); }
.tf-reading.is-off         .tf-reading-status { color: var(--tfc-warn-text); }
/* Muted, not coloured: an untargeted reading is making no claim. */
.tf-reading.is-untargeted  .tf-reading-status { color: var(--tfc-text-muted); font-weight: 600; }

/* ============================================================
   2. FACT CARD — one pool-overview fact (shape, size, depth, spa, surface)
   ------------------------------------------------------------
   <div class="tf-fact-grid">
     <div class="tf-fact">
       <svg class="tf-fact-ico">..</svg>
       <p class="tf-fact-label">Shape</p>
       <p class="tf-fact-value">Freeform</p>
     </div>
   </div>
   Replaces the flat label/value list. Same tile language as the reading
   card on purpose, so a report reads as one system top to bottom.
   ============================================================ */
.tf-fact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--space-3, 12px);
  margin: 0;
}
.tf-fact {
  background: var(--tfc-neutral-bg);
  border: 1px solid var(--tfc-neutral-border);
  border-radius: var(--radius-md, 10px);
  padding: 0.85rem 0.95rem;
}
.tf-fact-ico {
  width: var(--icon-lg, 24px);
  height: var(--icon-lg, 24px);
  color: var(--tfc-accent-ink);
  display: block;
  margin: 0 0 0.55rem;
}
.tf-fact-label {
  font-size: var(--fs-eyebrow, 0.72rem);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--tfc-text-muted);
  margin: 0 0 0.2rem;
}
.tf-fact-value {
  font-size: var(--fs-md, 1.0625rem);
  font-weight: 700;
  line-height: 1.3;
  color: var(--tfc-text-strong);
  margin: 0;
}
/* An unknown fact is a blank, not a failure. */
.tf-fact.is-unknown .tf-fact-value { font-weight: 600; color: var(--tfc-text-muted); }

/* ============================================================
   3. CONDITION PILL — equipment condition
   ------------------------------------------------------------
   <span class="tf-pill tf-pill-operating">Operating</span>

   The severity ladder matters more than the individual colours. Before
   this file, everything that was not "operating" rendered amber, so a
   20-year-old pool with ordinary aging read to a buyer's agent as a page
   of problems. Typical wear is normal for a pool's age and is NOT a
   warning, so it takes calm blue and leaves amber to mean "look at this".

     operating       green    working as intended
     typical-wear    blue     normal for its age, no action
     excessive-wear  amber    worn beyond its age, worth a look
     defective       red      broken
     missing         red, dashed outline — see below
     not-present     grey     never existed here, not a fault

   MISSING is distinguished from DEFECTIVE by SHAPE, not by hue: a dashed
   outline over the page ground reads as a hole where something should be,
   which is exactly what "missing" means, while defective is a solid red
   fill. Two reasons this beats giving missing its own colour. Brand coral
   is explicitly not a status colour (tokens.css: "coral says START HERE,
   the thing coral points at is usually perfectly healthy"), so spending it
   here would break the one-to-two-moments-per-page rule and blunt red.
   And the remaining status hues are already carrying meanings on this same
   ladder, so a fourth hue would have to be borrowed from one of them.
   ============================================================ */
.tf-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-pill, 999px);
  border: 1px solid transparent;
  font-size: var(--fs-xs, 0.8125rem);
  font-weight: 700;
  line-height: 1.4;
  white-space: nowrap;
}
/* The canonical glyph. True Flow icons are STROKED OUTLINES, never filled --
   an SVG with no rule paints solid black, which is how the check in a reading
   tile briefly rendered as a filled arrowhead on 2026-09-02 after the consuming
   page's own .ico rule was removed in favour of this file. Owning the size but
   not the paint left the component half-defined. */
.tf-ico {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.tf-pill .tf-ico { width: var(--icon-sm, 16px); height: var(--icon-sm, 16px); flex: none; }
/* Status dot. Carried up from the inspection report's local .pill, which is the
   version Meesha approved on 2026-09-01, when that surface became the first
   consumer of this file. It lives HERE rather than as a local override there,
   because a component the consumer has to patch is the drift this file exists
   to end. Unconditional rather than suppressed by :has() when an icon is
   present: this file's header requires every rule stay flattenable to an inline
   style for the summary email, and :has() is named there as forbidden. A
   surface wanting a glyph instead of the dot should add a modifier, not a
   selector the email cannot carry. */
.tf-pill::before { content: ""; width: 8px; height: 8px; border-radius: 50%;
                   background: currentColor; flex: none; }

.tf-pill-operating      { background: var(--tfc-ok-bg);   border-color: var(--tfc-ok-border);   color: var(--tfc-ok-text); }
.tf-pill-typical-wear   { background: var(--tfc-info-bg); border-color: var(--tfc-info-border); color: var(--tfc-info-text); }
.tf-pill-excessive-wear { background: var(--tfc-warn-bg); border-color: var(--tfc-warn-border); color: var(--tfc-warn-text); }
.tf-pill-defective      { background: var(--tfc-bad-bg);  border-color: var(--tfc-bad-border);  color: var(--tfc-bad-text); }
.tf-pill-missing        { background: transparent; border-style: dashed; border-color: var(--tfc-bad-text); color: var(--tfc-bad-text); }
.tf-pill-not-present    { background: var(--tfc-grey-bg); border-color: var(--tfc-grey-border); color: var(--tfc-grey-text); }

/* ============================================================
   4. DOSE LIST — what was added to the pool
   ------------------------------------------------------------
   <ul class="tf-dose-list">
     <li class="tf-dose">
       <svg class="tf-dose-ico">..</svg>
       <div class="tf-dose-main">
         <p class="tf-dose-name">Liquid chlorine</p>
         <p class="tf-dose-note">Brings free chlorine back into range</p>
       </div>
       <span class="tf-dose-amount">32 oz</span>
     </li>
   </ul>
   <p class="tf-dose-total">3 tabs added</p>

   Presentation only. The data behind it is phase 3.
   ============================================================ */
.tf-dose-list { list-style: none; padding: 0; margin: 0; }
.tf-dose {
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--tfc-card-border);
}
.tf-dose:first-child { padding-top: 0; }
.tf-dose:last-child  { border-bottom: none; padding-bottom: 0; }
.tf-dose-ico {
  width: var(--icon-lg, 24px);
  height: var(--icon-lg, 24px);
  flex: none;
  margin-top: 0.05rem;
  color: var(--tfc-accent-ink);
}
.tf-dose-main { flex: 1 1 auto; min-width: 0; }
.tf-dose-name {
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 700;
  color: var(--tfc-text-strong);
  margin: 0;
}
.tf-dose-note {
  font-size: var(--fs-sm, 0.875rem);
  color: var(--tfc-text-muted);
  margin: 0.1rem 0 0;
  line-height: 1.4;
}
/* Sized to its own content so a long product name cannot squeeze the
   amount into a wrap. */
.tf-dose-amount {
  flex: none;
  font-family: var(--font-display, Georgia, serif);
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 700;
  color: var(--tfc-text-strong);
  white-space: nowrap;
  padding-top: 0.05rem;
}
.tf-dose-total {
  margin: var(--space-3, 12px) 0 0;
  padding: 0.55rem 0.8rem;
  background: var(--tfc-accent-bg);
  border-radius: var(--radius-md, 10px);
  font-size: var(--fs-sm, 0.875rem);
  font-weight: 700;
  color: var(--tfc-accent-ink);
}
.tf-dose-empty {
  margin: 0;
  font-size: var(--fs-sm, 0.875rem);
  color: var(--tfc-text-muted);
}
