/* public/assets/tk/components.css — Tudor Heritage components */
.tk-icon { display: inline-block; vertical-align: middle; }

/* Screen-reader-only: for labels that would be redundant visually but are still
   required programmatically (e.g. the unit <select> inside a .tk-inputgroup). */
.tk-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* Buttons */
.tk-btn { font-family: var(--tk-font-display); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; font-size: .72rem; padding: .72rem 1.15rem; border-radius: var(--tk-radius);
  display: inline-flex; align-items: center; gap: .5rem; cursor: pointer; border: 1px solid transparent;
  transition: transform .2s var(--tk-ease), background-color .2s var(--tk-ease); }
.tk-btn:hover { transform: translateY(-1px); }
.tk-btn--solid { background: var(--tk-burgundy); color: #fbeef0; box-shadow: var(--tk-shadow-burg); }
.tk-btn--solid:hover { background: var(--tk-burgundy-hover); }
/* background:transparent is load-bearing, not tidiness. --solid sets a background and
   --link sets `none`, but --ghost set neither — so on an <a> it inherited the page, while
   on a <button> it took the UA's ButtonFace: measured rgb(239,239,239) behind --tk-text
   ivory, about 1.05:1. Every <button class="tk-btn tk-btn--ghost"> on the site was a
   near-white box with near-white text (81 of them across 20 templates; five on
   /community-tools alone). Found on the registry detail view, fixed here because that is
   where it belongs. */
.tk-btn--ghost { background: transparent;
  border-color: color-mix(in srgb, var(--tk-text) 28%, transparent); color: var(--tk-text); }
.tk-btn--ghost:hover { border-color: var(--tk-burgundy); color: var(--tk-accent-text); }
.tk-btn--block { display: flex; width: 100%; justify-content: center; }
/* De-emphasised inline text action (e.g. "Recalibrate", "Remove Watch 2"). */
.tk-btn--link { background: none; border-color: transparent; padding: 0; color: var(--tk-text-muted); }
.tk-btn--link:hover { color: var(--tk-accent-text); transform: none; text-decoration: underline; }
/* .tk-btn sets display:inline-flex, and an author rule beats the UA's [hidden]{display:none}
   — so every progressively-revealed button on the site (the Web Share trigger on
   /install-app is the first) shipped VISIBLE with `hidden` set. Same defect, same fix, as
   .tk-reg__stateic[hidden] and .tk-reg__proofbtns[hidden]. */
.tk-btn[hidden] { display: none; }

/* Badges */
.tk-badge { font-family: var(--tk-font-body); font-size: .625rem; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; padding: .22em .6em; border-radius: 4px; display: inline-block; border: 1px solid transparent; }
.tk-badge--stock { background: color-mix(in srgb, var(--tk-success) 20%, transparent);
  color: color-mix(in srgb, var(--tk-success) 65%, var(--tk-text)); border-color: color-mix(in srgb, var(--tk-success) 50%, transparent); }
.tk-badge--srp { background: color-mix(in srgb, var(--tk-burgundy) 22%, transparent);
  color: var(--tk-accent-text); border-color: color-mix(in srgb, var(--tk-burgundy) 50%, transparent); }
.tk-badge--sold { background: var(--tk-burgundy); color: #fff; }
.tk-badge--cond { background: color-mix(in srgb, var(--tk-lume) 16%, transparent); color: var(--tk-text);
  border-color: color-mix(in srgb, var(--tk-lume) 40%, var(--tk-border)); }
.tk-badge--feat { background: linear-gradient(#c79a45, var(--tk-gold)); color: #2a2110; }
/* "Just In". Solid lume with dark ink, so it reads the same in both themes the way
   --sold and --feat do. Deliberately not --cond's lume tint: this is a merchandising
   signal, not a spec chip, and the two must not look alike. */
.tk-badge--new { background: var(--tk-lume); color: #2a2620; }

/* Price */
.tk-price { font-family: var(--tk-font-body); font-weight: 600; color: var(--tk-text); }
.tk-price s { opacity: .45; font-weight: 400; margin-left: .35rem; }

/* Section header */
.tk-section-head { display: flex; align-items: baseline; gap: .75rem; margin-bottom: var(--tk-space-4);
  flex-wrap: wrap; }
.tk-section-head__title { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; font-size: var(--tk-fs-h2); margin: 0; }
.tk-section-head__action { margin-left: auto; font-size: .625rem; text-transform: uppercase;
  letter-spacing: .1em; color: var(--tk-accent-text); white-space: nowrap; flex-shrink: 0; }

/* Breadcrumb */
.tk-breadcrumb { font-size: .625rem; letter-spacing: .08em; text-transform: uppercase; color: var(--tk-text-muted);
  padding: var(--tk-space-4) 0; }
.tk-breadcrumb a { color: var(--tk-text-muted); }
.tk-breadcrumb a:hover { color: var(--tk-accent-text); }

/* Watch card */
.tk-card { background: var(--tk-surface); border: 1px solid var(--tk-border); border-radius: var(--tk-radius-lg);
  overflow: hidden; transition: transform .25s var(--tk-ease), border-color .25s var(--tk-ease); }
.tk-card:hover { transform: translateY(-4px); border-color: var(--tk-burgundy); }
.tk-card__frame { display: block; position: relative;
  background: radial-gradient(120% 90% at 50% 32%, #2b2827, #141312 78%);
  box-shadow: inset 0 -34px 44px -20px rgba(0,0,0,.75); }
body.tk-light .tk-card__frame { background: radial-gradient(120% 90% at 50% 32%, #fbf9f4, #ece4d4 90%);
  box-shadow: inset 0 0 0 1px var(--tk-border); }
/* Every card image. Defined once here rather than inline in six templates,
   because an inline style beats any stylesheet rule -- which is exactly why
   the 4:5 ratio could not be changed from CSS before.
   1/1 matches the source files (all 1920x1920), so this REMOVES the 20% crop
   the 4:5 box was applying to every photo. */
.tk-card__img { aspect-ratio: 1 / 1; object-fit: cover; width: 100%; display: block; }
.tk-card__flag { position: absolute; top: .5rem; left: .5rem; z-index: 2; }
.tk-card__body { padding: .75rem .8rem .9rem; }
.tk-card__title { font-size: .875rem; margin: 0; }
.tk-card__title a { color: var(--tk-text); }
.tk-card__ref { font-size: .625rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--tk-text-muted); margin: .2rem 0 .5rem; }
.tk-card__badges { display: flex; gap: .3rem; margin-top: .5rem; flex-wrap: wrap; }
.tk-card-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--tk-space-4); }
@media (max-width: 900px) { .tk-card-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Column-count modifiers, plus .tk-tilerow for rows of short text tiles. These exist
   because home.php was setting grid-template-columns as an INLINE style, which no @media
   rule can beat — so those rows kept their desktop column count all the way down to a
   375px phone. A bare `1fr` is minmax(auto, 1fr), so the tracks then bottomed out at their
   content's min-content width instead of shrinking, the row overflowed, and the document
   ended up 620px wide inside a 375px viewport. That also dragged the fixed concierge
   bubble off to the right: iOS expands the layout viewport that fixed elements anchor to
   out to the document's scroll width, so the bubble tracked the overflow, it did not
   cause it. minmax(0, …) on every track here for the same reason as .tk-reg__grid. */
.tk-card-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 900px) { .tk-card-grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* Drops to one column earlier than the image-card rows above: these tiles carry a
   description and a full-width CTA, which two 155px columns cannot hold legibly. */
@media (max-width: 620px) { .tk-card-grid--tiles { grid-template-columns: minmax(0, 1fr); } }

/* --- Mobile card layout -------------------------------------------------
   Cards in a row used to differ by up to 43px, because .tk-card was
   display:block: nothing equalised height and nothing pinned the badge row.
   A 2-line title added ~19px and a second badge wrapped to its own row for
   another ~24px, so the tallest card ate 48% of a 375px screen.

   flex:1 on the body plus margin-top:auto on the badges is what does the
   work — the grid stretches the cell, the body absorbs the slack, and the
   badge row lands on the floor of every card whatever the title length.
   The STRUCTURE below is unscoped, so rows are flush at every width. It was
   phone-only at first, on a "desktop must not change" constraint; once mobile
   shipped, desktop measured a 30px worst-row spread and the constraint was
   lifted deliberately. Only the phone TYPOGRAPHY stays behind the breakpoint.

   Children are named rather than using `> *`: the same grid class also carries
   non-card children elsewhere (the Community Tools tiles, /reviews thumbnails),
   which a blanket `display: flex` would have reached. */
.tk-card-grid > .tk-card,
.tk-card-grid > .tk-reveal { display: flex; }
.tk-card { display: flex; flex-direction: column; width: 100%; }
.tk-card__body { display: flex; flex-direction: column; flex: 1; }
.tk-card__badges { margin-top: auto; }

@media (max-width: 900px) {
  .tk-card__body { padding: .55rem .6rem .65rem; }

  /* Reserve two lines so a 1-line and a 2-line title occupy the same box.
     Without the reserve the cards would be equal height but the price and
     badges would still sit at different offsets. */
  .tk-card__title { font-size: .8125rem; line-height: 1.25; min-height: 2.5em;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden; }

  /* Editorial cards (home page's Under The Loupe) hand-roll .tk-card markup but carry
     headline-length titles, which the 2-line watch-card clamp cut mid-sentence. */
  .tk-card--editorial .tk-card__title { -webkit-line-clamp: unset; display: block; min-height: 0; }
  .tk-card__ref { font-size: .6563rem; letter-spacing: .06em; margin: .1rem 0 .3rem; }
  .tk-price { font-size: .9rem; line-height: 1.35; }

  /* nowrap so the struck SRP wraps as a whole unit. At this size it otherwise
     breaks INSIDE the price -- "PHP 212,000 PHP" / "266,500" -- which reads as
     a corrupted number rather than a discount. */
  .tk-price s { font-size: .75rem; display: inline-block; white-space: nowrap; }

  /* Not a smaller font -- the badge type is 10.5px, larger than the 10px desktop
     base. It's the tighter padding and letter-spacing below that keep the common
     "Below SRP" + "Just In" pair on one row at a 155px card width. The home page's
     hand-rolled Certified + condition-grade pair is longer and still wraps to two
     rows here regardless. */
  .tk-card__badges { gap: .25rem; padding-top: .4rem; }
  .tk-card__badges .tk-badge { font-size: .6563rem; padding: .16rem .3rem; letter-spacing: .02em; }
}

.tk-tilerow { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--tk-space-4); }
@media (max-width: 720px) { .tk-tilerow { grid-template-columns: minmax(0, 1fr); } }

/* Shield mark */
.tk-shield { width: 15px; height: 18px; background: var(--tk-burgundy); display: inline-block;
  clip-path: polygon(50% 0,100% 22%,100% 62%,50% 100%,0 62%,0 22%);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--tk-lume) 30%, transparent); }

/* Nav */
.tk-nav { position: sticky; top: 0; z-index: 50; background: color-mix(in srgb, var(--tk-page) 92%, transparent);
  backdrop-filter: blur(8px); border-bottom: 1px solid var(--tk-border); }
.tk-nav__bar { display: flex; align-items: center; gap: var(--tk-space-6); padding-block: .7rem; }
.tk-nav__brand { display: flex; align-items: center; gap: .5rem; font-family: var(--tk-font-display);
  font-weight: 800; letter-spacing: .1em; font-size: .8rem; }
.tk-nav .tk-nav__logo { height: 36px; width: auto; }
.tk-nav__links { display: flex; gap: 1.1rem; margin-left: auto; }
.tk-nav__link { font-size: .7rem; letter-spacing: .05em; text-transform: uppercase; color: var(--tk-text-muted);
  padding-block: .3rem; border-bottom: 2px solid transparent; }
.tk-nav__link:hover { color: var(--tk-text); }
.tk-nav__link[aria-current="page"] { color: var(--tk-text); border-bottom-color: var(--tk-burgundy); }
.tk-nav__actions { display: flex; align-items: center; gap: .9rem; }
.tk-nav__ic { color: var(--tk-text-muted); background: none; border: 0; cursor: pointer; padding: 0; display: inline-flex; }
.tk-nav__ic:hover { color: var(--tk-text); }
.tk-nav__burger { display: none; }
@media (max-width: 860px) {
  .tk-nav__links, .tk-nav__ic--account { display: none; }
  .tk-nav__burger { display: inline-flex; }
}
@media (max-width: 900px) {
  /* 20x20 glyphs with a 44x44 hit area. The negative margin cancels the padding
     so the glyph doesn't shift, but the wider gap below is still needed: without
     it the two 44x44 hit boxes overlap by 9.6px, leaving the left icon's real
     tap width at ~34px. Both rules stay together in this block -- splitting them
     re-opens that overlap. */
  .tk-nav__actions { gap: 1.6rem; }
  .tk-nav__ic { padding: 12px; margin: -12px; }
}

/* Drawer (mobile <dialog>) */
.tk-drawer { margin: 0 0 0 auto; height: 100dvh; max-height: 100dvh; width: min(80vw, 320px); border: 0;
  background: var(--tk-surface); color: var(--tk-text); padding: 0;
  border-left: 1px solid var(--tk-border); }
.tk-drawer::backdrop { background: rgba(0,0,0,.6); }
.tk-drawer__panel { height: 100%; padding: 1.25rem; display: flex; flex-direction: column; }
.tk-drawer__head { display: flex; align-items: center; justify-content: space-between;
  padding-bottom: 1rem; border-bottom: 1px solid var(--tk-border); }
.tk-drawer__links { display: block; margin-top: .5rem; }
.tk-drawer__link { display: flex; align-items: center; justify-content: space-between;
  font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  font-size: 1rem; padding: .95rem 0; border-bottom: 1px solid var(--tk-border); color: var(--tk-text); }
.tk-drawer__link[aria-current="page"] { color: var(--tk-accent-text); }
.tk-drawer__chev { color: var(--tk-text-muted); }
.tk-drawer__cta { margin-top: auto; }

/* Hero */
.tk-hero { padding-block: clamp(2.5rem, 7vw, 4.5rem); border-bottom: 1px solid var(--tk-border); }
.tk-hero__inner { display: grid; gap: var(--tk-space-6); }
.tk-hero__eyebrow { margin: 0 0 var(--tk-space-3); }
.tk-hero__title { font-family: var(--tk-font-display); font-weight: 800; font-size: var(--tk-fs-display);
  line-height: .96; margin: 0; }
.tk-hero__sub { color: var(--tk-text-muted); max-width: 42ch; margin: var(--tk-space-4) 0 var(--tk-space-6); }
.tk-hero__ctas { display: flex; gap: var(--tk-space-3); flex-wrap: wrap; }
/* Once the pair wraps it looks ragged -- 185px above 157px, left-aligned.
   Full width is the conventional stacked treatment and enlarges the tap area
   at the same time.

   The breakpoint MUST sit above the width where the pair actually wraps, or
   there is a window that stacks the buttons without widening them — which is
   the ragged look this rule exists to remove. The two labels plus the gap need
   354px and .tk-container eats 48px, so they wrap below 402px. A first attempt
   used a round 400px, i.e. 2px BELOW the wrap point, which left exactly one
   broken width: 402px — the iPhone 16 Pro, reported from a real device.

   480px is chosen to be safely above it: it covers every current iPhone (the
   Pro Max is 440) with 78px of headroom, so the rule cannot silently desync if
   a label gets longer. Above 480px the pair fits comfortably side by side. */
@media (max-width: 480px) {
  .tk-hero__ctas > .tk-btn { flex: 1 1 100%; }
}
.tk-hero__ctas > .tk-btn { min-height: 44px; }

/* Footer */
.tk-footer { background: color-mix(in srgb, var(--tk-page) 88%, #000); border-top: 1px solid var(--tk-border);
  margin-top: var(--tk-space-12); padding-block: var(--tk-space-8) var(--tk-space-6); }
.tk-footer__grid { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr 1fr; gap: var(--tk-space-6); }
@media (max-width: 780px) { .tk-footer__grid { grid-template-columns: 1fr 1fr; } }
.tk-footer__logo { height: 40px; width: auto; margin-bottom: var(--tk-space-3); }
.tk-footer__brand p { color: var(--tk-text-muted); font-size: .8rem; max-width: 28ch; }
.tk-footer__ct { font-family: var(--tk-font-display); text-transform: uppercase; letter-spacing: .12em;
  font-size: .625rem; color: var(--tk-text); margin: 0 0 var(--tk-space-3); }
.tk-footer__col a { display: block; color: var(--tk-text-muted); font-size: .8rem; padding-block: .25rem; }
.tk-footer__col a:hover { color: var(--tk-accent-text); }
.tk-footer__base { display: flex; justify-content: space-between; gap: var(--tk-space-4); flex-wrap: wrap;
  color: var(--tk-text-muted); font-size: .7rem; border-top: 1px solid var(--tk-border);
  margin-top: var(--tk-space-6); padding-top: var(--tk-space-4); }

/* Cookie consent */
.tk-cookie { position: fixed; left: 0; right: 0; bottom: 0; z-index: 60; background: var(--tk-surface);
  border-top: 1px solid var(--tk-border); padding: var(--tk-space-4) var(--tk-space-6);
  display: flex; align-items: center; gap: var(--tk-space-4); flex-wrap: wrap;
  box-shadow: 0 -10px 30px rgba(0,0,0,.3); }
.tk-cookie[hidden] { display: none; }
.tk-cookie__text { font-size: .8rem; color: var(--tk-text-muted); margin: 0; flex: 1; min-width: 240px; }
.tk-cookie__text a { color: var(--tk-accent-text); text-decoration: underline; }
.tk-cookie__actions { display: flex; gap: var(--tk-space-2); }

/* Card controls (compare + bookmark) — decluttered: reveal on hover, always shown on touch */
.tk-card { position: relative; }
.tk-card__controls { position: absolute; top: .5rem; right: .5rem; z-index: 3; display: flex;
  flex-direction: column; gap: .4rem; align-items: flex-end; opacity: 0; transform: translateY(-4px);
  transition: opacity .2s var(--tk-ease), transform .2s var(--tk-ease); }
.tk-card:hover .tk-card__controls, .tk-card:focus-within .tk-card__controls { opacity: 1; transform: none; }
@media (hover: none) { .tk-card__controls { opacity: 1; transform: none; } }
.tk-card__bookmark { width: 32px; height: 32px; border-radius: 50%; background: rgba(0,0,0,.55);
  border: 1px solid var(--tk-border); color: #fff; display: grid; place-items: center; cursor: pointer;
  backdrop-filter: blur(2px); }
.tk-card__bookmark svg { width: 16px; height: 16px; }
.tk-card__bookmark.active svg { fill: var(--tk-burgundy); stroke: var(--tk-burgundy); }
.tk-card__compare { display: inline-flex; align-items: center; gap: .35rem; background: rgba(0,0,0,.55);
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius-pill); padding: .25rem .6rem;
  font-size: .6rem; text-transform: uppercase; letter-spacing: .08em; color: #fff; cursor: pointer;
  backdrop-filter: blur(2px); }
.tk-card__compare input { accent-color: var(--tk-burgundy); margin: 0; }
@media (max-width: 900px) {
  .tk-card__bookmark { width: 44px; height: 44px; }
  .tk-card__bookmark svg { width: 18px; height: 18px; }
}

/* On phones the Compare label collapses to its checkbox: at 155px of card
   width the full pill sits directly on the watch dial. The label returns at
   601px. Note .tk-card__controls already has @media (hover:none){opacity:1},
   so these controls are permanently visible on touch -- this changes what the
   card looks like on a phone, not just a hover state. */
@media (max-width: 600px) {
  .tk-card__compare { font-size: 0; padding: .3rem; min-width: 44px; min-height: 44px;
    gap: 0; justify-content: center; }
  .tk-card__compare input { font-size: 1rem; width: 18px; height: 18px; }
}

/* The compare label is 9.6px from 601px up. Below 601px it is deliberately collapsed to
   font-size:0 with the checkbox carrying the aria-label, so the floor does not apply
   there. This raises it to the 10px floor for the tablet band only — desktop card
   rendering is frozen for this change, and raising it at >=901px widens the pill ~3px. */
@media (min-width: 601px) and (max-width: 900px) {
  .tk-card__compare { font-size: .625rem; }
}

/* Listing Soon teaser */
.tk-teaser__frame img { filter: blur(10px); transform: scale(1.05); }
.tk-teaser__body { filter: blur(4px); user-select: none; }
.tk-teaser__badge { position: absolute; inset: 0; margin: auto; width: fit-content; height: fit-content;
  z-index: 2; background: var(--tk-lume); color: #1a1408; font-family: var(--tk-font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .1em; font-size: .7rem; padding: .4rem .8rem; border-radius: 4px; }

/* Filter sidebar */
.tk-filters__panel { background: var(--tk-surface); border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius-lg); padding: var(--tk-space-6); }
.tk-filters__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--tk-space-4); }
.tk-filters__title { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; font-size: .8rem; }
.tk-filters__close { display: none; background: none; border: 0; color: var(--tk-text-muted); cursor: pointer; }
.tk-filters__form { display: flex; flex-direction: column; gap: var(--tk-space-4); }
.tk-field { display: flex; flex-direction: column; gap: .35rem; }
.tk-field > span, .tk-field > label { font-size: .625rem; text-transform: uppercase; letter-spacing: .1em; color: var(--tk-text-muted); }
.tk-field input, .tk-field select { background: var(--tk-page); border: 1px solid var(--tk-border);
  color: var(--tk-text); border-radius: var(--tk-radius); padding: .6rem .7rem; font: inherit; font-size: .85rem; }
.tk-field select { appearance: none; background-image: linear-gradient(45deg, transparent 50%, var(--tk-text-muted) 50%),
  linear-gradient(135deg, var(--tk-text-muted) 50%, transparent 50%); background-position: right 1rem center, right .75rem center;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; padding-right: 2rem; }
/* .tk-field skinned `input` and `select` but never `textarea`, so three pages in a row
   (trade proposal, document generator, and the trade calculator's notes box) each had to
   re-declare the same properties locally, and /contact would have been the fourth.
   Declared once, here; every existing local rule is more specific and still wins, so
   nothing already shipped changes. */
.tk-field textarea { background: var(--tk-page); border: 1px solid var(--tk-border);
  color: var(--tk-text); border-radius: var(--tk-radius); padding: .6rem .7rem;
  font: inherit; font-size: .85rem; width: 100%; resize: vertical; }
.tk-filters__clear { text-align: center; font-size: .75rem; color: var(--tk-text-muted); padding: .3rem; }
.tk-filters__clear:hover { color: var(--tk-accent-text); }

/* Bottom-sheet (mobile) — desktop keeps the element static */
.tk-no-scroll { overflow: hidden; }
.tk-sheet__scrim { display: none; }
@media (max-width: 900px) {
  .tk-sheet { position: fixed; inset: 0; z-index: 80; pointer-events: none; }
  .tk-sheet .tk-sheet__scrim { display: block; position: absolute; inset: 0; background: rgba(0,0,0,.6);
    opacity: 0; transition: opacity .25s var(--tk-ease); }
  .tk-sheet .tk-filters__panel { position: absolute; left: 0; right: 0; bottom: 0; max-height: 85vh; overflow: auto;
    border-radius: var(--tk-radius-lg) var(--tk-radius-lg) 0 0; transform: translateY(100%);
    transition: transform .3s var(--tk-ease); }
  .tk-sheet .tk-filters__close { display: inline-flex; }
  .tk-sheet.is-open { pointer-events: auto; }
  .tk-sheet.is-open .tk-sheet__scrim { opacity: 1; }
  .tk-sheet.is-open .tk-filters__panel { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .tk-sheet .tk-sheet__scrim, .tk-sheet .tk-filters__panel { transition: none; }
}

/* Listing layout */
.tk-listing { display: grid; grid-template-columns: 260px 1fr; gap: var(--tk-space-6); align-items: start; }
@media (min-width: 901px) { .tk-listing .tk-filters { position: sticky; top: 80px; } }
.tk-listing__grid { grid-template-columns: repeat(3, 1fr); }
.tk-filters__toggle { display: none; }
.tk-listing__empty { text-align: center; padding: var(--tk-space-12) 0; }
@media (max-width: 900px) {
  .tk-listing { grid-template-columns: 1fr; }
  .tk-listing__grid { grid-template-columns: repeat(2, 1fr); }
  .tk-filters__toggle { display: inline-flex; }
}
/* Floating compare button */
.tk-compare-btn { position: fixed; left: 50%; transform: translateX(-50%); bottom: 24px; z-index: 70;
  background: var(--tk-burgundy); color: #fbeef0; font-family: var(--tk-font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; font-size: .75rem; padding: .8rem 1.4rem;
  border-radius: var(--tk-radius-pill); box-shadow: var(--tk-shadow-burg); }
.tk-compare-btn[hidden] { display: none; }

/* Gallery */
.tk-gallery__stage { position: relative; background: radial-gradient(120% 90% at 50% 30%, #2b2827, #141312 78%);
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius-lg); overflow: hidden; }
body.tk-light .tk-gallery__stage { background: #fff; }
.tk-gallery__main { width: 100%; aspect-ratio: 1/1; object-fit: cover; display: block; }
.tk-gallery__nav { position: absolute; top: 50%; transform: translateY(-50%); width: 40px; height: 40px;
  display: grid; place-items: center; border: 1px solid var(--tk-border); border-radius: 50%;
  background: rgba(0,0,0,.5); color: #fff; cursor: pointer; backdrop-filter: blur(2px); }
.tk-gallery__nav--prev { left: .6rem; } .tk-gallery__nav--next { right: .6rem; }
.tk-gallery__thumbs { display: flex; gap: .5rem; margin-top: .6rem; overflow-x: auto; padding-bottom: .25rem; }
.tk-gallery__thumb { flex: 0 0 auto; width: 68px; aspect-ratio: 1/1; padding: 0; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); overflow: hidden; background: none; cursor: pointer; }
.tk-gallery__thumb.is-active { border-color: var(--tk-burgundy); }
.tk-gallery__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Spec grid */
.tk-specs { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--tk-space-4); margin: 0; }
@media (max-width: 900px) { .tk-specs { grid-template-columns: repeat(2, 1fr); } }
.tk-specs__item dt { font-size: .625rem; text-transform: uppercase; letter-spacing: .08em; color: var(--tk-text-muted); }
.tk-specs__item dd { margin: .15rem 0 0; font-size: .9rem; color: var(--tk-text); }
/* Product dialogs */
.tk-modal { border: 1px solid var(--tk-border); border-radius: var(--tk-radius-lg); background: var(--tk-surface);
  color: var(--tk-text); padding: 0; max-width: min(92vw, 640px); width: 100%; }
.tk-modal::backdrop { background: rgba(0,0,0,.7); }
.tk-modal__head { display: flex; align-items: center; justify-content: space-between; padding: var(--tk-space-4) var(--tk-space-6); border-bottom: 1px solid var(--tk-border); }
.tk-modal__body { padding: var(--tk-space-6); }
.tk-modal--media { max-width: min(92vw, 880px); }
.tk-modal--media .tk-modal__body { padding: 0; }
.tk-modal__video { aspect-ratio: 16/9; width: 100%; border: 0; display: block; }
.tk-modal__img { width: 100%; height: auto; display: block; border-radius: var(--tk-radius-lg); }
.tk-field label, .tk-modal__body .tk-field > span { font-size: .7rem; }
.tk-modal__body input, .tk-modal__body textarea { width: 100%; background: var(--tk-page); border: 1px solid var(--tk-border);
  color: var(--tk-text); border-radius: var(--tk-radius); padding: .6rem .7rem; font: inherit; margin-top: .3rem; }
/* Watch detail layout */
.tk-watch { display: grid; grid-template-columns: 1.05fr .95fr; gap: var(--tk-space-8); align-items: start; }
.tk-watch > * { min-width: 0; } /* prevent gallery image intrinsic size from blowing out the grid track */
@media (max-width: 900px) { .tk-watch { grid-template-columns: 1fr; gap: var(--tk-space-6); } }
.tk-watch__badges { display: flex; gap: .4rem; flex-wrap: wrap; margin-bottom: var(--tk-space-4); }
.tk-watch__titlerow { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
.tk-watch__title { font-family: var(--tk-font-display); font-weight: 700; font-size: clamp(1.4rem, 3vw, 1.9rem); margin: 0; }
.tk-watch__bookmark { flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%; border: 1px solid var(--tk-border);
  background: none; color: var(--tk-text-muted); display: grid; place-items: center; cursor: pointer; }
.tk-watch__bookmark.active { color: var(--tk-burgundy); } .tk-watch__bookmark.active svg { fill: var(--tk-burgundy); }
.tk-watch__ref { font-size: .7rem; letter-spacing: .1em; text-transform: uppercase; color: var(--tk-text-muted); margin: .35rem 0 var(--tk-space-4); }
.tk-watch__price { display: flex; flex-direction: column; gap: .4rem; margin-bottom: var(--tk-space-6); }
.tk-watch__pv { font-family: var(--tk-font-body); font-weight: 700; font-size: 1.9rem; color: var(--tk-text); }
.tk-watch__pv.is-sold { color: var(--tk-text-muted); text-decoration: line-through; }
.tk-watch__srp { font-size: .85rem; color: var(--tk-text-muted); }
.tk-watch__range { display: flex; flex-direction: column; gap: .1rem; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); padding: .5rem .75rem; margin-top: .3rem; }
.tk-watch__includes { margin-bottom: var(--tk-space-6); }
.tk-chiprow { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .5rem; }
.tk-chip { font-size: .7rem; border: 1px solid var(--tk-border); border-radius: var(--tk-radius-pill); padding: .3rem .7rem; color: var(--tk-text-muted); }
.tk-watch__cta { display: flex; flex-direction: column; gap: .5rem; margin-bottom: var(--tk-space-4); }
.tk-watch__cta-row { display: flex; gap: .5rem; } .tk-watch__cta-row .tk-btn { flex: 1; justify-content: center; }
.tk-watch__soldnote { color: var(--tk-text-muted); font-size: .85rem; text-align: center; margin: var(--tk-space-4) 0; }
.tk-watch__similar { display: flex; flex-direction: column; gap: .5rem; margin-bottom: var(--tk-space-4); }
.tk-watch__simitem { display: flex; gap: .75rem; align-items: center; background: var(--tk-surface); border: 1px solid var(--tk-border); border-radius: var(--tk-radius); padding: .5rem; }
.tk-watch__simitem img { width: 52px; height: 52px; object-fit: cover; border-radius: 6px; flex: 0 0 auto; }
.tk-watch__simtitle { display: block; font-weight: 600; font-size: .85rem; } .tk-watch__simprice { display: block; color: var(--tk-accent-text); font-size: .8rem; }
.tk-watch__meta { display: flex; align-items: center; justify-content: space-between; gap: 1rem; border-top: 1px solid var(--tk-border); margin-top: var(--tk-space-6); padding-top: var(--tk-space-4); position: relative; }
.tk-watch__metabtn { background: none; border: 0; color: var(--tk-text-muted); font: inherit; font-size: .8rem; cursor: pointer; display: inline-flex; align-items: center; gap: .4rem; }
.tk-watch__metabtn:hover { color: var(--tk-text); } .tk-watch__metabtn[disabled] { opacity: .5; cursor: not-allowed; }
.tk-share { position: relative; } .tk-share summary { list-style: none; } .tk-share summary::-webkit-details-marker { display: none; }
.tk-share__menu { position: absolute; right: 0; bottom: 100%; margin-bottom: .5rem; background: var(--tk-raised); border: 1px solid var(--tk-border); border-radius: var(--tk-radius); padding: .4rem; display: flex; flex-direction: column; min-width: 160px; z-index: 5; }
/* Opens downward instead — for triggers near the top of the page (e.g. a tool-page hero),
   where the default upward-opening menu would be clipped by the viewport top edge. */
.tk-share--down .tk-share__menu { bottom: auto; top: 100%; margin-bottom: 0; margin-top: .5rem; }
.tk-share__menu a, .tk-share__menu button { text-align: left; background: none; border: 0; color: var(--tk-text); font: inherit; font-size: .8rem; padding: .5rem .6rem; border-radius: 6px; cursor: pointer; }
.tk-share__menu a:hover, .tk-share__menu button:hover { background: var(--tk-surface); }
/* Separates the social targets from a trailing utility item (e.g. "Copy Link"). */
.tk-share__menu hr { border: 0; border-top: 1px solid var(--tk-border); margin: .35rem .2rem; }
.tk-watch__section { margin-top: var(--tk-space-8); padding-top: var(--tk-space-6); border-top: 1px solid var(--tk-border); }
.tk-watch__desc { color: var(--tk-text-muted); line-height: 1.7; max-width: 68ch; }
.tk-watch__notify { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-top: var(--tk-space-4);
  padding: var(--tk-space-4); border: 1px solid color-mix(in srgb, var(--tk-gold) 35%, transparent);
  background: color-mix(in srgb, var(--tk-gold) 8%, transparent); border-radius: var(--tk-radius); color: var(--tk-text); }
.tk-watch__notify-sub { display: block; color: var(--tk-text-muted); font-size: .8rem; margin-top: .15rem; }
.tk-watch__notify svg { color: var(--tk-text-muted); flex: 0 0 auto; }

/* Reviews / customer photos (watch detail secondary sections) */
.tk-yt iframe { aspect-ratio:16/9; width:100%; border:0; border-radius: var(--tk-radius); }
.tk-photo-grid { display:grid; grid-template-columns:repeat(6,1fr); gap:.5rem; }
@media(max-width:900px){.tk-photo-grid{grid-template-columns:repeat(3,1fr);}}
.tk-photo-grid img{width:100%;aspect-ratio:1;object-fit:cover;border-radius:var(--tk-radius);}
.tk-photo-grid button { padding:0; border:0; background:none; cursor:pointer; display:block; }

/* Sticky mobile DM bar */
.tk-dmbar { display: none; }
@media (max-width: 900px) {
  .tk-dmbar { display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 70; align-items: center; gap: 1rem;
    background: color-mix(in srgb, var(--tk-surface) 97%, transparent); backdrop-filter: blur(8px);
    border-top: 1px solid var(--tk-border); padding: .7rem 1rem calc(.7rem + env(safe-area-inset-bottom)); }

  /* .tk-eyebrow is display:inline, so the label and the price shared a line box and the
     PRICE ITSELF broke across two lines -- "PHP" above "282,000", which reads as a
     mangled number. A flex column puts the label above the price; nowrap guarantees the
     amount is never split whatever the currency or digit count. */
  .tk-dmbar__price { display: flex; flex-direction: column; line-height: 1.1; min-width: 0; }
  .tk-dmbar__price .tk-eyebrow { display: block; }
  .tk-dmbar__price strong { font-size: 1.05rem; white-space: nowrap; }

  /* The CTA was wrapping to three lines, taking the bar to 85px. nowrap plus a tighter
     inline padding holds "Reserve via DM" on one line down to 320px; flex-shrink keeps
     the price, not the button, absorbing the squeeze. */
  .tk-dmbar .tk-btn { margin-left: auto; white-space: nowrap; flex-shrink: 0; padding-inline: .85rem; }
  body.tk-dark .tk-footer { padding-bottom: 5rem; } /* clear the bar */
}

/* Pre-owned card */
.tk-preowned { position: relative; }
.tk-preowned__cert { position: absolute; top: .5rem; left: .5rem; z-index: 2; font-size: .625rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; padding: .2rem .5rem; border-radius: 4px;
  background: color-mix(in srgb, var(--tk-lume) 16%, transparent); color: var(--tk-lume); border: 1px solid color-mix(in srgb, var(--tk-lume) 30%, transparent); }
.tk-preowned__grade { display: inline-flex; align-items: center; gap: .35rem; font-size: .625rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; padding: .22em .6em; border-radius: 4px;
  color: var(--g); background: color-mix(in srgb, var(--g) 15%, transparent); border: 1px solid color-mix(in srgb, var(--g) 45%, transparent); }
.tk-preowned__grade::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--g); }
.tk-preowned__bookmark { position: absolute; top: .5rem; right: .5rem; z-index: 3; }
.tk-preowned__sold { position: absolute; inset: 0; z-index: 4; display: grid; place-items: center; background: rgba(0,0,0,.5);
  border-radius: var(--tk-radius-lg); font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .1em; font-size: .8rem; color: #fff; pointer-events: none; }
/* Grade pills + explainer */
.tk-grade-pills { display: flex; flex-wrap: wrap; gap: .5rem; }
.tk-grade-pill { font-size: .75rem; padding: .4rem .9rem; border-radius: var(--tk-radius-pill); border: 1px solid var(--tk-border); color: var(--tk-text-muted); }
.tk-grade-pill.is-active { color: #fff; background: var(--tk-burgundy); border-color: var(--tk-burgundy); }
.tk-grade-pill[data-grade].is-active { background: var(--g); border-color: var(--g); color: var(--gt, #10100e); }
.tk-grade-pill[data-grade] { color: var(--g); border-color: color-mix(in srgb, var(--g) 45%, transparent); }
.tk-grade-cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--tk-space-3); }
@media (max-width: 720px) { .tk-grade-cards { grid-template-columns: repeat(2, 1fr); } }
.tk-grade-card { background: var(--tk-surface); border: 1px solid var(--tk-border); border-left: 3px solid var(--g); border-radius: var(--tk-radius); padding: var(--tk-space-4); }
.tk-grade-card h3 { color: var(--g); font-size: .8rem; margin: 0 0 .35rem; }
.tk-grade-card p { color: var(--tk-text-muted); font-size: .75rem; line-height: 1.5; margin: 0; }
/* Pre-owned hero + trust bar */
.tk-po-hero { position: relative; overflow: hidden; border-bottom: 1px solid var(--tk-border); min-height: 240px; display: grid; }
.tk-po-hero__photo { position: absolute; inset: 0; z-index: 0; }
.tk-po-hero__photo img { width: 100%; height: 100%; object-fit: cover; }
.tk-po-hero__mask { position: absolute; inset: 0; z-index: 1; background: linear-gradient(100deg, var(--tk-page) 0%, color-mix(in srgb, var(--tk-page) 92%, transparent) 42%, transparent 100%); }
.tk-po-hero__copy { position: relative; z-index: 2; align-self: center; max-width: 420px; padding: var(--tk-space-8) var(--tk-space-6); }
.tk-po-trust { display: flex; overflow-x: auto; border-bottom: 1px solid var(--tk-border); }
.tk-po-trust > div { flex: 1; min-width: 90px; text-align: center; padding: var(--tk-space-4) .5rem; border-right: 1px solid var(--tk-border); font-size: .65rem; color: var(--tk-text-muted); }

/* ---- Light-theme corrections (first tk-light pages) ---- */
/* Footer's default color-mix(page, 12% #000) drops --tk-text-muted to 4.0:1 against the
   footer background — under AA for the footer nav/copy links. Lighten the tint to 4% black
   so muted text clears 4.5:1 while the footer still reads as a distinct band, not a slab. */
body.tk-light .tk-footer { background: color-mix(in srgb, var(--tk-page) 96%, #000); }
/* The struck-through "was" price relies on opacity:.45 over --tk-text, which only reaches
   2.8:1 on the ivory/white surfaces (vs ~3.9:1 on dark, already borderline there). Swap to
   the theme's solid muted color instead of a translucency hack. */
body.tk-light .tk-price s { opacity: 1; color: var(--tk-text-muted); }

/* ---- Editorial: article prose ---- */
.tk-prose { max-width: 68ch; font-size: 1.125rem; line-height: 1.75; color: var(--tk-text); }
.tk-prose > * + * { margin-top: 1.25em; }
.tk-prose h1, .tk-prose h2, .tk-prose h3 { font-family: var(--tk-font-display); font-weight: 700;
  line-height: 1.2; margin-top: 2.2em; margin-bottom: .6em; }
.tk-prose h2 { font-size: 1.5rem; } .tk-prose h3 { font-size: 1.2rem; }
/* :not(.tk-btn) is load-bearing. This rule is 0,2,0 and .tk-btn--solid's `color:#fbeef0`
   is 0,1,0, so a button placed in a prose block lost its ink to the accent colour and
   rendered burgundy-on-burgundy — 1.15:1, measured on /data-deletion's "Go to Facebook
   App Settings". It also stripped the button of its shape by underlining it. Excluding
   buttons costs the remaining links nothing: the selector's specificity is unchanged. */
.tk-prose a:not(.tk-btn) { color: var(--tk-accent-text); text-decoration: underline; text-underline-offset: 2px; }
.tk-prose img { border-radius: var(--tk-radius); margin-block: 1.5em; }
.tk-prose blockquote { border-left: 3px solid var(--tk-burgundy); padding-left: 1.25rem;
  color: var(--tk-text-muted); font-style: italic; margin-inline: 0; }
.tk-prose code { background: color-mix(in srgb, var(--tk-text) 8%, transparent); padding: .15em .4em;
  border-radius: 4px; font-size: .9em; }
.tk-prose pre { background: color-mix(in srgb, var(--tk-text) 8%, transparent); padding: 1rem;
  border-radius: var(--tk-radius); overflow-x: auto; }
.tk-prose ul, .tk-prose ol { padding-left: 1.4em; } .tk-prose li + li { margin-top: .4em; }
.tk-prose hr { border: 0; border-top: 1px solid var(--tk-border); margin-block: 2.5em; }

/* ---- Editorial: layout ---- */
.tk-article { display: grid; grid-template-columns: minmax(0,1fr) 320px; gap: var(--tk-space-8); align-items: start; }
@media (max-width: 980px) { .tk-article { grid-template-columns: 1fr; } }
.tk-article__aside { position: sticky; top: 90px; display: flex; flex-direction: column; gap: var(--tk-space-4); }
@media (max-width: 980px) { .tk-article__aside { position: static; } }
.tk-article__head { margin-bottom: var(--tk-space-6); }
.tk-article__title { font-family: var(--tk-font-display); font-weight: 700; font-size: clamp(1.8rem, 4vw, 2.6rem);
  line-height: 1.1; margin: .4rem 0 .6rem; max-width: 20ch; }
.tk-article__meta { font-size: .8rem; color: var(--tk-text-muted); }
.tk-article__hero { width: 100%; border-radius: var(--tk-radius-lg); margin-block: var(--tk-space-6); }

/* ---- Editorial: post card ---- */
.tk-post-card { background: var(--tk-surface); border: 1px solid var(--tk-border); border-radius: var(--tk-radius-lg);
  overflow: hidden; display: flex; flex-direction: column; transition: transform .25s var(--tk-ease), border-color .25s var(--tk-ease); }
.tk-post-card:hover { transform: translateY(-3px); border-color: var(--tk-burgundy); }
.tk-post-card__media img { width: 100%; aspect-ratio: 16/9; object-fit: cover; display: block; }
.tk-post-card__body { padding: var(--tk-space-4); display: flex; flex-direction: column; flex: 1; }
.tk-post-card__date { font-size: .7rem; text-transform: uppercase; letter-spacing: .1em; color: var(--tk-text-muted); }
.tk-post-card__title { font-family: var(--tk-font-display); font-weight: 700; font-size: 1.05rem; margin: .35rem 0 .5rem; }
.tk-post-card__title a { color: var(--tk-text); }
.tk-post-card__excerpt { color: var(--tk-text-muted); font-size: .875rem; line-height: 1.6; margin: 0 0 var(--tk-space-4); flex: 1; }
.tk-post-card__more { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; font-size: .7rem; color: var(--tk-accent-text); }
.tk-post-card--featured .tk-post-card__title { font-size: 1.5rem; }
.tk-post-card--featured .tk-post-card__media img { aspect-ratio: 21/9; }
.tk-post-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--tk-space-4); }
@media (max-width: 680px) { .tk-post-grid { grid-template-columns: 1fr; } }

/* ---- Editorial: sidebar widget + share ---- */
.tk-widget { background: var(--tk-surface); border: 1px solid var(--tk-border); border-radius: var(--tk-radius-lg); padding: var(--tk-space-4); }
.tk-widget__title { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; font-size: .75rem; margin: 0 0 var(--tk-space-3); }
.tk-widget ul { list-style: none; margin: 0; padding: 0; }
.tk-widget li + li { margin-top: .5rem; }
.tk-widget a { color: var(--tk-text); font-size: .85rem; }
.tk-widget a:hover { color: var(--tk-accent-text); }
.tk-widget p { color: var(--tk-text-muted); font-size: .85rem; line-height: 1.6; margin: 0; }
.tk-share-row { display: flex; gap: var(--tk-space-3); align-items: center; }
.tk-share-row a { color: var(--tk-text-muted); display: inline-flex; }
.tk-share-row a:hover { color: var(--tk-accent-text); }
.tk-postnav { display: grid; grid-template-columns: 1fr 1fr; gap: var(--tk-space-4); margin-top: var(--tk-space-8); }
@media (max-width: 680px) { .tk-postnav { grid-template-columns: 1fr; } }
.tk-postnav__item { background: var(--tk-surface); border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  padding: var(--tk-space-4); color: var(--tk-text); display: block; }
.tk-postnav__item:hover { border-color: var(--tk-burgundy); }
.tk-postnav__label { font-size: .65rem; text-transform: uppercase; letter-spacing: .1em; color: var(--tk-text-muted); }
.tk-postnav__t { display: block; font-weight: 600; font-size: .9rem; margin-top: .2rem; }
.tk-postnav__item--next { text-align: right; }

/* ---- Under the Loupe ---- */
.tk-loupe-hero { position: relative; border-bottom: 1px solid var(--tk-border); padding-block: clamp(3rem, 9vw, 6rem);
  background: linear-gradient(color-mix(in srgb, var(--tk-page) 82%, transparent), var(--tk-page)),
              var(--tk-loupe-hero-img, radial-gradient(120% 120% at 50% 0%, #262324, #131312)); background-size: cover; background-position: center; text-align: center; }
.tk-loupe-hero__title { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  font-size: clamp(2.2rem, 7vw, 4rem); line-height: .95; margin: 0; }
.tk-loupe-hero__sub { color: var(--tk-accent-text); text-transform: uppercase; letter-spacing: .22em;
  font-size: .75rem; margin-top: var(--tk-space-3); }
.tk-loupe-hero__links { display: flex; gap: var(--tk-space-4); justify-content: center; flex-wrap: wrap; margin-top: var(--tk-space-6); }
.tk-loupe-hero__link { display: inline-flex; flex-direction: column; align-items: center; gap: .4rem;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius); padding: .9rem 1.6rem;
  background: color-mix(in srgb, var(--tk-surface) 70%, transparent); color: var(--tk-text);
  font-size: .75rem; text-transform: uppercase; letter-spacing: .1em; }
.tk-loupe-hero__link:hover { border-color: var(--tk-burgundy); color: var(--tk-accent-text); }

.tk-loupe-card { background: var(--tk-surface); border: 1px solid var(--tk-border); border-radius: var(--tk-radius-lg);
  overflow: hidden; display: flex; flex-direction: column; height: 100%;
  transition: transform .25s var(--tk-ease), border-color .25s var(--tk-ease); }
.tk-loupe-card:hover { transform: translateY(-4px); border-color: var(--tk-burgundy); }
.tk-loupe-card__media { display: block; aspect-ratio: 4/3; background: var(--tk-raised); position: relative; }
.tk-loupe-card__media img { width: 100%; height: 100%; object-fit: cover; display: block;
  filter: grayscale(60%); transition: filter .35s var(--tk-ease); }
.tk-loupe-card:hover .tk-loupe-card__media img { filter: grayscale(0); }
.tk-loupe-card__ph { position: absolute; inset: 0; display: grid; place-items: center; color: var(--tk-text-muted); }
.tk-loupe-card__ph svg { width: 32px; height: 32px; }
.tk-loupe-card__body { padding: var(--tk-space-4); display: flex; flex-direction: column; gap: .5rem; flex: 1; }
.tk-loupe-card__title { font-family: var(--tk-font-display); font-weight: 700; font-size: 1rem; margin: 0; }
.tk-loupe-card__title a { color: var(--tk-text); }
.tk-loupe-card__desc { color: var(--tk-text-muted); font-size: .825rem; line-height: 1.6; margin: 0; }
.tk-loupe-card__foot { display: flex; align-items: center; justify-content: space-between;
  border-top: 1px solid var(--tk-border); padding: .7rem var(--tk-space-4);
  color: var(--tk-text-muted); font-size: .7rem; }
.tk-loupe-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--tk-space-4); }
@media (max-width: 900px) { .tk-loupe-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .tk-loupe-grid { grid-template-columns: 1fr; } }

/* Featured dossier spotlight */
.tk-loupe-featured { display: grid; grid-template-columns: 5fr 7fr; background: var(--tk-surface);
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius-lg); overflow: hidden; }
@media (max-width: 780px) { .tk-loupe-featured { grid-template-columns: 1fr; } }
.tk-loupe-featured img { width: 100%; height: 100%; min-height: 320px; object-fit: cover; display: block; filter: grayscale(45%); }
.tk-loupe-featured__body { padding: var(--tk-space-8); display: flex; flex-direction: column; justify-content: center; gap: .6rem; }
.tk-loupe-featured__meta { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
  border-top: 1px solid var(--tk-border); margin-top: var(--tk-space-4); padding-top: var(--tk-space-4);
  color: var(--tk-text-muted); font-size: .75rem; }

/* Manifesto */
.tk-loupe-manifesto { border-block: 1px solid var(--tk-border); background: color-mix(in srgb, var(--tk-page) 92%, #000);
  padding: var(--tk-space-12) var(--tk-space-6); }
.tk-loupe-manifesto__body { max-width: 62ch; margin-inline: auto; color: var(--tk-text-muted);
  font-size: 1.05rem; line-height: 1.8; text-align: center; }
.tk-loupe-manifesto__body strong { color: var(--tk-text); }
.tk-loupe-manifesto__sign { text-align: center; margin-top: var(--tk-space-6); }
.tk-loupe-manifesto__sign b { display: block; font-family: var(--tk-font-display); font-weight: 700;
  font-size: 1.1rem; color: var(--tk-lume); }
.tk-loupe-manifesto__sign span { font-size: .7rem; text-transform: uppercase; letter-spacing: .18em; color: var(--tk-text-muted); }

.tk-loupe-card__date { font-size: .65rem; text-transform: uppercase; letter-spacing: .1em; color: var(--tk-text-muted); }

.tk-loupe-volume__cover { width: 100%; max-height: 260px; object-fit: cover; border-radius: var(--tk-radius-lg);
  border: 1px solid var(--tk-border); margin-bottom: var(--tk-space-4); filter: grayscale(35%); }

/* ---- Loupe audio episode hero ---- */
.tk-ep-hero { position: relative; overflow: hidden; border-bottom: 1px solid var(--tk-border);
  padding-block: clamp(2.5rem, 6vw, 4rem); }
.tk-ep-hero__bg { position: absolute; inset: 0; z-index: 0; background: var(--tk-ep-hero-img, none) center/cover;
  filter: blur(70px) brightness(.32) saturate(1.15); transform: scale(1.15); opacity: .65; }
.tk-ep-hero__inner { position: relative; z-index: 1; display: grid; grid-template-columns: 300px 1fr;
  gap: var(--tk-space-8); align-items: center; }
@media (max-width: 900px) { .tk-ep-hero__inner { grid-template-columns: 1fr; text-align: center; } }
.tk-ep-hero__art { border-radius: var(--tk-radius-lg); overflow: hidden; border: 1px solid var(--tk-border);
  aspect-ratio: 1/1; max-width: 320px; margin-inline: auto; box-shadow: 0 25px 50px -12px rgba(0,0,0,.5); }
.tk-ep-hero__art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tk-ep-hero__title { font-family: var(--tk-font-display); font-weight: 800; font-size: clamp(1.7rem, 4vw, 2.5rem);
  line-height: 1.05; margin: var(--tk-space-3) 0; }
.tk-ep-hero__stats { display: flex; flex-wrap: wrap; gap: var(--tk-space-4); color: var(--tk-text-muted);
  font-size: .8rem; margin-bottom: var(--tk-space-6); }
@media (max-width: 900px) { .tk-ep-hero__stats { justify-content: center; } }
.tk-ep-hero__stats svg { margin-right: .3rem; vertical-align: -3px; color: var(--tk-accent-text); width: 15px; height: 15px; }

/* ---- Loupe audio player ---- */
.tk-player { background: color-mix(in srgb, var(--tk-surface) 85%, transparent); border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius-lg); padding: var(--tk-space-6); backdrop-filter: blur(16px); }
.tk-player__meta { display: flex; justify-content: space-between; margin-bottom: .35rem; }
.tk-player__time { font-size: .75rem; color: var(--tk-text-muted); font-variant-numeric: tabular-nums; min-width: 40px; }

/* Scrubber: outer element is the touch target (id="timelineContainer"); the visual 6px line and its
   buffer/progress fills live inside .tk-player__track; the handle is a sibling positioned against the
   outer scrubber, matching the JS's `progressHandle.style.left = pct + '%'` (percentage of scrubber width). */
.tk-player__scrubber { position: relative; padding-block: .5rem; margin-block: .15rem; cursor: pointer;
  touch-action: none; }
.tk-player__track { position: relative; height: 6px; border-radius: 3px; background: var(--tk-raised); overflow: hidden; }
.tk-player__buffer, .tk-player__fill { position: absolute; inset-block: 0; left: 0; width: 100%;
  transform-origin: left; transform: scaleX(0); border-radius: 3px; }
.tk-player__buffer { background: color-mix(in srgb, var(--tk-text) 18%, transparent); transition: transform .2s; z-index: 1; }
.tk-player__fill { background: var(--tk-burgundy); z-index: 2; will-change: transform; }
.tk-player__handle { position: absolute; top: 50%; left: 0%; transform: translate(-50%, -50%); width: 14px;
  height: 14px; border-radius: 50%; background: var(--tk-lume); box-shadow: 0 0 10px rgba(0,0,0,.5); z-index: 3;
  opacity: 0; pointer-events: none; transition: opacity .2s, transform .1s; }
.tk-player__scrubber:hover .tk-player__handle, .tk-player__scrubber.tk-is-scrubbing .tk-player__handle {
  opacity: 1; transform: translate(-50%, -50%) scale(1.2); }

.tk-player__row { display: flex; align-items: center; justify-content: space-between; gap: var(--tk-space-4);
  margin-top: .75rem; }
.tk-player__controls { display: flex; align-items: center; gap: .2rem; }
.tk-player__right { display: flex; align-items: center; gap: .6rem; }
.tk-player__btn { width: 40px; height: 40px; border-radius: 50%; display: inline-flex; align-items: center;
  justify-content: center; background: none; border: 0; color: var(--tk-text); cursor: pointer;
  transition: background-color .2s, color .2s, transform .2s; }
.tk-player__btn:hover { color: var(--tk-accent-text); background: color-mix(in srgb, var(--tk-text) 6%, transparent); }
.tk-player__btn[aria-disabled="true"] { opacity: .35; pointer-events: none; }
.tk-player__btn--skip { width: auto; min-width: 40px; padding-inline: .4rem; border-radius: var(--tk-radius-pill);
  font-family: var(--tk-font-display); font-weight: 700; font-size: .625rem; letter-spacing: .02em; }
.tk-player__btn--speed { width: auto; min-width: 40px; padding-inline: .55rem; border-radius: var(--tk-radius-pill);
  font-family: var(--tk-font-display); font-weight: 700; font-size: .68rem; }
.tk-player__btn--main { width: 56px; height: 56px; margin-inline: .2rem; background: var(--tk-burgundy);
  color: #fbeef0; box-shadow: var(--tk-shadow-burg); }
.tk-player__btn--main:hover { background: var(--tk-burgundy-hover); color: #fff; transform: scale(1.05); }
.tk-player__icon--pause { display: none; }
.tk-player__btn--main.tk-is-playing .tk-player__icon--play { display: none; }
.tk-player__btn--main.tk-is-playing .tk-player__icon--pause { display: block; }

.tk-player__speed { position: relative; display: flex; align-items: center; }
.tk-player__speed-menu { position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%);
  margin-bottom: .5rem; display: none; flex-direction: column; background: var(--tk-raised);
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius); padding: .3rem; min-width: 80px;
  z-index: 5; box-shadow: var(--tk-shadow); }
.tk-player__speed-menu.tk-is-open { display: flex; }
.tk-player__speed-opt { background: none; border: 0; color: var(--tk-text); font: inherit; font-size: .8rem;
  padding: .4rem .6rem; border-radius: 4px; cursor: pointer; text-align: center; }
.tk-player__speed-opt:hover { background: var(--tk-surface); }
.tk-player__speed-opt.tk-is-active { color: var(--tk-accent-text); font-weight: 600; }
.tk-player__mobspeed { display: none; }

.tk-player__volume { display: flex; align-items: center; width: 90px; }
.tk-player__voltrack { flex: 1; height: 4px; border-radius: 2px; background: var(--tk-raised); position: relative;
  cursor: pointer; }
.tk-player__volfill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 2px;
  background: var(--tk-text-muted); width: 100%; }

@media (max-width: 600px) {
  .tk-player__volume { display: none; }
  .tk-player__speed { display: none; }
  .tk-player__mobspeed { display: inline-flex; }
}

/* ---- Loupe episode share widget ---- */
.tk-ep-share__btn { width: 40px; height: 40px; border-radius: 50%; border: 1px solid var(--tk-border);
  background: none; color: var(--tk-text-muted); display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; }
.tk-ep-share__btn:hover { border-color: var(--tk-burgundy); color: var(--tk-accent-text); }

/* ---- Loupe dossier table of contents (sticky sidebar, JS-populated) ---- */
.tk-toc__link { display: block; padding-left: .75rem; margin-left: -.75rem; border-left: 2px solid transparent;
  transition: border-color .2s var(--tk-ease), color .2s var(--tk-ease); }
.tk-toc__link:hover, .tk-toc__link.tk-is-active { color: var(--tk-accent-text); border-left-color: var(--tk-burgundy); }

/* Pasted editorial content (e.g. dossier bodies) sometimes carries inline light-mode
   colors like `color: rgb(31,31,31)`, which render near-invisible on the dark page
   (measured 1.13:1). Inline styles beat stylesheets, so neutralize them inside the
   article body only — the text then inherits the theme's own colour. Scoped to
   .tk-prose so the design system elsewhere is unaffected. */
body.tk-dark .tk-prose [style*="color"] { color: inherit !important; }
/* Same pasted-content problem, two more vectors: legacy <font color> attributes
   (not inline styles, so the rule above misses them) and inline light backgrounds
   on table cells, which leave theme-light text on a near-white fill (1.04:1). */
body.tk-dark .tk-prose font { color: inherit !important; }
body.tk-dark .tk-prose [style*="background"],
body.tk-dark .tk-prose th, body.tk-dark .tk-prose td { background-color: transparent !important; }

/* ---- Tools ---- */
.tk-tool-hero { border-bottom: 1px solid var(--tk-border); padding-block: clamp(2.5rem, 7vw, 4rem); }
.tk-tool-hero__title { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  font-size: clamp(1.9rem, 5vw, 3rem); line-height: 1; margin: 0; }
.tk-tool-hero__sub { color: var(--tk-text-muted); max-width: 60ch; margin-top: var(--tk-space-4); }

.tk-tool-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--tk-space-4); }
@media (max-width: 980px) { .tk-tool-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .tk-tool-grid { grid-template-columns: 1fr; } }
.tk-tool-card { position: relative; display: flex; flex-direction: column; gap: .5rem; height: 100%;
  background: var(--tk-surface); border: 1px solid var(--tk-border); border-radius: var(--tk-radius-lg);
  padding: var(--tk-space-6); transition: transform .25s var(--tk-ease), border-color .25s var(--tk-ease); }
.tk-tool-card:hover { transform: translateY(-4px); border-color: var(--tk-burgundy); }
.tk-tool-card__icon { display: grid; place-items: center; width: 44px; height: 44px; border-radius: var(--tk-radius);
  background: color-mix(in srgb, var(--tk-burgundy) 12%, transparent); color: var(--tk-accent-text); }
.tk-tool-card__tag { position: absolute; top: var(--tk-space-4); right: var(--tk-space-4); }
.tk-tool-card__title { font-family: var(--tk-font-display); font-weight: 700; font-size: 1.05rem; margin: .3rem 0 0; }
.tk-tool-card__desc { color: var(--tk-text-muted); font-size: .875rem; line-height: 1.6; margin: 0; }
.tk-tool-card__audience { font-size: .75rem; color: var(--tk-text-muted); margin: 0; }
.tk-tool-card__audience span { text-transform: uppercase; letter-spacing: .12em; font-size: .625rem;
  color: var(--tk-accent-text); margin-right: .3rem; }
.tk-tool-card__cta { margin-top: auto; align-self: flex-start; }
.tk-tool-card__share { position: absolute; bottom: var(--tk-space-4); right: var(--tk-space-4); }
.tk-tool-card__sharebtn { list-style: none; cursor: pointer; color: var(--tk-text-muted);
  display: inline-flex; padding: .35rem; border-radius: var(--tk-radius); }
.tk-tool-card__sharebtn::-webkit-details-marker { display: none; }
.tk-tool-card__sharebtn:hover { color: var(--tk-accent-text); background: color-mix(in srgb, var(--tk-burgundy) 10%, transparent); }
.tk-tool-card--featured { border-left: 3px solid var(--tk-gold); }
.tk-tool-card--featured .tk-tool-card__icon { background: color-mix(in srgb, var(--tk-gold) 16%, transparent); color: var(--tk-gold); }

/* Tools hub (/tools-hub): the long-form counterpart to the card grid above — one
   hairline-separated section per tool, its mark and CTA in a narrow rail beside the
   copy. Replaces the old .tool-section inline <style> block, and the per-tool
   Bootstrap colour it carried: the mark takes the single burgundy accent instead. */
/* The rail is a fixed width, not content-sized: each section is its own grid, so a
   content-sized track would give every tool a different rail and jitter the copy
   column down the page. 23rem is the widest CTA + Share pair ("Manage Your
   Collection"), so no section's actions wrap. */
.tk-hub-tool { display: grid; grid-template-columns: 23rem minmax(0, 1fr);
  gap: var(--tk-space-8); padding-block: var(--tk-space-12); border-bottom: 1px solid var(--tk-border); }
.tk-hub-tool:last-child { border-bottom: 0; }
@media (max-width: 900px) {
  .tk-hub-tool { grid-template-columns: 1fr; gap: var(--tk-space-6); padding-block: var(--tk-space-8); }
}
.tk-hub-tool__icon { display: grid; place-items: center; width: 56px; height: 56px;
  border-radius: var(--tk-radius); background: color-mix(in srgb, var(--tk-burgundy) 12%, transparent);
  color: var(--tk-accent-text); margin-bottom: var(--tk-space-4); }
/* Sized here, not through tk_icon()'s $attrs: that helper already writes
   width="20" height="20" into the tag, and the first of a duplicated attribute is the
   one that counts, so a width attribute passed in is silently dropped. Same reason
   .tk-hub-tool__facthead svg sizes its marks in CSS. */
.tk-hub-tool__icon svg { width: 28px; height: 28px; }
.tk-hub-tool__title { font-size: 1.5rem; margin: 0 0 var(--tk-space-4); }
.tk-hub-tool__actions { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: var(--tk-space-2); }
/* The share trigger moves with the CTA: hard against the gutter when the CTA wraps
   (375px), well to the right when it doesn't. Anchored to the trigger, the menu hangs
   off one edge or the other in each case — off-screen and unreachable, since the page
   itself does not scroll sideways. Anchor it to the actions row instead, which is
   inside the container gutter in every layout. */
.tk-hub-tool__actions .tk-share { position: static; }
.tk-hub-tool__actions .tk-share__menu { right: auto; left: 0; }
.tk-hub-tool__tagline { font-size: 1.05rem; line-height: 1.7; max-width: 62ch; margin: 0 0 var(--tk-space-6); }
/* auto-fit, not a viewport media query: the copy column's width depends on the rail
   beside it, so the four answers drop to one column when their own column gets narrow. */
.tk-hub-tool__facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: var(--tk-space-6); }
@media (max-width: 640px) { .tk-hub-tool__facts { gap: var(--tk-space-4); } }
.tk-hub-tool__facthead { display: flex; align-items: center; gap: .4rem; font-size: .8rem;
  text-transform: uppercase; letter-spacing: .08em; color: var(--tk-accent-text); margin: 0 0 .35rem; }
.tk-hub-tool__facthead svg { flex: 0 0 auto; width: 16px; height: 16px; }
.tk-hub-tool__factbody { font-size: .875rem; line-height: 1.65; color: var(--tk-text-muted); margin: 0; }

/* Tool page: form + result */
.tk-tool-layout { display: grid; grid-template-columns: 1fr 1fr; gap: var(--tk-space-6); align-items: start; }
@media (max-width: 900px) { .tk-tool-layout { grid-template-columns: 1fr; } }
.tk-panel { background: var(--tk-surface); border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius-lg); padding: var(--tk-space-6); }
.tk-callout { border: 1px solid var(--tk-border); border-left: 3px solid var(--tk-burgundy);
  border-radius: var(--tk-radius); background: color-mix(in srgb, var(--tk-burgundy) 6%, transparent);
  padding: var(--tk-space-4) var(--tk-space-6); }
.tk-callout h2, .tk-callout h3 { font-family: var(--tk-font-display); font-weight: 700; font-size: .95rem; margin: 0 0 .4rem; }
.tk-callout p { color: var(--tk-text-muted); font-size: .85rem; line-height: 1.6; margin: 0; }
/* base.css gives every <a> `color: inherit; text-decoration: none`, so a link inside a
   callout was muted body text with no underline — indistinguishable from the sentence
   around it (WCAG 1.4.1). The first one is /stolen-watch-list's "submission form". */
.tk-callout a:not(.tk-btn) { color: var(--tk-accent-text); text-decoration: underline; text-underline-offset: 2px; }

.tk-result { background: var(--tk-raised); border: 1px solid var(--tk-border); border-radius: var(--tk-radius-lg);
  padding: var(--tk-space-8); text-align: center; display: flex; flex-direction: column; gap: .5rem; }
.tk-result__label { font-size: .7rem; text-transform: uppercase; letter-spacing: .18em; color: var(--tk-text-muted); }
.tk-result__value { font-family: var(--tk-font-body); font-weight: 700; font-size: clamp(2rem, 7vw, 3.2rem);
  line-height: 1; color: var(--tk-accent-text); }
.tk-result__note { font-size: .8rem; color: var(--tk-text-muted); }

/* Form extras (on top of .tk-field) */
.tk-form { display: flex; flex-direction: column; gap: var(--tk-space-4); }
.tk-field--range input[type="range"] { width: 100%; accent-color: var(--tk-burgundy); }
.tk-check { display: flex; align-items: center; gap: .5rem; font-size: .875rem; color: var(--tk-text); }
.tk-check input { accent-color: var(--tk-burgundy); width: 16px; height: 16px; }
.tk-inputgroup { display: flex; align-items: stretch; }
.tk-inputgroup__addon { display: grid; place-items: center; padding-inline: .75rem; font-size: .85rem;
  color: var(--tk-text-muted); background: var(--tk-raised); border: 1px solid var(--tk-border); }
/* Join any two adjacent controls (addon+input, input+addon, input+select) into one pill,
   regardless of which side the addon/select is on. */
.tk-inputgroup input, .tk-inputgroup select, .tk-inputgroup .tk-inputgroup__addon { border-radius: var(--tk-radius) !important; }
.tk-inputgroup > *:not(:last-child) { border-top-right-radius: 0 !important; border-bottom-right-radius: 0 !important; }
.tk-inputgroup > *:not(:first-child) { border-top-left-radius: 0 !important; border-bottom-left-radius: 0 !important; border-left-width: 0; }
.tk-actions { display: flex; gap: var(--tk-space-3); flex-wrap: wrap; }

/* ---- Coin flip ---- */
.tk-coin-scene { width: 200px; height: 200px; max-width: 100%; perspective: 1000px; margin: 0 auto var(--tk-space-6); }
.tk-coin { width: 100%; height: 100%; position: relative; transform-style: preserve-3d; }
.tk-coin-face { position: absolute; width: 100%; height: 100%; backface-visibility: hidden; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; box-shadow: 0 0 25px rgba(0,0,0,.25); overflow: hidden; }
.tk-coin-face img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
.tk-coin-face--tails { transform: rotateY(180deg); }

/* ---- Quiz ---- */
.tk-quiz { min-height: 400px; display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: var(--tk-space-2); }
.tk-quiz__step { width: 100%; }
.tk-quiz__icon { display: grid; place-items: center; width: 56px; height: 56px; border-radius: 50%;
  background: color-mix(in srgb, var(--tk-burgundy) 12%, transparent); color: var(--tk-accent-text);
  margin: 0 auto var(--tk-space-6); }
.tk-quiz__eyebrow { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .12em; font-size: .75rem; color: var(--tk-accent-text); margin: 0 0 var(--tk-space-2); }
.tk-quiz__title { font-family: var(--tk-font-display); font-weight: 700; font-size: clamp(1.15rem, 3vw, 1.5rem);
  margin: 0 0 var(--tk-space-6); }
.tk-quiz__options { display: grid; gap: var(--tk-space-3); max-width: 480px; margin: 0 auto; }
.tk-quiz__option { width: 100%; justify-content: flex-start; }

.tk-spinner { width: 3rem; height: 3rem; margin: 0 auto var(--tk-space-6);
  border: 3px solid color-mix(in srgb, var(--tk-burgundy) 25%, transparent);
  border-top-color: var(--tk-burgundy); border-radius: 50%; animation: tk-spin .8s linear infinite; }
@keyframes tk-spin { to { transform: rotate(360deg); } }

/* Reduced motion: the spinner and the coin flip are the only continuous/large
   movements in the system. Per the design spec all motion is gated on this,
   and spinning/rotating UI is a vestibular trigger. Keep the coin's final
   face (the result still reads correctly) and hold the spinner static. */
@media (prefers-reduced-motion: reduce) {
  .tk-spinner { animation: none; }
  .tk-coin { transition: none !important; }
}

/* ---- Transient button states (e.g. "Link copied!", "Nothing to share") ---- */
.tk-btn--success { border-color: var(--tk-success); color: var(--tk-success); background: transparent; }
/* --tk-gold-ink, not --tk-gold: the raw gold fails AA as text on the light page. */
.tk-btn--warn { border-color: var(--tk-gold-ink); color: var(--tk-gold-ink); background: transparent; }

/* ---- Callout severity modifiers ---- */
.tk-callout--warning { border-left-color: var(--tk-gold);
  background: color-mix(in srgb, var(--tk-gold) 12%, transparent); }

/* ---- Simple bordered link list (e.g. "browse all X" index) ---- */
.tk-linklist { display: flex; flex-direction: column; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius-lg); overflow: hidden; }
.tk-linklist__item { display: flex; align-items: center; justify-content: space-between; gap: var(--tk-space-4);
  padding: var(--tk-space-4) var(--tk-space-6); color: var(--tk-text); background: var(--tk-surface);
  border-top: 1px solid var(--tk-border); font-size: .9rem; }
.tk-linklist__item:first-child { border-top: 0; }
.tk-linklist__item:hover { background: var(--tk-raised); color: var(--tk-accent-text); }
.tk-linklist__item svg { color: var(--tk-text-muted); flex-shrink: 0; }

/* ---- Watch Alternatives: recommendation result card ---- */
.tk-alt-result { background: var(--tk-surface); border: 1px solid var(--tk-border); border-radius: var(--tk-radius-lg);
  overflow: hidden; }
.tk-alt-result__badge { display: flex; align-items: center; gap: .5rem; padding: var(--tk-space-4) var(--tk-space-6);
  border-bottom: 1px solid var(--tk-border); font-family: var(--tk-font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; font-size: .8rem; color: var(--tk-accent-text); }
.tk-alt-result__badge svg { color: var(--tk-gold); }
.tk-alt-result__grid { display: grid; grid-template-columns: minmax(200px, 300px) 1fr; gap: var(--tk-space-6);
  padding: var(--tk-space-6); }
@media (max-width: 720px) { .tk-alt-result__grid { grid-template-columns: 1fr; } }
.tk-alt-result__media { border-radius: var(--tk-radius); }
.tk-alt-result__lead { font-size: .85rem; color: var(--tk-text-muted); margin: 0 0 .35rem; }
.tk-alt-result__title { font-family: var(--tk-font-display); font-weight: 700; font-size: 1.35rem;
  color: var(--tk-accent-text); margin: 0 0 var(--tk-space-3); }
.tk-alt-result__quote { font-style: italic; color: var(--tk-text-muted); margin: 0 0 var(--tk-space-6); }
.tk-alt-result__specs-label { font-size: .7rem; text-transform: uppercase; letter-spacing: .1em;
  color: var(--tk-text-muted); margin: 0 0 var(--tk-space-3); }

/* ---- Paper watch cutout (print utility) ---- */
.tk-cutout-wrap { text-align: center; }
.tk-cutout { display: flex; flex-direction: column; align-items: center; gap: 0; margin: 0 auto; }
.tk-cutout__strap { border: 2px dashed var(--tk-text-muted); box-sizing: border-box; }
.tk-cutout__strap--top { border-bottom: none; border-radius: 3mm 3mm 0 0; }
.tk-cutout__strap--bottom { border-top: none; border-radius: 0 0 3mm 3mm; }
.tk-cutout__case { border: 2px dashed var(--tk-text); border-radius: 50%; display: flex; align-items: center;
  justify-content: center; font-size: 10px; font-weight: 700; background: var(--tk-surface); box-sizing: border-box;
  position: relative; }
.tk-cutout__case-label { text-align: center; line-height: 1.3; }
.tk-cutout__case-label span { font-size: 8px; font-weight: 400; }
.tk-cutout__ruler { width: 50mm; height: 5mm; background: var(--tk-text); margin: 8mm auto 2mm; }
.tk-cutout__ruler-note { font-size: .8rem; color: var(--tk-text-muted); }
.tk-print-page { padding-inline: var(--tk-space-6); }

/* ---- AI tools (maintenance assistant, listing generator) ---- */
.tk-aitool { display: grid; grid-template-columns: 1fr 1fr; gap: var(--tk-space-6); align-items: start; }
@media (max-width: 980px) { .tk-aitool { grid-template-columns: 1fr; } }
.tk-aitool__out { display: flex; flex-direction: column; gap: var(--tk-space-4); }
.tk-output { background: var(--tk-raised); border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  padding: var(--tk-space-4); min-height: 320px; white-space: pre-wrap; line-height: 1.65;
  font-size: .875rem; color: var(--tk-text); overflow: auto; }
textarea.tk-output { width: 100%; resize: vertical; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.tk-usage { display: flex; flex-direction: column; gap: .35rem; }
.tk-usage__row { display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  font-size: .7rem; text-transform: uppercase; letter-spacing: .1em; color: var(--tk-text-muted); }
.tk-usage__track { height: 6px; border-radius: 3px; background: var(--tk-raised); overflow: hidden; }
.tk-usage__bar { height: 100%; width: 0; background: var(--tk-burgundy); transition: width .3s var(--tk-ease); }
.tk-history { display: flex; flex-direction: column; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); overflow: hidden; }
.tk-history__item { padding: .7rem var(--tk-space-4); border-bottom: 1px solid var(--tk-border);
  font-size: .825rem; color: var(--tk-text); background: none; border-left: 0; border-right: 0; text-align: left; }
.tk-history__item:last-child { border-bottom: 0; }
.tk-history__item:hover { background: color-mix(in srgb, var(--tk-burgundy) 7%, transparent); }
@media (prefers-reduced-motion: reduce) { .tk-usage__bar { transition: none; } }
.tk-usage-badge { display: inline-block; font-family: var(--tk-font-body); font-size: .625rem; font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase; padding: .22em .6em; border-radius: 4px;
  background: color-mix(in srgb, var(--tk-burgundy) 12%, transparent); color: var(--tk-accent-text);
  border: 1px solid color-mix(in srgb, var(--tk-burgundy) 35%, transparent); }

/* Shared button-icon spinner (loading states, e.g. "Checking…") — hoisted from
   per-page style blocks so warranty-checker (which never defined it locally)
   gets it too. Reuses the @keyframes tk-spin already defined above. */
.tk-btn__spin { animation: tk-spin .8s linear infinite; }
@media (prefers-reduced-motion: reduce) { .tk-btn__spin { animation: none; } }

/* Map shell (service locator) */
.tk-map { width: 100%; height: 420px; border: 1px solid var(--tk-border); border-radius: var(--tk-radius-lg); overflow: hidden; }
@media (max-width: 700px) { .tk-map { height: 300px; } }

/* Service locator: search/filter panel + results list, paired with the .tk-map above */
.tk-locator { display: grid; grid-template-columns: 340px 1fr; gap: var(--tk-space-6); align-items: start; }
@media (max-width: 900px) {
  .tk-locator { grid-template-columns: 1fr; }
  .tk-locator__map { order: 1; }
  .tk-locator__panel { order: 2; }
}
.tk-locator__filters { display: flex; gap: .5rem; margin: var(--tk-space-4) 0; }
.tk-locator__filter { flex: 1; }
.tk-locator__filter input { position: absolute; opacity: 0; width: 1px; height: 1px; overflow: hidden; }
.tk-locator__filter label { display: block; text-align: center; font-family: var(--tk-font-display); font-weight: 700;
  font-size: .68rem; text-transform: uppercase; letter-spacing: .06em; padding: .5rem .4rem; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); color: var(--tk-text-muted); cursor: pointer;
  transition: background-color .2s var(--tk-ease), color .2s var(--tk-ease), border-color .2s var(--tk-ease); }
.tk-locator__filter input:checked + label { background: var(--tk-burgundy); border-color: var(--tk-burgundy); color: #fbeef0; }
.tk-locator__filter input:focus-visible + label { outline: 2px solid var(--tk-accent-text); outline-offset: 2px; }
.tk-locator__list { display: flex; flex-direction: column; gap: var(--tk-space-3); max-height: 420px;
  overflow-y: auto; padding-right: .2rem; margin-top: var(--tk-space-4); }
.tk-locator__item { text-align: left; background: var(--tk-raised); border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); padding: var(--tk-space-3) var(--tk-space-4); cursor: pointer;
  transition: border-color .2s var(--tk-ease); }
.tk-locator__item:hover, .tk-locator__item:focus-visible { border-color: var(--tk-burgundy); }
.tk-locator__item[hidden] { display: none; }
.tk-locator__item-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin-bottom: .35rem; }
.tk-locator__item-name { font-weight: 700; font-size: .875rem; color: var(--tk-text); }
.tk-locator__item-addr, .tk-locator__item-phone { display: flex; align-items: center; gap: .4rem;
  font-size: .78rem; color: var(--tk-text-muted); margin: .2rem 0 0; }
.tk-locator__item-addr svg, .tk-locator__item-phone svg { flex-shrink: 0; }
.tk-locator__item-cta { margin-top: .65rem; }
.tk-locator__empty { text-align: center; color: var(--tk-text-muted); padding: var(--tk-space-6) 0; margin: 0; }

/* Comparison table (compare.php) */
.tk-compare { width: 100%; border-collapse: collapse; }
.tk-compare th, .tk-compare td { padding: .7rem var(--tk-space-4); border-bottom: 1px solid var(--tk-border);
  font-size: .875rem; text-align: left; vertical-align: top; }
.tk-compare thead th { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; font-size: .7rem; color: var(--tk-text-muted); }
.tk-compare__label { color: var(--tk-text-muted); font-size: .7rem; text-transform: uppercase; letter-spacing: .08em; width: 28%; }
.tk-compare__diff { background: color-mix(in srgb, var(--tk-burgundy) 8%, transparent); }
.tk-compare-wrap { overflow-x: auto; }

/* ---- Warranty checker ---- */
/* Gold CTA modifier — reserved for this tool's "exclusive to community members"
   branding (matches --tk-gold's existing scope: featured flags + this). */
.tk-btn--gold { background: var(--tk-gold); color: #2a2110; box-shadow: 0 8px 20px color-mix(in srgb, var(--tk-gold) 40%, transparent); }
.tk-btn--gold:hover { background: color-mix(in srgb, var(--tk-gold) 85%, #fff); }

/* Privacy toggle — blurs sensitive result fields (serial number, card/NFC images)
   until the visitor explicitly opts to reveal them. */
.tk-privacy-blur { transition: filter .3s var(--tk-ease), opacity .3s var(--tk-ease); }
.tk-privacy-blur.tk-is-blurred { filter: blur(8px); opacity: .6; user-select: none; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .tk-privacy-blur { transition: none; } }

/* ---- Will It Fit ---- */
/* Asymmetric on purpose: the input column only has to hold the 85.6mm (~323px)
   calibration card, everything else is the visualisation. */
.tk-fit { display: grid; grid-template-columns: 380px minmax(0, 1fr); gap: var(--tk-space-6); align-items: start; }
@media (max-width: 980px) { .tk-fit { grid-template-columns: 1fr; } }
.tk-fit__head { display: flex; align-items: center; gap: .5rem; font-family: var(--tk-font-display);
  font-weight: 700; text-transform: uppercase; letter-spacing: .08em; font-size: .8rem;
  margin: 0 0 var(--tk-space-4); }
.tk-fit__head svg { color: var(--tk-accent-text); }
.tk-fit__result { min-height: 500px; display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center; }
.tk-fit__out { width: 100%; }
.tk-fit__out > * + * { margin-top: var(--tk-space-6); }
.tk-fit__empty { padding: var(--tk-space-12) var(--tk-space-6); color: var(--tk-text-muted); }
.tk-fit__emptyicon { width: 48px; height: 48px; margin-bottom: var(--tk-space-4); }
.tk-fit__empty h2 { font-family: var(--tk-font-display); font-weight: 700; font-size: 1.1rem;
  color: var(--tk-text); margin: 0 0 .35rem; }
.tk-fit__empty p { font-size: .875rem; margin: 0; }
.tk-fit__vizhead { display: flex; align-items: center; justify-content: center; gap: .4rem;
  font-size: .7rem; text-transform: uppercase; letter-spacing: .12em; color: var(--tk-text-muted);
  margin: var(--tk-space-6) 0 var(--tk-space-3); }
.tk-fit__vizhead svg { width: 14px; height: 14px; color: var(--tk-accent-text); }
.tk-fit__verdict { display: inline-block; font-family: var(--tk-font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; font-size: .8rem; padding: .5rem 1.1rem;
  border-radius: var(--tk-radius-pill); }
.tk-fit__verdict--good { background: color-mix(in srgb, var(--tk-success) 18%, transparent);
  color: color-mix(in srgb, var(--tk-success) 70%, var(--tk-text));
  border: 1px solid color-mix(in srgb, var(--tk-success) 45%, transparent); }
.tk-fit__verdict--warn { background: color-mix(in srgb, var(--tk-gold) 18%, transparent);
  color: var(--tk-gold-ink); border: 1px solid color-mix(in srgb, var(--tk-gold) 45%, transparent); }
.tk-fit__verdict--bad { background: color-mix(in srgb, var(--tk-burgundy) 15%, transparent);
  color: var(--tk-accent-text); border: 1px solid color-mix(in srgb, var(--tk-burgundy) 45%, transparent); }
.tk-fit__note { color: var(--tk-text-muted); font-size: .85rem; margin: .5rem 0 0; }
/* A note that carries an inline icon and an inline text action, so it has to
   stay in the text flow rather than become a flex row like .tk-fit__warn. */
.tk-fit__note--inline svg { width: 14px; height: 14px; color: var(--tk-accent-text); }
.tk-fit__note--inline .tk-btn { vertical-align: baseline; font-size: inherit; }
/* Thickness alert — a .tk-callout that holds an icon inline with its copy, and
   reads left-aligned inside the otherwise centred result panel. */
.tk-fit__warn { display: flex; align-items: flex-start; gap: .5rem; text-align: left;
  font-size: .85rem; line-height: 1.6; color: var(--tk-text-muted); margin-top: var(--tk-space-4); }
.tk-fit__warn svg { color: var(--tk-gold-ink); flex-shrink: 0; }
.tk-fit__warn strong { color: var(--tk-text); }
/* Watch 1 vs Watch 2 table — sits under the still-visible "compare" trigger. */
.tk-fit__compare { margin-top: var(--tk-space-6); }
.tk-fit__compare .tk-fit__head { justify-content: center; }
.tk-fit__print hr { border: 0; border-top: 1px solid var(--tk-border); margin: 0 0 var(--tk-space-4); }
/* Vertical stack inside one of the page's JS mount slots — the slots themselves are
   flat divs in the PHP, so the renderer supplies its own spacing. */
.tk-fit__group { display: flex; flex-direction: column; gap: var(--tk-space-4); }
.tk-fit__w2 { border-top: 1px solid var(--tk-border);
  padding-top: var(--tk-space-4); margin-top: var(--tk-space-4); }
/* The wrist unit <select> is fixed-width, so the number input has to take the rest of
   the row — otherwise the group stops short and misaligns with the plain mm fields.
   Scoped to this page: .tk-inputgroup has no grow rule of its own anywhere yet. */
.tk-fit .tk-inputgroup input { flex: 1; min-width: 0; }

/* Calibration widget. The card is deliberately sized in physical mm: the visitor
   scales it until it matches a real credit card, and the saved px-per-mm ratio is
   getBoundingClientRect().width / 85.6. Changing these two lengths silently
   changes every measurement the tool renders. */
.tk-cal { border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  padding: var(--tk-space-4); margin-bottom: var(--tk-space-4);
  background: color-mix(in srgb, var(--tk-burgundy) 5%, transparent); }
.tk-cal__card { width: 85.6mm; height: 53.98mm; transform-origin: center;
  margin: 0 auto .75rem; border-radius: 4mm; display: grid; place-items: center;
  background: linear-gradient(135deg, #24405e, #16283c);
  border: 1px solid color-mix(in srgb, #fff 25%, transparent);
  color: rgba(255,255,255,.72); font-size: .7rem; letter-spacing: .18em; text-transform: uppercase; }
.tk-cal__slider { width: 100%; accent-color: var(--tk-burgundy); }
.tk-cal__status { display: flex; align-items: center; justify-content: space-between; gap: var(--tk-space-3); }
.tk-cal__ok { display: inline-flex; align-items: center; gap: .35rem; font-size: .8rem; color: var(--tk-success); }
.tk-cal__intro { display: flex; align-items: flex-start; gap: .4rem; font-size: .8rem;
  color: var(--tk-text-muted); margin: 0 0 var(--tk-space-3); }
.tk-cal__intro svg { color: var(--tk-accent-text); flex-shrink: 0; }
.tk-cal__intro strong { color: var(--tk-text); }
.tk-cal__actions { display: flex; gap: .5rem; margin-top: var(--tk-space-3); }
.tk-cal__actions .tk-btn:first-child { flex: 1; justify-content: center; }

/* Thickness comparison chart */
.tk-fit__chart { display: flex; flex-direction: column; gap: .3rem; margin-top: var(--tk-space-6); }
.tk-fit__chart .tk-fit__vizhead { margin-top: 0; }
/* #ref-bars-container is a JS mount point *inside* the chart; display:contents lets
   the rows it renders take part in the chart's own flex gap. */
.tk-fit__rows { display: contents; }
.tk-fit__row { display: grid; grid-template-columns: 110px minmax(0, 1fr) 56px; align-items: center;
  gap: .5rem; font-size: .7rem; }
.tk-fit__rowlabel { color: var(--tk-text-muted); text-align: right; }
.tk-fit__rowlabel--self { color: var(--tk-accent-text); font-weight: 700; }
.tk-fit__track { height: 10px; border-radius: 5px; overflow: hidden;
  background: color-mix(in srgb, var(--tk-text) 10%, transparent); }
.tk-fit__bar { height: 100%; border-radius: 5px; background: color-mix(in srgb, var(--tk-text-muted) 55%, transparent);
  transition: width .4s var(--tk-ease); }
.tk-fit__bar--self { background: var(--tk-burgundy); }
@media (prefers-reduced-motion: reduce) { .tk-fit__bar { transition: none; } }
.tk-fit__rowval { color: var(--tk-text-muted); }
.tk-fit__rowval--self { color: var(--tk-accent-text); font-weight: 700; }

/* Fit visualisations — SVG chrome.
   The two diagrams are drawn by the page's own JS. Their fills are
   representational, not decoration: a tan wrist, a dark steel case, a dark
   dial, a leather strap. Those read correctly on ivory and are left exactly as
   drawn — nothing below touches a fill that depicts a physical object. What is
   themed here is the chrome that was tuned against the old dark page: the
   outlines where the watch meets the wrist, and the measurement annotations.
   overflow:visible is load-bearing, not decoration — the lug-to-lug and
   thickness annotations are drawn outside the SVG viewBox. */
.tk-fit__vizwrap { width: 100%; display: flex; justify-content: center; }
.tk-fit__viz { display: block; margin: 0 auto; width: 100%; height: auto; overflow: visible; }
.tk-fit__viz--td { max-width: 200px; }
.tk-fit__viz--sp { max-width: 260px; }
/* Steel. The case and crown outlines straddle the boundary between the dark
   case and the wrist/page, so they need a mid tone that reads against both. */
.tk-fit__vizedge { stroke: var(--tk-text-muted); }
.tk-fit__vizmetal { fill: var(--tk-text-muted); }
/* Interior hairlines — bezel ring, dial rim, strap edge, side-case inner. These
   only ever sit on the preserved dark fills, so they stay dark ink: --tk-border
   here would draw a bright ring the watch never had, which is a redesign, not a
   retune. Kept in the token system rather than as a raw #333. */
.tk-fit__vizhair { stroke: color-mix(in srgb, var(--tk-text) 78%, transparent); }
/* Overhang warning on the case ring. --tk-burgundy-soft, not --tk-burgundy: this
   stroke is painted on the near-black case, where the ink burgundy measures
   1.5:1 and would silently delete the warning. Must stay after .tk-fit__vizedge. */
.tk-fit__vizedge--over { stroke: var(--tk-burgundy-soft); }
/* Measurement annotations. Both brackets travel with case size, so they land on
   the tan wrist for small cases and on the white panel for large ones, and no
   flat colour clears AA on both. The ink is chosen for the panel and the labels
   carry a surface-coloured halo (paint-order: stroke) so they stay legible
   wherever they land. Watch 1 gets the accent; the compare overlay gets plain
   ink plus its dashed ring, so the two brackets never read as the same watch. */
.tk-fit__vizdim { stroke: var(--tk-accent-text); }
.tk-fit__vizthk { stroke: var(--tk-gold-ink); }
.tk-fit__vizcmp { stroke: var(--tk-text); }
/* The compare overlay's ring is the one mark that is drawn *on* the watch
   rather than beside it: when watch 2 is the smaller one it falls inside the
   near-black case, where ink measures 1.2:1. Lume instead — 13:1 on the case
   and 5:1 on the wrist. Same split as watch 1, whose ring is steel while its
   bracket is accent. */
.tk-fit__vizcmp-ring { stroke: var(--tk-lume); }
.tk-fit__vizlbl { paint-order: stroke; stroke: var(--tk-surface); stroke-width: 2.5px;
  stroke-linejoin: round; }
.tk-fit__vizlbl--dim { fill: var(--tk-accent-text); }
.tk-fit__vizlbl--thk { fill: var(--tk-gold-ink); }
.tk-fit__vizlbl--cmp { fill: var(--tk-text); }

/* ---- Trade calculator ---- */
.tk-trade { display: flex; flex-direction: column; gap: var(--tk-space-8); }
.tk-trade__how { max-width: 760px; margin-inline: auto; width: 100%; }
.tk-trade__how h2 { display: flex; align-items: center; gap: .5rem; margin-bottom: 0; }
.tk-trade__how ol { color: var(--tk-text-muted); font-size: .85rem; line-height: 1.6;
  margin: .6rem 0 0; padding-left: 1.1rem; }
.tk-trade__how strong { color: var(--tk-text); }
/* Native <details> disclosure. The default triangle is suppressed in favour of the
   chevron, which is pushed to the far edge and flips to point up when the panel is open. */
.tk-trade__howsum { cursor: pointer; list-style: none; }
.tk-trade__howsum::-webkit-details-marker { display: none; }
.tk-trade__howsum:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 3px; border-radius: 4px; }
.tk-trade__howchev { margin-left: auto; flex: 0 0 auto; color: var(--tk-text-muted);
  transition: transform .2s ease; }
.tk-trade__how[open] .tk-trade__howchev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .tk-trade__howchev { transition: none; } }

/* Give / receive columns */
.tk-trade__grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--tk-space-8); }
@media (max-width: 780px) { .tk-trade__grid { grid-template-columns: 1fr; } }
.tk-trade__side { display: flex; flex-direction: column; gap: var(--tk-space-3); }
.tk-trade__sidehead { display: flex; align-items: center; gap: .5rem; font-size: .9rem;
  text-transform: uppercase; letter-spacing: .08em; margin: 0; }
/* Value leaving your side reads as the debit colour, value arriving as the credit one. */
.tk-trade__sideic--out { color: var(--tk-accent-text); }
.tk-trade__sideic--in { color: var(--tk-success); }
.tk-trade__sidenote { color: var(--tk-text-muted); font-size: .8rem; margin: 0; }
.tk-trade__rows { display: flex; flex-direction: column; gap: var(--tk-space-2); }
.tk-trade__add { margin-top: .25rem; }

/* One watch row: the joined [name | ₱ | value | ×] control strip. Selectors key off
   the controls rather than :first-child/:last-child of everything, so the sr-only
   <label>s that sit in the row (and the rows JS builds without them) both work. */
.tk-trade__row { display: flex; }
.tk-trade__row > input, .tk-trade__row > span, .tk-trade__row > button { border-radius: 0; margin-left: -1px; }
.tk-trade__row > input:first-of-type { margin-left: 0;
  border-top-left-radius: var(--tk-radius); border-bottom-left-radius: var(--tk-radius); }
.tk-trade__row > :last-child { border-top-right-radius: var(--tk-radius); border-bottom-right-radius: var(--tk-radius); }
.tk-trade__row input { background: var(--tk-page); border: 1px solid var(--tk-border); color: var(--tk-text);
  padding: .6rem .7rem; font: inherit; font-size: .85rem; min-width: 0; }
.tk-trade__row input[type="text"] { flex: 2 1 0; }
.tk-trade__row input[type="number"] { flex: 1 1 0; }
/* z-index keeps the remove button's focus ring above the value input it overlaps. */
.tk-trade__row .remove-watch-btn { position: relative; z-index: 2; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center; padding-inline: .7rem;
  background: var(--tk-surface); border: 1px solid var(--tk-border); color: var(--tk-text-muted); cursor: pointer; }
.tk-trade__row .remove-watch-btn:hover { color: var(--tk-accent-text); border-color: var(--tk-burgundy); }

.tk-trade__rule { border: 0; border-top: 1px solid var(--tk-border); margin: var(--tk-space-6) 0; width: 100%; }

/* Cash direction — a fieldset so the three radios carry a group name. */
.tk-trade__cash { border: 0; padding: 0; margin: 0; min-width: 0; text-align: center; }
.tk-trade__cashlegend { font-size: .7rem; text-transform: uppercase; letter-spacing: .18em;
  color: var(--tk-text-muted); padding: 0; margin: 0 auto var(--tk-space-4); float: none; }
.tk-trade__radios { display: flex; justify-content: center; flex-wrap: wrap; gap: var(--tk-space-6); }
.tk-trade__cashfield { max-width: 300px; margin: var(--tk-space-4) auto 0; }
.tk-trade__notes textarea { width: 100%; background: var(--tk-page); border: 1px solid var(--tk-border);
  color: var(--tk-text); border-radius: var(--tk-radius); padding: .6rem .7rem; font: inherit;
  font-size: .85rem; resize: vertical; }
.tk-trade__actions { justify-content: center; margin-top: var(--tk-space-6); }
.tk-trade__actions .tk-btn { flex: 1 1 auto; justify-content: center; max-width: 320px; }
/* .tk-inputgroup carries no grow rule and no input skin of its own — everywhere else
   in the system it sits inside a .tk-field, which is what supplies both. The cash
   amount and the saved-trades filter do not, so without this they render as raw UA
   controls (2px inset border, Arial) beside a themed addon. Page-scoped, same as
   the .tk-fit fix. Values mirror .tk-field input / .tk-trade__row input.
   The border-left collapse is restated because the global rule that does it
   (.tk-inputgroup > *:not(:first-child)) is out-specified by the selector above it. */
.tk-trade .tk-inputgroup input { flex: 1; min-width: 0;
  background: var(--tk-page); border: 1px solid var(--tk-border); color: var(--tk-text);
  padding: .6rem .7rem; font: inherit; font-size: .85rem; }
.tk-trade .tk-inputgroup > *:not(:first-child) { border-left-width: 0; }
.tk-trade input:disabled, .tk-trade .tk-btn[disabled] { opacity: .55; cursor: not-allowed; }
.tk-trade .tk-btn[disabled]:hover { transform: none; }

/* Results. Visibility stays on the element's inline style — that is what the page's
   own JS toggles ('none' / 'block') — so nothing here may set display. */
.tk-trade__resulthead { display: flex; align-items: center; justify-content: space-between; gap: var(--tk-space-4);
  flex-wrap: wrap; border-bottom: 1px solid var(--tk-border); padding-bottom: var(--tk-space-3);
  margin-bottom: var(--tk-space-6); }
.tk-trade__resulthead h2 { font-size: var(--tk-fs-h2); margin: 0; }
.tk-trade__cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--tk-space-4); }
@media (max-width: 780px) { .tk-trade__cards { grid-template-columns: 1fr; } }
/* The card frame is anchored on the grid's children rather than on .tk-result: the
   gain/loss card's class list is reset wholesale by the page's JS on every calculation,
   so a class-based frame would vanish the moment a visitor calculates. The .tk-result
   classes stay on the markup (and keep the label/value type); this guarantees the box.
   Three cards side by side, so .tk-result's display size is dialled back too — a
   seven-figure peso amount has to fit inside a third of the row. */
.tk-trade__cards > * { background: var(--tk-raised); border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius-lg); padding: var(--tk-space-6); text-align: center;
  display: flex; flex-direction: column; gap: .5rem; justify-content: center; }
.tk-trade__cards .tk-result__value { font-size: clamp(1.35rem, 3.2vw, 1.9rem); color: var(--tk-text); }
.tk-trade__resultactions { display: flex; justify-content: center; margin-top: var(--tk-space-6); }

/* Net gain/loss states. Task 4 rewrites the hardcoded className reset in
   calculateTrade() with exactly:
     'tk-result tk-trade__net tk-trade__net--gain' | '--loss' | '--even'
   so these modifiers assume .tk-result is still on the element beside them. */
.tk-trade__net--gain { border-color: color-mix(in srgb, var(--tk-success) 45%, transparent);
  background: color-mix(in srgb, var(--tk-success) 10%, var(--tk-raised)); }
.tk-trade__net--gain .tk-result__value { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text)); }
.tk-trade__net--loss { border-color: color-mix(in srgb, var(--tk-burgundy) 45%, transparent);
  background: color-mix(in srgb, var(--tk-burgundy) 7%, var(--tk-raised)); }
.tk-trade__net--loss .tk-result__value { color: var(--tk-accent-text); }
.tk-trade__net--even { border-color: var(--tk-border);
  background: color-mix(in srgb, var(--tk-text) 5%, var(--tk-raised)); }
.tk-trade__net--even .tk-result__value { color: var(--tk-text-muted); }

/* Saved trades */
.tk-trade__savedhead { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-4); flex-wrap: wrap; margin-bottom: var(--tk-space-4); }
.tk-trade__savedtitle { display: flex; align-items: center; gap: .5rem; font-size: .9rem;
  text-transform: uppercase; letter-spacing: .08em; margin: 0; }
.tk-trade__savedtitle svg { color: var(--tk-accent-text); }
.tk-trade__savedsearch { flex: 1 1 200px; max-width: 250px; }
.tk-trade__list { display: flex; flex-direction: column; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); overflow: hidden; }
.tk-trade__item { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-4); flex-wrap: wrap; padding: var(--tk-space-3) var(--tk-space-4);
  border-top: 1px solid var(--tk-border); }
.tk-trade__item:first-child { border-top: 0; }
.tk-trade__itemtext { display: flex; flex-direction: column; min-width: 0; }
.tk-trade__itemsummary { font-weight: 600; font-size: .875rem; color: var(--tk-text); }
.tk-trade__itemdate { font-size: .75rem; color: var(--tk-text-muted); }
.tk-trade__itemactions { display: flex; gap: .4rem; flex: 0 0 auto; }
.tk-trade__itemactions .tk-btn { padding: .45rem .7rem; font-size: .65rem; }
/* The empty saved-trades card is hidden with the `hidden` attribute (`el.hidden`), so
   nothing here may set `display` on .tk-trade__saved — an author `display` declaration
   would beat the UA's `[hidden] { display: none }` and the card would never hide.
   .tk-panel sets no display of its own, which is why this needs no rule at all. */

/* Login-required dialog — a native <dialog> on .tk-modal; only the width and the
   footer/title trim are page-specific. */
.tk-trade__modal { max-width: min(92vw, 480px); }
.tk-trade__modaltitle { display: flex; align-items: center; gap: .5rem; font-size: 1rem; margin: 0; }
.tk-trade__modaltitle svg { color: var(--tk-gold-ink); }
.tk-trade__modalclose { background: none; border: 0; color: var(--tk-text-muted); cursor: pointer;
  display: inline-flex; padding: .25rem; }
.tk-trade__modalclose:hover { color: var(--tk-text); }
.tk-trade__modalfoot { display: flex; justify-content: flex-end; gap: .5rem;
  padding: 0 var(--tk-space-6) var(--tk-space-6); }

/* ---- Trader dashboard ---- */
/* An application screen rather than a calculator, so the layer is mostly grids:
   the panels, the result card, the fields and the buttons all come from
   .tk-panel / .tk-result / .tk-field / .tk-btn unchanged. What is new here is the
   hero's capacity pill, the headline figure's sign states, the stat pair, and the
   P&L result trio. */
.tk-dash { display: flex; flex-direction: column; gap: var(--tk-space-8);
  padding-block: var(--tk-space-8) var(--tk-space-12); }

/* Hero: title block on the left; capacity, share and the primary action on the right. */
.tk-dash__headrow { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--tk-space-6); flex-wrap: wrap; }
.tk-dash__title { display: flex; align-items: center; gap: .5rem; }
.tk-dash__title svg { width: .8em; height: .8em; flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-dash__actions { display: flex; align-items: center; gap: var(--tk-space-3); flex-wrap: wrap; }
/* "N / 50 Active" is a capacity read-out, not a status, so it is a quiet outlined
   pill rather than one of the .tk-badge fills. */
.tk-dash__cap { display: inline-flex; align-items: center; gap: .4rem; white-space: nowrap;
  background: var(--tk-surface); border: 1px solid var(--tk-border); border-radius: var(--tk-radius-pill);
  padding: .5rem .9rem; font-size: .7rem; text-transform: uppercase; letter-spacing: .08em;
  color: var(--tk-text-muted); }
.tk-dash__cap svg { width: 15px; height: 15px; color: var(--tk-accent-text); }
/* The add affordance is disabled at the 50-item cap — the hover lift has to go with it. */
.tk-dash__actions .tk-btn[disabled] { opacity: .55; cursor: not-allowed; }
.tk-dash__actions .tk-btn[disabled]:hover { transform: none; }

/* Headline figure. The gain/loss states deliberately mirror .tk-trade__net--gain/--loss
   so a profit and a loss read the same way across the two money tools. */
.tk-dash__total--gain { border-color: color-mix(in srgb, var(--tk-success) 45%, transparent);
  background: color-mix(in srgb, var(--tk-success) 10%, var(--tk-raised)); }
.tk-dash__total--gain .tk-result__value { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text)); }
.tk-dash__total--loss { border-color: color-mix(in srgb, var(--tk-burgundy) 45%, transparent);
  background: color-mix(in srgb, var(--tk-burgundy) 7%, var(--tk-raised)); }
.tk-dash__total--loss .tk-result__value { color: var(--tk-accent-text); }

/* The two stat panels */
.tk-dash__stats { display: grid; grid-template-columns: 1fr 1fr; gap: var(--tk-space-4); }
@media (max-width: 720px) { .tk-dash__stats { grid-template-columns: 1fr; } }
.tk-dash__stat { display: flex; flex-direction: column; gap: .35rem; }
.tk-dash__statlabel { font-size: .7rem; text-transform: uppercase; letter-spacing: .12em;
  color: var(--tk-text-muted); }
.tk-dash__statval { font-family: var(--tk-font-body); font-weight: 700;
  font-size: clamp(1.4rem, 3.5vw, 1.9rem); line-height: 1.1; color: var(--tk-accent-text); }
/* Capital already committed reads as the caution tone, the projection keeps the accent.
   --tk-gold-ink, not --tk-gold: the raw gold fails AA as text on the light page. */
.tk-dash__statval--cost { color: var(--tk-gold-ink); }

/* Profit & Loss block */
.tk-dash__panelhead { display: flex; align-items: center; gap: .5rem; font-size: .9rem;
  text-transform: uppercase; letter-spacing: .08em; margin: 0 0 var(--tk-space-4); }
.tk-dash__panelhead svg { color: var(--tk-accent-text); }
.tk-dash__plform { display: grid; grid-template-columns: 1fr 1fr auto; gap: var(--tk-space-3);
  align-items: end; }
.tk-dash__plgo { justify-content: center; }
@media (max-width: 620px) {
  .tk-dash__plform { grid-template-columns: 1fr; }
  .tk-dash__plgo { width: 100%; }
}
/* #pl-results and #pl-loading are shown and hidden by calculateProfitLoss() writing
   element.style.display ('none' / 'flex' / 'block'), so nothing here may set display
   on either — the rules below only describe how they look once the script shows them. */
.tk-dash__plgrid { gap: var(--tk-space-4); flex-wrap: wrap; border-top: 1px solid var(--tk-border);
  margin-top: var(--tk-space-6); padding-top: var(--tk-space-6); }
.tk-dash__plcell { flex: 1 1 140px; display: flex; flex-direction: column; gap: .3rem; text-align: center; }
.tk-dash__plcell + .tk-dash__plcell { border-left: 1px solid var(--tk-border); }
@media (max-width: 560px) { .tk-dash__plcell + .tk-dash__plcell { border-left: 0; } }
.tk-dash__pllabel { font-size: .65rem; text-transform: uppercase; letter-spacing: .12em;
  color: var(--tk-text-muted); }
/* The value type is anchored on the cell rather than on .tk-dash__plval, because
   calculateProfitLoss() rewrites #pl-profit's entire class attribute on every run
   (now with `tk-dash__plval tk-dash__plval--gain|--loss`). Anchoring it here means the
   figure keeps its size and weight even between runs, whatever the script writes. */
.tk-dash__plcell > div { font-family: var(--tk-font-body); font-weight: 700;
  font-size: clamp(1.15rem, 3vw, 1.5rem); line-height: 1.2; color: var(--tk-text); }
/* The three colour modifiers are written against the cell too. A bare
   `.tk-dash__plval--rev` is one class and loses to the `.tk-dash__plcell > div`
   above it on specificity, whatever the source order — measured: the revenue
   figure rendered as plain ink until these were qualified. */
.tk-dash__plcell > .tk-dash__plval--rev { color: var(--tk-accent-text); }
.tk-dash__plcell > .tk-dash__plval--gain { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text)); }
.tk-dash__plcell > .tk-dash__plval--loss { color: var(--tk-accent-text); }
.tk-dash__plloading { text-align: center; padding-block: var(--tk-space-4); color: var(--tk-text-muted); }

/* Logged-out gate — a complete page in its own right, shown instead of the dashboard. */
.tk-dash__gatewrap { padding-block: var(--tk-space-12); display: flex; justify-content: center; }
.tk-dash__gate { max-width: 480px; width: 100%; text-align: center; }
.tk-dash__gateic { display: grid; place-items: center; width: 64px; height: 64px;
  margin: 0 auto var(--tk-space-4); border-radius: 50%; color: var(--tk-accent-text);
  background: color-mix(in srgb, var(--tk-burgundy) 10%, transparent); }
.tk-dash__gateic svg { width: 30px; height: 30px; }
.tk-dash__gatetitle { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  font-size: 1.4rem; margin: 0 0 var(--tk-space-3); }
.tk-dash__gatetext { color: var(--tk-text-muted); font-size: .9rem; margin: 0 0 var(--tk-space-6); }
.tk-dash__gateactions { justify-content: center; }

/* "How to Use" — a native <details> on .tk-callout, the same disclosure shape as
   .tk-trade__how. Its default open state is server-decided, so no script touches it. */
.tk-dash__howsum { cursor: pointer; list-style: none; }
.tk-dash__howsum::-webkit-details-marker { display: none; }
.tk-dash__howsum:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 3px; border-radius: 4px; }
.tk-dash__how h2 { display: flex; align-items: center; gap: .5rem; margin: 0; }
.tk-dash__how h2 > svg:first-child { flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-dash__how p { color: var(--tk-text-muted); font-size: .85rem; margin: var(--tk-space-4) 0 0; }
.tk-dash__how ul { color: var(--tk-text-muted); font-size: .85rem; line-height: 1.75;
  margin: .4rem 0 0; padding-left: 1.1rem; }
.tk-dash__how strong { color: var(--tk-text); }
/* The two icons named inline in the copy, sized to sit on the text baseline. */
.tk-dash__howic { width: 15px; height: 15px; vertical-align: -3px; color: var(--tk-text); }
.tk-dash__howic--ok { color: var(--tk-success); }
.tk-dash__howchev { margin-left: auto; flex: 0 0 auto; color: var(--tk-text-muted);
  transition: transform .3s ease; }
.tk-dash__how[open] .tk-dash__howchev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .tk-dash__howchev { transition: none; } }

/* The four workflow dialogs. .tk-modal supplies the frame, .tk-field the controls;
   only the widths, the title trim and the form grids are page-specific. */
.tk-dash__modal { max-width: min(92vw, 620px); }
.tk-dash__modal--sm { max-width: min(92vw, 440px); }
.tk-dash__modal--lg { max-width: min(92vw, 820px); }
.tk-dash__modaltitle { font-family: var(--tk-font-display); font-weight: 700; font-size: 1rem; margin: 0; }
.tk-dash__modalname { color: var(--tk-accent-text); }
.tk-dash__modalfoot { display: flex; justify-content: flex-end; gap: .5rem; flex-wrap: wrap; }
.tk-dash__formgrid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--tk-space-4); }
@media (max-width: 560px) { .tk-dash__formgrid { grid-template-columns: 1fr; } }
/* .tk-modal__body gives every input a .3rem top margin (its label gap). Inside a
   .tk-inputgroup that offsets the field against its ₱ addon, so it is zeroed here —
   and .tk-inputgroup carries no grow rule of its own, hence the flex. */
.tk-dash__modal .tk-inputgroup input { flex: 1; min-width: 0; margin-top: 0; }

/* Leads dialog: the add form is a bordered sub-panel above the list. */
.tk-dash__leadform { border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  padding: var(--tk-space-4); margin-bottom: var(--tk-space-6); }
.tk-dash__leadformhead { font-size: .7rem; text-transform: uppercase; letter-spacing: .12em;
  color: var(--tk-text-muted); margin: 0; }
.tk-dash__leadgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--tk-space-3); }
@media (max-width: 620px) { .tk-dash__leadgrid { grid-template-columns: 1fr; } }
/* The items inside are written by loadLeads(). */
.tk-dash__leads { display: flex; flex-direction: column; }
/* One lead: who they are on the left, what they offered and its controls on the right.
   Rules rather than cards — the list is read as a ledger of offers, and a stack of
   bordered boxes inside an already-bordered dialog reads as noise. */
.tk-dash__lead { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--tk-space-4); flex-wrap: wrap; padding: var(--tk-space-4) 0;
  border-top: 1px solid var(--tk-border); }
.tk-dash__lead:first-child { border-top: 0; padding-top: 0; }
.tk-dash__leadwho { flex: 1 1 220px; min-width: 0; }
.tk-dash__leadname { font-family: var(--tk-font-body); font-size: .9rem; font-weight: 600;
  color: var(--tk-text); margin: 0; }
.tk-dash__leadcontact { font-size: .8rem; color: var(--tk-text); margin: .2rem 0 0; }
.tk-dash__leadnote { font-size: .78rem; color: var(--tk-text-muted); margin: .2rem 0 0; }
.tk-dash__leadside { display: flex; flex-direction: column; align-items: flex-end;
  gap: .5rem; flex: 0 0 auto; }
/* The offer is money, so it takes the same burgundy read as the leads column total. */
.tk-dash__leadoffer { background: color-mix(in srgb, var(--tk-burgundy) 12%, transparent);
  color: var(--tk-accent-text); border-color: color-mix(in srgb, var(--tk-burgundy) 35%, transparent); }
.tk-dash__leadctl { display: flex; align-items: center; gap: .5rem; }
/* The status <select> has no .tk-field wrapper (it is written by JS with an aria-label
   rather than a <label for>), so it carries its own copy of the .tk-field select skin.
   The background shorthand has to come before background-image or it would reset it. */
.tk-dash__leadstatus { width: 140px; max-width: 100%; background: var(--tk-page);
  border: 1px solid var(--tk-border); color: var(--tk-text); border-radius: var(--tk-radius);
  padding: .4rem 1.8rem .4rem .6rem; font: inherit; font-size: .78rem; appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--tk-text-muted) 50%),
    linear-gradient(135deg, var(--tk-text-muted) 50%, transparent 50%);
  background-position: right .9rem center, right .65rem center;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }
.tk-dash__leadstatus:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 2px; }
/* The delete control reuses .tk-dash__rowbtn: same 32px icon button as the table's row
   actions, and the leads dialog is the same "act on one record" vocabulary. */
.tk-dash__leadsloading { display: flex; align-items: center; justify-content: center; gap: .5rem;
  padding-block: var(--tk-space-6); color: var(--tk-text-muted); font-size: .85rem; }
.tk-dash__leadsloading svg { width: 15px; height: 15px; }
.tk-dash__leadsempty { text-align: center; padding-block: var(--tk-space-6);
  color: var(--tk-text-muted); font-size: .85rem; }

/* ---- Inventory table ----
   Page-scoped rather than a system-wide .tk-table, for the same reason .tk-compare is:
   this is the only sortable ledger in the system and its row internals (sold badge,
   profit sign, lead count) are all dashboard vocabulary. Nothing here sets `display`
   on a <tr> — the search filter hides and restores rows with row.style.display = ''
   / 'none', and an author `display` on the row would decide what '' restores to. */
.tk-dash__inv { padding: 0; overflow: hidden; }
.tk-dash__invhead { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-4); flex-wrap: wrap; padding: var(--tk-space-4) var(--tk-space-6);
  border-bottom: 1px solid var(--tk-border); }
.tk-dash__invhead .tk-dash__panelhead { margin: 0; }
/* Same shape as .tk-trade__savedsearch, and the same caveat: .tk-inputgroup only lays
   out — with no .tk-field wrapper the input has no skin of its own, so it is given one
   here (values mirror .tk-field input). The border-left collapse is restated because
   the global .tk-inputgroup rule that does it loses to the selector above it. */
.tk-dash__invsearch { flex: 1 1 180px; max-width: 260px; }
.tk-dash__invsearch .tk-inputgroup input { flex: 1; min-width: 0;
  background: var(--tk-page); border: 1px solid var(--tk-border); color: var(--tk-text);
  padding: .5rem .7rem; font: inherit; font-size: .85rem; }
.tk-dash__invsearch .tk-inputgroup > *:not(:first-child) { border-left-width: 0; }
.tk-dash__invsearch .tk-inputgroup__addon svg { width: 16px; height: 16px; }

.tk-dash__tablewrap { overflow-x: auto; }
.tk-dash__table { width: 100%; min-width: 780px; border-collapse: collapse; }
.tk-dash__table th, .tk-dash__table td { padding: .75rem var(--tk-space-4); text-align: left;
  vertical-align: middle; border-bottom: 1px solid var(--tk-border); font-size: .85rem; }
.tk-dash__table tbody tr:last-child td { border-bottom: 0; }
/* Column labels. The type is set on the cell so the sort <button> can inherit it. */
.tk-dash__table thead th { padding: 0; white-space: nowrap;
  background: color-mix(in srgb, var(--tk-page) 55%, var(--tk-surface));
  font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; font-size: .65rem; color: var(--tk-text-muted); }
.tk-dash__sort { display: flex; align-items: center; gap: .35rem; width: 100%;
  padding: .75rem var(--tk-space-4); background: none; border: 0; cursor: pointer;
  font: inherit; color: inherit; text-align: left; }
.tk-dash__sort:hover { color: var(--tk-text); }
.tk-dash__sort:hover .tk-dash__sortic { opacity: 1; }
.tk-dash__sort:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: -2px; }
/* The marker is the only thing saying "this column is sortable", so it has to clear
   3:1 as a state indicator — measured 3.27:1 at .75, 1.94:1 at the .45 first tried. */
.tk-dash__sortic { width: 13px; height: 13px; flex: 0 0 auto; opacity: .75; }
.tk-dash__colactions { padding: .75rem var(--tk-space-4); text-align: right; }
/* Total leads across the ledger, shown beside the column label. */
.tk-dash__leadtotal { background: color-mix(in srgb, var(--tk-burgundy) 12%, transparent);
  color: var(--tk-accent-text); border-color: color-mix(in srgb, var(--tk-burgundy) 35%, transparent); }

/* Rows. Sold rows carry a warm tint so the ledger's history reads apart from its stock;
   the hover state comes last so it also wins on a sold row (equal specificity). */
.tk-dash__table tbody tr.tk-dash__row--sold { background: color-mix(in srgb, var(--tk-page) 55%, transparent); }
.tk-dash__table tbody tr:hover { background: color-mix(in srgb, var(--tk-burgundy) 5%, transparent); }
.tk-dash__rowtitle { font-weight: 600; color: var(--tk-text); }
.tk-dash__rowref { color: var(--tk-text); font-variant-numeric: tabular-nums; }
.tk-dash__rowsub { display: block; color: var(--tk-text-muted); font-size: .75rem; line-height: 1.5; }
.tk-dash__num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.tk-dash__none { color: var(--tk-text-muted); }
/* SOLD is the closed deal, not the storefront's "you can't have this" — so it takes the
   success tone rather than .tk-badge--sold's burgundy, which would read as the opposite. */
.tk-dash__soldbadge { background: color-mix(in srgb, var(--tk-success) 18%, transparent);
  color: color-mix(in srgb, var(--tk-success) 62%, var(--tk-text));
  border-color: color-mix(in srgb, var(--tk-success) 50%, transparent); }
.tk-dash__soldprice { display: inline-block; margin-top: .25rem; font-weight: 600;
  color: color-mix(in srgb, var(--tk-success) 70%, var(--tk-text)); }
/* The sign states mirror .tk-dash__total--gain/--loss and .tk-trade__net--gain/--loss,
   so a profit and a loss read the same way everywhere in the money tools. */
.tk-dash__profit { font-weight: 700; }
.tk-dash__profit--gain { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text)); }
.tk-dash__profit--loss { color: var(--tk-accent-text); }

/* Row actions. These were rendering as ~16x6px empty boxes: Bootstrap's .btn is gone and
   the Font Awesome glyphs inside them never loaded, so they had no size at all. */
.tk-dash__cellactions { text-align: right; }
.tk-dash__rowbtns { display: flex; justify-content: flex-end; gap: .35rem; }
.tk-dash__rowbtn { display: inline-flex; align-items: center; justify-content: center;
  position: relative; flex: 0 0 auto; width: 32px; height: 32px; padding: 0;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-surface); color: var(--tk-text-muted); cursor: pointer;
  transition: color .2s var(--tk-ease), border-color .2s var(--tk-ease); }
.tk-dash__rowbtn:hover { color: var(--tk-accent-text); border-color: var(--tk-burgundy); }
.tk-dash__rowbtn:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 2px; }
.tk-dash__rowbtn svg { width: 16px; height: 16px; }
.tk-dash__rowbtn--ok { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text)); }
.tk-dash__rowbtn--ok:hover { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text));
  border-color: var(--tk-success); }
.tk-dash__rowbtn--danger:hover { color: var(--tk-accent-text); border-color: var(--tk-burgundy); }
@media (prefers-reduced-motion: reduce) { .tk-dash__rowbtn { transition: none; } }
/* Lead count, pinned to the corner of its button. */
.tk-dash__leadcount { position: absolute; top: -6px; right: -6px; min-width: 17px; height: 17px;
  display: grid; place-items: center; padding: 0 4px; border-radius: var(--tk-radius-pill);
  background: var(--tk-burgundy); color: #fbeef0; font-size: .6rem; font-weight: 700; line-height: 1; }

/* Empty ledger. Qualified so it out-specifies the .tk-dash__table td padding above. */
.tk-dash__table td.tk-dash__empty { text-align: center; color: var(--tk-text-muted);
  padding-block: var(--tk-space-12); }

/* ---- Collection manager ----
   The trader dashboard's sibling: same auth-gated application shape, and the panels,
   fields, buttons and dialogs all come from .tk-panel / .tk-field / .tk-btn / .tk-modal
   unchanged. What is page-specific is the three-up stats row (whose third cell is the AI
   curator rather than a figure), the analysis block, and the two ledgers — which, unlike
   the dashboard's, are written entirely by JS. Every class those rows will carry is
   therefore defined here up front, before the script that emits them is converted: a
   JS-referenced class with no rule is the defect this layer exists to prevent. */
.tk-coll { display: flex; flex-direction: column; gap: var(--tk-space-8);
  padding-block: var(--tk-space-8) var(--tk-space-12); }

/* Hero: title block on the left, share and the primary action on the right. */
.tk-coll__headrow { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--tk-space-6); flex-wrap: wrap; }
.tk-coll__title { display: flex; align-items: center; gap: .5rem; }
.tk-coll__title svg { width: .8em; height: .8em; flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-coll__actions { display: flex; align-items: center; gap: var(--tk-space-3); flex-wrap: wrap; }

/* Logged-out gate — a complete page in its own right, shown instead of the collection. */
.tk-coll__gatewrap { padding-block: var(--tk-space-12); display: flex; justify-content: center; }
.tk-coll__gate { max-width: 480px; width: 100%; text-align: center; }
.tk-coll__gateic { display: grid; place-items: center; width: 64px; height: 64px;
  margin: 0 auto var(--tk-space-4); border-radius: 50%; color: var(--tk-accent-text);
  background: color-mix(in srgb, var(--tk-burgundy) 10%, transparent); }
.tk-coll__gateic svg { width: 30px; height: 30px; }
.tk-coll__gatetitle { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  font-size: 1.4rem; margin: 0 0 var(--tk-space-3); }
.tk-coll__gatetext { color: var(--tk-text-muted); font-size: .9rem; margin: 0 0 var(--tk-space-6); }
.tk-coll__gateactions { justify-content: center; }

/* Stats row. Three up on the wide layout; below md the two figures share a line and the
   curator spans both — which is what the col-6 / col-6 / col-12 trio did. */
.tk-coll__stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--tk-space-4); }
@media (max-width: 767.98px) {
  .tk-coll__stats { grid-template-columns: 1fr 1fr; }
  .tk-coll__ai { grid-column: 1 / -1; }
}
.tk-coll__stat { display: flex; flex-direction: column; justify-content: center; gap: .35rem;
  text-align: center; }
.tk-coll__statlabel { font-size: .7rem; text-transform: uppercase; letter-spacing: .12em;
  color: var(--tk-text-muted); }
.tk-coll__statval { font-family: var(--tk-font-body); font-weight: 700;
  font-size: clamp(1.4rem, 3.5vw, 1.9rem); line-height: 1.1; color: var(--tk-accent-text); }
/* The longest-owned cell holds a watch name, not a figure, so it is set smaller and
   clipped rather than wrapped — the original used .text-truncate for the same reason. */
.tk-coll__statval--name { font-size: clamp(1.05rem, 2.6vw, 1.25rem); color: var(--tk-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tk-coll__statsub { font-size: .78rem; color: var(--tk-accent-text); }

/* AI curator cell. The original carried an inline `linear-gradient(135deg, #1a1a1a,
   #2c3e50)` — a fixed near-black tuned for the dark theme, which on tk-light would have
   been a black hole with an invisible outline button inside it. Rebuilt from the tokens
   so it still reads as the one "special" cell, in either theme. */
.tk-coll__ai { display: flex; flex-direction: column; justify-content: center; gap: .5rem;
  text-align: center; border-color: color-mix(in srgb, var(--tk-burgundy) 35%, var(--tk-border));
  background: linear-gradient(135deg, color-mix(in srgb, var(--tk-burgundy) 13%, var(--tk-surface)) 0%,
    var(--tk-surface) 72%); }
/* loadCollection() disables this button on both the limit-reached and up-to-date
   branches, and the quick-add button disables itself while it fetches. */
.tk-coll__ai .tk-btn[disabled], .tk-coll__quickadd .tk-btn[disabled] { opacity: .55; cursor: not-allowed; }
.tk-coll__ai .tk-btn[disabled]:hover, .tk-coll__quickadd .tk-btn[disabled]:hover { transform: none; }
/* Quota read-out. It stays a text label rather than becoming a tk_usage_meter bar: the
   script writes whole sentences into it ("You have used N/3 analyses today.", "Analysis
   is up-to-date. (N/3 used today)"), and a meter renders a ratio, not a sentence. */
.tk-coll__aihelp { font-size: .7rem; line-height: 1.4; color: var(--tk-text-muted); }

/* Analysis result. #ai-results is shown and hidden by renderAiResults() writing
   element.style.display, so nothing here may set `display` on it — which is why the
   class sits on the panel inside it rather than on the toggled element itself. */
.tk-coll__airesult { border-color: color-mix(in srgb, var(--tk-burgundy) 35%, var(--tk-border)); }
.tk-coll__panelhead { display: flex; align-items: center; gap: .5rem; font-size: .9rem;
  text-transform: uppercase; letter-spacing: .08em; margin: 0; }
.tk-coll__panelhead svg { flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-coll__panelhead--quiet, .tk-coll__panelhead--quiet svg { color: var(--tk-text-muted); }
.tk-coll__airesult .tk-coll__panelhead { margin-bottom: var(--tk-space-6); }
.tk-coll__aisub { font-family: var(--tk-font-body); font-weight: 600; font-size: .65rem;
  text-transform: uppercase; letter-spacing: .12em; color: var(--tk-text-muted);
  margin: 0 0 var(--tk-space-3); }
.tk-coll__aiprofile + .tk-coll__aisub { margin-top: var(--tk-space-6); }
.tk-coll__aiprofile { font-size: 1.05rem; line-height: 1.7; color: var(--tk-text); margin: 0;
  padding-bottom: var(--tk-space-6); border-bottom: 1px solid var(--tk-border); }
/* Recommendation cards. */
.tk-coll__recs { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--tk-space-4); }
@media (max-width: 900px) { .tk-coll__recs { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .tk-coll__recs { grid-template-columns: 1fr; } }
/* One card. Grid items stretch, so the equal heights .h-100 was asking for come
   for free; the CTA's auto top margin then pins it to the bottom edge, so three
   cards with different reason lengths still line their buttons up. */
/* min-width:0 because a grid item defaults to min-width:auto, so one unbreakable
   token — and a model name is Gemini's output, not a vetted string — widens its
   track and takes the whole page into horizontal scroll with it. Measured: a
   52-character model pushed the document from 375px to 669px before this. The
   two text blocks then wrap inside the card rather than clipping, which is the
   right trade here; the ledger rows clip because a table cell has nowhere to grow. */
.tk-coll__rec { display: flex; flex-direction: column; gap: var(--tk-space-3);
  min-width: 0; padding: var(--tk-space-4); border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); background: var(--tk-surface); text-align: center; }
/* Same box the inline style described: full width, capped at the frame's height,
   cropped rather than letterboxed. */
.tk-coll__recimg { display: block; width: 100%; max-height: 250px; object-fit: cover; }
.tk-coll__rectitle { font-family: var(--tk-font-body); font-weight: 700; font-size: .95rem;
  line-height: 1.3; color: var(--tk-accent-text); margin: 0; overflow-wrap: anywhere; }
/* The reason reads left-aligned inside a centred card, as .text-start had it. */
.tk-coll__recreason { font-size: .8rem; line-height: 1.6; color: var(--tk-text-muted);
  text-align: left; margin: 0; overflow-wrap: anywhere; }
/* The read-more pair. Which of the two shows is decided by inline style — the
   anchor's onclick writes element.style.display on both — so these carry the
   default only and nothing here may set `display` more strongly. */
.tk-coll__recshort, .tk-coll__recfull { display: inline; }
.tk-coll__recmore { color: var(--tk-accent-text); text-decoration: underline; white-space: nowrap; }
/* The card CTA. .tk-btn--block supplies the full width .w-100 gave it; margin-top:auto
   replaces .mt-3 and does the bottom-alignment above. The three variants reuse the
   system's own button tones rather than adding a fourth: ghost for the outbound search,
   --success for a watch in stock, --warn for the waitlist. tk_icon() ships at 20px,
   which towers over a .72rem button label, so it is sized down here. */
.tk-coll__recbtn { margin-top: auto; }
.tk-coll__recbtn svg { width: 15px; height: 15px; flex: 0 0 auto; }
/* The recommendation image frame and its four placeholder states.
   fetchImageForRecommendation() rewrites the placeholder's *whole* className on every
   branch — it used to write Font Awesome glyph names (fa-spinner, then fa-image /
   fa-search / fa-exclamation-triangle) — so each write has to restate .tk-coll__recph
   alongside its modifier, exactly as #pl-profit does on the dashboard. A bare modifier
   would leave the element with no size and no colour. The glyph itself is a
   server-rendered tk_icon() the script swaps in; these rules carry the tone. */
.tk-coll__recframe { display: flex; align-items: center; justify-content: center;
  height: 250px; overflow: hidden; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); background: var(--tk-page); }
.tk-coll__recph { display: grid; place-items: center; color: var(--tk-text-muted); }
.tk-coll__recph svg { width: 32px; height: 32px; }
.tk-coll__recph--loading { color: color-mix(in srgb, var(--tk-text-muted) 55%, transparent); }
.tk-coll__recph--broken { color: var(--tk-text-muted); }
.tk-coll__recph--none { color: color-mix(in srgb, var(--tk-text-muted) 70%, transparent); }
.tk-coll__recph--error { color: var(--tk-accent-text); }

/* The two ledgers. Both are real <table>s with a server-rendered <thead> and a <tbody>
   that loadCollection() fills; nothing here sets `display` on a <tr>. */
.tk-coll__list { padding: 0; overflow: hidden; }
.tk-coll__listhead { padding: var(--tk-space-4) var(--tk-space-6);
  border-bottom: 1px solid var(--tk-border); }
.tk-coll__tablewrap { overflow-x: auto; }
.tk-coll__table { width: 100%; border-collapse: collapse; }
.tk-coll__table th, .tk-coll__table td { padding: .75rem var(--tk-space-4); text-align: left;
  vertical-align: middle; border-bottom: 1px solid var(--tk-border); font-size: .85rem; }
.tk-coll__table tbody tr:last-child td { border-bottom: 0; }
.tk-coll__table thead th { white-space: nowrap;
  background: color-mix(in srgb, var(--tk-page) 55%, var(--tk-surface));
  font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; font-size: .65rem; color: var(--tk-text-muted); }
.tk-coll__table tbody tr:hover { background: color-mix(in srgb, var(--tk-burgundy) 5%, transparent); }
.tk-coll__colactions { text-align: right; }

/* Responsive columns. The original hid three cells with Bootstrap's d-none/d-*-table-cell
   pairs and showed two narrow-only lines with d-sm-none/d-md-none. Those four states are
   named here so the JS-written cells can carry the same names, and the breakpoints match
   Bootstrap's sm (576px) and md (768px) so nothing reflows at a different width. The
   hidden state is all that needs stating — shown, a cell falls back to table-cell. */
@media (max-width: 767.98px) { .tk-coll__col--md { display: none; } }
@media (max-width: 575.98px) { .tk-coll__col--sm { display: none; } }
.tk-coll__only--sm, .tk-coll__only--md { display: none; }
@media (max-width: 767.98px) { .tk-coll__only--md { display: block; } }
@media (max-width: 575.98px) { .tk-coll__only--sm { display: block; } }

/* Row internals. Task 3 writes these; the rules are defined now so no class ships
   without one. Same ledger vocabulary as the dashboard's inventory table. */
.tk-coll__rowtitle { font-weight: 600; color: var(--tk-text); max-width: 200px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A watch that has left the collection reads back one step, the way a sold row does. */
.tk-coll__rowtitle--past { color: var(--tk-text-muted); }
.tk-coll__rowsub { display: block; color: var(--tk-text-muted); font-size: .75rem; line-height: 1.5; }
/* The narrow-screen ownership line prefixes a calendar glyph. tk_icon() ships at 20px,
   which would tower over a .75rem line, so it is sized here rather than at the call. */
.tk-coll__rowsub svg { width: 13px; height: 13px; vertical-align: -2px; margin-right: .3rem; }
.tk-coll__rowserial { display: block; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .75rem; color: var(--tk-text-muted); }
.tk-coll__num { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Days owned, on .tk-badge. */
.tk-coll__days { background: color-mix(in srgb, var(--tk-lume) 16%, transparent); color: var(--tk-text);
  border-color: color-mix(in srgb, var(--tk-lume) 40%, var(--tk-border)); }
/* The bought/sold pair on a history row. No sign test lives here — the script does not
   compute one — these only give the two directions a consistent read. */
.tk-coll__money { display: block; white-space: nowrap; font-variant-numeric: tabular-nums; }
.tk-coll__moneylabel { color: var(--tk-text-muted); }
.tk-coll__moneylabel--out { color: color-mix(in srgb, var(--tk-success) 70%, var(--tk-text)); }

/* Row actions — the same 32px icon button as the dashboard's ledger. */
.tk-coll__rowbtns { display: flex; justify-content: flex-end; gap: .35rem; }
.tk-coll__rowbtn { display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; width: 32px; height: 32px; padding: 0;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-surface); color: var(--tk-text-muted); cursor: pointer;
  transition: color .2s var(--tk-ease), border-color .2s var(--tk-ease); }
.tk-coll__rowbtn:hover { color: var(--tk-accent-text); border-color: var(--tk-burgundy); }
.tk-coll__rowbtn:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 2px; }
.tk-coll__rowbtn svg { width: 16px; height: 16px; }
.tk-coll__rowbtn--ok { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text)); }
.tk-coll__rowbtn--ok:hover { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text));
  border-color: var(--tk-success); }
.tk-coll__rowbtn--danger:hover { color: var(--tk-accent-text); border-color: var(--tk-burgundy); }
@media (prefers-reduced-motion: reduce) { .tk-coll__rowbtn { transition: none; } }

/* Loading and empty rows. Qualified so they out-specify the cell padding above. */
.tk-coll__table td.tk-coll__loading, .tk-coll__table td.tk-coll__empty { text-align: center;
  color: var(--tk-text-muted); padding-block: var(--tk-space-12); }
.tk-coll__loading svg { width: 16px; height: 16px; vertical-align: -3px; }

/* Quick add from a listing URL. It lives inside the Add dialog, which sits outside
   .tk-coll in the document, so the skin is scoped to the block itself and not to the
   page. .tk-inputgroup only lays out — with no .tk-field wrapper the input has no skin
   of its own and would render as a raw ~22px UA widget beside a themed addon — so it is
   given one here; values mirror .tk-field input. The border-left collapse is restated
   because the global rule that does it is out-specified by the selector above it. */
.tk-coll__quickadd { padding-bottom: var(--tk-space-4); border-bottom: 1px solid var(--tk-border); }
.tk-coll__quickaddlabel { display: block; font-size: .7rem; text-transform: uppercase;
  letter-spacing: .1em; color: var(--tk-text-muted); margin-bottom: .35rem; }
.tk-coll__quickadd .tk-inputgroup input { flex: 1; min-width: 0;
  background: var(--tk-page); border: 1px solid var(--tk-border); color: var(--tk-text);
  padding: .6rem .7rem; font: inherit; font-size: .85rem; }
/* The deleted page-local <style> forced a placeholder colour because Bootstrap's dark
   form controls washed it out. The tk equivalent is the muted token, which measures
   5.2:1 on --tk-page; `opacity: 1` stops Firefox halving it back down. */
.tk-coll__quickadd .tk-inputgroup input::placeholder { color: var(--tk-text-muted); opacity: 1; }
.tk-coll__quickadd .tk-inputgroup > *:not(:first-child) { border-left-width: 0; }
.tk-coll__quickadd .tk-inputgroup__addon svg { width: 16px; height: 16px; }
.tk-coll__quickaddgo { white-space: nowrap; }
.tk-coll__quickaddnote { font-size: .75rem; color: var(--tk-text-muted); margin: .4rem 0 0; }

/* "How It Works" — the disclosure Task 2 converts to a native <details>. The chevron
   rules are the other half of the deleted page-local <style>: it rotated .transition-icon
   off Bootstrap's aria-expanded, which nothing sets once the Collapse component is gone,
   so the rotation is restated against [open] and the transition gated on reduced motion. */
.tk-coll__howsum { cursor: pointer; list-style: none; }
.tk-coll__howsum::-webkit-details-marker { display: none; }
.tk-coll__howsum:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 3px; border-radius: 4px; }
.tk-coll__how h2 { display: flex; align-items: center; gap: .5rem; margin: 0; }
.tk-coll__how h2 > svg:first-child { flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-coll__how ol { color: var(--tk-text-muted); font-size: .85rem; line-height: 1.75;
  margin: var(--tk-space-4) 0 0; padding-left: 1.2rem; }
.tk-coll__how strong { color: var(--tk-text); }
/* Inline affordance glyphs in the instructions. The "move it to your history" one was
   .text-danger — a warning red for an action that is routine rather than destructive —
   so it takes the same burgundy accent as every other affordance in this layer, which
   is the system's attention colour and needs no modifier of its own. */
.tk-coll__howic { width: 15px; height: 15px; vertical-align: -3px; color: var(--tk-accent-text); }
.tk-coll__howchev { margin-left: auto; flex: 0 0 auto; color: var(--tk-text-muted);
  transition: transform .3s ease; }
.tk-coll__how[open] .tk-coll__howchev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .tk-coll__howchev { transition: none; } }

/* The three workflow dialogs. .tk-modal supplies the frame and .tk-field the controls,
   so only the widths, the title trim and the date/price pairing are page-specific —
   the same split the dashboard's four dialogs use. */
.tk-coll__modal { max-width: min(92vw, 560px); }
.tk-coll__modal--sm { max-width: min(92vw, 440px); }
.tk-coll__modaltitle { font-family: var(--tk-font-display); font-weight: 700; font-size: 1rem; margin: 0; }
.tk-coll__modalfoot { display: flex; justify-content: flex-end; gap: .5rem; flex-wrap: wrap; }
/* Purchase date and purchase price shared a row (col-6 / col-6) and still do. */
.tk-coll__formgrid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--tk-space-4); }
@media (max-width: 480px) { .tk-coll__formgrid { grid-template-columns: 1fr; } }
/* "(Optional)" inside a label, which is already set at label size. */
.tk-coll__labelnote { text-transform: none; letter-spacing: 0; opacity: .8; }
/* .tk-modal__body gives every input a .3rem top margin (its label gap). Inside the
   quick-add .tk-inputgroup that offsets the field against its addon, so it is zeroed. */
.tk-coll__modal .tk-inputgroup input { margin-top: 0; }

/* Closing tools-policy footnote. */
.tk-coll__terms { font-size: .75rem; color: var(--tk-text-muted); text-align: center; margin: 0 0 var(--tk-space-6); }
.tk-coll__terms a { color: var(--tk-accent-text); text-decoration: underline; }

/* ---- Trade proposal ----
   The trade calculator's sibling — same give/receive form, same saved-items list with two
   auth branches, same login dialog — so the layer deliberately mirrors .tk-trade* and the
   panels, fields, buttons, badges and dialog all come from .tk-panel / .tk-field / .tk-btn
   / .tk-badge / .tk-modal unchanged. What is page-specific: the two column heads, the
   dashed "add watch" affordance, and the proposal card, which is an artifact rather than
   a screen (see below).
   Classes that only JS emits are defined here up front, before the script that writes them
   is converted, so no JS-referenced class ever ships without a rule. Each is marked with
   the task that starts using it. */
.tk-prop { display: flex; flex-direction: column; gap: var(--tk-space-8);
  padding-block: var(--tk-space-8) var(--tk-space-12); }

/* Hero: title block on the left, the share menu on the right. */
.tk-prop__headrow { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--tk-space-6); flex-wrap: wrap; }
.tk-prop__title { display: flex; align-items: center; gap: .5rem; }
.tk-prop__title svg { width: .8em; height: .8em; flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-prop__heroactions { display: flex; align-items: center; gap: var(--tk-space-3); flex-wrap: wrap; }

.tk-prop__panelhead { display: flex; align-items: center; gap: .5rem; font-size: .9rem;
  text-transform: uppercase; letter-spacing: .08em; margin: 0 0 var(--tk-space-6); }
.tk-prop__panelhead svg { flex: 0 0 auto; color: var(--tk-accent-text); }

/* Offering / seeking columns */
.tk-prop__grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--tk-space-8); }
@media (max-width: 780px) { .tk-prop__grid { grid-template-columns: 1fr; } }
.tk-prop__side { display: flex; flex-direction: column; gap: var(--tk-space-3); }
.tk-prop__sidehead { display: flex; align-items: center; gap: .75rem; font-size: .95rem;
  text-transform: uppercase; letter-spacing: .08em; margin: 0; }
/* The tinted square the deleted .icon-square drew, rebuilt from the tokens — its
   bg-info / bg-warning fills were Bootstrap's cyan and amber, neither of which is in
   this system. The two tones are the trade calculator's: value leaving your side reads
   as the accent (debit), value arriving as the success green (credit). */
.tk-prop__sideic { display: grid; place-items: center; flex: 0 0 auto; width: 42px; height: 42px;
  border-radius: var(--tk-radius); }
.tk-prop__sideic--out { background: color-mix(in srgb, var(--tk-burgundy) 12%, transparent);
  color: var(--tk-accent-text); }
.tk-prop__sideic--in { background: color-mix(in srgb, var(--tk-success) 14%, transparent);
  color: color-mix(in srgb, var(--tk-success) 78%, var(--tk-text)); }
.tk-prop__rows { display: flex; flex-direction: column; gap: var(--tk-space-3); }
/* "Add Watch": the dashed outline the deleted .border-dashed gave it, which is what
   distinguishes an "add another" affordance from a committed action. */
.tk-prop__addbtn { border-style: dashed; color: var(--tk-text-muted); }
.tk-prop__addbtn:hover { color: var(--tk-accent-text); }

.tk-prop__rule { border: 0; border-top: 1px solid var(--tk-border);
  margin: var(--tk-space-4) 0; width: 100%; }

/* Cash direction — a fieldset so the three radios carry a group name. */
.tk-prop__cash { border: 0; padding: 0; margin: 0; min-width: 0; text-align: center; }
.tk-prop__cashlegend { font-size: .7rem; text-transform: uppercase; letter-spacing: .18em;
  color: var(--tk-text-muted); padding: 0; margin: 0 auto var(--tk-space-4); float: none; }
.tk-prop__radios { display: flex; justify-content: center; flex-wrap: wrap; gap: var(--tk-space-6); }
.tk-prop__cashfield { max-width: 400px; margin: var(--tk-space-4) auto 0; }
/* The amount was .text-center.fw-bold — it is the one figure on the form. */
.tk-prop__cashfield input { text-align: center; font-weight: 700; }
.tk-prop__datefield { max-width: 220px; margin-inline: auto; align-items: center; }
.tk-prop__datefield input { width: 100%; }
/* .tk-field skins `input` and `select` but not `textarea`, so the notes box needs its
   own — same reason .tk-trade__notes has one. */
.tk-prop__notes textarea { width: 100%; background: var(--tk-page); border: 1px solid var(--tk-border);
  color: var(--tk-text); border-radius: var(--tk-radius); padding: .6rem .7rem; font: inherit;
  font-size: .85rem; resize: vertical; }
.tk-prop__actions { justify-content: center; margin-top: var(--tk-space-4); }
/* .tk-inputgroup only lays out; everywhere else in the system it sits inside a .tk-field,
   which is what supplies the input skin. The cash amount does not, so without this it
   renders as a raw ~22px UA widget beside a themed addon. Page-scoped, values mirror
   .tk-field input. The border-left collapse is restated because the global rule that does
   it (.tk-inputgroup > *:not(:first-child)) is out-specified by the selector above it. */
.tk-prop .tk-inputgroup input { flex: 1; min-width: 0;
  background: var(--tk-page); border: 1px solid var(--tk-border); color: var(--tk-text);
  padding: .6rem .7rem; font: inherit; font-size: .85rem; }
.tk-prop .tk-inputgroup > *:not(:first-child) { border-left-width: 0; }
/* The other half of the deleted page-local <style>: a ::placeholder colour that existed
   only because Bootstrap's dark form controls washed it out. The tk equivalent is the
   muted token; `opacity: 1` stops Firefox halving it back down. */
.tk-prop input::placeholder, .tk-prop textarea::placeholder { color: var(--tk-text-muted); opacity: 1; }
.tk-prop input:disabled, .tk-prop .tk-btn[disabled] { opacity: .55; cursor: not-allowed; }
.tk-prop .tk-btn[disabled]:hover { transform: none; }

/* One watch row (Task 4 — createInputRow()). The row keeps .input-row and its remove
   control keeps .remove-row: both are JS contracts (3 querySelectorAll + 1 closest, and a
   classList.contains test), so the tk classes sit alongside them rather than replacing
   them. Same for .watch-name / .watch-condition / .watch-scope, which getRowData() reads. */
.tk-prop__row { position: relative; display: flex; flex-direction: column; gap: .5rem;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-page); padding: var(--tk-space-3); }
.tk-prop__rowfield { display: flex; flex-direction: column; gap: .3rem; }
.tk-prop__rowfield label { font-size: .625rem; text-transform: uppercase; letter-spacing: .1em;
  color: var(--tk-text-muted); }
.tk-prop__row input, .tk-prop__row select { background: var(--tk-surface);
  border: 1px solid var(--tk-border); color: var(--tk-text); border-radius: var(--tk-radius);
  padding: .55rem .65rem; font: inherit; font-size: .85rem; width: 100%; min-width: 0; }
/* The two dropdowns share a line, as the col-6 pair did. */
.tk-prop__rowselects { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; }
@media (max-width: 420px) { .tk-prop__rowselects { grid-template-columns: 1fr; } }
/* The remove control replaces a .btn-close, which drew its own × glyph; the tk one is a
   real icon button, pinned to the corner it sat in. */
.tk-prop__rowremove { position: absolute; top: .35rem; right: .35rem; z-index: 2;
  display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px;
  padding: 0; border: 1px solid transparent; border-radius: var(--tk-radius);
  background: none; color: var(--tk-text-muted); cursor: pointer; }
.tk-prop__rowremove:hover { color: var(--tk-accent-text); border-color: var(--tk-border); }
.tk-prop__rowremove svg { width: 15px; height: 15px; pointer-events: none; }

/* Saved proposals. Same ledger shape as .tk-trade__list, and the same caveat as
   .tk-trade__saved: the empty card is hidden with the `hidden` attribute, so nothing here
   may set `display` on .tk-prop__saved — an author `display` would beat the UA's
   `[hidden] { display: none }` and the card would never hide. .tk-panel sets none. */
.tk-prop__savedhead { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-4); flex-wrap: wrap; margin-bottom: var(--tk-space-4); }
.tk-prop__savedtitle { display: flex; align-items: center; gap: .5rem; font-size: .9rem;
  text-transform: uppercase; letter-spacing: .08em; margin: 0; }
.tk-prop__savedtitle svg { color: var(--tk-accent-text); }
/* "N / 50" is a capacity read-out on .tk-badge. Its text is parsed back by the save and
   delete handlers (innerText.split('/')), so it must hold nothing but that string. */
.tk-prop__cap { background: var(--tk-page); color: var(--tk-text-muted);
  border-color: var(--tk-border); white-space: nowrap; }
.tk-prop__list { display: flex; flex-direction: column; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); overflow: hidden; }
/* One saved proposal. Task 4's live-insert builder writes this same markup, which is why
   the item classes are named rather than inlined. */
.tk-prop__item { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-4); flex-wrap: wrap; padding: var(--tk-space-3) var(--tk-space-4);
  border-top: 1px solid var(--tk-border); }
.tk-prop__item:first-child { border-top: 0; }
.tk-prop__itemtext { display: flex; flex-direction: column; min-width: 0; }
.tk-prop__itemsummary { font-weight: 600; font-size: .875rem; color: var(--tk-text);
  overflow-wrap: anywhere; }
.tk-prop__itemdate { font-size: .75rem; color: var(--tk-text-muted); }
.tk-prop__itemactions { display: flex; gap: .4rem; flex: 0 0 auto; }
.tk-prop__itemactions .tk-btn { padding: .45rem .7rem; font-size: .65rem; }

/* "How It Works" — the disclosure Task 2 converts to a native <details>, and the last
   two rules of the deleted page-local <style>: the chevron rotated off Bootstrap's
   aria-expanded, which nothing sets once the Collapse component is gone, so the rotation
   is restated against [open] and the transition gated on reduced motion. */
.tk-prop__howsum { cursor: pointer; list-style: none; }
.tk-prop__howsum::-webkit-details-marker { display: none; }
.tk-prop__howsum:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 3px; border-radius: 4px; }
.tk-prop__how h2 { display: flex; align-items: center; gap: .5rem; margin: 0; }
.tk-prop__how h2 > svg:first-child { flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-prop__how ol { color: var(--tk-text-muted); font-size: .85rem; line-height: 1.75;
  margin: var(--tk-space-4) 0 0; padding-left: 1.2rem; }
.tk-prop__how strong { color: var(--tk-text); }
.tk-prop__howchev { margin-left: auto; flex: 0 0 auto; color: var(--tk-text-muted);
  transition: transform .3s ease; }
.tk-prop__how[open] .tk-prop__howchev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .tk-prop__howchev { transition: none; } }

/* Login-required dialog (Task 2) — a native <dialog> on .tk-modal, the same shape as the
   trade calculator's; only the width and the footer/title trim are page-specific. */
.tk-prop__modal { max-width: min(92vw, 480px); }
.tk-prop__modaltitle { display: flex; align-items: center; gap: .5rem; font-size: 1rem; margin: 0; }
.tk-prop__modaltitle svg { color: var(--tk-gold-ink); }
.tk-prop__modalclose { background: none; border: 0; color: var(--tk-text-muted); cursor: pointer;
  display: inline-flex; padding: .25rem; }
.tk-prop__modalclose:hover { color: var(--tk-text); }
.tk-prop__modalfoot { display: flex; justify-content: flex-end; gap: .5rem;
  padding: 0 var(--tk-space-6) var(--tk-space-6); }

/* ---- The proposal card (Task 3) ----
   This is the page's product: html-to-image rasterises it to a PNG that a trader sends to
   a counterparty, with backgroundColor forced to #ffffff. It was already a light artifact
   on a dark page — white body, dark header — so on tk-light it is simply itself, and it is
   NOT to be redesigned into a dark surface.
   Its ink is written as literal colours rather than var()/color-mix(): the card must look
   the same in the PNG as on screen no matter what resolves at rasterisation time, and an
   artifact that leaves the site does not follow the site's theme. Everything around it
   uses the tokens as usual.
   #proposal-result itself is shown and hidden by the script writing element.style.display,
   so nothing here may set `display` on it — which is why the frame sits on the card. */
.tk-prop__card { max-width: 800px; margin-inline: auto; overflow: hidden;
  background: #ffffff; color: #23201b; border-radius: var(--tk-radius-lg); }
/* The shadow the export toggles off for clean image edges and back on afterwards
   (element.classList.remove/add), in place of Bootstrap's .shadow-lg. */
.tk-prop__card--shadow { box-shadow: 0 10px 30px rgba(0,0,0,.18); }
.tk-prop__cardhead { position: relative; overflow: hidden; padding: var(--tk-space-8) var(--tk-space-6);
  text-align: center; background: #1c1a1a; color: #f7f3ec; }
/* The soft highlight behind the header lettering, which used to be an inline
   background on a .position-absolute.w-100.h-100 filler div. */
.tk-prop__cardglow { position: absolute; inset: 0;
  background: radial-gradient(circle at center, rgba(255,255,255,.10) 0%, rgba(0,0,0,0) 70%); }
.tk-prop__cardheadinner { position: relative; z-index: 1; }
.tk-prop__cardtitle { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  letter-spacing: .18em; font-size: 1.4rem; margin: 0; color: #f7f3ec; }
.tk-prop__cardmeta { font-size: .7rem; text-transform: uppercase; letter-spacing: .12em;
  color: #b9b1a5; margin: .4rem 0 0; }
/* The save control is filtered out of the export by id, so it only ever has to look right
   on screen — it sits on the dark header, hence the light outline. */
.tk-prop__cardsave { position: absolute; top: var(--tk-space-4); right: var(--tk-space-4); z-index: 2;
  border-color: rgba(255,255,255,.45); color: #f7f3ec; background: none; }
.tk-prop__cardsave:hover { border-color: #ffffff; color: #ffffff; }
.tk-prop__cardbody { position: relative; padding: var(--tk-space-8) var(--tk-space-6); }
/* The column gap has to clear the 50px disc that sits on the centre line, or the cash
   line — which shrink-wraps and hugs the inner edge of its column — is rasterised with
   the disc sitting on top of it. This is the gutter the pe-md-5 / ps-md-5 pair used to
   hold open. It collapses with the columns on narrow screens. */
.tk-prop__cardgrid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--tk-space-6) 4.5rem; }
@media (max-width: 720px) { .tk-prop__cardgrid { grid-template-columns: 1fr; gap: var(--tk-space-6); } }
/* min-width:0 so a long watch name wraps inside its track instead of widening it and
   pushing the two columns out of alignment in the exported image. */
.tk-prop__cardcol { min-width: 0; }
.tk-prop__cardcol--out { text-align: right; }
.tk-prop__cardcol--in { text-align: left; }
@media (max-width: 720px) {
  .tk-prop__cardcol--out, .tk-prop__cardcol--in { text-align: center; }
}
.tk-prop__cardcolhead { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .14em; font-size: .7rem; color: #6b6459; margin: 0 0 var(--tk-space-4); }
/* The two dividers between the columns. Both are drawn with an inline <svg> exchange
   arrow in the markup — the dead .proposal-divider::after that used to sit in the page's
   <style> tried to draw one with a Font Awesome glyph through CSS `content`, which no tk
   page loads a font for and which would have rasterised as tofu. Nothing carries it. */
.tk-prop__vrule { position: absolute; top: 0; bottom: 50%; left: 50%; transform: translateX(-50%);
  border-left: 2px dashed #f0f0f0; }
.tk-prop__vs { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  display: grid; place-items: center; width: 50px; height: 50px; border-radius: 50%;
  background: #ffffff; border: 1px solid #e7e0d3; color: #6b6459; z-index: 2; }
/* The narrow-screen divider, which replaces the vertical rule once the two columns
   stack. It spans the whole grid and is a flex row — rule, arrow, rule — so the icon is
   centred on the line by construction rather than by a hand-tuned negative margin, which
   landed the glyph below the line in the rasterised image. The two rules are ::before /
   ::after; html-to-image reproduces pseudo-elements, and the exported phone-width PNG
   was checked to confirm it. */
.tk-prop__hrule { display: none; }
@media (max-width: 720px) {
  .tk-prop__vrule, .tk-prop__vs { display: none; }
  .tk-prop__hrule { display: flex; grid-column: 1 / -1; align-items: center; gap: .6rem;
    color: #6b6459; margin: var(--tk-space-2) 0; }
  .tk-prop__hrule::before, .tk-prop__hrule::after { content: ""; flex: 1 1 auto; height: 1px;
    background: #eeeae1; }
}
/* One offered/sought watch, written by renderList(). That builder came with the card
   rather than with Task 4's form markup: what it writes is the exported image's visible
   content, so leaving it on .badge.bg-dark would have rasterised the two pills as bare
   text in the deliverable. */
.tk-prop__dispitem { margin-bottom: var(--tk-space-4); }
.tk-prop__dispname { font-family: var(--tk-font-body); font-weight: 700; font-size: .95rem;
  color: #23201b; margin: 0 0 .35rem; overflow-wrap: anywhere; }
.tk-prop__dispbadge { display: inline-block; font-size: .625rem; font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase; padding: .3em .6em; border-radius: 4px;
  margin-right: .3rem; border: 1px solid transparent; }
.tk-prop__dispbadge--cond { background: #23201b; color: #ffffff; }
.tk-prop__dispbadge--scope { background: #f6f2ea; color: #23201b; border-color: #e7e0d3; }
.tk-prop__dispempty { font-style: italic; font-size: .8rem; color: #6b6459; }
/* The cash lines. Each is emptied and re-shown by the script, and the export filter drops
   whichever one has no text — so their visibility stays on the inline style. */
.tk-prop__cashline { margin-top: var(--tk-space-3); padding: .5rem .75rem; border-radius: var(--tk-radius);
  font-weight: 700; text-align: center; }
.tk-prop__cashline--add { background: rgba(123,30,43,.08); border: 1px solid rgba(123,30,43,.35);
  color: #6e1423; }
.tk-prop__cashline--req { background: rgba(46,120,80,.10); border: 1px solid rgba(46,120,80,.4);
  color: #1f5637; }
.tk-prop__cashlabel { display: block; font-size: .625rem; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: #6b6459; margin-bottom: .2rem; }
.tk-prop__notesblock { margin-top: var(--tk-space-6); padding-top: var(--tk-space-6);
  border-top: 1px solid #eeeae1; }
.tk-prop__noteshead { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .14em; font-size: .65rem; color: #6b6459; text-align: center; margin: 0 0 .5rem; }
.tk-prop__notestext { white-space: pre-wrap; font-size: .85rem; color: #6b6459;
  text-align: center; margin: 0; }
/* The button row, and the class the export filter drops it by — renamed from Bootstrap's
   .card-footer, which was being used as a JS predicate. If the markup and the predicate
   ever disagree these three buttons are rasterised into every exported PNG, a defect no
   DOM sweep can see, so the template writes both from one PHP variable
   ($export_foot_class) rather than repeating the literal. Renaming this class means
   changing that variable, and the predicate follows automatically. */
.tk-prop__cardfoot { padding: var(--tk-space-6); text-align: center; background: #ffffff;
  border-top: 1px solid #eeeae1; }
.tk-prop__cardfoothint { font-size: .8rem; color: #6b6459; margin: 0 0 var(--tk-space-3); }
.tk-prop__cardactions { display: flex; justify-content: center; gap: .5rem; flex-wrap: wrap; }
.tk-prop__feedback { font-size: .8rem; color: #1f5637; margin-top: .5rem;
  transition: opacity .3s; }
@media (prefers-reduced-motion: reduce) { .tk-prop__feedback { transition: none; } }

/* ---- Document generator ----
   A four-step wizard that captures two signatures on <canvas> and produces a legal
   PDF, so this layer carries more weight than the tool layers above it: the pane
   visibility below is load-bearing, not decoration.

   The panels, fields, buttons, badges and dialogs all come from .tk-panel /
   .tk-field / .tk-btn / .tk-badge / .tk-modal unchanged. What is page-specific is
   the stepper, the pane show/hide, the signature frames, and the documents ledger.

   The page-local <style> this replaces was 173 lines, the largest in the migration
   and effectively its own mini design system. templates/document-generator.php
   carries the rule-by-rule map of where each of them went; the notes below cover the
   ones whose translation is a decision rather than a rename.

   Classes that only JS emits or toggles are defined here up front, before the tasks
   that start writing them, so no JS-referenced class ever ships without a rule. Each
   is marked with the task that starts using it. */

/* .tk-container caps at --tk-container (1200px). This page is a single narrow column
   of form, which is what .main-container's max-width: 900px said, so the cap is
   tightened here; components.css loads after base.css, so it wins on source order at
   equal specificity. .tk-container keeps supplying the gutter and the centring. */
.tk-doc { max-width: 900px; display: flex; flex-direction: column; gap: var(--tk-space-8);
  padding-block: var(--tk-space-8) var(--tk-space-12); }
.tk-doc__headrow { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--tk-space-6); flex-wrap: wrap; }
.tk-doc__title { display: flex; align-items: center; gap: .5rem; }
.tk-doc__title svg { width: .8em; height: .8em; flex: 0 0 auto; color: var(--tk-accent-text); }

/* .glass-card, translated. The original was glassmorphism tuned for a dark page:
   rgba(26,26,26,.8) behind a 12px backdrop-filter, a 10%-white hairline, and a
   0 8px 32px rgba(0,0,0,.37) drop. On the ivory light theme a blurred translucent
   panel has nothing behind it to reveal, so the blur and the translucency are dropped
   rather than ported; .tk-panel supplies the surface, border and radius, and only the
   elevation survives, on the token. */
.tk-doc__card { box-shadow: var(--tk-shadow); }

/* ---- Wizard stepper ----
   `.active` and `.completed` keep their names: updateStepperUI() writes exactly those
   two tokens on #stepIndicator1..4, so renaming them would mean editing that function
   in the same breath for no gain. They are scoped under .tk-doc__step here so neither
   leaks into the page as a bare state class. */
.tk-doc__steps { position: relative; display: flex; justify-content: space-between;
  list-style: none; margin: 0; padding: 0 20px; }
/* The connector the four discs sit on. #333 on black becomes the border token. */
.tk-doc__steps::before { content: ''; position: absolute; top: 20px; left: 30px; right: 30px;
  height: 2px; background: var(--tk-border); z-index: 0; }
.tk-doc__step { position: relative; z-index: 1; width: 80px; text-align: center; }
.tk-doc__stepmark { display: flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; margin: 0 auto 8px; border-radius: 50%;
  background: var(--tk-surface); border: 2px solid var(--tk-border); color: var(--tk-text-muted);
  transition: background .4s var(--tk-ease), border-color .4s var(--tk-ease),
    color .4s var(--tk-ease), transform .4s var(--tk-ease); }
/* The discs held a Font Awesome glyph at font-size 1.1rem; tk_icon() ships at 20px,
   which is a shade large inside a 42px circle. */
.tk-doc__stepmark svg { width: 18px; height: 18px; }
.tk-doc__steplabel { display: block; font-family: var(--tk-font-display); font-size: .75rem;
  font-weight: 600; text-transform: uppercase; letter-spacing: .1em; color: var(--tk-text-muted);
  transition: color .3s var(--tk-ease); }
/* Active: the burgundy fill and the scale-up, with --tk-shadow-burg in place of the
   hand-rolled 0 0 15px rgba(177,15,46,.6) glow. */
.tk-doc__step.active .tk-doc__stepmark { background: var(--tk-burgundy);
  border-color: var(--tk-burgundy); color: #fbeef0; box-shadow: var(--tk-shadow-burg);
  transform: scale(1.1); }
.tk-doc__step.active .tk-doc__steplabel { color: var(--tk-text); }
/* Completed: the success tone. The label takes the darkened mix rather than the raw
   --tk-success, which measures 4.86:1 on --tk-page at this 12px size — over the line
   but not by much, and this label is the only thing saying a step is done. */
.tk-doc__step.completed .tk-doc__stepmark { background: var(--tk-success);
  border-color: var(--tk-success); color: #fff; }
.tk-doc__step.completed .tk-doc__steplabel { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text)); }
@media (prefers-reduced-motion: reduce) {
  .tk-doc__stepmark, .tk-doc__steplabel { transition: none; }
}
/* The deleted <style>'s only media query, at its own breakpoint: below 576px the
   labels come off and the connector tightens, leaving four discs on a line. */
@media (max-width: 576px) {
  .tk-doc__steplabel { display: none; }
  .tk-doc__steps { padding: 0; }
  .tk-doc__steps::before { left: 10px; right: 10px; }
}

/* ---- Wizard pane visibility ----
   Bootstrap's .tab-pane CSS was the ONLY thing hiding steps 2 to 4. Measured on the
   pre-migration page: #step1 computed display:block and #step2/3/4 display:none, all
   of it out of bootstrap.min.css. Removing Bootstrap without this rule renders all
   four steps at once as one long scrolling page and the wizard stops existing — a
   defect that a 200-status check, a class sweep and a screenshot of step 1 all pass.

   The mechanism is `display`, deliberately NOT opacity: showTab() sets an inline
   `target.style.opacity = 1` fifty milliseconds after every switch (the old fade
   hack), and an inline style beats any stylesheet rule, so an opacity-based hide
   would be undone on the first navigation. Nothing in this layer may set opacity on
   .tk-doc__pane for that reason.

   display flips synchronously with the class, which is what lets initCanvas()'s
   deferred resizeEvents() measure a non-zero parent width 100ms after step 3 appears;
   a canvas sized while its pane is hidden measures 0x0 and draws nowhere. */
.tk-doc__panes { min-width: 0; }
.tk-doc__pane { display: none; }
.tk-doc__pane.active { display: block; animation: tk-doc-fade .25s var(--tk-ease); }
/* Bootstrap's .fade, restated. An animation rather than a transition on purpose:
   animations sit above normal author declarations in the cascade — inline ones
   included — so the inline opacity above cannot fight it, and when the animation ends
   that inline 1 simply takes over at the value the animation finished on. */
@keyframes tk-doc-fade { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .tk-doc__pane.active { animation: none; } }
/* Step 4 is a success screen rather than a form: .text-center.py-5 became this. */
.tk-doc__pane--center { text-align: center; padding-block: var(--tk-space-12); }

/* Headings. .tk-doc__sectiontitle is .section-title: Playfair Display becomes the
   system display face, the tudor red the accent token, the #333 rule the border
   token. .tk-doc__panelhead is the quieter card header the ledger uses. */
.tk-doc__sectiontitle { display: flex; align-items: center; gap: .5rem;
  font-family: var(--tk-font-display); font-weight: 700; font-size: 1.05rem;
  text-transform: uppercase; letter-spacing: .06em; color: var(--tk-accent-text);
  border-bottom: 1px solid var(--tk-border); padding-bottom: 10px; margin: 0 0 20px; }
.tk-doc__sectiontitle svg { flex: 0 0 auto; }
.tk-doc__panelhead { display: flex; align-items: center; gap: .5rem; font-size: .9rem;
  text-transform: uppercase; letter-spacing: .08em; margin: 0; }
.tk-doc__panelhead svg { flex: 0 0 auto; color: var(--tk-accent-text); }

/* ---- The two full-page cards ----
   "Seller Login Required" and the expired-link notice each render instead of the
   wizard, so they are complete screens in their own right — the same shape as the
   trader dashboard's and the collection manager's gates. */
.tk-doc__gate { text-align: center; padding: var(--tk-space-12) var(--tk-space-6); }
.tk-doc__gateic { display: grid; place-items: center; width: 64px; height: 64px;
  margin: 0 auto var(--tk-space-4); border-radius: 50%; color: var(--tk-accent-text);
  background: color-mix(in srgb, var(--tk-burgundy) 10%, transparent); }
.tk-doc__gateic svg { width: 30px; height: 30px; }
.tk-doc__gateic--ok { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text));
  background: color-mix(in srgb, var(--tk-success) 12%, transparent); }
.tk-doc__gatetitle { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  font-size: 1.4rem; margin: 0 0 var(--tk-space-3); }
.tk-doc__gatetext { color: var(--tk-text-muted); font-size: .9rem; margin: 0 0 var(--tk-space-6); }
/* "For security, this link is no longer active" was .text-danger — an error red for a
   sentence that is a security note, not a failure — so it takes the accent instead. */
.tk-doc__gatenote { display: inline-flex; align-items: center; gap: .4rem; font-size: .8rem;
  color: var(--tk-accent-text); margin: 0 0 var(--tk-space-6); }
.tk-doc__gatenote svg { width: 14px; height: 14px; flex: 0 0 auto; }
.tk-doc__gateactions { justify-content: center; }

/* ---- Form controls (Task 3) ----
   .tk-field skins input and select; it does not skin a <textarea>, which is what the
   Terms & Notes box is, and it sets no placeholder colour. Both were carried by the
   deleted <style> only because Bootstrap's dark form controls washed them out, so the
   tk equivalents are here, page-scoped. `opacity: 1` stops Firefox halving the
   placeholder back down. */
.tk-doc .tk-field textarea, .tk-doc__notes textarea { width: 100%; background: var(--tk-page);
  border: 1px solid var(--tk-border); color: var(--tk-text); border-radius: var(--tk-radius);
  padding: .6rem .7rem; font: inherit; font-size: .85rem; resize: vertical; }
.tk-doc input::placeholder, .tk-doc textarea::placeholder { color: var(--tk-text-muted); opacity: 1; }
/* Every control on this form is served readonly or disabled in at least one of the
   five render modes — buyer signing, seller review and the invalid-token fallback all
   render the whole document as a record rather than a form — so "you cannot edit this"
   needs a read of its own rather than looking like an ordinary field. The attribute
   selector is what carries it past .tk-field input on specificity. */
.tk-doc input[readonly], .tk-doc textarea[readonly] { color: var(--tk-text-muted);
  background: color-mix(in srgb, var(--tk-page) 55%, var(--tk-surface)); cursor: default; }
.tk-doc input:disabled, .tk-doc select:disabled, .tk-doc textarea:disabled {
  color: var(--tk-text-muted); opacity: .75; cursor: not-allowed; }
.tk-doc .tk-btn[disabled] { opacity: .55; cursor: not-allowed; }
.tk-doc .tk-btn[disabled]:hover { transform: none; }

/* The .is-invalid replacement (Task 3). nextStep() marks every empty [required]
   control before it will advance, and Bootstrap's rule was the only thing making a
   failed validation visible — without a tk equivalent the visitor gets an alert() and
   no indication of which field. Qualified with .tk-doc so it out-specifies
   .tk-field input (two classes against one class and one type). Task 3 should set
   aria-invalid="true" alongside it, so the state is not colour-only. */
.tk-doc .tk-doc__invalid { border-color: var(--tk-burgundy);
  background: color-mix(in srgb, var(--tk-burgundy) 7%, var(--tk-page)); }

/* ---- Pane layout (Task 3) ----
   The panes were Bootstrap grid: .row.g-4 with .col-12 / .col-md-6, an <h5> in
   utility classes for the sub-heads, and .d-flex.justify-content-{end,between} for
   each pane's footer. Two columns that collapse to one is the whole of it. */
.tk-doc__formgrid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--tk-space-4); }
@media (max-width: 560px) { .tk-doc__formgrid { grid-template-columns: 1fr; } }
/* "Parties" — a group heading inside a pane, quieter than .tk-doc__sectiontitle,
   which is the pane's own title. */
.tk-doc__subhead { font-family: var(--tk-font-display); font-size: .7rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .12em; color: var(--tk-text-muted);
  margin: var(--tk-space-2) 0 0; }
/* The wizard's own navigation, ruled off from the fields above it. Step 1 has no Back,
   so its buttons sit right; the others put Back opposite. */
.tk-doc__nav { justify-content: flex-end; align-items: center; margin-top: var(--tk-space-8);
  padding-top: var(--tk-space-6); border-top: 1px solid var(--tk-border); }
.tk-doc__nav--split { justify-content: space-between; }
/* The three canned Terms & Notes phrases. Pills as before, on .tk-chip's metrics, but
   they are buttons: they take a pointer, a hover and the focus ring. */
.tk-doc__notebtns { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .25rem; }
.tk-doc__notebtn { font: inherit; font-size: .7rem; padding: .3rem .7rem; cursor: pointer;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius-pill);
  background: var(--tk-surface); color: var(--tk-text-muted); }
.tk-doc__notebtn:hover { color: var(--tk-accent-text); border-color: var(--tk-burgundy); }
/* The two icon+text notices: the buyer's "valid for 24 hours" note on step 3, and the
   "download immediately" warning on the buyer's step 4. Both were Bootstrap .alerts
   with hard-coded rgba fills; they are .tk-callout (+ --warning) with this layout on
   top. --alert re-centres it inside .tk-doc__pane--center and caps the measure. */
.tk-doc__notice { display: flex; align-items: flex-start; gap: .75rem; text-align: left;
  margin-bottom: var(--tk-space-6); }
.tk-doc__notice svg { flex: 0 0 auto; color: var(--tk-accent-text); margin-top: .15rem; }
.tk-doc__noticetitle { display: block; font-family: var(--tk-font-display); font-weight: 700;
  font-size: .85rem; margin-bottom: .15rem; }
.tk-doc__noticetext { font-size: .8rem; color: var(--tk-text-muted); line-height: 1.55; margin: 0; }
.tk-doc__notice--alert { max-width: 520px; margin-inline: auto; }
.tk-doc__notice--alert svg { color: var(--tk-gold-ink); }
/* "Email copy to (Optional)" — the one field on the signature step. */
.tk-doc__emailfield { max-width: 420px; margin-top: var(--tk-space-6); }

/* ---- Signature capture (Task 3) ----
   The frame stays white in either theme: it is the paper the signature is drawn on,
   and the same strokes are rasterised into the PDF, so its ink is written as literal
   colours rather than tokens (the same reasoning as .tk-prop__card).
   `.active` is the token initCanvas() adds to the canvas's PARENT while a stroke is in
   progress and removes on mouseup — kept as the name the script already writes.
   Nothing here sets a width or a height on the <canvas> itself: its drawing buffer is
   assigned from its parent's measured width by resizeEvents(), and a CSS size that
   disagreed with that buffer is exactly what puts the ink somewhere other than under
   the cursor. */
.tk-doc__sigs { display: grid; grid-template-columns: 1fr 1fr; gap: var(--tk-space-8); }
@media (max-width: 720px) { .tk-doc__sigs { grid-template-columns: 1fr; } }
/* min-width: 0 is load-bearing, not tidiness. resize() sets the drawing buffer to the
   frame's BORDER-box width, so the canvas ends up 8px wider than the content box it
   sits in. Without this a `1fr` track (min-width: auto) would size to that overflowing
   content, growing the column, which would grow the frame, which would grow the buffer
   on the next resize — the canvas would creep wider on every visit to step 3. The
   track is pinned to its share instead and .tk-doc__sig's overflow: hidden clips the
   8px, which is inside the frame and behind nothing the signature line reaches. */
.tk-doc__sigcol { min-width: 0; }
.tk-doc__siglabelhead { display: block; font-size: .7rem; text-transform: uppercase;
  letter-spacing: .12em; font-weight: 700; color: var(--tk-accent-text); margin-bottom: .5rem; }
.tk-doc__sig { position: relative; overflow: hidden; border-radius: var(--tk-radius);
  border: 4px solid var(--tk-border); background: #ffffff;
  transition: border-color .3s var(--tk-ease); }
.tk-doc__sig:hover { border-color: color-mix(in srgb, var(--tk-burgundy) 40%, var(--tk-border)); }
.tk-doc__sig.active { border-color: var(--tk-burgundy); }
@media (prefers-reduced-motion: reduce) { .tk-doc__sig { transition: none; } }
/* The guide rule is decorative: it says "write across here", and the SIGN HERE label
   below it is what actually names the affordance — so the rule stays a light dashed
   line (warmed off the original #ccc to sit with the ivory palette) and the label
   carries the contrast. The label's original #999 measured 2.85:1 on the white paper
   and failed AA outright; it takes the light theme's muted ink instead, 5.85:1. */
.tk-doc__sigline { position: absolute; bottom: 30px; left: 20px; right: 20px;
  border-bottom: 2px dashed #c9c4ba; pointer-events: none; }
.tk-doc__siglabel { position: absolute; bottom: 5px; right: 10px; font-size: .75rem;
  text-transform: uppercase; letter-spacing: .08em; color: #6b6459; pointer-events: none; }
/* Clear. It sits on the white paper, not on the page, so it is written in the same
   literal ink; pointer-events on the glyph would otherwise let a click land on the
   <svg> and miss the button. */
.tk-doc__sigclear { position: absolute; top: .4rem; right: .4rem; z-index: 2;
  display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px;
  padding: 0; border: 1px solid #8f8a82; border-radius: var(--tk-radius);
  background: #ffffff; color: #6b6459; cursor: pointer; }
.tk-doc__sigclear:hover { color: var(--tk-burgundy); border-color: var(--tk-burgundy); }
.tk-doc__sigclear svg { width: 15px; height: 15px; pointer-events: none; }
/* The placeholder shown where the counterparty's canvas would be: "Waiting for Buyer
   Signature" to the seller, "Buyer will sign via secure link" to nobody else. Same
   200px box as the canvas so the two columns stay level. */
.tk-doc__sigwait { display: grid; place-items: center; height: 200px; padding: var(--tk-space-4);
  background: #f4f2ee; color: #6b6459; text-align: center; font-size: .8rem; line-height: 1.5; }
.tk-doc__sigwait svg { width: 26px; height: 26px; margin: 0 auto .5rem; }
/* The seller's signature as rendered back to the buyer: a stored data URL, arriving
   from the API after load. Until it does the element has no src at all — an empty one
   resolves to the page URL and the browser fetches the document as an image — so the
   frame holds its height on its own and the <img> is simply not painted. `hidden`
   would not do here: the display: block above out-specifies the UA rule for it. */
.tk-doc__sigimg { display: block; width: 100%; height: 150px; object-fit: contain; }
.tk-doc__sigimg:not([src]) { display: none; }
.tk-doc__sig--view { min-height: 150px; }

/* ---- Step 4: the handover screen ---- */
.tk-doc__done { display: flex; flex-direction: column; align-items: center;
  gap: var(--tk-space-3); }
.tk-doc__doneic { display: grid; place-items: center; width: 72px; height: 72px;
  border-radius: 50%; color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text));
  background: color-mix(in srgb, var(--tk-success) 12%, transparent); }
.tk-doc__doneic svg { width: 34px; height: 34px; }
.tk-doc__donetitle { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  font-size: 1.4rem; margin: 0; }
.tk-doc__donetext { color: var(--tk-text-muted); font-size: .9rem; margin: 0; }
/* The share-link box. Its input has no .tk-field wrapper, and .tk-inputgroup only lays
   out, so the skin is stated here; values mirror .tk-field input. The border-left
   collapse is restated because the global rule that does it is out-specified. */
.tk-doc__sharebox { width: 100%; max-width: 500px; margin: var(--tk-space-6) auto 0;
  padding: var(--tk-space-4); border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); background: var(--tk-page); text-align: left; }
.tk-doc__sharelabel { display: block; font-size: .65rem; text-transform: uppercase;
  letter-spacing: .12em; color: var(--tk-text-muted); margin-bottom: .4rem; }
.tk-doc__sharebox .tk-inputgroup input { flex: 1; min-width: 0; background: var(--tk-surface);
  border: 1px solid var(--tk-border); color: var(--tk-text); padding: .6rem .7rem;
  font: inherit; font-size: .8rem; }
.tk-doc__sharebox .tk-inputgroup > *:not(:first-child) { border-left-width: 0; }
.tk-doc__shareactions { justify-content: center; margin-top: var(--tk-space-3); }
/* The QR code is a remote PNG on a white ground; it must stay white whatever the
   theme or a dark-mode scan fails. */
.tk-doc__qr { display: block; width: 120px; height: 120px; margin: var(--tk-space-6) auto 0;
  padding: .5rem; background: #ffffff; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); }
/* Same reasoning as .tk-doc__sigimg: no src until createSecureLink() assigns one, and
   an empty frame with a caption under it would be worse than nothing. */
.tk-doc__qr:not([src]), .tk-doc__qr:not([src]) + .tk-doc__qrnote { display: none; }
.tk-doc__qrnote { font-size: .75rem; color: var(--tk-text-muted); margin: .5rem 0 0; }
.tk-doc__doneactions { justify-content: center; margin-top: var(--tk-space-6); }

/* ---- Documents ledger (chrome here, rows in Task 4) ----
   Same shape as the collection manager's tables. Nothing here sets `display` on a
   <tr>: the rows are replaced wholesale by loadDocuments(). */
.tk-doc__list { padding: 0; overflow: hidden; }
.tk-doc__listhead { padding: var(--tk-space-4) var(--tk-space-6);
  border-bottom: 1px solid var(--tk-border); }
.tk-doc__tablewrap { overflow-x: auto; }
.tk-doc__table { width: 100%; min-width: 620px; border-collapse: collapse; }
.tk-doc__table th, .tk-doc__table td { padding: .75rem var(--tk-space-4); text-align: left;
  vertical-align: middle; border-bottom: 1px solid var(--tk-border); font-size: .85rem; }
.tk-doc__table tbody tr:last-child td { border-bottom: 0; }
.tk-doc__table thead th { white-space: nowrap;
  background: color-mix(in srgb, var(--tk-page) 55%, var(--tk-surface));
  font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; font-size: .65rem; color: var(--tk-text-muted); }
.tk-doc__table tbody tr:hover { background: color-mix(in srgb, var(--tk-burgundy) 5%, transparent); }
.tk-doc__colactions { text-align: right; }
.tk-doc__rowdate { color: var(--tk-text-muted); white-space: nowrap; }
/* An unsigned document has no buyer yet; the cell says so rather than sitting empty. */
.tk-doc__rownone { color: var(--tk-text-muted); font-style: italic; }
/* The three status reads, on .tk-badge. A draft is quiet, a document out for signature
   is the caution tone, a completed one the success tone — --tk-gold-ink rather than
   --tk-gold because the raw gold fails AA as text on this theme. The pending fill is
   12% rather than the 16% the other tinted badges use: measured, gold-ink on a 16%
   gold wash over white comes to 4.10:1, and this label sets at .625rem. At 12% it is
   5.28:1 and still plainly the caution tone. */
.tk-doc__status--done { background: color-mix(in srgb, var(--tk-success) 18%, transparent);
  color: color-mix(in srgb, var(--tk-success) 62%, var(--tk-text));
  border-color: color-mix(in srgb, var(--tk-success) 50%, transparent); }
.tk-doc__status--draft { background: var(--tk-page); color: var(--tk-text-muted);
  border-color: var(--tk-border); }
.tk-doc__status--pending { background: color-mix(in srgb, var(--tk-gold) 12%, transparent);
  color: var(--tk-gold-ink); border-color: color-mix(in srgb, var(--tk-gold) 45%, transparent); }
/* Row actions — the same 32px icon button as the dashboard's and the collection's. */
.tk-doc__rowbtns { display: flex; justify-content: flex-end; gap: .35rem; }
.tk-doc__rowbtn { display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; width: 32px; height: 32px; padding: 0;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-surface); color: var(--tk-text-muted); cursor: pointer;
  transition: color .2s var(--tk-ease), border-color .2s var(--tk-ease); }
.tk-doc__rowbtn:hover { color: var(--tk-accent-text); border-color: var(--tk-burgundy); }
.tk-doc__rowbtn:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 2px; }
.tk-doc__rowbtn svg { width: 16px; height: 16px; }
.tk-doc__rowbtn--ok { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text)); }
.tk-doc__rowbtn--ok:hover { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text));
  border-color: var(--tk-success); }
@media (prefers-reduced-motion: reduce) { .tk-doc__rowbtn { transition: none; } }
/* Empty ledger. Qualified so it out-specifies the cell padding above. */
.tk-doc__table td.tk-doc__empty { text-align: center; color: var(--tk-text-muted);
  padding-block: var(--tk-space-12); }

/* ---- The two dialogs (Task 2) ----
   .tk-modal supplies the frame; only the widths and the title/footer trim are
   page-specific. The preview one is wider because it holds a rendered document. */
.tk-doc__modal { max-width: min(92vw, 480px); }
.tk-doc__modal--preview { max-width: min(92vw, 820px); }
.tk-doc__modaltitle { font-family: var(--tk-font-display); font-weight: 700; font-size: 1rem; margin: 0; }
.tk-doc__modalclose { background: none; border: 0; color: var(--tk-text-muted); cursor: pointer;
  display: inline-flex; padding: .25rem; }
.tk-doc__modalclose:hover { color: var(--tk-text); }
.tk-doc__modalfoot { display: flex; justify-content: flex-end; gap: .5rem; flex-wrap: wrap;
  padding: 0 var(--tk-space-6) var(--tk-space-6); }
/* #previewContent is a preview of the printed document, so it is a white page in
   either theme — it stands for the PDF, not for the site. Its inner markup is written
   by previewDocument() with its own inline colours (Task 4). */
.tk-doc__preview { background: #ffffff; color: #23201b; border-radius: var(--tk-radius);
  padding: var(--tk-space-6); max-height: 70vh; overflow: auto; }
/* The sheet's contents (Task 4). previewDocument() carried these as a style="" on every
   element it wrote; they are rules here instead. The palette stays literal for the same
   reason .tk-doc__preview's does — a theme token would repaint the paper in dark mode,
   and this is a picture of the printed document. The geometry is the inline block's:
   the same 20px/40px rhythm, the same 10px cells, the same 50px signature strip. */
.tk-doc__pvdoc { font-size: .9rem; line-height: 1.6; }
.tk-doc__pvtitle { font-family: var(--tk-font-display); font-weight: 800; font-size: 1.05rem;
  text-align: center; text-transform: uppercase; letter-spacing: .06em;
  border-bottom: 2px solid #23201b; padding-bottom: 10px; margin: 0; }
.tk-doc__pvmeta { margin: 20px 0; font-size: .85rem; }
.tk-doc__pvparties { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
.tk-doc__pvparties td { width: 50%; padding: 10px; border: 1px solid #ddd;
  background: #f9f9f9; vertical-align: top; }
.tk-doc__pvhead { font-family: var(--tk-font-display); font-weight: 700; font-size: .8rem;
  text-transform: uppercase; letter-spacing: .06em;
  border-bottom: 1px solid #ddd; padding-bottom: .35rem; margin: 0 0 10px; }
.tk-doc__pvbody { margin: 0; }
.tk-doc__pvnotes { background: #f5f5f5; border-radius: 5px; padding: 10px; margin: 20px 0; }
.tk-doc__pvnoteslabel { display: block; font-size: .7rem; text-transform: uppercase;
  letter-spacing: .08em; color: #57514a; margin-bottom: .25rem; }
.tk-doc__pvsigs { display: flex; justify-content: space-between; gap: var(--tk-space-4);
  margin-top: 40px; }
.tk-doc__pvsig { width: 45%; text-align: center; }
/* A fixed-height ink slot rather than the old trailing <br>: the two signature lines
   then sit level whether one side, both or neither has signed. */
.tk-doc__pvsigink { height: 50px; }
.tk-doc__pvsigimg { display: block; height: 50px; max-width: 100%; margin: 0 auto;
  object-fit: contain; }
.tk-doc__pvsigline { border-top: 1px solid #23201b; padding-top: 5px; font-size: .8rem; }

/* Print: strip site chrome and instructional copy down to just the physical
   cutout + calibration bar, at true 1:1 scale on the page. */
@media print {
  .tk-no-print { display: none !important; }
  .tk-nav, .tk-footer { display: none !important; }
  body.tk-body { background: #fff; margin: 0; }
  .tk-print-page { padding: 20mm !important; }
}

/* ---- Safe trade zones ----
   Unlike every other tool, this page is not a document — it is a two-panel application
   shell: a scrolling sidebar of filters and results beside a map that fills the rest of
   the viewport. So it takes almost nothing from .tk-container / .tk-panel and instead
   states its own frame here, while the controls inside it stay .tk-field / .tk-btn /
   .tk-badge / .tk-modal like everywhere else.

   Classes that only JS emits are defined up front, before the script that writes them is
   converted, so no JS-referenced class ever ships without a rule. Each is marked with the
   task that starts using it.

   ON THE MAP'S OWN COLOUR — a decision that cannot be checked locally, so it is written
   down. The map is created with `mapId`, i.e. its palette is cloud styling attached to
   GOOGLE_MAPS_MAP_ID in the owner's Google Cloud console; there is no `styles` array in
   the page and nothing here can read or change it. Since the sidebar is now ivory, if
   that Map ID carries a dark style the seam between the two panels becomes a hard
   light/dark edge. Three things are done about it and one is deliberately not:
     · the two panels are separated by a real 1px --tk-border hairline, so the edge reads
       as a deliberate division rather than as two mismatched surfaces butted together;
     · the map panel's own ground is --tk-page, not Bootstrap's mid grey, so the panel
       belongs to this site whenever Google's canvas is absent — which is the whole time
       on localhost, where the key is domain-restricted;
     · the InfoWindow is retuned light in Task 3, which is the one piece of map-adjacent
       chrome this stylesheet actually controls.
   What is NOT done: injecting a `styles` array or a second Map ID to force the map light.
   That would change google.maps usage the migration is barred from touching, on a
   surface no one can see locally, to override a choice the owner made in their console. */
.tk-zone {
  /* The tk nav measures 59.4px; the old rule subtracted the Bootstrap navbar's 76. The
     vh line is the fallback for browsers without dvh and must stay first. */
  --tk-zone-nav: 60px;
  display: grid; grid-template-columns: 380px minmax(0, 1fr);
  height: calc(100vh - var(--tk-zone-nav));
  height: calc(100dvh - var(--tk-zone-nav));
}
/* The sidebar scrolls inside itself on the wide layout, which is what lets the head stay
   put. min-height: 0 is what allows a grid item to be shorter than its content and
   therefore to scroll at all. The scrollbar was #555 on #222 — a dark-theme thumb — and
   is mixed off the text token now so it works in either theme. */
/* min-width: 0 is load-bearing and was measured, not added for tidiness. A grid item's
   automatic minimum size is its min-content width, and the pill rail's is the full
   559px of pills — a scroll container's own minimum is 0, but that only applies to the
   rail itself, not to the panel around it. On the wide layout the panel's overflow-y
   made it a scroll container and hid the problem; the moment the narrow layout sets
   overflow: visible the track grew to 607px and took the whole document into horizontal
   scroll at 390px wide. */
.tk-zone__side { display: flex; flex-direction: column; min-height: 0; min-width: 0;
  overflow-y: auto;
  background: var(--tk-surface); border-right: 1px solid var(--tk-border);
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--tk-text) 28%, transparent) transparent; }
.tk-zone__side::-webkit-scrollbar { width: 6px; }
.tk-zone__side::-webkit-scrollbar-track { background: transparent; }
.tk-zone__side::-webkit-scrollbar-thumb { border-radius: 3px;
  background-color: color-mix(in srgb, var(--tk-text) 28%, transparent); }

/* One column below 900px, map first — the same arrangement column-reverse gave the old
   layout. `order` rather than a reversed DOM: the sidebar holds the search and the
   filters, so it must stay first in the source for reading and tab order. The map takes
   a fixed height because a grid row cannot resolve a percentage of an auto-height
   parent, and the list then runs as long as it likes under it.
   This block sits AFTER the two panels' base rules on purpose: everything in it is a
   plain class selector overriding another plain class selector, so it wins on source
   order and on nothing else. Measured before it was moved here, the sidebar kept its
   internal scroll container and its right-hand hairline on the narrow layout. */
@media (max-width: 900px) {
  .tk-zone { grid-template-columns: 1fr; height: auto; }
  .tk-zone__mapcol { order: 1; height: 55vh; min-height: 320px; }
  /* overflow: visible, not auto — a scroll area nested inside a scrolling page is a trap
     on touch, and the sidebar has no height of its own here to scroll within. Dropping
     the scroll container is also what lets the sticky head stick to the viewport instead
     of to a box that never moves. */
  .tk-zone__side { order: 2; overflow: visible; border-right: 0;
    border-top: 1px solid var(--tk-border); }
}

/* Head: title + Add Spot + search. Sticky within the scrolling sidebar, as .sticky-top
   made it. It needs an opaque background of its own or the cards scroll under it. */
.tk-zone__head { position: sticky; top: 0; z-index: 2; background: var(--tk-surface);
  padding: var(--tk-space-6) var(--tk-space-6) var(--tk-space-4); }
/* top: 0 is right on the wide layout, where the head sticks inside .tk-zone__side's own
   scroll box and the whole shell scrolls away under the nav like any other content. On the
   narrow layout the sidebar drops that scroll container, so the head sticks to the VIEWPORT
   instead — and .tk-nav is also sticky at top: 0, with z-index 50 against this layer's 2.
   Measured at 390px with the page scrolled: the nav painted over the head's first 59px, so
   elementFromPoint on the "Add Spot" button returned .tk-nav__bar and on the <h1> returned
   the nav logo. The page's one primary action was untappable for as long as the list was
   scrolled. Clearing the nav is what .tk-listing .tk-filters (top: 80px) and
   .tk-article__aside (top: 90px) already do; --tk-zone-nav is the same 60px the shell
   subtracts, inherited from .tk-zone. This must sit AFTER the base rule above, not in the
   max-width: 900px block before it — same specificity, and source order decides. */
@media (max-width: 900px) { .tk-zone__head { top: var(--tk-zone-nav); } }
.tk-zone__headrow { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-3); margin-bottom: var(--tk-space-4); }
/* An <h1> sized like a sidebar heading rather than like a page title — this page has no
   hero, and the panel is 380px wide. */
.tk-zone__title { display: flex; align-items: center; gap: .45rem; margin: 0;
  font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  letter-spacing: .08em; font-size: .95rem; }
.tk-zone__title svg { width: 18px; height: 18px; flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-zone__add { flex: 0 0 auto; padding: .5rem .85rem; font-size: .65rem; border-radius: var(--tk-radius-pill); }
.tk-zone__add svg { width: 14px; height: 14px; }

/* Search. The input is inside .tk-field, so .tk-field input skins it; only the room for
   the magnifier is stated here. */
.tk-zone__searchwrap { position: relative; display: flex; }
.tk-zone__searchwrap input { width: 100%; padding-left: 2.1rem; }
.tk-zone__searchic { position: absolute; left: .7rem; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; color: var(--tk-text-muted); pointer-events: none; }
.tk-zone input::placeholder, .tk-zone textarea::placeholder { color: var(--tk-text-muted); opacity: 1; }

/* Filters block. */
.tk-zone__filters { padding: 0 var(--tk-space-6) var(--tk-space-4);
  border-bottom: 1px solid var(--tk-border); }
.tk-zone__filterlabel { display: block; font-size: .625rem; text-transform: uppercase;
  letter-spacing: .1em; font-weight: 600; color: var(--tk-text-muted); margin-bottom: .5rem; }
/* The pill rail. Horizontal scroll with the scrollbar suppressed and the right edge
   faded, exactly as .filter-scroll-container had it — the fade is the only affordance
   saying there is more to the right. */
.tk-zone__pills { display: flex; gap: .4rem; overflow-x: auto; padding-bottom: 5px;
  scrollbar-width: none; -ms-overflow-style: none;
  mask-image: linear-gradient(90deg, #000 90%, transparent); }
.tk-zone__pills::-webkit-scrollbar { display: none; }
/* Pills are <button>s now (they were <span>s with a click handler: not focusable, not
   operable by keyboard, and not announced as controls). `active` keeps its name — the
   handler writes exactly that token — and is scoped here so it never leaks as a bare
   state class. */
.tk-zone__pill { flex: 0 0 auto; white-space: nowrap; font: inherit; font-size: .75rem;
  padding: .35rem .8rem; border: 1px solid var(--tk-border); border-radius: var(--tk-radius-pill);
  background: var(--tk-page); color: var(--tk-text-muted); cursor: pointer;
  transition: background-color .2s var(--tk-ease), color .2s var(--tk-ease),
    border-color .2s var(--tk-ease); }
.tk-zone__pill:hover { color: var(--tk-text); border-color: color-mix(in srgb, var(--tk-text) 25%, transparent); }
.tk-zone__pill.active { background: var(--tk-burgundy); border-color: var(--tk-burgundy);
  color: #fbeef0; font-weight: 600; }
@media (prefers-reduced-motion: reduce) { .tk-zone__pill { transition: none; } }
/* City select and Near Me shared a row (col-8 / col-4) and still do; the button aligns
   to the bottom of the field so it sits level with the <select>, not with its label. */
.tk-zone__filterrow { display: flex; align-items: flex-end; gap: .5rem; margin-top: var(--tk-space-3); }
.tk-zone__cityfield { flex: 1 1 auto; min-width: 0; }
/* The block padding is set so the button measures the same 43px as the <select> beside
   it — .tk-btn's own .72rem padding is tuned for its .72rem label, and this one is set
   smaller to fit the 380px rail. */
.tk-zone__near { flex: 0 0 auto; padding: .84rem .7rem; font-size: .62rem; }
.tk-zone__near svg { width: 14px; height: 14px; }

/* The results list. flex: 1 lets it take the rest of the sidebar so the footnote below
   stays at the bottom on a short list. Its ground is the page tone rather than the
   panel's white, which is the figure/ground separation the old #121212-on-#212529 had. */
.tk-zone__list { flex: 1 1 auto; display: flex; flex-direction: column; gap: .5rem;
  padding: var(--tk-space-4); background: var(--tk-page); }
/* Loading / empty / failed — one centred line each. .tk-zone__error is the only one that
   is not neutral: it reports a failed fetch. */
.tk-zone__loading, .tk-zone__empty, .tk-zone__error { display: flex; flex-direction: column;
  align-items: center; gap: .5rem; text-align: center; color: var(--tk-text-muted);
  font-size: .8rem; padding: var(--tk-space-8) var(--tk-space-4); margin: 0; }
.tk-zone__loading svg, .tk-zone__empty svg { width: 22px; height: 22px; }
.tk-zone__error { color: var(--tk-accent-text); }
/* The standing disclaimer under the list. */
.tk-zone__note { display: flex; align-items: center; justify-content: center; gap: .35rem;
  margin: 0; padding: .6rem var(--tk-space-4); font-size: .65rem; color: var(--tk-text-muted);
  background: var(--tk-surface); border-top: 1px solid var(--tk-border); }
.tk-zone__note svg { width: 13px; height: 13px; flex: 0 0 auto; }

/* Map panel. See the note at the head of this layer for why its ground is --tk-page. */
.tk-zone__mapcol { position: relative; min-width: 0; background: var(--tk-page); }
.tk-zone__map { width: 100%; height: 100%; }
/* Task 4. toggleMobileView() currently adds and removes Bootstrap's .d-none on these two
   panels, which no longer exists; these are the rules for the native replacement, so the
   conversion is a one-word change (el.hidden = true) rather than a new class. Both
   selectors are needed because each panel sets a `display` of its own, which would
   otherwise out-specify the UA's [hidden] rule. */
.tk-zone__side[hidden], .tk-zone__mapcol[hidden] { display: none; }

/* The floating mobile view toggle. It has never actually rendered — the old id rule set
   display: none at every width and nothing ever set it back — so it keeps that state
   here, deliberately, rather than being revived in a task that cannot also fix the
   handler behind it. Everything else about it is ported so that reviving it is a
   one-line change: delete the display and let the media query below show it. */
.tk-zone__toggle { display: none; position: fixed; left: 50%; bottom: 20px;
  transform: translateX(-50%); z-index: 40; align-items: center; gap: .4rem;
  padding: .6rem 1.4rem; border: 0; border-radius: var(--tk-radius-pill);
  background: var(--tk-burgundy); color: #fbeef0; cursor: pointer;
  font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .1em; font-size: .72rem; box-shadow: var(--tk-shadow-burg); }
.tk-zone__toggle svg { width: 15px; height: 15px; }

/* ---- Result cards (Task 4) ----
   renderLocationList() still writes .location-card; these are the rules waiting for it.
   The card is a <button> or a [role] element with a click handler either way, so it is
   given a pointer, a hover and a focus ring. `active` keeps its name: focusLocation()
   writes exactly that token, and the 4px burgundy edge is what marks the card whose
   marker is currently open. */
.tk-zone__card { position: relative; overflow: hidden; width: 100%; text-align: left;
  display: block; background: var(--tk-surface); border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); padding: var(--tk-space-4); cursor: pointer;
  transition: border-color .2s var(--tk-ease), transform .2s var(--tk-ease); }
.tk-zone__card:hover { border-color: color-mix(in srgb, var(--tk-burgundy) 45%, var(--tk-border));
  transform: translateY(-2px); }
.tk-zone__card:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 2px; }
.tk-zone__card.active { border-color: var(--tk-burgundy);
  background: color-mix(in srgb, var(--tk-burgundy) 6%, var(--tk-surface)); }
.tk-zone__card.active::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0;
  width: 4px; background: var(--tk-burgundy); }
@media (prefers-reduced-motion: reduce) { .tk-zone__card { transition: none; }
  .tk-zone__card:hover { transform: none; } }
.tk-zone__cardtitle { font-family: var(--tk-font-body); font-weight: 700; font-size: .92rem;
  line-height: 1.3; color: var(--tk-text); margin: 0; overflow-wrap: anywhere; }
.tk-zone__cardmeta { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem;
  margin: .4rem 0 .5rem; }
/* The type badge, on .tk-badge's metrics. The old one took its fill and its ink from
   getMarkerStyle()'s per-type colour as an inline style — six Bootstrap hues, chosen for
   map pins on a dark canvas, several of which fail AA as text on ivory. One neutral read
   is used instead; the pin colours stay where they belong, on the pins. */
.tk-zone__cardtype { background: color-mix(in srgb, var(--tk-lume) 16%, transparent);
  color: var(--tk-text); border-color: color-mix(in srgb, var(--tk-lume) 40%, var(--tk-border)); }
.tk-zone__cardaddr { display: flex; align-items: flex-start; gap: .35rem; font-size: .78rem;
  line-height: 1.5; color: var(--tk-text-muted); margin: 0 0 .5rem; overflow-wrap: anywhere; }
.tk-zone__cardaddr svg { width: 13px; height: 13px; flex: 0 0 auto; margin-top: .15rem; }
.tk-zone__cardfoot { display: flex; align-items: center; justify-content: space-between;
  gap: .5rem; padding-top: .5rem; border-top: 1px solid var(--tk-border); }
.tk-zone__cardcount { font-size: .7rem; color: var(--tk-text-muted); }
.tk-zone__cardactions { display: flex; gap: .6rem; }
/* Rate / Report. Text actions inside a clickable card, so they are quiet until hovered. */
.tk-zone__cardbtn { font: inherit; font-size: .72rem; padding: 0; border: 0; background: none;
  color: var(--tk-text-muted); cursor: pointer; text-decoration: underline;
  text-underline-offset: 2px; }
.tk-zone__cardbtn:hover { color: var(--tk-accent-text); }

/* ---- Rating stars (Tasks 3 and 4) ----
   Three places render stars: a card's average, the InfoWindow's, and the modal's
   interactive widget. All three used a filled/hollow Font Awesome pair; here one 'star'
   mark carries both states on colour, since the registry has no second outline glyph and
   a star that is not earned is better read as absent than as a different shape.
   --tk-gold-ink rather than --tk-gold: the raw gold fails AA on this theme's surfaces. */
.tk-zone__stars { display: inline-flex; align-items: center; gap: 1px; color: var(--tk-gold-ink); }
.tk-zone__stars svg { width: 13px; height: 13px; }
.tk-zone__star { color: color-mix(in srgb, var(--tk-text-muted) 40%, transparent); }
.tk-zone__star--on { color: var(--tk-gold-ink); fill: currentColor; }
.tk-zone__ratingval { font-size: .72rem; font-weight: 600; color: var(--tk-text); }
/* The dialog's interactive widget — bigger, and every star is a real <button>, so the
   rating is settable from the keyboard rather than by click only. */
.tk-zone__ratestars { display: flex; justify-content: center; gap: .25rem; }
.tk-zone__ratestar { padding: .15rem; border: 0; background: none; cursor: pointer;
  color: color-mix(in srgb, var(--tk-text-muted) 40%, transparent); line-height: 0; }
.tk-zone__ratestar svg { width: 30px; height: 30px; }
.tk-zone__ratestar.active, .tk-zone__ratestar:hover { color: var(--tk-gold-ink); }
.tk-zone__ratestar.active svg { fill: currentColor; }

/* ---- The four dialogs (Task 2) ----
   .tk-modal supplies the frame; only the widths and the title/footer trim are
   page-specific, the same split the other tools' dialogs use. */
.tk-zone__modal { max-width: min(92vw, 520px); }
.tk-zone__modal--sm { max-width: min(92vw, 420px); }
.tk-zone__modaltitle { display: flex; align-items: center; gap: .5rem; margin: 0;
  font-family: var(--tk-font-display); font-weight: 700; font-size: 1rem; }
.tk-zone__modaltitle svg { width: 17px; height: 17px; flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-zone__modalclose { background: none; border: 0; color: var(--tk-text-muted); cursor: pointer;
  display: inline-flex; padding: .25rem; }
.tk-zone__modalclose:hover { color: var(--tk-text); }
.tk-zone__modalfoot { display: flex; justify-content: flex-end; gap: .5rem; flex-wrap: wrap;
  padding: 0 var(--tk-space-6) var(--tk-space-6); }
/* The submission dialog's three safety checkboxes sat in a .d-flex.gap-3 row. */
.tk-zone__checkrow { display: flex; flex-wrap: wrap; gap: var(--tk-space-4); }
/* .tk-modal__body skins every input inside a dialog — full width, field padding and a
   .3rem label gap — which is right for the text boxes and wrong for a checkbox. .tk-check
   restates the box's own 16px size but nothing else, so the rest is undone here. The one
   that actually shows is margin-top: Chrome ignores padding and border-radius on a
   checkbox at appearance: auto but honours the margin, which pushed each box 4.8px below
   its own label. Same specificity as .tk-modal__body input and later in the file, so it
   wins; the other two are declared for the browsers that do honour them. */
.tk-zone__checkrow input { padding: 0; margin-top: 0; border-radius: 0; }
/* The "*" beside a required label. It is decorative — the [required] attribute is what
   is announced — so the markup hides it from the accessibility tree. */
.tk-zone__req { color: var(--tk-accent-text); }
/* .form-text / <small class="text-muted"> — the line under a control that explains it. */
.tk-zone__hint { margin: 0; font-size: .72rem; color: var(--tk-text-muted); }
.tk-zone__hint--center { text-align: center; }
/* The rating dialog's subject line: which spot is being rated. */
.tk-zone__ratingname { margin: 0; text-align: center; font-family: var(--tk-font-display);
  font-weight: 700; font-size: 1rem; }
/* The thank-you screen: the success disc that pops in, and the quotation under it.
   @keyframes scaleIn is tk-zone-pop, now gated on reduced motion. */
.tk-zone__thanks { text-align: center; }
.tk-zone__thanksic { display: grid; place-items: center; width: 72px; height: 72px;
  margin: 0 auto var(--tk-space-4); border-radius: 50%;
  color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text));
  background: color-mix(in srgb, var(--tk-success) 12%, transparent); }
.tk-zone__thanksic svg { width: 34px; height: 34px; }
.tk-zone__thanksquote { font-style: italic; font-size: .85rem; line-height: 1.6;
  color: var(--tk-text-muted); border-left: 3px solid var(--tk-border);
  padding: .25rem 0 .25rem var(--tk-space-4); margin: var(--tk-space-6) 0; text-align: left; }
.tk-zone__pop { animation: tk-zone-pop .5s cubic-bezier(.175,.885,.32,1.275) forwards; }
@keyframes tk-zone-pop {
  0% { transform: scale(0); opacity: 0; }
  60% { transform: scale(1.2); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .tk-zone__pop { animation: none; } }

/* ---- The marker InfoWindow (Task 3) ----
   buildInfoWindowContent() returns this subtree as a string and Google plants it inside
   its own bubble. Two inherited things have to be restated or nothing here is on the type
   system: .gm-style sets font-family: Roboto, Arial and font-size: 11px on the whole map,
   and this subtree is a descendant of it. Everything else is the ordinary layer.
   The width pair is the old inline style's, unchanged — Google sizes the bubble from its
   content, so those two numbers are what stop it collapsing or sprawling. */
.tk-zone__iw { font-family: var(--tk-font-body); font-size: 13px; line-height: 1.5;
  color: var(--tk-text); background: var(--tk-surface);
  min-width: 260px; max-width: 300px; }
.tk-zone__iwhead { padding: var(--tk-space-4); border-bottom: 1px solid var(--tk-border); }
.tk-zone__iwtop { display: flex; align-items: flex-start; justify-content: space-between;
  gap: .5rem; }
/* Was an <h6>, four levels below the dialog headings and naming nothing structural. It is
   the bubble's subject, so it is the section heading for it. overflow-wrap: anywhere
   because the name is free text and a 300px bubble has nowhere to put a long token. */
.tk-zone__iwtitle { margin: 0; font-family: var(--tk-font-body); font-weight: 700;
  font-size: 1rem; line-height: 1.3; color: var(--tk-text); overflow-wrap: anywhere; }
.tk-zone__iwaddr { margin: .35rem 0 0; font-size: .8rem; line-height: 1.45;
  color: var(--tk-text-muted); overflow-wrap: anywhere; }
/* The average rating strip. Its ground is the page tone against the bubble's white — the
   same figure/ground the old #222-on-#1a1a1a gave it. */
.tk-zone__iwmeta { display: flex; align-items: center; justify-content: space-between;
  gap: .5rem; padding: .6rem var(--tk-space-4); background: var(--tk-page); }
.tk-zone__iwrating { display: inline-flex; align-items: center; gap: .3rem; }
.tk-zone__iwby { font-size: .7rem; font-style: italic; text-align: right;
  color: var(--tk-text-muted); overflow-wrap: anywhere; }
/* The settable stars, shown only to a signed-in visitor. Smaller than the dialog's
   .tk-zone__ratestar because they sit in a 300px bubble rather than in a modal. */
.tk-zone__iwrate { display: flex; align-items: center; justify-content: space-between;
  gap: .5rem; padding: .5rem var(--tk-space-4); border-top: 1px solid var(--tk-border); }
.tk-zone__iwratelabel { font-size: .65rem; text-transform: uppercase; letter-spacing: .08em;
  font-weight: 600; color: var(--tk-text-muted); }
.tk-zone__iwstars { display: flex; gap: .1rem; }
.tk-zone__iwstar { padding: .15rem; border: 0; background: none; cursor: pointer;
  line-height: 0; }
.tk-zone__iwstar svg { width: 16px; height: 16px; }
/* The hover has to out-specify .tk-zone__star, which sets the muted colour on the <svg>
   itself — a colour on the <button> would never reach it. This is what .hover-gold did,
   as a global, from inside the template string. */
.tk-zone__iwstar:hover svg, .tk-zone__iwstar:focus-visible svg { color: var(--tk-gold-ink); }
.tk-zone__iwstar:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 1px;
  border-radius: 3px; }
.tk-zone__iwfoot { padding: var(--tk-space-3) var(--tk-space-4);
  border-top: 1px solid var(--tk-border); }
/* .tk-btn--block gives the full width the old display: block did; only the scale is set
   here, since .tk-btn's own padding is tuned for a page button, not for this bubble. */
.tk-zone__iwdir { font-size: .68rem; padding: .55rem .8rem; }
.tk-zone__iwdir svg { width: 14px; height: 14px; }
.tk-zone__iwdisc { margin: 0; padding: .5rem var(--tk-space-4); text-align: center;
  font-size: .62rem; font-style: italic; line-height: 1.4; color: var(--tk-text-muted);
  background: var(--tk-page); border-top: 1px solid var(--tk-border); }

/* ---- Google's own InfoWindow chrome ----
   .gm-style-iw and friends are class names Google generates at runtime for the frame it
   draws around our content — the same kind of third-party contract as .pac-* below, kept
   under Google's names rather than renamed. These four rules lived in a <style> block
   INSIDE buildInfoWindowContent()'s returned string until Task 3, which meant the browser
   re-parsed and re-applied them on every single marker click; they are injected once now,
   from here. !important stays for the reason it was written: Google's stylesheet is
   injected at runtime, i.e. after ours, so it would otherwise win.

   SCOPED UNDER .tk-zone, WHICH .pac-* DELIBERATELY IS NOT, and the difference is not
   inconsistency. The Autocomplete list is appended to <body>, so it has no ancestor to
   scope to, and Google draws it identically on every page that uses it — a global is both
   forced and harmless. This bubble is neither. It is rendered inside the map element, so
   it does have an ancestor; and safe-trade-zones is not the only page with a Google map.
   service-locator.php opens an InfoWindow too, with markup that has no padding of its own
   because it relies on Google's default chrome — an unscoped `padding: 0 !important` here
   would push that page's content flush to the bubble edge, a regression on a page this
   task has no business touching. Scoping costs nothing: if a future Maps release ever
   moved the bubble out of the map subtree these rules would simply stop applying and the
   bubble would fall back to Google's own frame around content that is already light.

   Retuned from a dark bubble to a light one. The two colours are tokens rather than
   literals — unlike .pac-*, this bubble's contents are entirely ours, so it should follow
   the theme rather than pin itself to one. The 8px radius is kept literal: it is Google's
   own bubble geometry and the ::after tail is cut to match it.

   THE CLOSE BUTTON'S filter: invert(1) grayscale(100%) IS GONE. Google draws that ✕ as a
   dark grey glyph for a light bubble, which is exactly what this is now; the invert existed
   only to flip it to white against #1a1a1a, and left in place it would have painted a
   near-white ✕ on white. The two position declarations stay — those are not a colour hack,
   they compensate for the padding: 0 above, which moves the button off Google's own
   default offset. */
.tk-zone .gm-style-iw { padding: 0 !important; overflow: hidden !important;
  border-radius: 8px !important; background-color: var(--tk-surface) !important;
  box-shadow: var(--tk-shadow) !important; }
.tk-zone .gm-style-iw-d { overflow: hidden !important; max-height: none !important; }
.tk-zone .gm-style-iw-tc::after { background: var(--tk-surface) !important; }
.tk-zone .gm-ui-hover-effect { top: 5px !important; right: 5px !important; }

/* ---- Google Places Autocomplete dropdown ----
   Deliberately global and deliberately not renamed: .pac-* is Google's own DOM, appended
   to <body> when the address field on the submission form is focused, so these are a
   third-party contract in the same way .gm-style-iw is. The palette stays literal —
   Google draws this list white in either theme, so a token here would repaint half of a
   panel we do not own. !important stays on the z-index for the reason it was written:
   Google's stylesheet is injected at runtime, i.e. after ours, and would otherwise win.

   TASK 2 DID READ THIS, and took the second of the two options below. The z-index was
   1100 to clear Bootstrap's modal at 1055, and a native <dialog> opened with showModal()
   is in the TOP LAYER, which no z-index can reach — so the list would paint behind the
   submission dialog and the address field would look broken. The two structural fixes on
   offer were (a) open that one dialog with show() instead of showModal(), or (b) move the
   .pac-container node inside the <dialog>.

   (a) was rejected: a non-modal dialog is position: absolute at the top of the document
   rather than centred, draws no ::backdrop, traps no focus and does not close on Escape,
   and ui.js's backdrop-click close has no backdrop to receive the click — four
   regressions on the page's largest form to avoid one re-parenting.

   (b) is what safe-trade-zones.php's anchorPlacesDropdown() does, on each open of the
   dialog. A descendant of a top-layer element is painted with it, so the list clears the
   dialog with no z-index involved. What the move costs is Google's own positioning:
   it writes left/top on this node in document coordinates, and inside a modal <dialog>,
   which is position: fixed and therefore a containing block, those land the list
   somewhere else entirely. So when it is inside the dialog the anchor supplies the
   position instead and Google's inline values are overridden. .tk-zone__pacanchor wraps
   the address input alone. This rule is scoped to that anchor, so a .pac-container still
   parented to <body> — which is every other Autocomplete on the site — is untouched. */
.pac-container { z-index: 1100 !important; font-family: var(--tk-font-body); }
.pac-item { padding: 8px 10px; cursor: pointer; }
.pac-item:hover { background-color: #f1f1f1; }
.pac-item-query { font-size: 14px; }
.tk-zone__pacanchor { position: relative; }
.tk-zone__pacanchor .pac-container { position: absolute !important; top: 100% !important;
  left: 0 !important; right: auto !important; width: 100% !important; margin-top: 2px; }

/* ---- Model browser (/models, /models/{slug}) ----
   Replaces a responsive duplicate: the Bootstrap page shipped a `d-none d-md-flex`
   button grid AND a `d-block d-md-none` <select> whose only navigation was an inline
   onchange, so on a phone the whole browser died without JS and the control carried no
   label. One wrapping row of real links serves every width instead — 13 short model
   names wrap to a few rows on a 375px viewport, and each stays a crawlable <a>.
   The active model is marked with aria-current="page" (also stated by the <h1> and the
   breadcrumb, so the burgundy fill is not the only carrier of that state). Metrics
   follow .tk-grade-pill, the storefront's other filter row, on --tk-text rather than
   --tk-text-muted because this is the page's primary navigation, not a filter chip. */
.tk-modelnav { display: flex; flex-wrap: wrap; gap: .5rem; margin: var(--tk-space-4) 0 var(--tk-space-8); }
.tk-modelnav__item { font-size: .75rem; padding: .45rem .9rem; border-radius: var(--tk-radius-pill);
  border: 1px solid var(--tk-border); color: var(--tk-text);
  transition: color .2s var(--tk-ease), border-color .2s var(--tk-ease); }
.tk-modelnav__item:hover { color: var(--tk-accent-text); border-color: var(--tk-burgundy); }
.tk-modelnav__item[aria-current="page"] { background: var(--tk-burgundy); border-color: var(--tk-burgundy);
  color: #fbeef0; }
@media (prefers-reduced-motion: reduce) { .tk-modelnav__item { transition: none; } }

/* ---- Watch registry (/registry and the six owner screens) ----
   One layer for the whole feature, not for one page: /registry is the public,
   un-gated face of it, but the dashboard, the add/edit forms, the detail view and
   the transfer flow all speak the same vocabulary — a panel with a marked head, the
   three-tier verification ladder, and the stolen state. Anything named here is meant
   to be reused by those templates rather than re-invented per screen. */
.tk-reg { display: flex; flex-direction: column; gap: var(--tk-space-8);
  padding-block: var(--tk-space-4) var(--tk-space-12); }

/* Hero: title block on the left, share on the right — the same headrow shape the
   trader dashboard and the collection manager use. */
.tk-reg__headrow { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--tk-space-4); flex-wrap: wrap; }
.tk-reg__title { display: flex; align-items: center; gap: .5rem;
  font-size: var(--tk-fs-h1); text-transform: uppercase; letter-spacing: .04em; margin: 0; }
/* Sized here, not through tk_icon()'s $attrs: that helper writes width="20" height="20"
   before it appends $attrs, so a passed width lands as a duplicate attribute and the
   first one wins. CSS is the only lever that actually resizes the glyph. */
.tk-reg__title svg { width: .8em; height: .8em; flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-reg__lead { color: var(--tk-text-muted); max-width: 60ch; margin: var(--tk-space-3) 0 0; }
/* margin-left:auto, not just the row's space-between: once the row wraps (it does at
   375px) space-between no longer separates anything, and the share trigger lands at the
   *left* of its own line. .tk-share__menu is anchored right:0 to that trigger, so the
   160px menu then hung 23px off the left edge of the viewport with its labels clipped.
   Pushing the trigger to the right on the wrapped line keeps the menu inside the page. */
.tk-reg__heroactions { display: flex; align-items: center; gap: var(--tk-space-3);
  flex-wrap: wrap; margin-left: auto; }
/* margin-left:auto alone only moves the *row*. The dashboard and the add form put a
   primary CTA in this row as well as the share trigger, and once those two wrap onto
   separate lines the trigger sits at the left of its own line again — the same 23px
   clip, back. Right-aligning the wrapped lines is what actually anchors the menu; it is
   a no-op on the public page, whose row has a single child already pushed right. */
@media (max-width: 520px) { .tk-reg__heroactions { justify-content: flex-end; } }

/* Panel head — the icon tile plus title/subtitle rule that opens each registry panel.
   flex-wrap and the min-width:0 on the text block are what let a third child (the detail
   view's event count, pushed right with margin-left:auto) sit here at all: without them
   the count hung 7px past the right edge of a 320px viewport and took the whole document
   into horizontal scroll. Both are no-ops on the two-child heads the other screens use. */
.tk-reg__panelhead { display: flex; align-items: center; gap: var(--tk-space-4);
  flex-wrap: wrap; padding-bottom: var(--tk-space-4); margin-bottom: var(--tk-space-6);
  border-bottom: 1px solid var(--tk-border); }
.tk-reg__panelhead > div { min-width: 0; }
.tk-reg__panelic { display: grid; place-items: center; flex: 0 0 auto; width: 48px; height: 48px;
  border-radius: 50%; background: color-mix(in srgb, var(--tk-burgundy) 14%, transparent);
  color: var(--tk-accent-text); }
.tk-reg__panelic svg { width: 24px; height: 24px; }
.tk-reg__paneltitle { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .1em; font-size: .95rem; margin: 0; }
.tk-reg__panelsub { color: var(--tk-text-muted); font-size: .8rem; margin: .2rem 0 0; }

/* Verification ladder — Gray(1) / Silver(2) / Gold(3), from src/VerificationLevel.php.
   This is the registry's core trust language and every owner screen renders it, so all
   three modifiers are defined together here even though the public page shows none of
   them: a class referenced from JS with no rule behind it has shipped twice already.
   Worn on .tk-badge, e.g. <span class="tk-badge tk-reg__level tk-reg__level--gold">.

   The ladder is carried by fill weight, not hue alone — quiet neutral, then outlined
   lume, then solid gold — and each badge also names its level in text, so the step is
   legible without colour. Gold is the one place outside the Featured flag and the
   community-member CTA where --tk-gold is spent; the level is literally called Gold,
   and the badge puts dark ink on it exactly as .tk-badge--feat does, which is what
   keeps it AA. */
.tk-reg__level { display: inline-flex; align-items: center; gap: .35rem; vertical-align: middle; }
.tk-reg__level svg { width: 12px; height: 12px; flex: 0 0 auto; }
.tk-reg__level--gray { background: color-mix(in srgb, var(--tk-text) 10%, transparent);
  color: var(--tk-text); border-color: color-mix(in srgb, var(--tk-text) 22%, transparent); }
.tk-reg__level--silver { background: color-mix(in srgb, var(--tk-lume) 14%, transparent);
  color: var(--tk-text); border-color: color-mix(in srgb, var(--tk-lume) 55%, transparent); }
.tk-reg__level--gold { background: linear-gradient(#c79a45, var(--tk-gold)); color: #2a2110;
  border-color: transparent; }
/* "Reported Stolen" — the flag on a registry watch, worn on .tk-badge. Solid burgundy
   with light ink: the one state on the whole feature that has to out-shout everything
   around it. Same treatment as .tk-badge--sold, which is why it is not that class —
   these two must not be interchangeable. */
.tk-reg__stolen { display: inline-flex; align-items: center; gap: .35rem; vertical-align: middle;
  background: var(--tk-burgundy); color: #fbeef0; border-color: var(--tk-burgundy); }
.tk-reg__stolen svg { width: 12px; height: 12px; flex: 0 0 auto; }

/* Search form. .tk-inputgroup only lays out — the input skin comes from .tk-field input,
   so every group here sits inside a .tk-field — and it carries no grow rule either, so
   the input has to be told to take the space the addon leaves. */
.tk-reg .tk-inputgroup input { flex: 1; min-width: 0; }
.tk-reg input::placeholder { color: var(--tk-text-muted); opacity: 1; }
.tk-reg__req { color: var(--tk-accent-text); }
.tk-reg__serialinput { letter-spacing: .06em; font-weight: 700; }
/* The "where to find it" line under the serial field: an icon inline with its copy. */
.tk-reg__hint { display: flex; align-items: flex-start; gap: .4rem; margin: .1rem 0 0;
  font-size: .78rem; color: var(--tk-text-muted); }
.tk-reg__hint svg { width: 14px; height: 14px; flex: 0 0 auto; margin-top: .15rem; color: var(--tk-accent-text); }
/* minmax(0, …) on every track, never a bare 1fr: a `1fr` track is minmax(auto, 1fr), and
   the auto floor is the item's min-content — which for the security-check group is the
   addon plus a number input Chrome refuses to shrink below its default intrinsic width.
   That floor (278px) out-measured the 222px row at 320px wide and pushed the field, its
   label and the submit button off the right edge. Same idiom .tk-fit already uses. */
.tk-reg__grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--tk-space-4); }
/* Security check and the submit share a line, with the button matching the control's
   height rather than the label's — hence align-items:end. */
.tk-reg__submitrow { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--tk-space-4); align-items: end; }
@media (max-width: 640px) {
  .tk-reg__grid, .tk-reg__submitrow { grid-template-columns: minmax(0, 1fr); }
}
.tk-reg__sum { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap;
  color: var(--tk-text); }
.tk-reg__captcha { text-align: center; font-weight: 700; }
.tk-reg__go { justify-content: center; padding-block: .7rem; }
/* The rate-limit / wrong-answer notice, on .tk-callout--warning: icon inline with copy. */
.tk-reg__alert { display: flex; align-items: flex-start; gap: .6rem; margin-bottom: var(--tk-space-6); }
.tk-reg__alert svg { width: 20px; height: 20px; flex: 0 0 auto; color: var(--tk-gold-ink); }
.tk-reg__alert p { color: var(--tk-text); }

/* Result. One frame, two states — clean and stolen — distinguished by the border, the
   status glyph and a named heading, never by colour on its own. overflow:hidden so the
   stolen banner can bleed to the panel's rounded edge. */
.tk-reg__result { text-align: center; overflow: hidden; }
.tk-reg__result:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 2px; }
.tk-reg__result--clean { border-color: color-mix(in srgb, var(--tk-success) 45%, transparent); }
.tk-reg__result--stolen { border-color: var(--tk-burgundy); padding-top: 0; }
/* Full-bleed alarm strip: pulled out to the panel's edges, so the panel's own padding
   has to be cancelled on three sides. */
.tk-reg__banner { display: flex; align-items: center; justify-content: center; gap: .5rem;
  margin: 0 calc(var(--tk-space-6) * -1) var(--tk-space-6);
  padding: var(--tk-space-4) var(--tk-space-6); background: var(--tk-burgundy); color: #fbeef0;
  font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .14em; font-size: .95rem; }
.tk-reg__banner svg { width: 20px; height: 20px; flex: 0 0 auto; }
.tk-reg__resultic { display: grid; place-items: center; width: 64px; height: 64px; margin: 0 auto var(--tk-space-4);
  border-radius: 50%; }
.tk-reg__resultic svg { width: 32px; height: 32px; }
.tk-reg__result--clean .tk-reg__resultic { background: color-mix(in srgb, var(--tk-success) 18%, transparent);
  color: color-mix(in srgb, var(--tk-success) 70%, var(--tk-text)); }
.tk-reg__resulttitle { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  letter-spacing: .1em; font-size: 1.35rem; margin: 0 0 var(--tk-space-3); }
/* 70% success / 30% text, not the raw --tk-success: the token is a background colour and
   measures 2.2:1 as text on --tk-surface. This mix clears AA at normal size too, so the
   heading does not have to rely on being large. */
.tk-reg__result--clean .tk-reg__resulttitle { color: color-mix(in srgb, var(--tk-success) 70%, var(--tk-text)); }
.tk-reg__resultlead { color: var(--tk-text); font-size: 1.05rem; line-height: 1.7;
  max-width: 60ch; margin: 0 auto var(--tk-space-6); }
/* The searched / matched serial, quoted back to the visitor. Monospace because it is an
   identifier they will compare character by character against the watch in front of them. */
.tk-reg__serial { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700;
  word-break: break-all; padding: .05em .3em; border-radius: 4px; }
.tk-reg__result--clean .tk-reg__serial { color: var(--tk-text);
  background: color-mix(in srgb, var(--tk-success) 16%, transparent); }
.tk-reg__result--stolen .tk-reg__serial { color: var(--tk-accent-text);
  background: color-mix(in srgb, var(--tk-burgundy) 18%, transparent); }
.tk-reg__disclaimer { display: flex; align-items: flex-start; gap: .6rem; text-align: left;
  max-width: 620px; margin: 0 auto; }
.tk-reg__disclaimer svg { width: 18px; height: 18px; flex: 0 0 auto; margin-top: .15rem; color: var(--tk-accent-text); }
.tk-reg__disclaimer strong { color: var(--tk-text); }
.tk-reg__rule { border: 0; border-top: 1px solid var(--tk-border); margin: var(--tk-space-8) 0 var(--tk-space-6); }
.tk-reg__subhead { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .18em; font-size: .7rem; color: var(--tk-text-muted); margin: 0 0 var(--tk-space-4); }
.tk-reg__extchecks { justify-content: center; }
.tk-reg__extchecks svg { width: 14px; height: 14px; }
/* Report details on a match: a two-column ledger, left-aligned inside a centred panel. */
.tk-reg__report { max-width: 460px; margin: 0 auto var(--tk-space-6); text-align: left;
  background: var(--tk-page); }
.tk-reg__dl { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .5rem var(--tk-space-4);
  margin: 0; font-size: .9rem; }
.tk-reg__dl dt { color: var(--tk-text-muted); font-size: .7rem; text-transform: uppercase; letter-spacing: .1em;
  align-self: center; }
.tk-reg__dl dd { margin: 0; color: var(--tk-text); font-weight: 600; word-break: break-word; }
.tk-reg__resultnote { color: var(--tk-text-muted); font-size: .9rem; margin: 0 0 var(--tk-space-6); }

/* "How does this work?" / "Report a Stolen Watch" — the Bootstrap accordion rebuilt as
   two native <details> on .tk-callout, the same disclosure shape the trade calculator
   and the collection manager already use. No script, and each panel is open-able with
   JS off, which the data-bs-toggle version was not on a tk page. */
.tk-reg__info { display: flex; flex-direction: column; gap: var(--tk-space-3); }
.tk-reg__disclosure summary { cursor: pointer; list-style: none; }
.tk-reg__disclosure summary::-webkit-details-marker { display: none; }
.tk-reg__disclosure summary:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 3px; border-radius: 4px; }
.tk-reg__disclosure h2 { display: flex; align-items: center; gap: .5rem; font-size: .95rem; margin: 0; }
.tk-reg__disclosure h2 > svg:first-child { flex: 0 0 auto; width: 18px; height: 18px; color: var(--tk-accent-text); }
.tk-reg__disclosurechev { margin-left: auto; flex: 0 0 auto; color: var(--tk-text-muted);
  transition: transform .2s var(--tk-ease); }
.tk-reg__disclosure[open] .tk-reg__disclosurechev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .tk-reg__disclosurechev { transition: none; } }
.tk-reg__disclosure ol { color: var(--tk-text-muted); font-size: .85rem; line-height: 1.75;
  margin: var(--tk-space-4) 0 0; padding-left: 1.3em; }
.tk-reg__disclosure strong { color: var(--tk-text); }
.tk-reg__reportcta { text-align: center; margin-top: var(--tk-space-4); }
.tk-reg__reportcta p { color: var(--tk-text-muted); font-size: .85rem; margin: 0 0 var(--tk-space-4); }
/* The two outcome words named in the instructions. They repeat, in text, the distinction
   the result panel draws, so neither state is signalled by colour alone. */
.tk-reg__clean { color: color-mix(in srgb, var(--tk-success) 70%, var(--tk-text)); font-weight: 600; }
.tk-reg__alarm { color: var(--tk-accent-text); font-weight: 600; }

/* Leaving-the-site confirmation — a native <dialog> on .tk-modal. The Bootstrap modal it
   replaces called `new bootstrap.Modal(...)`, and window.bootstrap is undefined on a tk
   page, so the external-database links were dead ends. ui.js's initDialogs() supplies
   the close button and backdrop-click wiring; Escape is native. */
.tk-reg__modal { max-width: min(92vw, 480px); }
.tk-reg__modaltitle { display: flex; align-items: center; gap: .5rem; font-size: 1rem; margin: 0; }
.tk-reg__modaltitle svg { width: 18px; height: 18px; flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-reg__modalclose { background: none; border: 0; color: var(--tk-text-muted); cursor: pointer;
  display: inline-flex; padding: .25rem; }
.tk-reg__modalclose:hover { color: var(--tk-text); }
.tk-reg__modalbody p { color: var(--tk-text-muted); font-size: .9rem; line-height: 1.7; }
.tk-reg__modalbody p:first-child { color: var(--tk-text); }
.tk-reg__modalfoot { display: flex; justify-content: flex-end; gap: .5rem; flex-wrap: wrap;
  margin-top: var(--tk-space-6); }

/* ---- Owner screens (dashboard / add / edit) ----
   Everything below the login gate. Same vocabulary as the public page above: the panel
   head, the verification ladder and the stolen flag are all reused rather than restated. */

/* Anything with a `display` of its own outranks the UA sheet's [hidden] { display: none }
   — a class selector beats a bare attribute one — so every registry element that both
   sets a display and gets toggled with the `hidden` attribute has to opt back in. Caught
   with the add form's alert box, which shipped as an empty burgundy bar above the fields
   because .tk-reg__alert's display:flex was quietly winning. Same idiom .tk-cookie[hidden]
   and .tk-compare-btn[hidden] already use. */
.tk-reg__stats[hidden], .tk-reg__cards[hidden], .tk-reg__alert[hidden] { display: none; }

/* Login gate and the edit route's ownership refusal — one shape, two headings. The
   refusal adds --deny for a burgundy edge, and deliberately does not say which of "no
   such watch" and "someone else's watch" it is, so the route cannot be used to enumerate
   live hashids. */
.tk-reg__gatewrap { padding-block: var(--tk-space-12); display: flex; justify-content: center; }
.tk-reg__gate { max-width: 480px; width: 100%; text-align: center; }
.tk-reg__gateic { display: grid; place-items: center; width: 64px; height: 64px;
  border-radius: 50%; margin: 0 auto var(--tk-space-6);
  background: color-mix(in srgb, var(--tk-burgundy) 14%, transparent); color: var(--tk-accent-text); }
.tk-reg__gateic svg { width: 30px; height: 30px; }
.tk-reg__gate--deny { border-color: color-mix(in srgb, var(--tk-burgundy) 45%, var(--tk-border)); }
.tk-reg__gatetitle { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  letter-spacing: .08em; font-size: 1.35rem; margin: 0 0 var(--tk-space-3); }
.tk-reg__gatetext { color: var(--tk-text-muted); font-size: .9rem; margin: 0 0 var(--tk-space-6); }
.tk-reg__gateactions { justify-content: center; }
.tk-reg__gateactions svg { width: 15px; height: 15px; }

/* Dashboard stats. Four figures, so two rows of two on a phone rather than a 4-wide
   squeeze; minmax(0, …) on every track so a large count cannot widen a column. */
.tk-reg__stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--tk-space-4); }
@media (max-width: 700px) { .tk-reg__stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.tk-reg__stat { display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .35rem; text-align: center; padding: var(--tk-space-4); }
.tk-reg__statlabel { font-size: .65rem; text-transform: uppercase; letter-spacing: .12em;
  color: var(--tk-text-muted); }
.tk-reg__statval { font-family: var(--tk-font-body); font-weight: 700;
  font-size: clamp(1.5rem, 4vw, 1.9rem); line-height: 1; color: var(--tk-text); }
/* --tk-gold-ink, not --tk-gold: on tk-light the raw token fails AA as text. It resolves
   to --tk-gold on tk-dark, so the figure still reads gold here. */
.tk-reg__statval--gold { color: var(--tk-gold-ink); }
.tk-reg__statval--silver { color: var(--tk-lume); }

/* Loading / error / empty. All three are `hidden`-driven, never d-none: this page loads
   no Bootstrap CSS, so d-none had no rule behind it and every state showed at once. */
.tk-reg__listwrap { display: flex; flex-direction: column; gap: var(--tk-space-6); }
.tk-reg__state { text-align: center; padding-block: var(--tk-space-12); }
.tk-reg__state--error { border-color: color-mix(in srgb, var(--tk-burgundy) 45%, var(--tk-border)); }
.tk-reg__stateic { display: grid; place-items: center; width: 56px; height: 56px; border-radius: 50%;
  margin: 0 auto var(--tk-space-4);
  background: color-mix(in srgb, var(--tk-burgundy) 12%, transparent); color: var(--tk-accent-text); }
.tk-reg__stateic svg { width: 26px; height: 26px; }
.tk-reg__statetitle { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; font-size: 1.1rem; margin: 0 0 var(--tk-space-3); }
.tk-reg__statetext { color: var(--tk-text-muted); font-size: .9rem; margin: 0; }
.tk-reg__stateactions { justify-content: center; margin-top: var(--tk-space-6); }
.tk-reg__stateactions svg { width: 15px; height: 15px; }

/* Watch cards. auto-fill with a min(100%, 300px) floor rather than fixed breakpoints:
   the min() is what stops the 300px track out-measuring a 288px column at 320px. */
.tk-reg__cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  gap: var(--tk-space-4); }
.tk-reg__cards:empty { display: none; }
.tk-reg__card { display: flex; flex-direction: column; gap: var(--tk-space-3);
  transition: border-color .2s var(--tk-ease); }
.tk-reg__card:hover { border-color: color-mix(in srgb, var(--tk-burgundy) 45%, var(--tk-border)); }
@media (prefers-reduced-motion: reduce) { .tk-reg__card { transition: none; } }
/* A stolen watch keeps the burgundy edge whether or not it is hovered — it must not need
   an interaction to look different from a clean one. The badge inside says so in words. */
.tk-reg__card--stolen { border-color: var(--tk-burgundy); }
.tk-reg__cardhead { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--tk-space-3); flex-wrap: wrap; }
.tk-reg__cardbrand { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; font-size: .95rem; margin: 0; min-width: 0; overflow-wrap: anywhere; }
.tk-reg__cardmodel { color: var(--tk-text); font-size: 1.05rem; margin: 0; overflow-wrap: anywhere; }
.tk-reg__cardmeta { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .3rem var(--tk-space-4);
  margin: 0; font-size: .8rem; }
.tk-reg__cardmeta dt { color: var(--tk-text-muted); font-size: .65rem; text-transform: uppercase;
  letter-spacing: .1em; align-self: center; }
.tk-reg__cardmeta dd { margin: 0; color: var(--tk-text); overflow-wrap: anywhere; }
.tk-reg__cardserial { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700;
  letter-spacing: .04em; }
/* margin-top:auto so the badge/CTA row sits on the floor of every card in the row,
   however many lines the model name above it took. */
.tk-reg__cardfoot { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-3); flex-wrap: wrap; margin-top: auto;
  padding-top: var(--tk-space-4); border-top: 1px solid var(--tk-border); }
.tk-reg__cardbtn { font-size: .7rem; padding: .45rem .8rem; }

/* Add / edit form panel. */
.tk-reg__formpanel .tk-reg__alert { margin-top: calc(var(--tk-space-2) * -1); }
.tk-reg__alert--ok { border-left-color: var(--tk-success);
  background: color-mix(in srgb, var(--tk-success) 12%, transparent); }
.tk-reg__alertlabel { color: var(--tk-text); }
/* .tk-reg__alerttext has no rule and wants none — it is the hook showAlert() writes the
   API's message into with textContent, and it inherits its colour from .tk-reg__alert p.
   Named here so a sweep for "class with no rule behind it" does not read it as a miss. */
/* The serial is shown but not editable — readonly, not disabled, so it stays in the tab
   order and can be read back. Dimmed so the lock is visible as well as stated. Written
   as .tk-field input.tk-reg__locked rather than leaning on !important: the skin it has
   to beat is .tk-field input, which outranks a bare class. */
.tk-field input.tk-reg__locked { background: var(--tk-raised); color: var(--tk-text-muted);
  cursor: not-allowed; }
.tk-reg__formfoot { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-3); flex-wrap: wrap; margin-top: var(--tk-space-4); }
.tk-reg__formfoot svg { width: 15px; height: 15px; flex: 0 0 auto; }

/* ---- Detail view (/registry/view/{hashid}) ----
   The certificate itself: identity and the owner's actions on the left, the record —
   registration facts, the provenance timeline, the verification ledger — on the right.
   Everything the dashboard already names is reused: the verification ladder, the stolen
   flag, .tk-reg__panelhead, .tk-reg__dl, .tk-reg__state and .tk-reg__modal. */

/* .tk-reg's own gap is space-8, which is right between top-level sections but too loose
   between the two columns' stacked panels; the tracks set their own space-6 rhythm. */
.tk-reg__detail { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--tk-space-6); align-items: start; }
@media (max-width: 900px) { .tk-reg__detail { grid-template-columns: minmax(0, 1fr); } }
.tk-reg__col { display: flex; flex-direction: column; gap: var(--tk-space-6); min-width: 0; }

/* Identity panel. The h1 lives here — the page had no h1 at all before, only a card
   title, so the brand is now the document's heading. */
.tk-reg__idpanel { text-align: center; }
/* A stolen watch keeps a burgundy edge with no interaction needed, exactly as the
   dashboard card does; the badge below it says so in words as well. */
.tk-reg__idpanel--stolen { border-color: var(--tk-burgundy); }
.tk-reg__idic { display: grid; place-items: center; width: 80px; height: 80px; margin: 0 auto var(--tk-space-4);
  border-radius: 50%; background: var(--tk-raised); border: 1px solid var(--tk-border);
  color: var(--tk-text-muted); }
/* Sized here, never through tk_icon()'s $attrs — that helper writes width="20" height="20"
   before it appends $attrs, so a passed width is a duplicate the parser ignores. */
.tk-reg__idic svg { width: 38px; height: 38px; }
.tk-reg__idbrand { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  letter-spacing: .06em; font-size: 1.3rem; margin: 0; overflow-wrap: anywhere; }
.tk-reg__idmodel { color: var(--tk-text-muted); font-size: 1.05rem; margin: .3rem 0 0;
  overflow-wrap: anywhere; }
.tk-reg__idbadges { display: flex; flex-wrap: wrap; justify-content: center; gap: .4rem;
  margin: var(--tk-space-4) 0 0; }

/* Serial block: the identifier the whole registry exists to protect, with a copy button.
   minmax(0, 1fr) on the text track so a long serial wraps instead of pushing the button
   out of the panel. */
.tk-reg__serialbox { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  gap: var(--tk-space-3); text-align: left; margin-top: var(--tk-space-6);
  padding: var(--tk-space-3) var(--tk-space-4); border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); background: var(--tk-page); }
.tk-reg__seriallabel { display: block; font-size: .625rem; text-transform: uppercase;
  letter-spacing: .12em; color: var(--tk-text-muted); }
.tk-reg__serialval { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700;
  letter-spacing: .04em; color: var(--tk-text); overflow-wrap: anywhere; }
/* 40px, not the 32px row button: this is a single primary affordance, not one of a row,
   and it has to clear the 24px touch-target floor comfortably on a phone. */
.tk-reg__copybtn { display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; width: 40px; height: 40px; padding: 0; cursor: pointer;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-surface); color: var(--tk-text-muted);
  transition: color .2s var(--tk-ease), border-color .2s var(--tk-ease); }
.tk-reg__copybtn:hover { color: var(--tk-accent-text); border-color: var(--tk-burgundy); }
.tk-reg__copybtn:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 2px; }
.tk-reg__copybtn svg { width: 18px; height: 18px; }
@media (prefers-reduced-motion: reduce) { .tk-reg__copybtn { transition: none; } }

/* Owner actions. A stacked column of full-width controls, divided off from the identity
   above it, with the two transfer routes sharing one line while there is room. */
.tk-reg__ownacts { display: flex; flex-direction: column; gap: .5rem;
  margin-top: var(--tk-space-6); padding-top: var(--tk-space-6);
  border-top: 1px solid var(--tk-border); }
.tk-reg__ownacts .tk-btn { justify-content: center; }
.tk-reg__ownacts svg { width: 15px; height: 15px; flex: 0 0 auto; }
.tk-reg__actpair { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: .5rem; }
@media (max-width: 420px) { .tk-reg__actpair { grid-template-columns: minmax(0, 1fr); } }
/* "In Collection" is a statement of fact, not an affordance — disabled, and dimmed so it
   does not read as a button waiting to be pressed. */
.tk-reg__ownacts .tk-btn[disabled] { opacity: .65; cursor: default; }
.tk-reg__ownacts .tk-btn[disabled]:hover { transform: none; }

/* The two standing notices — a verification under review, a transfer awaiting acceptance.
   Both are .tk-callout; only the heading row and the recipient line are new. */
.tk-reg__note h2 { display: flex; align-items: center; gap: .45rem; font-size: .8rem;
  text-transform: uppercase; letter-spacing: .1em; }
.tk-reg__note h2 svg { width: 16px; height: 16px; flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-reg__noteval { color: var(--tk-text); font-weight: 600; margin: .4rem 0 var(--tk-space-4);
  overflow-wrap: anywhere; }

/* Panel head trailer — the event count sits at the end of the provenance head. */
.tk-reg__panelcount { margin-left: auto; flex: 0 0 auto; }

/* Provenance timeline. A real <ol> — this is an ordered record, and it reads as one to a
   screen reader without any of the markers below. The rail is a border on the <li> and
   the dot is pulled onto it, so the last item can end the rail rather than trailing a
   stub past the final event (the !important hack the old inline <style> needed). */
.tk-reg__tl { list-style: none; margin: 0; padding: 0; }
.tk-reg__tlitem { position: relative; padding: 0 0 var(--tk-space-6) var(--tk-space-8);
  border-left: 1px solid var(--tk-border); margin-left: 15px; }
.tk-reg__tlitem:last-child { border-left-color: transparent; padding-bottom: 0; }
.tk-reg__tlic { position: absolute; left: -15px; top: 0; display: grid; place-items: center;
  width: 30px; height: 30px; border-radius: 50%; background: var(--tk-page);
  border: 1px solid var(--tk-border); color: var(--tk-text-muted); }
.tk-reg__tlic svg { width: 15px; height: 15px; }
/* Tone follows the event, but never carries it alone: every entry is titled in words
   directly beside its dot. Success is the 70/30 mix rather than raw --tk-success, which
   measures 2.2:1 as a foreground on --tk-surface. */
.tk-reg__tlic--ok { border-color: color-mix(in srgb, var(--tk-success) 55%, transparent);
  color: color-mix(in srgb, var(--tk-success) 70%, var(--tk-text)); }
.tk-reg__tlic--alarm { border-color: var(--tk-burgundy); color: var(--tk-accent-text); }
.tk-reg__tlic--warn { border-color: color-mix(in srgb, var(--tk-gold) 55%, transparent);
  color: var(--tk-gold-ink); }
.tk-reg__tlic--info { border-color: color-mix(in srgb, var(--tk-lume) 45%, transparent);
  color: var(--tk-lume); }
.tk-reg__tlhead { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--tk-space-3); flex-wrap: wrap; }
.tk-reg__tltitle { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; font-size: .8rem; margin: 0; color: var(--tk-text); }
.tk-reg__tldate { font-size: .7rem; color: var(--tk-text-muted); white-space: nowrap; }
.tk-reg__tltext { color: var(--tk-text-muted); font-size: .85rem; line-height: 1.6;
  margin: .35rem 0 0; overflow-wrap: anywhere; }
.tk-reg__tluser { display: block; font-size: .7rem; color: var(--tk-text-muted);
  font-style: italic; margin-top: .25rem; overflow-wrap: anywhere; }
/* The timeline's own empty state sits inside a panel that already has padding, so it
   takes less vertical room than the dashboard's page-level one. */
.tk-reg__state--inset { padding-block: var(--tk-space-6); }

/* Verification ledger. Same table shape as the documents ledger and the collection's. */
.tk-reg__tablewrap { overflow-x: auto; }
/* 560px, not the documents ledger's 620: this table sits in the 7-of-12 track rather than
   full width, so it has 608px to play with at the 1200px container — 640 put it into
   horizontal scroll on a desktop, which a five-column table with one short row should not
   need. Below 560 it scrolls, as every ledger in the system does. */
.tk-reg__table { width: 100%; min-width: 560px; border-collapse: collapse; }
.tk-reg__table th, .tk-reg__table td { padding: .7rem var(--tk-space-4); text-align: left;
  vertical-align: middle; border-bottom: 1px solid var(--tk-border); font-size: .85rem; }
.tk-reg__table tbody tr:last-child td { border-bottom: 0; }
.tk-reg__table thead th { white-space: nowrap;
  background: color-mix(in srgb, var(--tk-page) 55%, var(--tk-surface));
  font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; font-size: .65rem; color: var(--tk-text-muted); }
.tk-reg__rowdate { color: var(--tk-text-muted); white-space: nowrap; }
.tk-reg__rownote { color: var(--tk-text-muted); font-style: italic; overflow-wrap: anywhere; }
/* The three proof verdicts, on .tk-badge. Same values as .tk-doc__status--*: the pending
   fill is 12% gold rather than 16% because --tk-gold-ink on a 16% wash measures 4.10:1
   at this size on the light theme, and 12% brings it to 5.28:1. */
.tk-reg__pstatus--approved { background: color-mix(in srgb, var(--tk-success) 18%, transparent);
  color: color-mix(in srgb, var(--tk-success) 62%, var(--tk-text));
  border-color: color-mix(in srgb, var(--tk-success) 50%, transparent); }
.tk-reg__pstatus--pending { background: color-mix(in srgb, var(--tk-gold) 12%, transparent);
  color: var(--tk-gold-ink); border-color: color-mix(in srgb, var(--tk-gold) 45%, transparent); }
.tk-reg__pstatus--rejected { background: var(--tk-burgundy); color: #fbeef0;
  border-color: var(--tk-burgundy); }
.tk-reg__proofbtns { display: flex; gap: .35rem; flex-wrap: wrap; }
/* Shares the card CTA's metrics — one small-button size across the whole feature. */
.tk-reg__cardbtn, .tk-reg__proofbtn { font-size: .7rem; padding: .45rem .8rem; }
.tk-reg__proofbtn svg { width: 13px; height: 13px; flex: 0 0 auto; }
.tk-reg__badfiles { display: inline-flex; align-items: center; gap: .35rem;
  color: var(--tk-accent-text); font-size: .8rem; }
.tk-reg__badfiles svg { width: 14px; height: 14px; flex: 0 0 auto; }
.tk-reg__challengerow { display: flex; align-items: center; gap: .35rem; margin-top: .3rem;
  font-size: .75rem; color: var(--tk-gold-ink); }
.tk-reg__challengerow svg { width: 13px; height: 13px; flex: 0 0 auto; }

/* ---- The four dialogs ----
   .tk-modal supplies the frame and .tk-reg__modal* the trim, both already defined for the
   public page's leaving-the-site dialog. Every one of these replaces a Bootstrap modal
   whose data-bs-toggle did nothing and whose bootstrap.Modal(...) threw: window.bootstrap
   is undefined on a tk page and this page never loaded a bundle of its own. */

/* A proof is usually a phone photo, and a portrait one is far taller than the viewport.
   .tk-modal__img constrains width only, so the Bootstrap version's max-height: 80vh has to
   be restated; width:auto keeps the aspect ratio while the cap does the work. */
/* .tk-admin__docimg rides along rather than copying the rule: /admin/dashboard's document
   viewer opens the very same registry_proofs files this one does. */
.tk-reg__docimg, .tk-admin__docimg { max-height: 78vh; width: auto; max-width: 100%; margin-inline: auto; }

/* File inputs — the first in the system. .tk-field input already skins the box; only the
   browser's own picker button needs saying, and it inherits nothing by default. */
.tk-field input[type="file"] { padding: .45rem; font-size: .85rem; }
.tk-field input[type="file"]::file-selector-button { font: inherit; font-size: .75rem;
  font-family: var(--tk-font-display); font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; margin-right: .6rem; padding: .4rem .7rem; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--tk-text) 28%, transparent);
  border-radius: var(--tk-radius); background: transparent; color: var(--tk-text); }
.tk-field input[type="file"]::file-selector-button:hover { border-color: var(--tk-burgundy);
  color: var(--tk-accent-text); }
.tk-reg__fieldnote { font-size: .75rem; color: var(--tk-text-muted); margin: .3rem 0 0; }

/* Time-challenge block inside the verification dialog. */
.tk-reg__challenge { border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-page); padding: var(--tk-space-4); margin-bottom: var(--tk-space-4); }
.tk-reg__challenge h3 { display: flex; align-items: center; gap: .4rem; font-family: var(--tk-font-display);
  font-weight: 700; text-transform: uppercase; letter-spacing: .08em; font-size: .75rem;
  margin: 0 0 .4rem; color: var(--tk-text); }
.tk-reg__challenge h3 svg { width: 15px; height: 15px; flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-reg__challengebox { text-align: center; padding: var(--tk-space-4); margin: var(--tk-space-4) 0;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius); background: var(--tk-surface); }
.tk-reg__challengelabel { display: block; font-size: .7rem; color: var(--tk-text-muted); }
.tk-reg__challengetime { display: block; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 700; font-size: 1.6rem; letter-spacing: .06em; color: var(--tk-gold-ink); margin-top: .2rem; }
/* The loading and failed reads of the challenge, written with textContent into the same
   element — so the "waiting" and "broken" states must not look like a time. */
.tk-reg__challengetime--wait { font-size: .9rem; letter-spacing: normal; color: var(--tk-text-muted); }
.tk-reg__challengetime--fail { font-size: .9rem; letter-spacing: normal; color: var(--tk-accent-text); }
.tk-reg__challengebtn { width: 100%; justify-content: center; margin-bottom: var(--tk-space-4); }

/* QR dialog. The box is fixed-square and white because a QR code has to be scanned off
   the screen; it is `hidden` until a code exists, and grid needs the opt-out below. */
/* aspect-ratio rather than a fixed height: at 320px the 260px box has to shrink to fit the
   dialog's content column, and a fixed height would leave it a letterbox with the code
   floating in it. */
.tk-reg__qrbox { display: grid; place-items: center; width: 260px; aspect-ratio: 1;
  max-width: 100%; margin: var(--tk-space-4) auto; padding: var(--tk-space-3);
  background: #fff; border-radius: var(--tk-radius); }
.tk-reg__qrbox img { display: block; max-width: 100%; height: auto; }
.tk-reg__qrexpired { display: grid; place-items: center; width: 100%; height: 100%;
  color: var(--tk-burgundy); }
.tk-reg__qrexpired svg { width: 72px; height: 72px; }
.tk-reg__qrtimer { text-align: center; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 700; color: var(--tk-gold-ink); margin: 0; }
.tk-reg__qrmsg { text-align: center; }
.tk-reg__steps { color: var(--tk-text-muted); font-size: .85rem; line-height: 1.7;
  margin: 0 0 var(--tk-space-4); padding-left: 1.3em; }
.tk-reg__dialogrule { border: 0; border-top: 1px solid var(--tk-border); margin: var(--tk-space-4) 0; }

/* Same opt-out as the dashboard's: a class that sets `display` outranks the UA sheet's
   bare [hidden] attribute selector, so every element here that both sets a display and is
   toggled with `hidden` has to say so. .tk-reg__alert is already covered above.
   .tk-reg__qrexpired is in this list because it was caught doing exactly that: it sets
   display:grid and shares a grid cell with the code itself, so the burgundy "expired"
   cross was drawn over a live, scannable QR from the moment one was generated. */
.tk-reg__qrbox[hidden], .tk-reg__qrtimer[hidden], .tk-reg__qrmsg[hidden],
.tk-reg__qrexpired[hidden], .tk-reg__challengerow[hidden],
.tk-reg__proofbtns[hidden] { display: none; }

/* ---- The transfer flow (/registry/transfers, /registry/accept-transfer) ----
   The ledger rows reuse .tk-reg__table, .tk-reg__tablewrap and .tk-reg__state from the
   detail view; only the two badge ladders and the row's two-line watch cell are new. */

/* Transfer status. Four states, where the proof ladder has three, and they do not map
   onto each other — a completed transfer is not an "approved" proof — so this carries its
   own modifiers on the same tokens rather than borrowing .tk-reg__pstatus--*. Fill weight
   does the ranking and each badge names its state in text, so it reads without colour. */
.tk-reg__tstatus { display: inline-flex; align-items: center; gap: .35rem; vertical-align: middle;
  white-space: nowrap; }
.tk-reg__tstatus svg { width: 12px; height: 12px; flex: 0 0 auto; }
.tk-reg__tstatus--pending { background: color-mix(in srgb, var(--tk-gold) 12%, transparent);
  color: var(--tk-gold-ink); border-color: color-mix(in srgb, var(--tk-gold) 45%, transparent); }
.tk-reg__tstatus--completed { background: color-mix(in srgb, var(--tk-success) 18%, transparent);
  color: color-mix(in srgb, var(--tk-success) 62%, var(--tk-text));
  border-color: color-mix(in srgb, var(--tk-success) 50%, transparent); }
/* Cancelled takes the quiet neutral wash .tk-reg__level--gray uses, and full --tk-text on
   it: the quietness is the fill, not the ink. Muted ink on a 10% wash misses AA. */
.tk-reg__tstatus--cancelled { background: color-mix(in srgb, var(--tk-text) 10%, transparent);
  color: var(--tk-text); border-color: color-mix(in srgb, var(--tk-text) 22%, transparent); }
.tk-reg__tstatus--expired { background: var(--tk-burgundy); color: #fbeef0;
  border-color: var(--tk-burgundy); }

/* Transfer channel — email or QR. Deliberately unranked: it is a fact about the transfer,
   not a verdict on it, so it stays on the plain .tk-badge shell. */
.tk-reg__ttype { display: inline-flex; align-items: center; gap: .35rem; vertical-align: middle;
  white-space: nowrap; }
.tk-reg__ttype svg { width: 12px; height: 12px; flex: 0 0 auto; }

/* The watch cell: brand over model, both owner-supplied free text, so both wrap anywhere
   rather than widening the row past the table's min-width. */
.tk-reg__rowbrand { display: block; font-family: var(--tk-font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; font-size: .75rem; color: var(--tk-text);
  overflow-wrap: anywhere; }
.tk-reg__rowmodel { display: block; color: var(--tk-text-muted); font-size: .8rem;
  overflow-wrap: anywhere; }
/* Recipient identified only by email — the address is the value, the mark is the hint. */
.tk-reg__rowto { display: inline-flex; align-items: flex-start; gap: .35rem;
  overflow-wrap: anywhere; }
.tk-reg__rowto svg { width: 14px; height: 14px; flex: 0 0 auto; margin-top: .15rem;
  color: var(--tk-text-muted); }
.tk-reg__cardbtn svg { width: 13px; height: 13px; flex: 0 0 auto; }

/* ---- /registry/accept-transfer ----
   A single decision on one column: three states in the same slot, only one ever shown. */
.tk-reg__accept { max-width: 560px; width: 100%; margin-inline: auto; }
.tk-reg__acceptwatch { text-align: center; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); background: var(--tk-page);
  padding: var(--tk-space-6); margin-bottom: var(--tk-space-6); }
/* .tk-reg__idbrand is an <h1> on the certificate and a <p> here — the page's own h1 is the
   title above — so the margin the heading reset gave it has to be said. */
.tk-reg__acceptwatch .tk-reg__idbrand { margin: 0; }
.tk-reg__accept .tk-reg__note { margin-bottom: var(--tk-space-6); }
.tk-reg__accept .tk-reg__note h3 { display: flex; align-items: center; gap: .45rem;
  font-size: .8rem; text-transform: uppercase; letter-spacing: .08em; }
.tk-reg__accept .tk-reg__note h3 svg { width: 16px; height: 16px; flex: 0 0 auto;
  color: var(--tk-gold-ink); }
/* The success twin of .tk-reg__state--error, which the detail view already defines. */
.tk-reg__state--ok { border-color: color-mix(in srgb, var(--tk-success) 45%, transparent); }
.tk-reg__stateic--ok { background: color-mix(in srgb, var(--tk-success) 18%, transparent);
  color: color-mix(in srgb, var(--tk-success) 70%, var(--tk-text)); }
/* Same opt-out again: .tk-reg__stateic sets display:grid, and the accept screen toggles
   its two icons with `hidden`, so without this both the tick and the warning are drawn. */
.tk-reg__stateic[hidden] { display: none; }

/* ---- Account & auth (/login, /register, /forgot-password, and the rest of the set) ----
   One layer for every screen that is *about* the account rather than about a watch: the
   three logged-out forms here, and the token-gated and signed-in screens that follow.
   The shape is the registry's login gate — one centred panel on an otherwise empty page —
   with a form in it instead of two buttons, so .tk-acct__ic/__title/__sub deliberately
   read as .tk-reg__gateic/__gatetitle/__gatetext. Everything else is borrowed whole:
   .tk-panel is the card, .tk-form the stack, .tk-field the controls, .tk-check the
   remember-me box, .tk-callout the messages, .tk-btn the actions. */

.tk-acct { display: flex; justify-content: center; padding-block: var(--tk-space-12); }
/* Two widths: 26rem is a one-column sign-in, 34rem takes the register form's paired
   name fields without squeezing them. width:100% so both floor at the container. */
.tk-acct__card { width: 100%; max-width: 26rem; }
.tk-acct__card--wide { max-width: 34rem; }

/* Head. The nav above already carries the wordmark, so the card opens on the same round
   icon tile the registry gate uses rather than repeating the 180px logo the Bootstrap
   card drew. */
.tk-acct__head { text-align: center; margin-bottom: var(--tk-space-6); }
.tk-acct__ic { display: grid; place-items: center; width: 56px; height: 56px; border-radius: 50%;
  margin: 0 auto var(--tk-space-4);
  background: color-mix(in srgb, var(--tk-burgundy) 14%, transparent); color: var(--tk-accent-text); }
.tk-acct__ic svg { width: 26px; height: 26px; }
.tk-acct__title { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  letter-spacing: .08em; font-size: 1.35rem; margin: 0 0 var(--tk-space-2); }
.tk-acct__sub { color: var(--tk-text-muted); font-size: .9rem; margin: 0; }

/* Messages. Wrong password, taken email, weak password, failed captcha and the rate
   limiter all land here, so the error tone is the one that has to read first: burgundy
   edge, and a mark beside the text so the state is not carried by colour alone. */
.tk-acct__alert { display: flex; align-items: flex-start; gap: .6rem; margin-bottom: var(--tk-space-6); }
.tk-acct__alert p { color: var(--tk-text); margin: 0; }
.tk-acct__alert svg { width: 18px; height: 18px; flex: 0 0 auto; margin-top: .1rem; }
.tk-acct__alert--error { border-left-color: var(--tk-burgundy);
  background: color-mix(in srgb, var(--tk-burgundy) 8%, transparent); }
.tk-acct__alert--error svg { color: var(--tk-accent-text); }
.tk-acct__alert--ok { border-left-color: var(--tk-success);
  background: color-mix(in srgb, var(--tk-success) 12%, transparent); }
.tk-acct__alert--ok svg { color: color-mix(in srgb, var(--tk-success) 75%, var(--tk-text)); }
/* The dev-only mock verification link. Quiet by design — it must never be mistaken for
   part of the flow it stands in for. */
.tk-acct__alert--dev { border-left-color: var(--tk-text-muted); }
.tk-acct__alert--dev svg { color: var(--tk-text-muted); }
.tk-acct__alertlink { color: var(--tk-accent-text); text-decoration: underline;
  text-underline-offset: 2px; }

/* Fields. The paired first/last name row: minmax(0, 1fr), never a bare 1fr — a bare
   track floors at min-content and a long placeholder would widen the card. */
.tk-acct__grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--tk-space-4); }
@media (max-width: 480px) { .tk-acct__grid { grid-template-columns: minmax(0, 1fr); } }
/* "Password" and "Forgot Password?" share a line. The label is no longer a direct child
   of .tk-field once it is wrapped, so the label skin has to be restated here. */
.tk-acct__labelrow { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--tk-space-4); }
.tk-acct__labelrow > label { font-size: .625rem; text-transform: uppercase; letter-spacing: .1em;
  color: var(--tk-text-muted); }
.tk-acct__link { font-size: .75rem; color: var(--tk-accent-text); }
.tk-acct__link:hover { text-decoration: underline; }
/* .tk-inputgroup lays the row out but carries no grow rule, so the input keeps its
   intrinsic width and the reveal button is pushed off the edge without this. */
.tk-acct .tk-inputgroup input { flex: 1; min-width: 0; }
.tk-acct input::placeholder { color: var(--tk-text-muted); opacity: 1; }

/* Reveal control. Not a .tk-btn: it has to match the input's height and border exactly,
   which the button's uppercase display type and padding would fight. */
.tk-acct__reveal { display: inline-flex; align-items: center; padding-inline: .75rem;
  background: var(--tk-page); border: 1px solid var(--tk-border); color: var(--tk-text-muted);
  cursor: pointer; }
.tk-acct__reveal:hover { color: var(--tk-text); }
.tk-acct__reveal:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: -2px; }
/* tk_icon() hardcodes width/height="20" before any $attrs are merged, so the eye can
   only be sized here. */
.tk-acct__eyeic { width: 17px; height: 17px; }
/* .tk-icon sets display:inline-block and a class beats the UA sheet's bare
   [hidden] { display: none }, so the closed eye ships visible — both glyphs stacked in
   the button — unless it opts back in. Same idiom as .tk-reg__stateic[hidden]. */
.tk-acct__eyeic[hidden] { display: none; }

/* Password strength. The bar colours are written as inline styles by
   password-strength.js, so the only thing this can own is the geometry; the palette is
   handed to the script by the page (see /register). aria-hidden, because the bars say
   nothing a screen reader can use — the note under them carries the score in words,
   which is also what keeps the meter off colour-alone (WCAG 1.4.1). */
.tk-acct__meter { display: flex; gap: 2px; height: 5px; margin-top: .5rem; }
.tk-acct__meter > div { flex: 1 1 0; min-width: 0; border-radius: var(--tk-radius-pill);
  background: var(--tk-border); transition: background-color .3s var(--tk-ease); }
@media (prefers-reduced-motion: reduce) { .tk-acct__meter > div { transition: none; } }
/* min-height, not height: the note reserves its line so the panel does not jump as the
   text appears, but a wrapped suggestion must still be allowed to grow. */
.tk-acct__meternote { color: var(--tk-text-muted); font-size: .75rem; line-height: 1.4;
  min-height: 1.4em; margin: .35rem 0 0; }

.tk-acct__check { margin-top: var(--tk-space-2); }
.tk-acct__submit { margin-top: var(--tk-space-2); }
/* /register disables the button while grecaptcha.execute() is in flight, so the state
   has to be visible — and the hover lift has to stop with it. */
.tk-acct__submit[disabled] { opacity: .6; cursor: progress; }
.tk-acct__submit[disabled]:hover { transform: none; background: var(--tk-burgundy); }
.tk-acct__submit svg { width: 15px; height: 15px; }

/* The OR rule between the password form and Google. The lines are drawn by the flex
   children rather than by an absolutely positioned <hr> over a background-matched
   label, which is what the Bootstrap version did and which only worked while the card
   background and the page background were the same colour. */
.tk-acct__or { display: flex; align-items: center; gap: var(--tk-space-4);
  margin-block: var(--tk-space-6); color: var(--tk-text-muted);
  font-size: .625rem; text-transform: uppercase; letter-spacing: .2em; }
.tk-acct__or::before, .tk-acct__or::after { content: ""; flex: 1 1 0;
  border-top: 1px solid var(--tk-border); }

/* Google. The mark keeps its four brand colours — it is Google's asset, not ours — so
   the button has to stay a light surface for it to sit on at any theme. */
.tk-acct__google { background: #fff; color: #1f1f1f; border-color: #dadce0; }
.tk-acct__google:hover { background: #f3f3f3; color: #1f1f1f; border-color: #c6c9cc; }
.tk-acct__googleic { width: 18px; height: 18px; flex: 0 0 auto; }

/* Foot: the sign-in/register cross-link, and the back link on /forgot-password. */
.tk-acct__foot { text-align: center; color: var(--tk-text-muted); font-size: .85rem;
  margin-top: var(--tk-space-6); }
.tk-acct__foot a { color: var(--tk-accent-text); font-weight: 600; }
.tk-acct__foot a:hover { text-decoration: underline; }
.tk-acct__back { display: inline-flex; align-items: center; gap: .35rem; }
.tk-acct__back svg { width: 14px; height: 14px; }

/* ---- Verdict screens (/verify-email, and the two terminal states of /reset-password) ----
   The token-gated screens are not forms; they are a single answer to a link that was
   clicked. So they reuse the head above — tile, title, line of copy — with no form under
   it and one action instead, which is the registry gate's shape arriving from the other
   direction. Kept in .tk-acct rather than borrowed from .tk-reg__gate so the whole
   account set stays one vocabulary. */
.tk-acct__card--status { text-align: center; }
/* The tile is the only thing that changes between the three verdicts, and it never
   carries the meaning on its own: the title says "Email Verified" / "Already Verified" /
   "Link Not Valid" in text, and the glyph is aria-hidden. The default tile (burgundy) is
   the refusal, matching .tk-acct__alert--error. */
.tk-acct__ic--ok { background: color-mix(in srgb, var(--tk-success) 16%, transparent);
  color: color-mix(in srgb, var(--tk-success) 78%, var(--tk-text)); }
.tk-acct__ic--warn { background: color-mix(in srgb, var(--tk-gold-ink) 16%, transparent);
  color: var(--tk-gold-ink); }
/* .tk-actions supplies the row; this only centres it under a status card and stops the
   buttons stretching edge to edge on a narrow screen. */
.tk-acct__actions { justify-content: center; margin-top: var(--tk-space-6); }
.tk-acct__actions svg { width: 15px; height: 15px; }
/* The status card's copy is the payload, not a caption, so it reads at body size and in
   the body colour rather than inheriting .tk-acct__sub's muted 0.9rem. */
.tk-acct__statusmsg { color: var(--tk-text); font-size: .95rem; line-height: 1.6; margin: 0; }
/* The countdown note under a success verdict, where the page is about to move on. */
.tk-acct__statusnote { color: var(--tk-text-muted); font-size: .78rem; margin: var(--tk-space-4) 0 0; }

/* ---- Signed-in account screens (/account, /profile-settings, /dashboard) ----
   The last three of the set. Same .tk-acct* vocabulary as the sign-in card, but these are
   full-width pages rather than one centred panel, so --page turns the block's centring off
   instead of forking a second layer — .tk-acct .tk-inputgroup input and .tk-acct
   input::placeholder above are written against the block, and the password rows here need
   both. Everything below is either layout for the wider shape or a control the narrow card
   never had: the avatar, the username availability meter, the device row and the chart. */
.tk-acct--page { display: block; padding-block: var(--tk-space-8); }
.tk-acct__headrow { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--tk-space-4); flex-wrap: wrap; margin-bottom: var(--tk-space-8); }
.tk-acct__h1 { font-size: var(--tk-fs-h1); text-transform: uppercase; letter-spacing: .04em;
  margin: 0; overflow-wrap: anywhere; }
.tk-acct__lead { color: var(--tk-text-muted); font-size: .9rem; max-width: 60ch;
  margin: var(--tk-space-3) 0 0; }
/* margin-left:auto rather than the row's space-between alone: once the row wraps (it does
   at 375px) space-between separates nothing and the back link lands at the left of its own
   line, under the lead. The same correction .tk-reg__heroactions carries. */
.tk-acct__headacts { margin-left: auto; }

/* Section heads inside the panels. .tk-acct__title is a centred *page* heading on the
   narrow card; these are left-aligned rules over a block, so they are their own thing. */
.tk-acct__paneltitle { display: flex; align-items: center; gap: .5rem;
  font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .1em; font-size: .95rem; margin: 0 0 var(--tk-space-6);
  padding-bottom: var(--tk-space-3); border-bottom: 1px solid var(--tk-border); }
.tk-acct__paneltitle svg { width: 18px; height: 18px; flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-acct__stack { display: flex; flex-direction: column; gap: var(--tk-space-6); }

/* /account: the profile rail beside the tools grid. minmax(0, …) on both tracks — a bare
   1fr floors at min-content, and an email address with no break opportunity in it would
   push the page wider than the viewport. */
.tk-acct__cols { display: grid; grid-template-columns: minmax(0, 19rem) minmax(0, 1fr);
  gap: var(--tk-space-6); align-items: start; }
@media (max-width: 900px) { .tk-acct__cols { grid-template-columns: minmax(0, 1fr); } }
.tk-acct__avatar { display: grid; place-items: center; overflow: hidden;
  width: 120px; height: 120px; border-radius: 50%; margin: 0 auto var(--tk-space-6);
  background: color-mix(in srgb, var(--tk-burgundy) 10%, transparent);
  border: 1px solid var(--tk-border); color: var(--tk-accent-text); }
.tk-acct__avatar img { width: 100%; height: 100%; object-fit: cover; }
/* base.css:13 is `:where(.tk-dark, .tk-light) img { …; display: block }`. :where() carries no
   specificity, but *any* author rule outranks the UA sheet's [hidden] { display: none } — so
   on a tk page an <img hidden> ships visible, and /profile-settings' empty preview <img>
   rendered as a broken-image glyph beside the avatar until this opted back out. Measured:
   display was `block` with a 118x45 box; it is `none` now. Same idiom as
   .tk-acct__eyeic[hidden] and .tk-reg__stateic[hidden]. */
.tk-acct__avatar img[hidden] { display: none; }
.tk-acct__avatar svg { width: 48px; height: 48px; }
/* Term above value, one pair per row — the ledger .tk-reg__dl draws, stacked because this
   column is 19rem wide and an email address will not sit beside its own label. */
.tk-acct__dl { display: grid; gap: var(--tk-space-4); margin: 0; }
.tk-acct__dl dt { font-size: .625rem; text-transform: uppercase; letter-spacing: .1em;
  color: var(--tk-text-muted); }
.tk-acct__dl dd { margin: .25rem 0 0; font-weight: 600; overflow-wrap: anywhere; }
.tk-acct__profileacts { margin-top: var(--tk-space-6); }
.tk-acct__profileacts svg { width: 15px; height: 15px; }
/* Sign out, and "log out others", are the two controls on these pages that end a session.
   They read burgundy rather than as a neutral ghost, without becoming --solid, which on
   /account would outrank the tools the page is actually for. */
.tk-acct__signout { border-color: color-mix(in srgb, var(--tk-burgundy) 45%, transparent);
  color: var(--tk-accent-text); }
.tk-acct__signout:hover { border-color: var(--tk-burgundy);
  background: color-mix(in srgb, var(--tk-burgundy) 8%, transparent); }
/* Two across inside the tools column, not the hub's three — this grid is two thirds of the
   page, not all of it. Declared after .tk-tool-grid's own media queries so it wins at every
   width, with its own single-column break underneath. */
.tk-acct__toolgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 640px) { .tk-acct__toolgrid { grid-template-columns: minmax(0, 1fr); } }
.tk-acct__toolsfoot { justify-content: center; margin-top: var(--tk-space-6); }
/* .tk-tool-card lifts 4px on hover and has never been gated. It is reused here, so gate it. */
@media (prefers-reduced-motion: reduce) {
  .tk-tool-card { transition: none; }
  .tk-tool-card:hover { transform: none; }
}

/* /profile-settings — avatar. The file input is .tk-sr-only, not `hidden`: display:none
   takes a control out of the accessibility tree and out of the tab order, so the only way
   to reach it would be a <label>, and labels are not focusable. Clipped, it keeps both. */
.tk-acct__avatarrow { display: flex; align-items: center; gap: var(--tk-space-6);
  flex-wrap: wrap; }
.tk-acct__avatarlab { position: relative; display: inline-block; cursor: pointer; }
.tk-acct__avatarlab .tk-acct__avatar { margin: 0; }
.tk-acct__avatarlab:hover .tk-acct__avatar { border-color: var(--tk-burgundy); }
/* The upload spinner sits over the avatar, which is why the label is the positioned
   ancestor. inset:0 + margin:auto centres it without a transform. */
.tk-acct__avatarspin { position: absolute; inset: 0; margin: auto;
  width: 2rem; height: 2rem; border-width: 2px; }
/* The clipped input still takes focus, so its ring has to be drawn on the visible label. */
.tk-acct__avatarfile:focus-visible + .tk-acct__avatarbtn {
  outline: 2px solid var(--tk-burgundy); outline-offset: 2px; }
.tk-acct__avatarnote { color: var(--tk-text-muted); font-size: .75rem; margin: .6rem 0 0; }

/* Username availability meter, rebuilt from a Bootstrap .progress and three text-muted
   chips. The verdict is named in the status line as well as coloured, so the meter does
   not carry its meaning in colour alone (WCAG 1.4.1). */
.tk-acct__umeter { margin-top: var(--tk-space-3); }
.tk-acct__ustatus { display: flex; align-items: center; gap: .5rem; margin-bottom: .35rem; }
.tk-acct__ustatustext { font-size: .75rem; color: var(--tk-text-muted); margin-right: auto; }
.tk-acct__ustatustext--ok { color: color-mix(in srgb, var(--tk-success) 78%, var(--tk-text)); }
.tk-acct__ustatustext--bad { color: var(--tk-accent-text); }
.tk-acct__ustatusic { display: inline-flex; flex: 0 0 auto; color: var(--tk-text-muted); }
.tk-acct__ustatusic svg { width: 15px; height: 15px; }
.tk-acct__ustatusic--ok { color: color-mix(in srgb, var(--tk-success) 78%, var(--tk-text)); }
.tk-acct__ustatusic--bad { color: var(--tk-accent-text); }
.tk-acct__uspin { animation: tk-spin .8s linear infinite; }
@media (prefers-reduced-motion: reduce) { .tk-acct__uspin { animation: none; } }
.tk-acct__utrack { height: 4px; border-radius: var(--tk-radius-pill);
  background: var(--tk-border); overflow: hidden; }
.tk-acct__ufill { height: 100%; width: 0; border-radius: var(--tk-radius-pill);
  background: var(--tk-text-muted);
  transition: width .3s var(--tk-ease), background-color .3s var(--tk-ease); }
@media (prefers-reduced-motion: reduce) { .tk-acct__ufill { transition: none; } }
.tk-acct__ufill--warn { background: var(--tk-gold-ink); }
.tk-acct__ufill--bad { background: var(--tk-burgundy); }
.tk-acct__ufill--ok { background: var(--tk-success); }
/* The three requirements, side by side under the track. list-style:none because they are
   state markers beside a live status line, not an enumeration to be read out in order. */
.tk-acct__urules { display: flex; flex-wrap: wrap; gap: .35rem var(--tk-space-4);
  list-style: none; padding: 0; margin: .45rem 0 0; }
.tk-acct__urule { display: inline-flex; align-items: center; gap: .3rem;
  font-size: .7rem; color: var(--tk-text-muted); }
.tk-acct__urule svg { width: 12px; height: 12px; flex: 0 0 auto; }
.tk-acct__urule--ok { color: color-mix(in srgb, var(--tk-success) 78%, var(--tk-text)); }

/* A readonly control is still a control: it keeps the field skin and swaps the page
   background for the raised one, so it reads as "shown, not editable" rather than as an
   empty box. Same treatment .tk-reg__locked gives the registry's fixed fields. */
.tk-acct .tk-field input[readonly] { background: var(--tk-raised);
  color: var(--tk-text-muted); cursor: not-allowed; }
.tk-acct__fieldnote { color: var(--tk-text-muted); font-size: .75rem; margin: .35rem 0 0; }
/* The right-hand side of .tk-acct__labelrow is a link on /login ("Forgot Password?"). Here it
   is a note, so it must not borrow .tk-acct__link's accent colour and read as one. */
.tk-acct__labelhint { font-size: .7rem; color: var(--tk-text-muted); text-align: right; }
.tk-acct__submitrow { display: flex; justify-content: flex-end; margin-top: var(--tk-space-2); }

/* Warning tone — "you must enter your current password", the one message on these pages
   that is neither a refusal nor a success. Gold edge, matching .tk-callout--warning and
   .tk-acct__ic--warn; --tk-gold-ink rather than --tk-gold, which fails AA as ink. */
.tk-acct__alert--warn { border-left-color: var(--tk-gold-ink);
  background: color-mix(in srgb, var(--tk-gold-ink) 8%, transparent); }
.tk-acct__alert--warn svg { color: var(--tk-gold-ink); }

/* Device management: copy on the left, the control on the right, each stacking under the
   other once the panel is narrower than the two of them together. */
.tk-acct__devrow { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-4); flex-wrap: wrap; }
.tk-acct__devtitle { font-family: var(--tk-font-body); font-weight: 700; font-size: .9rem; margin: 0; }
.tk-acct__devtext { color: var(--tk-text-muted); font-size: .8rem; line-height: 1.6;
  max-width: 60ch; margin: .3rem 0 0; }

/* /dashboard. Chart.js sizes its canvas to the parent box, so the parent needs a real
   height — a bare <canvas> in a grid or flex parent either collapses or grows unbounded. */
.tk-acct__chart { position: relative; height: clamp(240px, 40vh, 380px); }
.tk-acct__chartnote { color: var(--tk-text-muted); font-size: .85rem; text-align: center;
  margin: 0; padding-block: var(--tk-space-8); }

/* ---- Static informational pages (/about-us, /contact, /data-deletion,
   /return-policy, /install-app) ----

   .tk-prose already solves the hard part — 68ch, 18px, 1.75, headings, lists, links,
   rules — and every paragraph on these five pages is rendered by it unchanged. This
   layer is only the three things a prose block genuinely does not have:

     1. A page header. .tk-prose styles body copy; it has no centred title/lead block,
        and the two that exist nearby do not fit: .tk-article__title caps at 20ch and
        left-aligns (a blog headline), and .tk-tool-hero is a full-bleed bordered band
        built for the tool pages' hero.
     2. A column. .tk-prose sets its own 68ch measure but does not centre itself or
        carry page padding, so on its own it hugs the left edge of .tk-container.
     3. Structure prose has no vocabulary for: bordered panels with an icon'd header
        (the Bootstrap .card/.card-header these pages were built from), and the small
        feature grids on /about-us and /install-app.

   The column IS the measure, so .tk-prose inside it drops its own max-width — otherwise
   a 612px paragraph block sits ragged beside a 704px panel. */
.tk-page { max-width: 44rem; margin-inline: auto;
  padding-inline: var(--tk-space-6); padding-block: var(--tk-space-8) var(--tk-space-12); }
.tk-page--wide { max-width: 68rem; }
.tk-page > .tk-prose { max-width: none; }
.tk-page > * + * { margin-top: var(--tk-space-8); }

/* Header. Uppercase display type to match .tk-tool-hero__title and .tk-reg__title, the
   two nearest headings in the system. */
.tk-page__head { text-align: center; }
.tk-page__title { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  letter-spacing: .03em; font-size: clamp(1.8rem, 5vw, 2.6rem); line-height: 1.05;
  margin: var(--tk-space-2) 0 0; }
.tk-page__lead { color: var(--tk-text-muted); font-size: 1.05rem; line-height: 1.65;
  max-width: 56ch; margin: var(--tk-space-4) auto 0; }
.tk-page__lead a { color: var(--tk-accent-text); text-decoration: underline; text-underline-offset: 2px; }
.tk-page__meta { color: var(--tk-text-muted); font-size: .75rem; font-style: italic;
  margin: var(--tk-space-3) 0 0; }

/* Panel with a titled head — the .card/.card-header shape these pages were built from.
   Same anatomy as .tk-reg__panelhead, un-scoped so any informational page can use it. */
.tk-page__panel { padding: 0; overflow: hidden; }
.tk-page__panelhead { display: flex; align-items: center; gap: var(--tk-space-3);
  padding: var(--tk-space-4) var(--tk-space-6); border-bottom: 1px solid var(--tk-border);
  background: color-mix(in srgb, var(--tk-page) 55%, var(--tk-surface)); }
/* Sized here, never through tk_icon()'s $attrs: that helper writes width="20" height="20"
   before it appends $attrs, so a passed width lands as a duplicate attribute and loses. */
.tk-page__panelic { display: inline-flex; flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-page__panelic svg { width: 20px; height: 20px; }
.tk-page__paneltitle { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; font-size: .85rem; margin: 0; }
.tk-page__panelbody { padding: var(--tk-space-6); }
.tk-page__panelbody > :first-child { margin-top: 0; }

/* Feature grid — /about-us's three mission pillars and /install-app's four benefits.
   auto-fit rather than a fixed count so the same class carries both, and a min() floor so
   the 160px track cannot out-measure a 288px column at 320px. */
.tk-page__features { display: grid; gap: var(--tk-space-6);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr)); }
.tk-page__feature { text-align: center; }
.tk-page__featureic { display: grid; place-items: center; width: 44px; height: 44px;
  margin: 0 auto var(--tk-space-3); border-radius: var(--tk-radius);
  background: color-mix(in srgb, var(--tk-burgundy) 12%, transparent); color: var(--tk-accent-text); }
.tk-page__featureic svg { width: 22px; height: 22px; }
.tk-page__featuretitle { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; font-size: .75rem; margin: 0 0 .35rem; }
.tk-page__featuretext { color: var(--tk-text-muted); font-size: .85rem; line-height: 1.6; margin: 0; }

/* Two-column split — /contact's details-beside-form, /install-app's two platform panels. */
.tk-page__cols { display: grid; gap: var(--tk-space-6);
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); align-items: start; }
.tk-page__cols--even { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/* The same split the other way round — a wide narrative column with a narrow rail beside
   it, which is what the partner pages are. Declared here beside its siblings rather than
   in the partner layer, because the stacking rule below has to see it: a --rev defined
   later in the file would out-order this media query and keep 7fr/5fr on a phone. */
.tk-page__cols--rev { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
@media (max-width: 860px) {
  .tk-page__cols, .tk-page__cols--even, .tk-page__cols--rev { grid-template-columns: minmax(0, 1fr); }
}

/* Icon-led list — the three questions on /about-us, the contact channels on /contact.
   list-style:none because the mark is the bullet. */
.tk-page__iconlist { list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: var(--tk-space-4); }
.tk-page__iconlist > li { display: flex; align-items: flex-start; gap: .65rem; }
.tk-page__iconlist svg { flex: 0 0 auto; width: 18px; height: 18px; margin-top: .2rem;
  color: var(--tk-accent-text); }
.tk-page__itemlabel { display: block; font-family: var(--tk-font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; font-size: .7rem; color: var(--tk-text-muted); }
.tk-page__itemvalue { color: var(--tk-text); overflow-wrap: anywhere; }
.tk-page__itemvalue:hover { color: var(--tk-accent-text); }

/* Numbered steps — /install-app's per-platform instructions. A real <ol> with CSS
   counters: the order is in the markup, so a screen reader announces it without the four
   hand-written <span class="badge">1</span> the Bootstrap version carried. */
.tk-page__steps { list-style: none; counter-reset: tk-step; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: var(--tk-space-4); }
/* The disc is absolutely positioned, NOT a flex item, and the reason is measured. As a
   flex row the <li> turned every inline run into its own flex item, so `gap` fell between
   the words as well as beside the disc: /install-app's "Open <strong>Safari</strong> and
   navigate…" rendered with a 12px hole either side of "Safari" (measured: prev text run
   ends at x=121.73, <strong> starts at 133.73). Eight steps across two lists there, and
   any step carrying a link or a <strong> anywhere else. Out of flow, the copy is ordinary
   inline text again and wraps under itself against the padding. */
.tk-page__steps > li { counter-increment: tk-step; position: relative;
  padding-left: calc(24px + .75rem);
  color: var(--tk-text-muted); font-size: .9rem; line-height: 1.6; }
.tk-page__steps > li::before { content: counter(tk-step); position: absolute; left: 0; top: 0;
  display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%;
  background: color-mix(in srgb, var(--tk-burgundy) 14%, transparent); color: var(--tk-accent-text);
  font-family: var(--tk-font-display); font-weight: 700; font-size: .7rem; }
.tk-page__steps strong { color: var(--tk-text); }
/* base.css gives every <a> inherited colour and no underline, so a link inside a step was
   muted body text indistinguishable from the sentence around it (WCAG 1.4.1). Same defect
   and same fix as .tk-callout a:not(.tk-btn); the first one is the Warranty Checker link
   in Leather Craft Manila's redemption steps. */
.tk-page__steps a:not(.tk-btn) { color: var(--tk-accent-text); text-decoration: underline;
  text-underline-offset: 2px; }
.tk-page__steps svg { width: 15px; height: 15px; vertical-align: -2px; color: var(--tk-accent-text); }

/* Closing block — portrait, pull quote, one call to action. */
.tk-page__foot { text-align: center; }
.tk-page__portrait { width: 120px; height: 120px; border-radius: 50%; object-fit: cover;
  margin: 0 auto var(--tk-space-6); border: 1px solid var(--tk-border); padding: 4px; }
.tk-page__quote { font-size: 1.1rem; line-height: 1.7; font-style: italic; color: var(--tk-text);
  max-width: 46ch; margin: 0 auto; text-align: center; }
.tk-page__quoteby { color: var(--tk-text-muted); font-size: .85rem; margin: var(--tk-space-3) 0 var(--tk-space-6); }

/* The install QR, for a desktop visitor scanning with a phone. This is the old
   `d-none d-lg-block`, which has no rule at all on a tk page — the block shipped visible
   on every phone, where a QR pointing at the page you are already on is noise. A media
   query, not [hidden]: the visibility is a function of viewport width. */
.tk-page__qr { display: none; text-align: center; }
@media (min-width: 992px) { .tk-page__qr { display: block; } }
.tk-page__qrlabel { font-size: .7rem; text-transform: uppercase; letter-spacing: var(--tk-tracking-label);
  color: var(--tk-text-muted); margin: 0 0 var(--tk-space-3); }
/* White plate, in both themes: a QR needs light quiet-zone and dark modules to scan. */
.tk-page__qrframe { display: inline-block; background: #fff; padding: .5rem; border-radius: var(--tk-radius); }

/* ---- /policies ---- */

/* The nav is `position: sticky`, and nothing in the system had ever set a scroll margin —
   so every in-page anchor on every tk page lands its target UNDER the nav bar. It has not
   bitten before because no migrated page had in-page anchors; /policies is built on four of
   them, reached from seven links across five files. Measured against .tk-nav: .7rem padding
   top and bottom around a 34px brand row, ~56px, plus breathing room. */
.tk-policy { scroll-margin-top: 5rem; padding: 0; overflow: hidden; }

/* summary is `display: list-item` by default and flex removes the marker in Blink/WebKit;
   list-style + ::-webkit-details-marker cover Gecko and older WebKit, so the disclosure
   state is drawn by .tk-policy__marker alone and can be positioned. */
.tk-policy__summary { display: flex; align-items: center; gap: var(--tk-space-3);
  padding: var(--tk-space-4) var(--tk-space-6); cursor: pointer; list-style: none;
  background: color-mix(in srgb, var(--tk-page) 55%, var(--tk-surface)); }
.tk-policy__summary::-webkit-details-marker { display: none; }
.tk-policy__summary:hover { background: color-mix(in srgb, var(--tk-burgundy) 5%, var(--tk-surface)); }
/* The UA outlines the whole summary on focus; keep that, it is the only focus target here. */
.tk-policy__summary:focus-visible { outline: 2px solid var(--tk-accent-text); outline-offset: -2px; }
.tk-policy__ic { display: inline-flex; flex: 0 0 auto; color: var(--tk-accent-text); }
.tk-policy__ic svg { width: 20px; height: 20px; }
.tk-policy__title { font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; font-size: .85rem; margin: 0; flex: 1 1 auto; }
.tk-policy__marker { display: inline-flex; flex: 0 0 auto; color: var(--tk-text-muted);
  transition: transform .2s var(--tk-ease); }
.tk-policy__marker svg { width: 18px; height: 18px; }
.tk-policy[open] > .tk-policy__summary { border-bottom: 1px solid var(--tk-border); }
.tk-policy[open] > .tk-policy__summary .tk-policy__marker { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .tk-policy__marker { transition: none; } }
.tk-policy__body { padding: var(--tk-space-6); }
.tk-policy__body > :first-child { margin-top: 0; }
.tk-policy__body > :last-child { margin-bottom: 0; }
.tk-policy__meta { color: var(--tk-text-muted); font-size: .75rem; font-style: italic;
  margin-top: var(--tk-space-8); }

/* Table of contents. Rows rather than a bulleted list — it is the .list-group these were
   built from, and the full-width hit area matters more on a phone than the marker does. */
.tk-policy__toc { list-style: none; margin: 0; padding: 0; }
.tk-policy__toc > li + li { border-top: 1px solid var(--tk-border); }
.tk-policy__toc a { display: block; padding: var(--tk-space-3) var(--tk-space-6);
  color: var(--tk-text); font-size: .9rem; }
.tk-policy__toc a:hover { background: color-mix(in srgb, var(--tk-burgundy) 6%, transparent);
  color: var(--tk-accent-text); }
.tk-policy__toc a:focus-visible { outline: 2px solid var(--tk-accent-text); outline-offset: -2px; }
/* Scroll-spy. A left rule and weight rather than a fill, so the row keeps body-text
   contrast — the Bootstrap version tinted the whole row #495057 and the label went to
   3.9:1 against it. */
.tk-policy__toc a.tk-is-current { box-shadow: inset 3px 0 0 var(--tk-burgundy);
  background: color-mix(in srgb, var(--tk-burgundy) 7%, transparent);
  color: var(--tk-accent-text); font-weight: 600; }

/* ---- /faq ---- */

/* The eight questions ride the /policies <details> layer — same summary, same marker, same
   body — and change only what a question needs that a section label does not.
   .tk-policy__title is UPPERCASE at .85rem with .08em tracking, which is right for
   "Privacy Policy" and unreadable across "How are your prices lower than the suggested
   retail price (SRP)?". Sentence case, a step up in size, and a line-height that holds up
   once the question wraps to three lines on a 320px screen. */
.tk-faq__q { text-transform: none; letter-spacing: 0; font-size: 1rem; line-height: 1.45; }

/* A wrapped question is a block, and centring a 20px icon against three lines of text
   floats it into the middle of the sentence. Pin both affordances to the first line: the
   offsets are (line-box - icon) / 2 at the size above, 2px for the 20px icon and 3px for
   the 18px marker. */
.tk-faq > .tk-policy__summary { align-items: flex-start; }
.tk-faq .tk-policy__ic { margin-top: 2px; }
.tk-faq .tk-policy__marker { margin-top: 3px; }

/* .tk-page > * + * puts 2rem between children, which is the right gap between /policies'
   four long-form documents and far too much between eight one-line questions — it turns a
   scannable list into eight islands. Tighter, so the questions read as one column. */
.tk-faq + .tk-faq { margin-top: var(--tk-space-3); }

/* ---- /offline ---- */

/* Centred, and tall enough that the message is not pinned to the nav on a desktop viewport.
   min-height rather than the old 50vh row so it cannot push the footer off a short phone. */
.tk-offline { text-align: center; display: flex; flex-direction: column; align-items: center;
  justify-content: center; min-height: 60vh; }
.tk-offline > * + * { margin-top: 0; }
.tk-offline__mark { display: grid; place-items: center; width: 88px; height: 88px;
  border-radius: 50%; margin-bottom: var(--tk-space-6);
  background: color-mix(in srgb, var(--tk-burgundy) 10%, transparent);
  color: var(--tk-accent-text); }
.tk-offline__mark svg { width: 44px; height: 44px; }
.tk-offline__actions { margin-top: var(--tk-space-8); }
/* Reserved so the live region does not shift the button when it fills in. */
.tk-offline__status { min-height: 1.2em; margin-top: var(--tk-space-3); font-size: .8rem;
  color: var(--tk-text-muted); }

/* ---- Signed-out gate / empty state ---- */

/* /report-stolen's "Login Required" card. Un-scoped because the same block already exists
   twice under a page prefix — .tk-dash__gate* and .tk-coll__gate*, which are line-for-line
   identical to each other — and this would have been the third.
   /bookmarks' "no bookmarked watches" state is the second user: same mark, same title,
   same explanatory line, same pair of actions. Reused rather than made into a fourth copy
   of the same 64px disc, which is exactly what this block exists to prevent. */
.tk-page__gate { max-width: 34rem; margin-inline: auto; text-align: center;
  padding: var(--tk-space-12) var(--tk-space-8); }
.tk-page__gateic { display: grid; place-items: center; width: 64px; height: 64px;
  margin: 0 auto var(--tk-space-6); border-radius: 50%;
  background: color-mix(in srgb, var(--tk-burgundy) 10%, transparent); color: var(--tk-accent-text); }
.tk-page__gateic svg { width: 30px; height: 30px; }
.tk-page__gatetitle { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  letter-spacing: .03em; font-size: 1.5rem; margin: 0 0 var(--tk-space-3); }
.tk-page__gatetext { color: var(--tk-text-muted); font-size: .95rem; line-height: 1.7;
  max-width: 44ch; margin: 0 auto var(--tk-space-8); }
.tk-page__gateactions { justify-content: center; }

/* ---- Public form pages (/report-stolen, /subscribe; /sell-your-watch is the same shapes) ---- */

/* Grouped sections. A real <fieldset>/<legend> rather than a heading over a div: the group
   name is what a screen reader announces alongside each control inside it, which is the
   whole point of "Watch Details" / "Verification Documents" / "Additional Details".
   min-width:0 is load-bearing — the UA gives every fieldset `min-width: min-content`, so
   without it a fieldset refuses to shrink and the page overflows at 320px. */
.tk-fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
.tk-fieldset + .tk-fieldset { margin-top: var(--tk-space-6); }
.tk-fieldset__legend { display: flex; align-items: center; gap: .5rem; width: 100%;
  padding: 0 0 var(--tk-space-3); margin-bottom: var(--tk-space-4);
  border-bottom: 1px solid var(--tk-border); font-family: var(--tk-font-display);
  font-weight: 700; text-transform: uppercase; letter-spacing: .1em; font-size: .7rem;
  color: var(--tk-text-muted); }
.tk-fieldset__legend svg { flex: 0 0 auto; width: 16px; height: 16px; color: var(--tk-accent-text); }
/* A fieldset cannot itself be the flex column — <legend> is rendered out of flow against
   the (removed) border — so a section with more than one block inside stacks in a body. */
.tk-fieldset__body { display: flex; flex-direction: column; gap: var(--tk-space-4); }

/* Two-up field row. An explicit 2 rather than auto-fit: these are paired fields (serial
   beside date, ID beside police report) and a third track would break the pairing. */
.tk-form__grid { display: grid; gap: var(--tk-space-4); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 560px) { .tk-form__grid { grid-template-columns: minmax(0, 1fr); } }
.tk-form__wide { grid-column: 1 / -1; }
/* Dashed inset — the optional-looking drop area the documents section was built as. */
.tk-form__inset { border: 1px dashed var(--tk-border); border-radius: var(--tk-radius);
  background: color-mix(in srgb, var(--tk-page) 55%, var(--tk-surface)); padding: var(--tk-space-4); }
.tk-form__foot { text-align: center; font-size: .75rem; line-height: 1.6;
  color: var(--tk-text-muted); margin: 0; }
.tk-form__foot svg { width: 14px; height: 14px; vertical-align: -3px; }
.tk-form__foot a { color: var(--tk-accent-text); text-decoration: underline; text-underline-offset: 2px; }
/* .tk-form is display:flex, an author rule, so it beats the UA's [hidden]{display:none} —
   the same defect .tk-btn[hidden] and .tk-reg__alert[hidden] already close. /subscribe
   swaps its whole form out for the confirmation state, so this one is load-bearing. */
.tk-form[hidden] { display: none; }
/* Same story one level down: .tk-icon sets display:inline-block, so a tk_icon() rendered
   with hidden — or any <svg class="tk-icon" hidden> — ships VISIBLE. Every existing
   progressive-reveal icon in the repo wraps the svg in a bare <span hidden> to dodge this;
   closing it here means the wrapper is a choice rather than a requirement. */
.tk-icon[hidden] { display: none; }
/* And .tk-field, which is display:flex for its label/control stack. /admin/under-the-loupe
   shows the Investment Rating control only for dossiers — the Bootstrap version reached for
   ratingSelect.parentElement.style.display to do it, i.e. it hid whatever element happened
   to wrap the <select>. Hiding the field itself is what it meant, and without this rule the
   field would have carried `hidden` and stayed on screen. */
.tk-field[hidden] { display: none; }

/* Help text under a control, and the required marker. Both were Bootstrap utility stacks
   (.form-text.text-muted.small, .text-danger) with no equivalent in the system. */
.tk-field__note { font-size: .75rem; line-height: 1.55; color: var(--tk-text-muted); margin: 0; }
.tk-field__note svg { width: 13px; height: 13px; vertical-align: -2px; }
.tk-req { color: var(--tk-accent-text); }

/* File inputs. .tk-field skinned input and select and (since Task 1) textarea, but the
   file picker still shipped as a raw UA button on a skinned box. */
.tk-field input[type="file"] { padding: .4rem .5rem; font-size: .8rem; }
.tk-field input[type="file"]::file-selector-button { font-family: var(--tk-font-display);
  font-weight: 700; text-transform: uppercase; letter-spacing: .06em; font-size: .62rem;
  margin: 0 .6rem 0 0; padding: .45rem .7rem; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); background: var(--tk-raised); color: var(--tk-text); cursor: pointer; }
.tk-field input[type="file"]::file-selector-button:hover { border-color: var(--tk-burgundy);
  color: var(--tk-accent-text); }

/* .tk-check centres its box against a single line, which is wrong for a consent sentence
   that wraps to four. Its links also needed the .tk-callout a treatment — base.css strips
   every <a> to inherited colour and no underline, and "Terms of Service" inside a
   paragraph of body text has to be distinguishable by more than colour (WCAG 1.4.1). */
.tk-check--start { align-items: flex-start; }
.tk-check--start input { margin-top: .2rem; }
.tk-check--start label { line-height: 1.6; }
.tk-check a { color: var(--tk-accent-text); text-decoration: underline; text-underline-offset: 2px; }

/* Inline alert — .tk-reg__alert's shape, un-scoped, for the two public forms and the
   static "all reports are verified" note. display:flex again beats [hidden]. */
.tk-formalert { display: flex; align-items: flex-start; gap: .6rem; font-size: .85rem;
  line-height: 1.6; }
.tk-formalert[hidden] { display: none; }
.tk-formalert svg { flex: 0 0 auto; width: 18px; height: 18px; margin-top: .12rem; }

/* Callout severity: the success counterpart of .tk-callout--warning, which /registry has
   only ever had as the scoped .tk-reg__alert--ok. */
.tk-callout--success { border-left-color: var(--tk-success);
  background: color-mix(in srgb, var(--tk-success) 10%, transparent); }

/* Multi-select chip grid. The checkbox is CLIPPED, not display:none — display:none takes a
   control out of the tab order and the accessibility tree, and a <label> is not focusable,
   so the Bootstrap version (`d-none`, which on a tk page styled nothing at all and shipped
   the raw boxes visible) could not be operated from a keyboard either way. Clipped, the
   real control keeps its place in the tab order and the accessibility tree — measured:
   Tab lands on it, it matches :focus-visible — and the tick is drawn on the label. */
.tk-choicegrid { display: grid; gap: .5rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 150px), 1fr)); }
.tk-choice { position: relative; display: flex; align-items: center; gap: .55rem;
  padding: .65rem .75rem; border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-page); color: var(--tk-text-muted); font-size: .8rem;
  cursor: pointer; user-select: none;
  transition: border-color .2s var(--tk-ease), color .2s var(--tk-ease), background-color .2s var(--tk-ease); }
.tk-choice:hover { border-color: color-mix(in srgb, var(--tk-burgundy) 45%, transparent);
  color: var(--tk-text); }
.tk-choice__box { flex: 0 0 auto; display: grid; place-items: center; width: 16px; height: 16px;
  border: 1px solid color-mix(in srgb, var(--tk-text) 40%, transparent); border-radius: 4px;
  color: transparent; }
.tk-choice__box svg { width: 11px; height: 11px; }
/* The tick is a sibling selector so it works without :has(); only the label's own tint
   below needs it, and the checked state still reads if that ever fails to match. */
.tk-choice input:checked ~ .tk-choice__box { background: var(--tk-burgundy);
  border-color: var(--tk-burgundy); color: #fbeef0; }
.tk-choice:has(input:checked) { border-color: var(--tk-burgundy); color: var(--tk-accent-text);
  background: color-mix(in srgb, var(--tk-burgundy) 6%, transparent); }
/* The clipped input can still take focus; the ring has to be drawn on what is visible. */
.tk-choice:has(input:focus-visible) { outline: 2px solid var(--tk-burgundy); outline-offset: 2px;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--tk-burgundy) 30%, transparent); }
@media (prefers-reduced-motion: reduce) { .tk-choice { transition: none; } }

/* Post-submit confirmation — the panel swaps the form out for this. */
.tk-done { text-align: center; padding-block: var(--tk-space-8) var(--tk-space-4); }
.tk-done__mark { display: grid; place-items: center; width: 64px; height: 64px; border-radius: 50%;
  margin: 0 auto var(--tk-space-6); color: var(--tk-success);
  background: color-mix(in srgb, var(--tk-success) 14%, transparent); }
.tk-done__mark svg { width: 30px; height: 30px; }
.tk-done__title { font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  letter-spacing: .03em; font-size: 1.4rem; margin: 0 0 var(--tk-space-3); }
.tk-done__text { color: var(--tk-text-muted); font-size: .9rem; line-height: 1.7;
  max-width: 42ch; margin: 0 auto; }

/* Reassurance row under a form ("No Spam", "Unsubscribe Anytime", …). */
.tk-trustrow { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap;
  justify-content: center; gap: var(--tk-space-3) var(--tk-space-6); }
.tk-trustrow li { display: flex; align-items: center; gap: .4rem; font-size: .75rem;
  color: var(--tk-text-muted); }
.tk-trustrow svg { flex: 0 0 auto; width: 15px; height: 15px; color: var(--tk-accent-text); }
.tk-done__actions { justify-content: center; margin-top: var(--tk-space-6); }

/* ---- Multi-step public form (/sell-your-watch) ---- */

/* The vertical stack inside a panel body. This is .tk-form's box without .tk-form's
   element coupling: .tk-form is on the <form> itself, where it also buys the
   .tk-form[hidden] fix, and a second .tk-form nested inside the first would read as a
   nested form. Same three declarations, deliberately. */
.tk-form__stack { display: flex; flex-direction: column; gap: var(--tk-space-4); }

/* A wizard's Back/Next pair. Both buttons share the row evenly (the Bootstrap version was
   .d-flex.gap-2 with .flex-fill on each); step 1 has no Back, so its single Next fills the
   row on its own, which is what .w-100 used to do there. flex-basis 0 rather than auto so
   "Next Step" and "Back" get equal halves regardless of label length. */
.tk-form__nav { display: flex; gap: var(--tk-space-3); }
.tk-form__nav > .tk-btn { flex: 1 1 0; justify-content: center; min-width: 0; }

/* The stepper. .tk-doc__steps is the same component page-scoped to the document
   generator, but its connector is a single bar behind all four discs — it cannot show
   partial progress. This one draws a segment per gap so a completed leg fills, which is
   what the four .step-line elements did here. Left un-scoped so the two can be folded
   together later without a third copy appearing first.

   is-active / is-done rather than the bare `active` / `completed` the deleted <style>
   used: those are two of the most collidable class names there are, and this page has no
   Bootstrap left to inherit them from. */
.tk-stepper { display: flex; align-items: flex-start; list-style: none; margin: 0;
  padding: var(--tk-space-4) var(--tk-space-2); border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius-lg); background: var(--tk-surface); }
/* display:flex is an author rule and beats the UA's [hidden]{display:none} — the same
   defect .tk-btn[hidden], .tk-form[hidden] and .tk-icon[hidden] already close. The
   confirmation state hides the stepper, so this one is load-bearing. */
.tk-stepper[hidden] { display: none; }
.tk-stepper__item { position: relative; flex: 1 1 0; min-width: 0; display: flex;
  flex-direction: column; align-items: center; text-align: center; }
/* The connector, from 22px right of this disc's centre to 22px left of the next one's
   (16px radius + a 6px breath). It is drawn on the item so a finished leg can fill with
   the item's own state, and it is never the last item's. */
.tk-stepper__item:not(:last-child)::after { content: ''; position: absolute; top: 15px;
  left: calc(50% + 22px); right: calc(22px - 50%); height: 2px; background: var(--tk-border);
  transition: background-color .3s var(--tk-ease); }
.tk-stepper__item.is-done::after { background: var(--tk-success); }
.tk-stepper__dot { display: grid; place-items: center; width: 32px; height: 32px;
  border-radius: 50%; background: var(--tk-page); border: 1px solid var(--tk-border);
  color: var(--tk-text-muted); font-family: var(--tk-font-display); font-weight: 700;
  font-size: .8rem;
  transition: background-color .3s var(--tk-ease), border-color .3s var(--tk-ease),
    color .3s var(--tk-ease); }
/* The tick swaps in for the number entirely in CSS, so the script never writes text into
   the disc — which is also why the disc can stay aria-hidden and the label carry the name. */
.tk-stepper__tick { display: none; width: 16px; height: 16px; }
.tk-stepper__item.is-done .tk-stepper__num { display: none; }
.tk-stepper__item.is-done .tk-stepper__tick { display: block; }
.tk-stepper__label { display: block; margin-top: .4rem; font-family: var(--tk-font-display);
  font-weight: 700; text-transform: uppercase; letter-spacing: .1em; font-size: .625rem;
  color: var(--tk-text-muted); transition: color .3s var(--tk-ease); }
.tk-stepper__item.is-active .tk-stepper__dot { background: var(--tk-burgundy);
  border-color: var(--tk-burgundy); color: #fbeef0; }
.tk-stepper__item.is-active .tk-stepper__label { color: var(--tk-text); }
.tk-stepper__item.is-done .tk-stepper__dot { background: var(--tk-success);
  border-color: var(--tk-success); color: #fff; }
/* The darkened mix, not the raw --tk-success: at this size the token measures 4.86:1 on
   --tk-page, over the line but not by much, and this label is the only thing saying a step
   is finished. Same call as .tk-doc__step.completed. */
.tk-stepper__item.is-done .tk-stepper__label {
  color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text)); }
@media (prefers-reduced-motion: reduce) {
  .tk-stepper__item::after, .tk-stepper__dot, .tk-stepper__label { transition: none; }
}
/* 320px: four labels at .625rem still fit an 80px track, so they stay — dropping them would
   leave four unlabelled discs, and the discs are aria-hidden. The connector tightens. */
@media (max-width: 400px) {
  .tk-stepper { padding-inline: 0; }
  .tk-stepper__item:not(:last-child)::after { left: calc(50% + 19px); right: calc(19px - 50%); }
  .tk-stepper__label { letter-spacing: .06em; }
}

/* A step's contents: the card, its alert, and the nav row. Set explicitly rather than
   inherited, so [hidden] needs restating — measured both ways. */
.tk-steppanel { display: flex; flex-direction: column; gap: var(--tk-space-6); }
.tk-steppanel[hidden] { display: none; }

/* Photo uploads. What this replaces was six file inputs each absolutely positioned at
   opacity 0 over a fake dropzone — with `position-absolute top-0 start-0 w-100 h-100
   opacity-0`, four Bootstrap utilities that style nothing here, so all six would have
   shipped as opaque, statically-positioned pickers stacked under their own captions. They
   are ordinary .tk-field controls now, on the ::file-selector-button skin above. */
.tk-photogrid { display: grid; gap: var(--tk-space-4);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 200px), 1fr)); }
.tk-photo { min-width: 0; padding: var(--tk-space-3); border: 1px dashed var(--tk-border);
  border-radius: var(--tk-radius); background: color-mix(in srgb, var(--tk-page) 55%, var(--tk-surface));
  transition: border-color .2s var(--tk-ease), background-color .2s var(--tk-ease); }
.tk-photo:hover { border-color: color-mix(in srgb, var(--tk-burgundy) 45%, transparent); }
/* A chosen file: solid border, success ink on the hint, which now holds the file name. */
.tk-photo.is-filled { border-style: solid; border-color: var(--tk-success);
  background: color-mix(in srgb, var(--tk-success) 6%, transparent); }
.tk-photo.is-filled .tk-photo__hint {
  color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text)); font-weight: 600; }
/* The file name can be arbitrarily long and is written in by the visitor's own picker;
   without this one cell would push the grid past the viewport. */
.tk-photo__hint { overflow-wrap: anywhere; }
@media (prefers-reduced-motion: reduce) { .tk-photo { transition: none; } }

/* A value that is fixed and cannot be edited (Brand: Tudor). It was a readonly, nameless
   text input dressed as a live field; it reads as data now, on .tk-field's label. */
.tk-sell__fixed { margin: 0; padding: .6rem .7rem; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); background: var(--tk-raised); color: var(--tk-accent-text);
  font-weight: 600; font-size: .85rem; }

/* ---- 404 ---- */

/* The not-found page is built on .tk-page (the centred informational column) plus three
   declarations prose has no vocabulary for: fill the viewport, centre in it, and draw the
   numeral. Deliberately NOT .tk-offline, which is the same anatomy but leads with an 88px
   icon disc — the numeral IS the mark here, and it is the one thing a reader recognises
   before reading a word. .tk-page's own `> * + *` rhythm is dropped: at space-8 the
   numeral floated away from the heading it belongs to. */
.tk-nf { min-height: 60vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center; }
.tk-nf > * + * { margin-top: 0; }
/* aria-hidden in the markup: "404" is already the first word of the <title>, and read
   aloud on its own it says nothing the heading below does not say better. --tk-accent-text
   rather than a decorative tint, because a 7.5rem numeral is read whatever we call it. */
.tk-nf__code { font-family: var(--tk-font-display); font-weight: 800; line-height: .9;
  font-size: clamp(4rem, 20vw, 7.5rem); letter-spacing: .04em;
  color: var(--tk-accent-text); margin: 0 0 var(--tk-space-4); }
.tk-nf__actions { justify-content: center; margin-top: var(--tk-space-8); }

/* ---- /bookmarks ---- */

/* On the storefront the bookmark control is decluttered away until the card is hovered
   (.tk-card__controls ships at opacity 0). On THIS page removing a bookmark is the only
   thing there is to do, so the control that does it cannot be hidden behind a hover — and
   a hover is not available to a keyboard or a stylus at all. :focus-within already covers
   the keyboard; this covers everyone else. */
#bookmarks-grid .tk-card__controls { opacity: 1; transform: none; }

/* The grid itself carries `hidden` while the list is empty or still loading, and
   .tk-card-grid sets display:grid — an author rule, so it beats the UA's
   [hidden]{display:none} and the grid would ship visible. Same defect and same fix as
   .tk-btn[hidden], .tk-form[hidden], .tk-icon[hidden], .tk-stepper[hidden]. Declared on
   the shared class rather than on #bookmarks-grid: every other page that hides a card grid
   would have hit this too. */
.tk-card-grid[hidden] { display: none; }

/* ---- /reviews ---- */

.tk-revsec { padding-bottom: var(--tk-space-12); border-bottom: 1px solid var(--tk-border); }
/* .tk-section-head is a left-aligned baseline row; these two headings are centred over
   their galleries, and the Carousell one carries the layout toggle beside it. */
.tk-revsec__head { justify-content: center; align-items: center; flex-wrap: wrap;
  gap: var(--tk-space-4); margin-bottom: var(--tk-space-6); }

/* Layout toggle. One <button> per view with aria-pressed, and the pressed look is drawn
   from that attribute — so the script only ever flips state, it never juggles classes,
   and the visual and the announced state cannot drift apart. */
.tk-viewtoggle { display: inline-flex; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); overflow: hidden; }
.tk-viewtoggle .tk-btn { border-radius: 0; border-color: transparent; box-shadow: none;
  background: transparent; color: var(--tk-text-muted); }
.tk-viewtoggle .tk-btn:hover { transform: none; color: var(--tk-accent-text); }
.tk-viewtoggle .tk-btn[aria-pressed="true"] { background: var(--tk-burgundy); color: #fbeef0; }
.tk-viewtoggle .tk-btn + .tk-btn { border-left: 1px solid var(--tk-border); }

/* Grid view: four tracks, then three, then two — the col-lg-3 / col-md-4 / col-6 these
   thumbnails were built as. .tk-card-grid on its own drops straight from four to two at
   900px, which leaves a 9:16 screenshot 600px tall on a tablet. Measured, not assumed: an
   auto-fill floor of 130px resolved to a SINGLE 272px column at 320px, because auto-fill
   counts the gap — floor((272 + 16) / (130 + 16)) is 1, not 2. */
.tk-revgrid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 900px) { .tk-revgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 560px) { .tk-revgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Carousel. The track is focusable and labelled in the markup: a horizontally scrollable
   region has to be operable from a keyboard (WCAG 2.1.1) and Blink will not make one
   focusable on its own. */
.tk-revcar { position: relative; }
.tk-revcar__track { display: flex; gap: var(--tk-space-4); overflow-x: auto;
  padding-block: var(--tk-space-2); scroll-snap-type: x proximity;
  -ms-overflow-style: none; scrollbar-width: none; }
.tk-revcar__track::-webkit-scrollbar { display: none; }
/* A fixed basis, not the old `flex: 0 0 auto`. The images are lazy and carry no width or
   height, so with auto the track measured narrower than its own content until they
   loaded — and the nav buttons, which are revealed from that measurement, stayed hidden. */
.tk-revcar__item { flex: 0 0 clamp(150px, 30vw, 210px); scroll-snap-align: start; }

/* The nav buttons reuse .tk-gallery__nav's disc; only the ground and the placement differ.
   rgba(0,0,0,.5) is right over a watch photo, but these sit on the ivory page between two
   screenshots, so they take the page's own surface. */
.tk-revcar__nav { background: var(--tk-surface); color: var(--tk-text);
  border-color: var(--tk-border); box-shadow: var(--tk-shadow); }
.tk-revcar__nav:hover { color: var(--tk-accent-text); border-color: var(--tk-burgundy); }
.tk-revcar__nav--prev { left: 0; }
.tk-revcar__nav--next { right: 0; }
/* .tk-gallery__nav sets display:grid, which outranks the UA's [hidden] — without this the
   two buttons ship visible over a list that does not scroll. */
.tk-revcar__nav[hidden] { display: none; }

/* One screenshot: a real <button>, because it opens the lightbox. cursor:zoom-in says so
   without adding a second affordance to the tile. */
.tk-revshot { display: block; width: 100%; padding: 0; overflow: hidden;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-surface); cursor: zoom-in;
  transition: border-color .2s var(--tk-ease), transform .2s var(--tk-ease); }
.tk-revshot:hover { border-color: var(--tk-burgundy); transform: translateY(-2px); }
.tk-revshot img { display: block; width: 100%; aspect-ratio: 9 / 16; object-fit: cover; }
.tk-revshot--strip { width: auto; }
@media (prefers-reduced-motion: reduce) { .tk-revshot { transition: none; } }

/* The lightbox. A Carousell screenshot is a portrait phone capture, so at
   .tk-modal--media's 880px cap it renders ~1470px tall and the reader has to scroll the
   dialog to reach the end of the review. Cap by height and let the width follow. */
.tk-revmodal { width: auto; max-width: min(92vw, 560px); }
.tk-revmodal .tk-modal__img { width: auto; max-height: 72vh; margin-inline: auto; }

/* Closing call to action. A solid burgundy plate with its own ink, so it reads the same on
   either theme; the ivory button is scoped here because .tk-btn--solid would put burgundy
   on burgundy and .tk-btn--ghost draws its border and label from --tk-text, which is near
   black on the light page. */
.tk-ctaband { text-align: center; background: var(--tk-burgundy); color: #fbeef0;
  border-radius: var(--tk-radius-lg); padding: var(--tk-space-12) var(--tk-space-6); }
.tk-ctaband__title { font-family: var(--tk-font-display); font-weight: 800;
  text-transform: uppercase; letter-spacing: .03em; color: inherit;
  font-size: clamp(1.4rem, 3.5vw, 1.9rem); margin: 0 0 var(--tk-space-4); }
/* A solid mix rather than an alpha, so the measured ratio is the rendered one: ~7.8:1. */
.tk-ctaband__text { color: color-mix(in srgb, #fbeef0 88%, var(--tk-burgundy));
  max-width: 52ch; line-height: 1.7; margin: 0 auto var(--tk-space-8); }
.tk-ctaband .tk-btn { background: #fbeef0; color: var(--tk-burgundy);
  border-color: #fbeef0; box-shadow: none; }
.tk-ctaband .tk-btn:hover { background: #fff; }

/* ---- Ecosystem partners (/partners and the two partner pages) ---- */

/* The two detail templates were near-twins — 133 and 155 lines, 38 and 42 Bootstrap
   classes, the same hero / member-benefit / contact-rail anatomy with different copy —
   so they share ONE shape here rather than carrying two parallel sets of rules. Almost
   all of it is reuse: .tk-page--wide is the column, .tk-page__head + .tk-page__portrait
   the hero, .tk-page__cols--rev the split, .tk-page__panel the panels, .tk-page__steps
   the redemption steps, .tk-page__iconlist + .tk-page__itemlabel/__itemvalue the contact
   rail, .tk-share-row the social marks, .tk-callout the terms notes. What follows is
   only what none of those carry.

   The old hero does not survive onto an ivory page and should not: it was a 100px band
   of `linear-gradient(rgba(0,0,0,.7), rgba(0,0,0,.9))` over the partner's photo, with
   THE SAME 600x600 photo again as a circular logo on top of it — one image downloaded
   twice, at 100% and at 120px, to build a black slab. Both files are square logos, so
   the photo is simply the portrait now, once, at the size it was drawn for. */

/* Directory row. Wrap-and-centre, NOT a grid, and the reason is measured. ONE partner is
   live today (the second entry and its route are both commented out — see the template),
   so a `1fr` grid stretched that single card across the whole column, and pinning the
   track to a fixed max to stop it broke the auto-fill count instead: with a definite max
   the repetition uses the MAX, so `minmax(min(100%,18rem), 20rem)` resolved to ONE 320px
   track in a 656px column — two partners would have stacked vertically on a desktop.
   Flex wrap has neither problem: the basis is the card's natural width, a lone card is
   centred under the centred header rather than hard left with 380px of void beside it,
   and a card narrower than its basis simply shrinks. Measured, one card and two: 304px
   centred in a 656px column at 901px viewport and two of them side by side on one row;
   304px centred and stacked at 375; 272px (shrunk to the column) and stacked at 320. No
   page-level overflow in any of the six. */
.tk-partner-grid { list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; justify-content: center; gap: var(--tk-space-6); }

/* One directory entry, on .tk-card: the storefront card already supplies the surface,
   border, radius, overflow clip, hover lift and position:relative. min-width:0 because a
   flex item's default `min-width: auto` is its min-content — one long word in the summary
   would otherwise push the card past a 320px viewport. */
.tk-partner-card { flex: 0 1 19rem; min-width: 0; display: flex; flex-direction: column; }
.tk-partner-card__media { position: relative; }
/* 16/10 rather than the file's own 1:1 — these are 600x600 logo plates and a square
   banner leaves the card mostly image. object-fit keeps the crop centred. */
.tk-partner-card__photo { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
/* A flag laid over a photograph cannot use .tk-badge--srp's 22% wash: the ink is
   --tk-accent-text and what shows through underneath is whatever the picture happens to
   be. Solid burgundy with the ivory ink .tk-btn--solid uses — measured 9.01:1, and
   independent of the image. Deliberately not .tk-badge--feat, whose gold is reserved for
   the Featured flag, and not --sold, which means sold. */
.tk-partner-card__flag { position: absolute; top: var(--tk-space-3); right: var(--tk-space-3);
  z-index: 2; background: var(--tk-burgundy); color: #fbeef0; }
.tk-partner-card__body { flex: 1 1 auto; display: flex; flex-direction: column;
  gap: var(--tk-space-3); padding: var(--tk-space-6); }
.tk-partner-card__name { font-size: 1.05rem; margin: 0; }
.tk-partner-card__desc { color: var(--tk-text-muted); font-size: .875rem; line-height: 1.6; margin: 0; }
.tk-partner-card__cta { margin-top: auto; }
/* .tk-card lifts 4px on hover and has never been gated — the same gap .tk-tool-card had
   before it was reused. Gated here for the card this page introduces rather than on
   .tk-card itself, which is the storefront's watch card on nine other templates. */
@media (prefers-reduced-motion: reduce) {
  .tk-partner-card { transition: none; }
  .tk-partner-card:hover { transform: none; }
}

/* The narrative column of a detail page. .tk-page's own `> * + *` rhythm reaches direct
   children only, and these sections are one level down inside the split. */
.tk-partner__main { display: flex; flex-direction: column; gap: var(--tk-space-8); }

/* The perk line — the one thing the directory card and the detail page's benefit panel
   genuinely share, so it is namespaced to the partner rather than to either. */
.tk-partner__perkline { display: flex; align-items: center; gap: .4rem; margin: 0;
  font-size: .8rem; font-weight: 600; color: var(--tk-accent-text); }
/* Sized here, never through tk_icon()'s $attrs: that helper writes width="20" height="20"
   before it appends $attrs, so a passed width lands as a duplicate and the first wins. */
.tk-partner__perkline svg { flex: 0 0 auto; width: 16px; height: 16px; }

/* Member benefit. .tk-page__panel is the frame and its head; this is the headline and the
   body copy inside it. */
.tk-partner__perk { font-family: var(--tk-font-display); font-weight: 700; font-size: 1.15rem;
  margin: 0 0 var(--tk-space-3); }
.tk-partner__perktext { color: var(--tk-text-muted); line-height: 1.7; margin: 0 0 var(--tk-space-4); }
.tk-partner__perktext strong { color: var(--tk-text); }

/* The terms note: a .tk-callout holding its icon inline with the copy, the same row
   .tk-reg__alert and .tk-fit__warn are. */
.tk-partner__terms { display: flex; align-items: flex-start; gap: .6rem; }
.tk-partner__terms svg { flex: 0 0 auto; width: 18px; height: 18px;
  margin-top: .15rem; color: var(--tk-accent-text); }
.tk-partner__terms p { color: var(--tk-text-muted); font-size: .85rem; line-height: 1.6; margin: 0; }

/* A value in the contact rail that is NOT a link — service hours, a street address.
   .tk-page__itemvalue carries a hover colour, which on a plain paragraph is a false
   affordance, so the two are deliberately different classes. */
.tk-partner__value { display: block; color: var(--tk-text); margin: 0; overflow-wrap: anywhere; }

/* A labelled group in the contact rail. The labels themselves are .tk-page__itemlabel;
   this is only the stack and the hairline that replaces the old <hr class="border-secondary">. */
.tk-partner__group > * + * { margin-top: var(--tk-space-3); }
.tk-partner__group + .tk-partner__group { border-top: 1px solid var(--tk-border);
  margin-top: var(--tk-space-6); padding-top: var(--tk-space-6); }

/* The embedded map. .tk-map is the service locator's full-bleed 420px plate; this one
   lives in a rail and has to follow its width, so it is a ratio box instead — the
   `ratio ratio-16x9` these pages were built from. The iframe fills it. */
.tk-partner__map { width: 100%; aspect-ratio: 16 / 10; overflow: hidden;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius); background: var(--tk-page); }
.tk-partner__map iframe { display: block; width: 100%; height: 100%; border: 0; }

/* ================================================================================
   ADMIN BACK OFFICE (.tk-admin*)
   The 16 admin templates on tk-light. Almost all of it is reuse: .tk-page__panel /
   __panelhead / __panelic / __paneltitle are the .card/.card-header these pages were
   built from, .tk-page__gate* is the access-denied screen, .tk-linklist is the
   "Management Sections" rail, .tk-panel / .tk-badge / .tk-btn / .tk-callout / .tk-field
   are unchanged. What follows is only the four things a back office genuinely has that
   no public page does: a dense wide data table, a filter toolbar, status pills for a
   record's workflow state, and an empty ledger.
   ================================================================================ */

/* The page column. Wider than .tk-page (44rem) because every one of these is a table,
   not prose; --full drops the cap for the ledgers that need the whole viewport. */
.tk-admin { max-width: var(--tk-container); margin-inline: auto;
  padding-inline: var(--tk-space-6); padding-block: var(--tk-space-8) var(--tk-space-12); }
.tk-admin--full { max-width: none; }
.tk-admin > * + * { margin-top: var(--tk-space-6); }

/* Title row: heading left, "back" affordance right, wrapping rather than squeezing. */
.tk-admin__head { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-4); flex-wrap: wrap; }
.tk-admin__title { display: flex; align-items: center; gap: .6rem;
  font-family: var(--tk-font-display); font-weight: 800; text-transform: uppercase;
  letter-spacing: .03em; font-size: clamp(1.3rem, 3.5vw, 1.75rem); line-height: 1.1; margin: 0; }
/* Sized here, never through tk_icon()'s $attrs — that helper writes width="20" height="20"
   before it appends $attrs, so a passed width lands as a duplicate attribute and loses. */
.tk-admin__title svg { flex: 0 0 auto; width: 24px; height: 24px; color: var(--tk-accent-text); }
.tk-admin__lead { color: var(--tk-text-muted); font-size: .9rem; margin: .35rem 0 0; }
.tk-admin__headactions { display: flex; gap: var(--tk-space-2); flex-wrap: wrap; }
.tk-admin__headactions svg { width: 15px; height: 15px; }

/* A count sitting at the far end of a .tk-page__panelhead (that head is a flex row and
   sets no auto margin of its own). */
.tk-admin__count { margin-left: auto; flex: 0 0 auto; }

/* ---- Section rail ----
   .tk-linklist supplies the frame and the rows; this only changes the axis. As a stack of
   six full-width rows it measured ~330px tall and pushed every admin table below the fold
   on a laptop — the rail was taking more of the page than the data it navigates. Above
   720px it becomes a wrapping strip of tabs; below that it stays the stack, which is the
   right shape on a phone. The links, their order and their markup are identical either
   way, so nothing here is a second navigation to keep in sync. */
@media (min-width: 720px) {
  .tk-admin .tk-linklist { flex-direction: row; flex-wrap: wrap; }
  .tk-admin .tk-linklist__item { flex: 1 1 auto; justify-content: center; gap: 0;
    border-top: 0; border-left: 1px solid var(--tk-border);
    padding: var(--tk-space-3) var(--tk-space-4); font-size: .8rem; }
  .tk-admin .tk-linklist__item:first-child { border-left: 0; }
  /* The chevron says "there is more below" on a stack; on a tab strip it says nothing. */
  .tk-admin .tk-linklist__item > svg:last-child { display: none; }
  .tk-admin .tk-linklist__item[aria-current="page"] { box-shadow: inset 0 -3px 0 var(--tk-burgundy); }
}

/* ---- Toolbar ----
   A row of filter pills above a ledger. Same metrics as .tk-grade-pill, which is the
   nearest shape in the system, but that one is the watch-condition vocabulary and this
   is a workflow-state filter — one class doing both would say neither. */
.tk-admin__toolbar { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }
.tk-admin__filter { display: inline-flex; align-items: center; gap: .4rem; cursor: pointer;
  font: inherit; font-size: .75rem; padding: .4rem .9rem; border-radius: var(--tk-radius-pill);
  border: 1px solid var(--tk-border); background: var(--tk-surface); color: var(--tk-text-muted);
  transition: color .2s var(--tk-ease), border-color .2s var(--tk-ease); }
.tk-admin__filter:hover { color: var(--tk-text); border-color: var(--tk-burgundy); }
.tk-admin__filter.is-active { background: var(--tk-burgundy); border-color: var(--tk-burgundy);
  color: #fbeef0; }
/* The tally inside a pill. On the active pill it has to stay legible against burgundy,
   so it flips to a translucent light wash rather than keeping the muted body colour. */
.tk-admin__filtercount { font-variant-numeric: tabular-nums; font-weight: 700;
  padding: 0 .4em; border-radius: var(--tk-radius-pill);
  background: color-mix(in srgb, var(--tk-text) 8%, transparent); color: var(--tk-text); }
.tk-admin__filter.is-active .tk-admin__filtercount { background: rgba(255,255,255,.22); color: #fff; }
@media (prefers-reduced-motion: reduce) { .tk-admin__filter { transition: none; } }

/* ---- The dense ledger ----
   Deliberately not .tk-dash__table or .tk-reg__table: both are page-scoped to a single
   feature whose row internals (profit sign, sold badge, proof verdict) are that feature's
   vocabulary. This is the back office's, and it is denser than either — admin rows are
   two-line and admin tables run to seven columns.
   Nothing here sets `display` on a <tr>: the trade-in queue's filter hides and restores
   rows with row.style.display = '' / 'none', and an author `display` on the row would
   decide what '' restores to. */
.tk-admin__tablewrap { overflow-x: auto; }
.tk-admin__table { width: 100%; min-width: 680px; border-collapse: collapse; }
/* Seven columns with two-line cells; below this the columns start colliding. The wrap
   above scrolls, so the page body never does. */
.tk-admin__table--wide { min-width: 900px; }
.tk-admin__table th, .tk-admin__table td { padding: .6rem var(--tk-space-4); text-align: left;
  vertical-align: middle; border-bottom: 1px solid var(--tk-border); font-size: .825rem; }
.tk-admin__table tbody tr:last-child td { border-bottom: 0; }
.tk-admin__table thead th { white-space: nowrap;
  background: color-mix(in srgb, var(--tk-page) 55%, var(--tk-surface));
  font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; font-size: .65rem; color: var(--tk-text-muted); }
.tk-admin__table tbody tr:hover { background: color-mix(in srgb, var(--tk-burgundy) 5%, transparent); }
/* A row someone has flagged for review. This is (0,2,2), the same as the :hover rule
   above it, so being declared later it wins outright and a flagged row would otherwise
   lose its pointer feedback entirely — hence the third rule, at (0,3,2). */
.tk-admin__table tbody tr.tk-admin__row--flagged { background: color-mix(in srgb, var(--tk-gold) 10%, transparent); }
.tk-admin__table tbody tr.tk-admin__row--flagged:hover { background: color-mix(in srgb, var(--tk-gold) 18%, transparent); }

/* Cell internals. */
.tk-admin__rowtitle { font-weight: 600; color: var(--tk-text); }
.tk-admin__rowsub { display: block; color: var(--tk-text-muted); font-size: .7rem; line-height: 1.5; }
.tk-admin__rowdate { color: var(--tk-text-muted); white-space: nowrap; }
.tk-admin__num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.tk-admin__mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .02em; }
.tk-admin__none { color: var(--tk-text-muted); }
/* Community-submitted free text — an address, a list of report reasons, a contributor
   list. Clamped so one long submission cannot set the table's width, and allowed to break
   mid-token because none of it is guaranteed to contain a space. */
.tk-admin__trunc { display: block; max-width: 24ch; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; color: var(--tk-text-muted); font-size: .75rem; }
/* Free text that has to be readable in full (a rating's feedback). */
.tk-admin__wrap { white-space: normal; min-width: 240px; }
.tk-admin__wrap p { color: var(--tk-text-muted); font-size: .78rem; line-height: 1.55;
  margin: 0; overflow-wrap: anywhere; }
.tk-admin__removed { color: var(--tk-accent-text); font-style: italic; }
/* An inline link inside a cell — base.css strips every <a> to inherited colour and no
   underline, the same gap .tk-callout a and .tk-page__steps a close. */
.tk-admin__link { display: inline-flex; align-items: center; gap: .3rem;
  color: var(--tk-accent-text); font-size: .75rem; }
.tk-admin__link:hover { text-decoration: underline; text-underline-offset: 2px; }
.tk-admin__link svg { flex: 0 0 auto; width: 13px; height: 13px; }
/* The listing thumbnail + title pair. */
.tk-admin__watch { display: flex; align-items: center; gap: .75rem; }
.tk-admin__thumb { flex: 0 0 auto; width: 44px; height: 44px; object-fit: cover;
  border-radius: var(--tk-radius); border: 1px solid var(--tk-border); }

/* Row actions. */
.tk-admin__cellactions { text-align: right; }
.tk-admin__rowbtns { display: flex; justify-content: flex-end; gap: .35rem; flex-wrap: wrap; }

/* The empty ledger. Qualified so it out-specifies the cell padding above. */
.tk-admin__table td.tk-admin__empty { text-align: center; color: var(--tk-text-muted);
  padding-block: var(--tk-space-12); }

/* ---- Status pills ----
   A record's position in a workflow, on .tk-badge. The fills and inks are the ones
   already measured for .tk-doc__status--* and .tk-reg__pstatus--*: 18% success, 12% gold
   (16% drops --tk-gold-ink to 4.10:1 on the light theme; 12% brings it to 5.28:1), and
   solid burgundy with ivory ink for the terminal negative. */
.tk-admin__pill--ok { background: color-mix(in srgb, var(--tk-success) 18%, transparent);
  color: color-mix(in srgb, var(--tk-success) 62%, var(--tk-text));
  border-color: color-mix(in srgb, var(--tk-success) 50%, transparent); }
.tk-admin__pill--warn { background: color-mix(in srgb, var(--tk-gold) 12%, transparent);
  color: var(--tk-gold-ink); border-color: color-mix(in srgb, var(--tk-gold) 45%, transparent); }
.tk-admin__pill--info { background: color-mix(in srgb, var(--tk-burgundy) 10%, transparent);
  color: var(--tk-accent-text); border-color: color-mix(in srgb, var(--tk-burgundy) 35%, transparent); }
.tk-admin__pill--danger { background: var(--tk-burgundy); color: #fbeef0; border-color: var(--tk-burgundy); }
.tk-admin__pill--neutral { background: var(--tk-page); color: var(--tk-text-muted);
  border-color: var(--tk-border); }

/* A count of incoming reports on one location — a tally, not a workflow state, so it
   takes the burgundy wash rather than one of the verdict colours above. */
.tk-admin__tally { font-variant-numeric: tabular-nums;
  background: color-mix(in srgb, var(--tk-burgundy) 12%, transparent);
  color: var(--tk-accent-text); border-color: color-mix(in srgb, var(--tk-burgundy) 35%, transparent); }

/* ---- Rating stars ----
   tk_icon() hardcodes fill="none" ahead of $attrs, so a filled star cannot be asked for
   through the helper — but a CSS `fill` beats a presentation attribute, so it is set here
   instead. The row is labelled for assistive tech by an adjacent .tk-sr-only span, NOT by
   text inside a cell: .tk-sr-only clips rather than hides and its text lands in innerText,
   which any future sort or filter on this table would then read. */
.tk-admin__stars { display: inline-flex; gap: .1rem; color: var(--tk-gold-ink); white-space: nowrap; }
.tk-admin__stars svg { width: 14px; height: 14px; }
.tk-admin__star--on { fill: currentColor; }
.tk-admin__star--off { opacity: .35; }

/* ---- Access denied ----
   .tk-page__gate* is the whole card; this only centres it in the viewport the way
   .tk-dash__gatewrap does for the trader dashboard's. */
.tk-admin__denywrap { padding-block: var(--tk-space-12); display: flex; justify-content: center; }

/* ================================================================================
   SHARED: small button size + the 32px icon button
   Both are un-scoped because they are not admin's. .tk-btn--sm is the `btn-sm` that
   appears on every admin template and on three public ones. The icon button already
   exists three times over as .tk-dash__rowbtn, .tk-coll__rowbtn and .tk-doc__rowbtn,
   line-for-line identical; this is that shape declared once so admin does not become the
   fourth copy. Those three are left alone here — folding them in is its own change.
   ================================================================================ */
.tk-btn--sm { font-size: .7rem; padding: .45rem .8rem; }
.tk-btn--sm svg { width: 13px; height: 13px; }
/* The destructive ghost, alongside .tk-btn--success and .tk-btn--warn. Burgundy is this
   system's only alarm colour — there is no separate red — so it borrows --accent-text,
   which is what every other destructive affordance here already hovers to. */
.tk-btn--danger { border-color: color-mix(in srgb, var(--tk-burgundy) 45%, var(--tk-border));
  color: var(--tk-accent-text); background: transparent; }
.tk-btn--danger:hover { border-color: var(--tk-burgundy);
  background: color-mix(in srgb, var(--tk-burgundy) 8%, transparent); }

.tk-iconbtn { display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; width: 32px; height: 32px; padding: 0;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-surface); color: var(--tk-text-muted); cursor: pointer;
  transition: color .2s var(--tk-ease), border-color .2s var(--tk-ease); }
.tk-iconbtn:hover { color: var(--tk-accent-text); border-color: var(--tk-burgundy); }
.tk-iconbtn:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 2px; }
.tk-iconbtn[disabled] { opacity: .5; cursor: not-allowed; }
.tk-iconbtn svg { width: 16px; height: 16px; }
.tk-iconbtn--ok { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text)); }
.tk-iconbtn--ok:hover { color: color-mix(in srgb, var(--tk-success) 80%, var(--tk-text));
  border-color: var(--tk-success); }
.tk-iconbtn--danger { color: var(--tk-accent-text); }
/* "This toggle is currently on" — the flagged state of a moderation button. Solid, not the
   14%-gold wash first written here: a flagged rating ALSO tints its row gold, so the wash
   measured 1.14:1 against the very row it always appears on and the on-state was invisible
   exactly when it mattered. Solid --tk-gold-ink measures 5.37:1 against that tinted row and
   5.95:1 against a plain one, and carries its white ink at 5.95:1. */
.tk-iconbtn.is-on { background: var(--tk-gold-ink); border-color: var(--tk-gold-ink); color: #fff; }
.tk-iconbtn.is-on:hover { background: color-mix(in srgb, var(--tk-gold-ink) 85%, #000);
  border-color: color-mix(in srgb, var(--tk-gold-ink) 85%, #000); color: #fff; }
@media (prefers-reduced-motion: reduce) { .tk-iconbtn { transition: none; } }

/* ---- .tk-linklist: the active entry ----
   The admin rail marks where you are with aria-current, which had no visual counterpart.
   Attribute selectors are 0,1,0 like a class, and this is declared after .tk-linklist__item
   and its :hover, so it wins on both. */
.tk-linklist__item[aria-current="page"] { background: var(--tk-raised);
  color: var(--tk-accent-text); font-weight: 600;
  box-shadow: inset 3px 0 0 var(--tk-burgundy); }
.tk-linklist__item[aria-current="page"] svg:first-child { color: var(--tk-accent-text); }
.tk-linklist__label { display: inline-flex; align-items: center; gap: .6rem; min-width: 0; }
.tk-linklist__label svg { width: 17px; height: 17px; }

/* ---- [hidden] opt-outs for the admin layer ----
   Every class below sets `display` in an author rule, which beats the UA's
   [hidden]{display:none} — so an element carrying `hidden` ships VISIBLE. This project has
   now hit that defect five separate times (.tk-btn, .tk-form, .tk-icon, .tk-stepper,
   .tk-card-grid), each time after the fact. Admin is the surface most likely to toggle a
   toolbar, a row's actions or a single control from script, and the remaining nine
   templates in this migration are full of exactly that, so the opt-out is declared up
   front for the whole layer rather than one class at a time as each page trips over it.
   (Note for anyone extending this: an <img> is a separate case — base.css's
   `:where(.tk-dark,.tk-light) img{display:block}` beats [hidden] on EVERY image.) */
.tk-admin__head[hidden],
.tk-admin__headactions[hidden],
.tk-admin__toolbar[hidden],
.tk-admin__filter[hidden],
.tk-admin__rowbtns[hidden],
.tk-admin__cellactions[hidden],
.tk-admin__watch[hidden],
.tk-admin__stars[hidden],
.tk-admin__link[hidden],
.tk-admin__trunc[hidden],
.tk-admin__denywrap[hidden],
.tk-iconbtn[hidden],
.tk-linklist__label[hidden] { display: none; }

/* ================================================================================
   UNDER THE LOUPE — ADMIN (.tk-loupe*)
   The two editorial back-office screens, /admin/audio-guides and
   /admin/investment-dossiers. (A third, admin_under_the_loupe.php, shared this layer and
   was deleted with the Bootstrap cut — it had no route and was reachable from nowhere.)
   They share one shape: a list of volumes, each holding a list of episodes, plus two
   edit dialogs.
   The frame is all reuse — .tk-admin for the page column and its head, .tk-linklist for
   the rail, .tk-page__panel for the card, .tk-modal / .tk-field / .tk-form for the
   dialogs, .tk-callout for the generator box, .tk-badge / .tk-btn / .tk-iconbtn
   unchanged. What is declared here is only what a nested editorial tree needs: the
   volume disclosure, the episode row, and the trim on the two long forms.
   ================================================================================ */

.tk-loupe__vols { padding: var(--tk-space-6); display: flex; flex-direction: column;
  gap: var(--tk-space-3); }
.tk-loupe__vols[hidden] { display: none; }
.tk-loupe__loading { color: var(--tk-text-muted); font-size: .85rem; text-align: center;
  margin: 0; padding-block: var(--tk-space-8); }

/* ---- One volume: a native <details> ----
   The Bootstrap original was an .accordion whose items carried data-bs-parent, i.e. it
   was exclusive. `name` on <details> is that same behaviour with no script at all, so
   the exclusivity survives the component's removal; where the attribute is unsupported
   the volumes simply open independently, which is a fair degradation for a back office. */
.tk-loupe__vol { border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-surface); overflow: hidden; }
.tk-loupe__volsum { display: flex; align-items: center; gap: .6rem; cursor: pointer;
  list-style: none; padding: var(--tk-space-3) var(--tk-space-4); }
.tk-loupe__volsum::-webkit-details-marker { display: none; }
.tk-loupe__volsum:hover { background: color-mix(in srgb, var(--tk-burgundy) 5%, transparent); }
.tk-loupe__volsum:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: -2px; }
/* The chevron points right when shut and down when open. Restated against [open]
   because the Bootstrap version rotated off aria-expanded, which nothing sets once the
   Collapse component is gone. */
.tk-loupe__volchev { flex: 0 0 auto; display: inline-flex; color: var(--tk-text-muted);
  transition: transform .25s var(--tk-ease); }
.tk-loupe__volchev svg { width: 16px; height: 16px; }
.tk-loupe__vol[open] .tk-loupe__volchev { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .tk-loupe__volchev { transition: none; } }
.tk-loupe__volnum { flex: 0 0 auto; font-family: var(--tk-font-display); font-weight: 800;
  text-transform: uppercase; letter-spacing: .06em; font-size: .72rem; color: var(--tk-accent-text); }
.tk-loupe__voltitle { min-width: 0; font-weight: 600; font-size: .9rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tk-loupe__volcount { margin-left: auto; flex: 0 0 auto; }

.tk-loupe__volbody { border-top: 1px solid var(--tk-border); background: var(--tk-page);
  padding: var(--tk-space-4); }
.tk-loupe__volbar { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: .4rem;
  padding-bottom: var(--tk-space-3); margin-bottom: var(--tk-space-3);
  border-bottom: 1px solid var(--tk-border); }

/* ---- The episode rows ---- */
.tk-loupe__eps { list-style: none; margin: 0; padding: 0; display: flex;
  flex-direction: column; gap: .4rem; }
.tk-loupe__eps[hidden] { display: none; }
.tk-loupe__ep { display: flex; align-items: center; gap: .75rem; padding: .55rem .75rem;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius); background: var(--tk-surface); }
.tk-loupe__ep:hover { border-color: color-mix(in srgb, var(--tk-burgundy) 35%, var(--tk-border)); }
.tk-loupe__epnum { flex: 0 0 auto; }
.tk-loupe__epmain { flex: 1 1 auto; min-width: 0; }
.tk-loupe__eptitle { display: block; font-weight: 600; font-size: .85rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tk-loupe__epslug { display: block; color: var(--tk-text-muted); font-size: .7rem;
  overflow-wrap: anywhere; }
/* No `display` here on purpose: this <p> is toggled with the `hidden` attribute and a
   block default lets the UA's [hidden]{display:none} do its job unopposed. */
.tk-loupe__empty { color: var(--tk-text-muted); font-size: .8rem; text-align: center;
  margin: 0; padding-block: var(--tk-space-4); }

/* ---- The two dialogs ----
   .tk-modal supplies the frame; only the width and the scroll are page-specific. The
   episode form runs to fourteen controls plus a rich-text editor, so the BODY scrolls
   rather than the dialog — a `display` on the <dialog> itself would beat the UA's
   `dialog:not([open]) { display: none }` and ship the modal open on page load. */
.tk-loupe__modal { max-width: min(94vw, 620px); }
.tk-loupe__modal--lg { max-width: min(94vw, 880px); }
.tk-loupe__modal .tk-modal__body { max-height: 72vh; overflow-y: auto; }
.tk-loupe__modaltitle { font-family: var(--tk-font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; font-size: 1rem; margin: 0; }
.tk-loupe__modalfoot { display: flex; justify-content: flex-end; gap: var(--tk-space-3);
  flex-wrap: wrap; padding-top: var(--tk-space-2); }
.tk-loupe__grid { display: grid; gap: var(--tk-space-4); grid-template-columns: 1fr; }
@media (min-width: 620px) {
  .tk-loupe__grid--2 { grid-template-columns: 1fr 1fr; }
  .tk-loupe__grid--minor { grid-template-columns: 1fr 3fr; }
}
/* A label with an action alongside it (the "Format with AI" pair). */
.tk-loupe__labelrow { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-3); flex-wrap: wrap; margin-bottom: .35rem; }
.tk-loupe__labelrow > label { font-size: .7rem; text-transform: uppercase;
  letter-spacing: .1em; color: var(--tk-text-muted); }
.tk-loupe__labelbtns { display: flex; gap: .35rem; flex-wrap: wrap; }
/* .tk-modal__body already gives every input a .3rem top margin (its label gap); a file
   input that follows a text input inside one .tk-field does not want a second one. */
.tk-loupe__filerow { display: flex; gap: .4rem; align-items: center; margin-top: .3rem; }
.tk-loupe__status { display: flex; align-items: center; gap: .35rem; margin: .35rem 0 0;
  font-size: .72rem; color: var(--tk-text-muted); min-height: 1rem; }
.tk-loupe__status svg { flex: 0 0 auto; width: 13px; height: 13px; }
.tk-loupe__status--ok { color: color-mix(in srgb, var(--tk-success) 62%, var(--tk-text)); }
.tk-loupe__status--err { color: var(--tk-accent-text); }

/* ---- Banner / cover preview ----
   The `hidden` toggle goes on this container and NEVER on the <img>: base.css's
   `:where(.tk-dark,.tk-light) img { display: block }` beats the UA's [hidden] on every
   image in the system, so an <img hidden> ships visible. */
.tk-loupe__preview { display: flex; align-items: flex-start; gap: var(--tk-space-3);
  margin-top: .5rem; }
.tk-loupe__preview[hidden] { display: none; }
.tk-loupe__previewimg { max-height: 110px; width: auto; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); }

/* ---- The AI generator box ----
   .tk-callout is the frame (it is the `p-4 bg-dark border rounded-3` this was); this is
   only the stack inside it. */
.tk-loupe__gen { display: flex; flex-direction: column; gap: var(--tk-space-3); }
.tk-loupe__gen[hidden] { display: none; }
.tk-loupe__gentitle { display: flex; align-items: center; gap: .5rem; margin: 0;
  font-family: var(--tk-font-display); font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; font-size: .8rem; color: var(--tk-accent-text); }
.tk-loupe__gentitle svg { flex: 0 0 auto; width: 16px; height: 16px; }
.tk-loupe__gennote { color: var(--tk-text-muted); font-size: .78rem; margin: 0; }
.tk-loupe__result { display: flex; flex-direction: column; gap: .5rem; }
.tk-loupe__result[hidden] { display: none; }
.tk-loupe__resultok { display: flex; align-items: center; gap: .4rem; margin: 0;
  font-size: .78rem; color: color-mix(in srgb, var(--tk-success) 62%, var(--tk-text)); }
.tk-loupe__resultok svg { flex: 0 0 auto; width: 14px; height: 14px; }
.tk-loupe__audio { width: 100%; height: 36px; }
.tk-loupe__resultnote { color: var(--tk-text-muted); font-size: .72rem; margin: 0; }

/* ---- Summernote ----
   The rich-text editor is a third-party widget that writes its own .note-* DOM, and it
   is the only non-tk markup on these pages. It is not Bootstrap and nothing in this
   migration removes it; what is set here is the readability of the editing surface,
   which is the whole of the page-local <style> the Bootstrap version carried. The paper
   stays literally white because it is a picture of the published article, exactly as
   .tk-doc__preview is a picture of the printed document. */
.tk-loupe__modal .note-editor.note-frame { border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); margin-bottom: 0; }
.tk-loupe__modal .note-toolbar { background: var(--tk-raised);
  border-bottom: 1px solid var(--tk-border); }
.tk-loupe__modal .note-statusbar { background: var(--tk-raised);
  border-top: 1px solid var(--tk-border); }
.tk-loupe__modal .note-editable { background: #ffffff; color: #16130f;
  font-family: var(--tk-font-body); font-size: 1rem; line-height: 1.75;
  padding: var(--tk-space-6); }

/* ================================================================================
   ADMIN — THE THREE WORKING DESKS
   /admin/dashboard, /admin/blog and /admin/trade-in/review/{id}. The frame is the
   .tk-admin* layer unchanged — page column, head, section rail, ledger, status pills,
   .tk-iconbtn, .tk-btn--sm/--danger — plus .tk-panel / .tk-page__panel / .tk-field /
   .tk-form / .tk-callout / .tk-modal / .tk-badge as they stand. Two of the layouts here
   are also pure reuse: the review screen's main-plus-rail split is .tk-page__cols--rev
   (which already stacks below 860px) and its two summary cards are .tk-page__cols--even.
   What follows is only what these three have that the seven ledger pages did not: a
   detail screen, a Markdown editor, and a dialog footer.
   ================================================================================ */

/* The dialog footer. Un-scoped, alongside .tk-modal__head and .tk-modal__body, because it
   is not admin's: this exact rule already exists EIGHT times over as .tk-trade__modalfoot,
   .tk-dash__modalfoot, .tk-coll__modalfoot, .tk-prop__modalfoot, .tk-doc__modalfoot,
   .tk-zone__modalfoot, .tk-reg__modalfoot and .tk-loupe__modalfoot, line for line. This is
   that shape declared once so admin does not become the ninth copy; folding the existing
   eight in is its own change and is left alone here. */
.tk-modal__foot { display: flex; justify-content: flex-end; gap: .5rem; flex-wrap: wrap;
  margin-top: var(--tk-space-4); }
.tk-modal__foot[hidden] { display: none; }
.tk-admin__modaltitle { font-family: var(--tk-font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; font-size: 1rem; margin: 0; }
.tk-admin__confirmtext { font-size: .9rem; line-height: 1.6; margin: 0; }

/* .tk-admin__docimg — the proof photo in the document dialog — is NOT declared here. It
   was added to the .tk-reg__docimg selector list at the top of this file instead:
   /admin/dashboard opens the very same registry_proofs files that /registry/{id} does, so
   there is one height cap for one image rather than two that can drift apart. */

/* ---- Content left, control right ----
   The geo-block row and the review screen's status bar. Same metrics as .tk-admin__head,
   deliberately not that class: __head is the page's title row, and a second element
   claiming to be the head of something is how a rail ends up rendered twice. */
.tk-admin__split { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-4); flex-wrap: wrap; }
.tk-admin__split[hidden] { display: none; }
.tk-admin__geotext { font-size: .9rem; margin: 0; }

/* A dense, left-aligned button group inside a table cell — the proof column's two or three
   view buttons. .tk-admin__rowbtns is the right-aligned sibling for the actions column;
   this one cannot reuse it because a stretched proof column would push its buttons to the
   far edge, away from the data they belong to. */
.tk-admin__cellbtns { display: flex; flex-wrap: wrap; gap: .35rem; }
.tk-admin__cellbtns[hidden] { display: none; }

/* A column of panels inside one half of a .tk-page__cols split. .tk-admin > * + * only
   spaces the page's own children, and these are a level deeper. */
.tk-admin__stack { display: flex; flex-direction: column; gap: var(--tk-space-6); }
.tk-admin__stack[hidden] { display: none; }
/* The offer rail. 80px is the same offset .tk-listing .tk-filters uses — the sticky nav is
   59px and this clears it by the same margin. Only above the point .tk-page__cols stops
   splitting: stuck to the top of a stacked one-column page it would cover the content. */
@media (min-width: 861px) { .tk-admin__sticky { position: sticky; top: 80px; } }

/* ---- Detail cards (review screen) ----
   A micro-label, a headline and a run of secondary lines, inside a plain .tk-panel. */
.tk-admin__cardtitle { font-family: var(--tk-font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .1em; font-size: .65rem;
  color: var(--tk-text-muted); margin: 0 0 .5rem; }
.tk-admin__cardlead { font-weight: 700; font-size: 1rem; margin: 0; }
.tk-admin__cardline { display: flex; align-items: center; gap: .4rem; margin: .3rem 0 0;
  color: var(--tk-text-muted); font-size: .82rem; overflow-wrap: anywhere; }
.tk-admin__cardline svg { flex: 0 0 auto; width: 14px; height: 14px; }
.tk-admin__cardline strong { color: var(--tk-text); }
.tk-admin__cardfoot { margin-top: var(--tk-space-4); padding-top: var(--tk-space-4);
  border-top: 1px solid var(--tk-border); }
/* The seller's asking price. The one figure on the screen, so it is the only thing set at
   display scale. Body font, not display: it is a number to read, not a heading. */
.tk-admin__price { font-family: var(--tk-font-body); font-weight: 700; font-size: 1.5rem;
  color: var(--tk-text); margin: 0; font-variant-numeric: tabular-nums; }
/* What came in the box. The Bootstrap original tinted the missing ones red; they are the
   neutral pill now — an inclusion the seller simply does not have is not an error. */
.tk-admin__incl { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: var(--tk-space-3); }
.tk-admin__incl[hidden] { display: none; }

/* ---- Submitted photos ---- */
.tk-admin__photos { display: flex; flex-wrap: wrap; gap: var(--tk-space-3); }
.tk-admin__photos[hidden] { display: none; }
.tk-admin__photo { display: flex; flex-direction: column; align-items: center; gap: .3rem;
  color: var(--tk-text-muted); font-size: .65rem; text-transform: uppercase;
  letter-spacing: .06em; }
.tk-admin__photo img { width: 90px; height: 90px; object-fit: cover;
  border-radius: var(--tk-radius); border: 1px solid var(--tk-border); }
.tk-admin__photo:hover img { border-color: var(--tk-burgundy); }

/* ---- Status bar + save feedback (review screen) ---- */
.tk-admin__statusrow { display: flex; align-items: center; gap: .5rem; }
.tk-admin__statusrow[hidden] { display: none; }
.tk-admin__statuslabel { font-family: var(--tk-font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .1em; font-size: .65rem; color: var(--tk-text-muted); }
/* No `display` on purpose: this <p> is toggled with the `hidden` attribute and a block
   default lets the UA's [hidden]{display:none} do its job unopposed. */
.tk-admin__savestatus { font-size: .75rem; margin: var(--tk-space-3) 0 0;
  color: var(--tk-text-muted); }
.tk-admin__savestatus--ok { color: color-mix(in srgb, var(--tk-success) 62%, var(--tk-text)); }
.tk-admin__savestatus--err { color: var(--tk-accent-text); }
.tk-admin__offeractions { display: flex; flex-direction: column; gap: .5rem;
  margin-top: var(--tk-space-4); }
.tk-admin__offeractions[hidden] { display: none; }
/* The live message preview. Deliberately literal: it is a picture of what WhatsApp will
   show the seller, so it keeps the newlines and the monospace-ish measure. */
.tk-admin__wapreview { margin-top: var(--tk-space-4); padding: var(--tk-space-4);
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius); background: var(--tk-page); }
.tk-admin__wapreview p { white-space: pre-wrap; overflow-wrap: anywhere; margin: 0;
  color: var(--tk-text-muted); font-size: .72rem; line-height: 1.7; }

/* ---- The post editor (/admin/blog) ---- */
.tk-admin__formgrid { display: grid; gap: var(--tk-space-4); grid-template-columns: 1fr; }
.tk-admin__formgrid[hidden] { display: none; }
.tk-admin__aigrid { display: grid; gap: var(--tk-space-4); grid-template-columns: 1fr;
  align-items: end; }
.tk-admin__aigrid[hidden] { display: none; }
@media (min-width: 720px) {
  .tk-admin__formgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .tk-admin__formgrid--wide { grid-column: 1 / -1; }
  .tk-admin__aigrid { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) auto; }
}
/* A label with an action alongside it (the Markdown preview toggle). */
.tk-admin__labelrow { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-3); flex-wrap: wrap; }
.tk-admin__labelrow[hidden] { display: none; }
.tk-admin__labelrow > label { font-size: .625rem; text-transform: uppercase;
  letter-spacing: .1em; color: var(--tk-text-muted); }

/* The featured-image drop zone. A real <button> rather than the original <div> with a
   cursor: it is operated by keyboard as well as by drag, and a div is neither focusable
   nor announced. */
.tk-admin__drop { display: flex; flex-direction: column; align-items: center; gap: .5rem;
  width: 100%; padding: var(--tk-space-6); cursor: pointer; font: inherit;
  font-size: .78rem; color: var(--tk-text-muted); background: var(--tk-page);
  border: 1px dashed var(--tk-border); border-radius: var(--tk-radius);
  transition: color .2s var(--tk-ease), border-color .2s var(--tk-ease); }
.tk-admin__drop[hidden] { display: none; }
.tk-admin__drop svg { width: 26px; height: 26px; }
.tk-admin__drop:hover, .tk-admin__drop.is-over { color: var(--tk-accent-text);
  border-color: var(--tk-burgundy); }
.tk-admin__drop:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 2px; }
.tk-admin__drop[disabled] { opacity: .5; cursor: progress; }
@media (prefers-reduced-motion: reduce) { .tk-admin__drop { transition: none; } }

/* The `hidden` toggle goes on this wrapper and NEVER on the <img> inside it: base.css's
   `:where(.tk-dark,.tk-light) img { display: block }` beats the UA's [hidden] on every
   image in the system, so an <img hidden> ships visible. */
.tk-admin__preview { display: block; margin-top: .5rem; }
.tk-admin__preview[hidden] { display: none; }
.tk-admin__previewimg { max-height: 120px; width: auto; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); }

/* The rendered-Markdown pane. Sets no `display`, so the UA's [hidden] hides it unopposed.
   The paper stays literally white because it is a picture of the published article, the
   same reasoning as .tk-doc__preview and the Summernote surface. */
.tk-admin__previewpane { margin-top: .5rem; padding: var(--tk-space-6);
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: #ffffff; color: #16130f; font-family: var(--tk-font-body);
  line-height: 1.75; overflow-wrap: anywhere; }
.tk-admin__previewpane img { max-width: 100%; height: auto; }
.tk-admin__previewpane a { color: #6d1520; text-decoration: underline; text-underline-offset: 2px; }
.tk-admin__previewpane pre { overflow-x: auto; }

/* The Markdown source itself. .tk-field textarea supplies the skin; only the face changes. */
textarea.tk-admin__mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .82rem; line-height: 1.6; }

/* An actions column of icon buttons must not be squeezed into a vertical stack. `width: 1%`
   is the standard table idiom for "shrink to content", and nowrap keeps the pair on one
   line when a neighbouring free-text column (a blog slug, a 24ch address) tries to take the
   width — /admin/blog was rendering its Edit and Delete buttons one above the other. The
   ledger sits in .tk-admin__tablewrap, so on a narrow viewport this scrolls the table
   rather than the page. */
.tk-admin__table th.tk-admin__cellactions,
.tk-admin__table td.tk-admin__cellactions { width: 1%; white-space: nowrap; }
/* Both button rows, for the same reason: white-space does not reach a flex container, so
   a shrink-to-content cell would wrap its buttons into a column instead. /admin/listings
   has a second such column (the Facebook + Instagram controls) alongside the actions one. */
.tk-admin__cellactions .tk-admin__rowbtns,
.tk-admin__cellactions .tk-admin__cellbtns { flex-wrap: nowrap; }

/* ================================================================================
   ADMIN — THE BUYER LIST AND THE COMPOSER
   /admin/subscribers and /admin/email-compose, the pair that sends real mail to real
   buyers. The frame is the .tk-admin* layer unchanged, plus .tk-panel /
   .tk-page__panel* / .tk-field / .tk-fieldset / .tk-check / .tk-form / .tk-callout /
   .tk-badge / .tk-btn / .tk-iconbtn / .tk-modal as they stand, and the composer's
   two-column split is .tk-page__cols--rev — the same main-plus-rail the trade-in review
   screen uses, which already stacks below 860px, exactly where .tk-admin__sticky stops.
   What follows is only what these two have that no admin page so far did: a demand
   chart, a scrolling checklist, a per-record disclosure, and a drag-and-drop composer.
   ================================================================================ */

/* ---- Three shared gaps, none of them admin's ----
   .tk-btn[disabled] exists SIX times over already, scoped to .tk-trade, .tk-dash__actions,
   .tk-coll__ai/.tk-coll__quickadd, .tk-prop and .tk-doc, byte-for-byte the same pair of
   rules — so a disabled .tk-btn anywhere else (the "Compose Email" action with an empty
   list, the composer's Send) looked fully enabled. Declared once here; all six scoped
   copies are more specific and identical, so nothing already shipped moves. */
.tk-btn[disabled] { opacity: .55; cursor: not-allowed; }
.tk-btn[disabled]:hover { transform: none; }
/* .tk-badge sets `display: inline-block`, which beats the UA's [hidden] { display: none }
   — so a badge carrying `hidden` ships VISIBLE. This is the same defect as .tk-btn[hidden],
   .tk-form[hidden], .tk-icon[hidden], .tk-stepper[hidden] and the .tk-admin* list above,
   and it bites here on the composer's "Pre-owned" chip, which is hidden in the card
   template and revealed per card. */
.tk-badge[hidden] { display: none; }
/* .tk-modal__body input is a blanket rule (full width, field padding, a label gap) written
   for text inputs, and inside a dialog it lands on checkboxes too: the Add Subscriber
   model list rendered its boxes with .6rem of padding and a .3rem top offset. .tk-check
   input already re-states width and height and wins on order; this adds the two it does
   not. */
.tk-modal__body .tk-check input { padding: 0; margin-top: 0; }

/* ---- Stat cards (/admin/subscribers) ----
   Three figures over the ledger. The label is .tk-admin__cardtitle and the sub-line
   .tk-admin__cardline — both already the review screen's — so the only thing new is the
   grid and the size of the number itself. Deliberately not a fourth copy of
   .tk-dash__stat / .tk-coll__stat / .tk-reg__stat: those three carry their own feature's
   colour vocabulary (cost gold, tier silver) and this carries none. */
.tk-admin__stats { display: grid; gap: var(--tk-space-6); grid-template-columns: minmax(0, 1fr); }
.tk-admin__stats[hidden] { display: none; }
@media (min-width: 720px) { .tk-admin__stats { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
/* Body font, not display: it is a number to read, not a heading — the same reasoning as
   .tk-admin__price, which is the seller's asking figure and stays its own class. */
.tk-admin__statval { font-family: var(--tk-font-body); font-weight: 700; font-size: 2rem;
  line-height: 1; font-variant-numeric: tabular-nums; margin: 0; }

/* A wrapping run of .tk-badge chips — the models one subscriber asked about, the verdict
   tallies on one blast. .tk-admin__incl is the same shape but carries a top margin and
   means "what came in the box"; inside a table cell that margin is wrong. */
.tk-admin__chips { display: flex; flex-wrap: wrap; gap: .25rem; }
.tk-admin__chips[hidden] { display: none; }

/* "(optional)" inside a .tk-field label, which is uppercase micro-type — this is the run
   that opts back out of it. */
.tk-admin__optional { text-transform: none; letter-spacing: 0; font-weight: 400; }

/* An empty panel body. .tk-admin__empty is the empty LEDGER and is qualified to a <td>;
   this is the same sentence outside a table. */
.tk-admin__emptynote { color: var(--tk-text-muted); font-size: .85rem; text-align: center;
  margin: 0; padding-block: var(--tk-space-6); }

/* A scrolling list of checkboxes — the model picker in the Add Subscriber dialog, the
   recipient picker in the composer. Both run to every model / every subscriber, so both
   have to cap their height and scroll inside themselves rather than push the form's
   actions off the screen. */
.tk-admin__checklist { display: flex; flex-direction: column; gap: .35rem;
  max-height: 15rem; overflow-y: auto; padding: var(--tk-space-3);
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius); background: var(--tk-page); }
.tk-admin__checklist[hidden] { display: none; }

/* A two-column ledger nested inside a record (the recipients of one blast). The 680px
   floor on .tk-admin__table is for the seven-column desks and would force a horizontal
   scrollbar on an email-plus-verdict pair at any width. */
.tk-admin__table--narrow { min-width: 0; }

/* ---- Demand chart (/admin/subscribers) ----
   Label, track, tally — one grid row per model, so the three columns line up down the
   chart. Not .tk-usage__track/__bar (an AI quota that animates its width) and not
   .tk-fit__track/__bar (a two-value size comparison): this is a static ranked bar and the
   label and the count are part of it. The fill's width is the only inline style on either
   of these two pages and it is an int cast from a ratio, so nothing can be injected there.
   The bar itself is decorative — the model name and the count either side of it are real
   text, so the chart reads with CSS off and to a screen reader. */
.tk-admin__bars { display: flex; flex-direction: column; gap: .5rem; }
.tk-admin__bar { display: grid; grid-template-columns: minmax(0, 9rem) minmax(0, 1fr) auto;
  align-items: center; gap: var(--tk-space-4); }
.tk-admin__barlabel { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: .78rem; color: var(--tk-text); }
.tk-admin__bartrack { height: 8px; border-radius: 4px; background: var(--tk-page);
  border: 1px solid var(--tk-border); overflow: hidden; }
.tk-admin__barfill { display: block; height: 100%; border-radius: 4px; background: var(--tk-burgundy); }
/* "Other" is a free-text write-in, not one of the catalogue's models, so it is the gold
   ink rather than burgundy — the same distinction .tk-admin__pill--warn draws. */
.tk-admin__barfill--other { background: var(--tk-gold-ink); }
.tk-admin__barnum { font-variant-numeric: tabular-nums; font-size: .78rem;
  color: var(--tk-text-muted); text-align: right; min-width: 2ch; }
@media (max-width: 560px) { .tk-admin__bar { grid-template-columns: minmax(0, 6rem) minmax(0, 1fr) auto; } }

/* ---- One email blast in the history ---- */
.tk-admin__blast { padding: var(--tk-space-4); }
.tk-admin__blastmain { flex: 1 1 18rem; min-width: 0; }
.tk-admin__blastmeta { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  margin: 0 0 .35rem; font-size: .75rem; }

/* ---- A disclosure inside a record ----
   The Bootstrap .collapse this replaces needed a Collapse component that no longer exists
   on the page, so the "N recipients" button was dead. <details> is the same disclosure
   with no script at all. The trigger wears .tk-btn tk-btn--ghost tk-btn--sm, whose
   `display: inline-flex` also removes the disclosure triangle everywhere that honours the
   display type; the two rules below cover the engines that do not. */
.tk-admin__reveal { margin-top: var(--tk-space-3); }
.tk-admin__reveal[hidden] { display: none; }
.tk-admin__revealsum { list-style: none; }
.tk-admin__revealsum::-webkit-details-marker { display: none; }
.tk-admin__revealchev { display: inline-flex; transition: transform .25s var(--tk-ease); }
.tk-admin__reveal[open] .tk-admin__revealchev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .tk-admin__revealchev { transition: none; } }

/* ================================================================================
   THE COMPOSER (.tk-mail*)
   /admin/email-compose. The Bootstrap version was a 100vh two-pane app shell —
   `height: calc(100vh - 72px)` with two independently scrolling columns and a fixed
   280px palette — which had no stacked form at all: below about 700px the composer was
   a 280px-wide sliver and unusable. It is an ordinary page now, in the same
   .tk-page__cols--rev main-plus-rail every other admin detail screen uses, so it stacks
   at 860px like they do. What is declared here is only the drag-and-drop: the palette
   card you drag from, the zone you drop into, and the card that appears there.
   ================================================================================ */

/* The palette body. Capped and scrolling inside the rail so a long catalogue cannot make
   the page itself taller than the composer beside it. */
.tk-mail__list { padding: var(--tk-space-4); max-height: min(60vh, 34rem); overflow-y: auto; }
.tk-mail__grouphead { margin-top: var(--tk-space-4); }
.tk-mail__selectors { margin-bottom: var(--tk-space-4); }
.tk-mail__radios { display: flex; flex-wrap: wrap; gap: var(--tk-space-4) var(--tk-space-6); }

/* A draggable card — the palette source and the card in the drop zone. One shape, two
   classes, because only the zone's card carries a remove button and only the palette's is
   copied rather than moved. */
.tk-mail__card, .tk-mail__item { display: flex; align-items: flex-start; gap: .5rem;
  padding: .55rem .7rem; border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-surface); cursor: grab; user-select: none; }
.tk-mail__card + .tk-mail__card, .tk-mail__item + .tk-mail__item { margin-top: .5rem; }
.tk-mail__card:hover, .tk-mail__item:hover { border-color: color-mix(in srgb, var(--tk-burgundy) 35%, var(--tk-border)); }
.tk-mail__card:active, .tk-mail__item:active { cursor: grabbing; }
.tk-mail__card.is-dragging, .tk-mail__item.is-dragging { opacity: .45; }
.tk-mail__grip { flex: 0 0 auto; color: var(--tk-text-muted); margin-top: .1rem; }
.tk-mail__grip svg { width: 14px; height: 14px; }
.tk-mail__cardmain, .tk-mail__itemmain { flex: 1 1 auto; min-width: 0; display: block; }
.tk-mail__cardtitle, .tk-mail__itemtitle { display: block; font-weight: 600; font-size: .82rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tk-mail__cardmeta, .tk-mail__itemmeta { display: flex; flex-wrap: wrap; align-items: center;
  gap: .35rem; margin-top: .15rem; }
/* No `display` on either of these: the reference is toggled with the `hidden` attribute
   on the composer's cloned card, and a block default lets the UA's [hidden] do its job
   unopposed. (The "Pre-owned" chip beside it is a .tk-badge, which DOES set display —
   hence .tk-badge[hidden] above.) */
.tk-mail__cardref, .tk-mail__itemref { color: var(--tk-text-muted); font-size: .7rem; }
.tk-mail__cardprice, .tk-mail__itemprice { display: block; margin-top: .15rem;
  font-family: var(--tk-font-body); font-weight: 700; font-size: .85rem;
  color: var(--tk-accent-text); font-variant-numeric: tabular-nums; }

/* The drop zone. `position: relative` is what the empty-state message is centred against;
   it is NOT a flex column, because its children are laid out by the reorder logic and a
   flex order would fight it. */
.tk-mail__drop { position: relative; min-height: 9rem; padding: var(--tk-space-3);
  border: 2px dashed var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-page); transition: border-color .2s var(--tk-ease); }
.tk-mail__drop.is-over { border-color: var(--tk-burgundy);
  background: color-mix(in srgb, var(--tk-burgundy) 5%, transparent); }
@media (prefers-reduced-motion: reduce) { .tk-mail__drop { transition: none; } }
/* Sets `display: flex` to centre itself in the box, so it needs the [hidden] opt-out —
   without it the placeholder would sit under every card that lands on top of it. */
.tk-mail__dropempty { position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; margin: 0; padding: var(--tk-space-4); text-align: center;
  pointer-events: none; color: var(--tk-text-muted); font-size: .8rem; }
.tk-mail__dropempty[hidden] { display: none; }

/* One row of the recipient checklist: the checkbox and address on the left, the models
   that subscriber asked about on the right. */
.tk-mail__subrow { display: flex; align-items: center; justify-content: space-between;
  gap: .5rem; }
.tk-mail__sublabel { flex: 1 1 auto; min-width: 0; }
.tk-mail__subemail { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: .8rem; }

/* ================================================================================
   ADMIN — THE INVENTORY TOOL (/admin/listings)
   The owner's primary desk and the largest template in the migration: a 46-row ledger
   of every listing plus a four-step create/edit wizard behind it.

   Almost none of it is declared here. The ledger is .tk-admin__table--wide with
   __watch / __thumb / __rowtitle / __rowsub / __rowdate / __num / __pill--* / __empty
   / __cellactions / __rowbtns exactly as the seven table pages left them; the wizard's
   rail is .tk-stepper, its panels are .tk-steppanel, its controls are .tk-field /
   .tk-fieldset / .tk-check / .tk-form, its section grid is .tk-admin__formgrid, its
   label-plus-action rows are .tk-admin__labelrow and its warranty thumbnails are
   .tk-admin__preview / __previewimg — every one of those already in the file. What
   follows is only the three things this page has that no admin page so far did: a
   severity control, an image gallery you reorder, and a listing preview.
   ================================================================================ */

/* ---- The wizard's step rail ----
   .tk-stepper__item sets `display: flex`, an author rule that beats the UA's
   [hidden]{display:none} — the same defect .tk-btn[hidden], .tk-form[hidden],
   .tk-icon[hidden], .tk-stepper[hidden], .tk-badge[hidden] and the .tk-admin* list all
   close. It is load-bearing here: a BRAND-NEW listing has no condition report, so
   showStep() hides step 3's disc outright, and without this the disc would ship visible
   over a step the wizard refuses to open. Declared on the shared class, not on an id,
   because any future stepper with an optional leg hits the same wall. */
.tk-stepper__item[hidden] { display: none; }

/* ---- Invalid control ----
   changeStep() marks every empty [required] field before it will advance, and
   Bootstrap's .is-invalid was the only thing making that visible. Scoped to .tk-admin so
   it out-specifies `.tk-field input` (0,2,0 against 0,1,1) — the same construction and
   the same reasoning as .tk-doc .tk-doc__invalid above. The script sets aria-invalid
   alongside it, so the state is not colour-only. */
.tk-admin .tk-admin__invalid { border-color: var(--tk-burgundy);
  background: color-mix(in srgb, var(--tk-burgundy) 7%, var(--tk-page)); }

/* The reference-preset autofill flashes each field it wrote. The Bootstrap version added
   .bg-secondary for 500ms, which on a light theme would have been a grey smear. */
.tk-admin .tk-admin__flash { border-color: var(--tk-burgundy);
  background: color-mix(in srgb, var(--tk-burgundy) 6%, var(--tk-page)); }

/* ---- A read-only value with its picker beside it ----
   The two warranty-image rows: a filename input the owner never types into, and the
   button that fills it. (.tk-loupe__filerow is the same shape but is scoped to the
   editorial dialogs and carries their label offset.) */
.tk-admin__filerow { display: flex; gap: .4rem; align-items: center; }
.tk-admin__filerow[hidden] { display: none; }
.tk-admin__filerow input { flex: 1 1 auto; min-width: 0; }

/* ---- Upload progress ----
   Sets no `display` on purpose: it is a plain block <div> toggled with the `hidden`
   attribute, so the UA's [hidden] does its job unopposed. The bar inside is never
   hidden — it is emptied by setting its width back to 0. */
.tk-admin__progress { height: 6px; margin-top: .5rem; overflow: hidden;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius-pill);
  background: var(--tk-page); }
.tk-admin__progressbar { display: block; height: 100%; width: 0;
  background: var(--tk-success); transition: width .3s var(--tk-ease); }
@media (prefers-reduced-motion: reduce) { .tk-admin__progressbar { transition: none; } }

/* ---- The gallery you reorder ----
   The main image list. Order is meaning here — position 1 is the listing's hero — so
   each tile carries a left/right pair rather than a drag handle, which is operable by
   keyboard and announced. */
.tk-admin__gallery { display: flex; flex-wrap: wrap; gap: var(--tk-space-4); }
.tk-admin__gallery[hidden] { display: none; }
.tk-admin__galitem { width: 140px; }
.tk-admin__galframe { position: relative; height: 120px; overflow: hidden;
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-page); }
.tk-admin__galframe img { width: 100%; height: 100%; object-fit: cover; }
.tk-admin__galbtns { display: flex; gap: .25rem; margin-top: .35rem; }
.tk-admin__galbtns[hidden] { display: none; }
/* The pair fills the tile's width rather than sitting at 32px in a 140px box. */
.tk-admin__galbtns .tk-iconbtn { flex: 1 1 0; width: auto; height: 28px; }

/* The delete corner on a tile or a zone thumbnail. Deliberately not .tk-iconbtn: that is
   a 32px control and these sit on top of an 52-120px image. */
.tk-admin__thumbdel { position: absolute; top: .25rem; right: .25rem;
  display: grid; place-items: center; width: 20px; height: 20px; padding: 0;
  border: 1px solid var(--tk-border); border-radius: 50%; cursor: pointer;
  background: var(--tk-surface); color: var(--tk-accent-text); }
.tk-admin__thumbdel:hover { border-color: var(--tk-burgundy); background: var(--tk-burgundy); color: #fbeef0; }
.tk-admin__thumbdel:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 1px; }
.tk-admin__thumbdel svg { width: 11px; height: 11px; }

/* ---- The condition report editor ----
   Eight zones, each a severity choice and — once the choice is anything but "none" — a
   note and a set of flaw photos. What the buyer eventually reads is rendered by
   /pre-owned/{slug}; this is the desk it is written at. */
.tk-admin__zone { border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-surface); }
.tk-admin__zone + .tk-admin__zone { margin-top: .5rem; }
.tk-admin__zonehead { display: flex; align-items: center; justify-content: space-between;
  gap: var(--tk-space-3); flex-wrap: wrap; padding: .55rem var(--tk-space-4); }
.tk-admin__zonelabel { font-weight: 600; font-size: .85rem; }
.tk-admin__zonebody { display: flex; flex-direction: column; gap: .5rem;
  padding: 0 var(--tk-space-4) var(--tk-space-4); }
.tk-admin__zonebody[hidden] { display: none; }
.tk-admin__zonephotos { display: flex; flex-wrap: wrap; gap: .5rem; }
.tk-admin__zonephotos[hidden] { display: none; }
.tk-admin__zonethumb { position: relative; width: 52px; height: 52px; }
.tk-admin__zonethumb img { width: 52px; height: 52px; object-fit: cover;
  border-radius: var(--tk-radius); border: 1px solid var(--tk-border); }
.tk-admin__zonethumb .tk-admin__thumbdel { top: -6px; right: -6px; width: 18px; height: 18px; }
/* The "add a photo" tile. A <label> wrapping a hidden file input, so the whole tile is
   the picker; the input carries the accessible name. */
.tk-admin__zoneadd { display: grid; place-items: center; width: 52px; height: 52px;
  cursor: pointer; border: 1px dashed var(--tk-border); border-radius: var(--tk-radius);
  color: var(--tk-text-muted); background: var(--tk-page);
  transition: color .2s var(--tk-ease), border-color .2s var(--tk-ease); }
.tk-admin__zoneadd:hover, .tk-admin__zoneadd:focus-within { color: var(--tk-accent-text);
  border-color: var(--tk-burgundy); }
.tk-admin__zoneadd svg { width: 18px; height: 18px; }
@media (prefers-reduced-motion: reduce) { .tk-admin__zoneadd { transition: none; } }

/* ---- The severity control ----
   Four toggle buttons per zone with aria-pressed, which is the state; the fill is only
   its picture. The four fills are NOT new colours — they are the four already measured
   for .tk-admin__pill--ok / --warn / --info / --danger above (18% success, 12% gold at
   --tk-gold-ink, 10% burgundy at --tk-accent-text, and solid burgundy with ivory ink),
   carried in on --sev-* so the vocabulary of "how bad is it" is one vocabulary. The
   Bootstrap version wrote six inline styles per button from a JS colour table whose four
   entries (#28c864, #f0c040, #fd7e14, #dc3545) were picked for a dark theme and measured
   1.9:1 to 3.4:1 on ivory — every one of them failed AA on the page they now render on. */
.tk-admin__sevrow { display: flex; gap: .3rem; flex-wrap: wrap; }
.tk-admin__sevrow[hidden] { display: none; }
.tk-admin__sev { font: inherit; font-size: .68rem; font-weight: 600; letter-spacing: .03em;
  padding: .25rem .7rem; border-radius: var(--tk-radius-pill); cursor: pointer;
  border: 1px solid var(--tk-border); background: var(--tk-surface);
  color: var(--tk-text-muted);
  transition: color .2s var(--tk-ease), border-color .2s var(--tk-ease); }
.tk-admin__sev:hover { color: var(--tk-text); border-color: var(--tk-burgundy); }
.tk-admin__sev:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 2px; }
.tk-admin__sev[aria-pressed="true"] { background: var(--sev-bg); color: var(--sev-ink);
  border-color: var(--sev-line); }
.tk-admin__sev[data-sev="none"] {
  --sev-bg: color-mix(in srgb, var(--tk-success) 18%, transparent);
  --sev-ink: color-mix(in srgb, var(--tk-success) 62%, var(--tk-text));
  --sev-line: color-mix(in srgb, var(--tk-success) 50%, transparent); }
.tk-admin__sev[data-sev="cosmetic"] {
  --sev-bg: color-mix(in srgb, var(--tk-gold) 12%, transparent);
  --sev-ink: var(--tk-gold-ink);
  --sev-line: color-mix(in srgb, var(--tk-gold) 45%, transparent); }
.tk-admin__sev[data-sev="moderate"] {
  --sev-bg: color-mix(in srgb, var(--tk-burgundy) 10%, transparent);
  --sev-ink: var(--tk-accent-text);
  --sev-line: color-mix(in srgb, var(--tk-burgundy) 35%, transparent); }
.tk-admin__sev[data-sev="significant"] {
  --sev-bg: var(--tk-burgundy); --sev-ink: #fbeef0; --sev-line: var(--tk-burgundy); }
@media (prefers-reduced-motion: reduce) { .tk-admin__sev { transition: none; } }

/* ---- Step 4: the listing preview ----
   A picture of what the buyer will see, so it is laid out like the listing page rather
   than like the form that feeds it. */
.tk-admin__pv { display: grid; gap: var(--tk-space-6); grid-template-columns: minmax(0, 1fr); }
.tk-admin__pv[hidden] { display: none; }
@media (min-width: 720px) { .tk-admin__pv { grid-template-columns: minmax(0, 260px) minmax(0, 1fr); } }
.tk-admin__pvimg { width: 100%; border: 1px solid var(--tk-border);
  border-radius: var(--tk-radius); }
.tk-admin__pvtitle { font-family: var(--tk-font-display); font-weight: 800;
  text-transform: uppercase; letter-spacing: .03em; font-size: 1.15rem; margin: 0; }
.tk-admin__pvprice { font-family: var(--tk-font-body); font-weight: 700; font-size: 1.5rem;
  color: var(--tk-accent-text); font-variant-numeric: tabular-nums; margin: .35rem 0 0; }
.tk-admin__pvmeta { display: flex; flex-wrap: wrap; gap: var(--tk-space-4);
  margin: .5rem 0 0; color: var(--tk-text-muted); font-size: .78rem; }
.tk-admin__pvdesc { margin: var(--tk-space-4) 0 0; font-size: .85rem; line-height: 1.7;
  white-space: pre-wrap; overflow-wrap: anywhere; }
.tk-admin__pvspecs { margin: var(--tk-space-4) 0 0; padding: var(--tk-space-3);
  border: 1px solid var(--tk-border); border-radius: var(--tk-radius);
  background: var(--tk-page); color: var(--tk-text-muted); font-size: .78rem; }

/* ---- The share dialogs ----
   Facebook and Instagram. Wider than the default .tk-modal because the body is a caption
   the owner reads in full plus a grid of up to ten thumbnails. */
.tk-admin__sharemodal { max-width: min(94vw, 680px); }
.tk-admin__sharemodal .tk-modal__body { max-height: 74vh; overflow-y: auto; }
.tk-admin__picker { display: flex; flex-wrap: wrap; gap: .5rem; }
.tk-admin__picker[hidden] { display: none; }
/* One selectable image. A real <button> with aria-pressed, not a bare <img> with an
   onclick: the Bootstrap version was neither focusable nor announced, and its selected
   state was a 3px blue border and nothing else. */
.tk-admin__pick { position: relative; padding: 0; width: 80px; height: 80px; cursor: pointer;
  border: 2px solid var(--tk-border); border-radius: var(--tk-radius); overflow: hidden;
  background: var(--tk-page); }
.tk-admin__pick img { width: 100%; height: 100%; object-fit: cover; }
.tk-admin__pick:focus-visible { outline: 2px solid var(--tk-burgundy); outline-offset: 2px; }
.tk-admin__pick[aria-pressed="true"] { border-color: var(--tk-burgundy); }
/* The tick that says "this one is in the post", so selection is not border-colour alone. */
.tk-admin__pick::after { content: ''; position: absolute; inset: 0;
  background: color-mix(in srgb, var(--tk-burgundy) 22%, transparent); opacity: 0; }
.tk-admin__pick[aria-pressed="true"]::after { opacity: 1; }
.tk-admin__picknum { position: absolute; top: 3px; left: 3px; z-index: 1;
  display: none; place-items: center; min-width: 18px; height: 18px; padding: 0 .3em;
  border-radius: var(--tk-radius-pill); background: var(--tk-burgundy); color: #fbeef0;
  font-size: .6rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.tk-admin__pick[aria-pressed="true"] .tk-admin__picknum { display: grid; }
/* The dialog's error line. Sets no `display`, so [hidden] hides it unopposed. */
.tk-admin__modalerr { margin: var(--tk-space-3) 0 0; font-size: .78rem;
  color: var(--tk-accent-text); }

/* Chat logs — /admin/chat-logs conversation transcripts. */
.tk-chatlog__filters { display: flex; flex-wrap: wrap; gap: .75rem; align-items: end;
  margin-bottom: 1rem; }
.tk-chatlog__thread { border-top: 1px solid var(--tk-border); padding: .5rem 0; }
/* Same trap as .tk-policy__summary (see the note above it): `display: flex` removes the
   marker in Blink/WebKit, so list-style + ::-webkit-details-marker clear the rest and
   .tk-chatlog__marker draws the disclosure state instead. Without it these rows read as
   plain text on the one page whose whole purpose is opening them. */
.tk-chatlog__summary { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center;
  cursor: pointer; padding: .5rem 0; list-style: none; }
.tk-chatlog__summary::-webkit-details-marker { display: none; }
.tk-chatlog__summary:focus-visible { outline: 2px solid var(--tk-accent-text); outline-offset: 2px; }
.tk-chatlog__marker { display: inline-flex; flex: 0 0 auto; margin-left: auto;
  color: var(--tk-text-muted); transition: transform .2s var(--tk-ease); }
.tk-chatlog__marker svg { width: 18px; height: 18px; }
.tk-chatlog__thread[open] > .tk-chatlog__summary .tk-chatlog__marker { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .tk-chatlog__marker { transition: none; } }
.tk-chatlog__when { font-variant-numeric: tabular-nums; color: var(--tk-text-muted);
  font-size: .8125rem; white-space: nowrap; }
.tk-chatlog__preview { flex: 1 1 12rem; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.tk-chatlog__turns { padding: .5rem 0 1rem; display: grid; gap: 1rem; }
.tk-chatlog__turn { border-left: 2px solid var(--tk-border); padding-left: .875rem; }
.tk-chatlog__meta { font-size: .75rem; color: var(--tk-text-muted); margin: 0 0 .375rem; }
.tk-chatlog__q { font-weight: 600; margin: 0 0 .375rem; overflow-wrap: anywhere; }
.tk-chatlog__a { margin: 0; color: var(--tk-text-muted); overflow-wrap: anywhere; }
.tk-chatlog__a--none { font-style: italic; }
.tk-chatlog__pager { display: flex; gap: .75rem; align-items: center;
  justify-content: center; padding-top: 1rem; }
.tk-chatlog__pagenum { font-size: .8125rem; color: var(--tk-text-muted); }
