/* Contact Card — the card's OWN shell only.
 * The shared parts live in card-chrome.css (close X, .uc-sep ruled label, dialog anatomy,
 * .field rows, .detail-preview), which is SYNCED in — never re-type those rules here; edit
 * the canonical file instead. (The reality capsule is not used by this card.)
 * The glass recipe below is the family's card surface, identical to the Reality Card's. */
:root {
  color-scheme: dark;
  font-family: "Inter", system-ui, sans-serif;
  color: #e5e5e5;
  background: transparent;
  /* the shared click ripple reads --accent-green; point it at the plugged reality's hue so the
     press sphere wears the brand like everything else on the card */
  --accent-green: var(--accent, #38ff38);
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { margin: 0; min-width: 280px; background: transparent; }
body { min-height: 0; padding: 34px 1px 1px; }
body.embed { padding: 1px; }
button { font: inherit; }

/* THE card sits at the Card family's width — the Reality Card is 480, the Experience Card
   488. It earns that by stacking (picture + identity over full-width rows) instead of
   putting a column beside the rows, which is what forced the wide alternative to 560. */
.card {
  position: relative;
  width: 100%;
  max-width: 488px;
  margin-inline: auto;
  border: 1px solid rgba(255,255,255,.105);
  border-radius: 18px;
  padding: 14px 16px 16px;
  background: linear-gradient(180deg,rgba(255,255,255,.042) 0%,rgba(26,31,27,.90) 18%,rgba(16,20,17,.92) 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.10), inset 0 -1px 0 rgba(0,0,0,.38), 0 18px 54px rgba(0,0,0,.28);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.liquid-sheen,
.liquid-hairline {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  border-radius: 18px;
}
.liquid-sheen {
  inset: 0;
  background: radial-gradient(80% 54% at 18% 0%,rgba(255,255,255,.11),transparent 58%), radial-gradient(54% 42% at 92% 18%,rgba(255,255,255,.06),transparent 62%);
}
.liquid-hairline {
  inset: 1px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.18), inset 0 0 0 1px rgba(255,255,255,.035);
}

/* everything the card paints sits ABOVE the sheen/hairline layers */
.uc-sep,
.bcard,
.coupon-line,
.details-panel,
.loading,
.error {
  position: relative;
  z-index: 10;
}

/* ---- the business-card body: the brand IMAGE on the left, everything that is words on
   the right (name, title, company, then the contact rows). No reality capsule — a business
   card carries a mark, not a plug. ---- */
.bcard {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 18px;
  align-items: center;
  margin-top: 10px;
}
/* no brand mark resolved (or no reality): the words take the whole card */
.bcard.no-mark { grid-template-columns: minmax(0, 1fr); }

/* ---- STACKED layout (?layout=stacked): picture + identity on top, details full-width
   underneath. Nothing sits side-by-side with the rows, so this one fits the Card family's
   own width (the Reality Card is 480, the Experience Card 488) instead of needing 560. ---- */
.bcard.is-stacked { grid-template-columns: minmax(0, 1fr); gap: 14px; }
.bcard-head {
  display: flex;
  align-items: center;
  gap: 16px;
  min-width: 0;
}
.bcard.is-stacked .brand-shot { flex: 0 0 auto; }
.bcard.is-stacked .person { margin: 0; }

body.layout-side .brand-shot { width: 116px; height: 116px; }
body.layout-side .card { max-width: 560px; }

/* one column below this: the image sits above the words rather than beside them.
   NOT justify-items:start — that shrink-wraps .bcard-main to its content and leaves the
   contact panel a narrow stub in a full-width card. Only the image is start-aligned. */
@media (max-width: 479px) {
  .bcard { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .brand-shot { justify-self: start; }
}

/* CIRCLE — the profile-photo convention every platform shares (FB, Google, IG, LinkedIn, X),
   so it reads as "a person" before anything is read. `cover` centre-crops, which is exactly
   how those platforms fit a portrait photo into a round frame. The slight outline keeps it
   from floating; the accent ring ties it to the plugged brand. */
.brand-shot {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 132px;
  height: 132px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  border: 1px solid rgba(255,255,255,.16);
  border-radius: 50%;
  background: rgba(255,255,255,.025);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.10), 0 0 0 4px rgba(var(--shot-rgb, 56,255,56), .07);
}
.brand-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}
.brand-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font-size: 40px;
  line-height: 1;
}
/* NO REALITY: the person silhouette stands in for the brand mark — quiet grey, drawn in the
   card's own icon style, filling most of the circle like a profile photo would */
.person-fallback { color: rgba(255,255,255,.38); }
.person-fallback svg { width: 62%; height: 62%; }

/* the one-line explanation, on the identity line the reality's name would fill (it replaces
   .person-org in the unhooked state, so it takes that line's spacing). A note, not an error —
   hence quiet grey rather than the accent .person-org wears. */
.no-reality-note {
  display: block;
  margin-top: 11px;
  font-size: 13px;
  color: rgba(229,229,229,.55);
}

.bcard-main { min-width: 0; }
/* a client's name or company can be a single very long word — let it break rather than
   push the whole card sideways */
.person { margin: 0 0 14px; padding-left: 2px; min-width: 0; overflow-wrap: anywhere; }
/* the name is the loudest thing on a business card after the logo */
.person-name {
  display: block;
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 25px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -.01em;
  color: #f2f2f2;
  text-shadow: 0 0 3px #0d100e, 0 0 6px #0d100e;
}
.person-title {
  display: block;
  margin-top: 7px;
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(255,255,255,.78);          /* was .55 — the job title was too dim to read */
}
/* the company — it was on the capsule's shaft before, so without the capsule it has to be
   said in words. Still not authored here: it is whichever reality is plugged in. */
.person-org {
  display: block;
  margin-top: 11px;
  font-size: 14px;
  font-weight: 600;
  color: var(--accent, #38ff38);
  text-shadow: 0 0 12px rgba(var(--accent-rgb, 56,255,56), .35);
}

/* the ruled header is the SHARED .uc-sep (card-chrome). The card only resets the h1
   element margin and keeps its dark-ink shadow so the label stays legible on any host
   background — same rule the Reality Card carries. */
/* The card's own title is deliberately QUIET here (owner 2026-07-29). On the Reality Card the
   accent label is the loudest thing because there is no CTA competing with it; this card has
   a green primary button, and two greens fight. Dim grey, no glow — it names the card and
   then gets out of the way. */
.uc-sep h1.uc-sep-label {
  margin: 0;
  line-height: 1;
  color: rgba(255,255,255,.34);
  text-shadow: none;
}

/* ---- the panel holding the detail rows — IDENTICAL to the Reality Card's, value for
   value. This is the one surface both cards share by eye, so it does not get "tuned" per
   card: a lighter fill here was drift, not design. Lightness is bought with spacing OUTSIDE
   the shared parts (card padding, gaps), never by redefining them. ---- */
.details-panel {
  border: 1px solid rgba(255,255,255,.14);
  border-radius: 16px;
  padding: 4px 14px;
  background:
    radial-gradient(80% 54% at 18% 0%,rgba(255,255,255,.11),transparent 58%),
    radial-gradient(54% 42% at 92% 18%,rgba(255,255,255,.06),transparent 62%),
    rgba(255,255,255,.07);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.18), inset 0 0 0 1px rgba(255,255,255,.035), 0 12px 28px rgba(0,0,0,.12);
}

/* the row anatomy (.field / dt / dd, ink shadow included) is the SHARED card chrome —
   card-chrome.css owns it. Only the dl reset stays local, exactly as on the Reality Card. */
dl { margin: 0; }
/* NOTHING about .field / dt / dd is redefined here. Row pitch, the 96px label gutter, the
   divider and the 14px value size all come from card-chrome.css so this card and the
   Reality Card stay pixel-identical. If a row needs to change, change the canonical file. */


/* The coupon note sits OUTSIDE the card, under it — it is campaign machinery for one
   visit, not part of the card's identity, and printing it inside made the card look like
   it permanently carries a promo. */
.coupon-line {
  max-width: 560px;
  margin: 10px auto 0;
  padding: 0 2px;
  font-size: 13px;
  line-height: 1.5;
  color: rgba(229,229,229,.75);
}
.coupon-line b {
  color: var(--accent, #38ff38);
  font-weight: 700;
  letter-spacing: .06em;
  user-select: all;
  -webkit-user-select: all;
  -webkit-touch-callout: default;
}

/* ---- inside a popup ----
 * The panel, the link rows and the .about-text-block/.about-copy pair all come from the
 * SHARED chrome. The only card-owned bits are the value's selectability and the action row. */
/* the chrome's .about-copy carries a 12px top margin to clear a section label; this card's
   value popups have no label (the dialog header is the title), so that would be dead space */
.about-docs .about-text-block > .about-copy:first-child { margin-top: 0; }
.dlg-value {
  overflow-wrap: anywhere;
  user-select: all;                       /* long-press-to-copy still works on iOS */
  -webkit-user-select: all;
  -webkit-touch-callout: default;
}
/* CENTRED, with a wider gap than a normal row: these two do different things and sat too
   close to tell apart at a glance. 1.1rem is the same widened gap immersive's inventory uses
   between Inspect and Throw for exactly that reason. */
.dlg-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1.1rem;
  margin-top: 18px;
}

/* Copy / Send / Save wear immersive's inventory ACTION PILL — the Inspect/Throw shape the
   owner pointed at: full pill (5rem), 1.5px #577757 border, #161616 fill, Poppins uppercase
   with letter-spacing, an icon-then-label flex row on a 0.375rem gap, and on hover/press the
   border flips to green with the fill left alone. Font size is stepped down from immersive's
   17px because two of these must sit side by side inside a 544px popup, not across a screen. */
.btn-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .375rem;
  min-height: 44px;
  padding: .75rem 1.6rem;
  border: 1.5px solid #577757;
  border-radius: 5rem;
  background: #161616;
  color: #e5e5e5;
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 15px;
  font-weight: 500;
  line-height: 1rem;
  letter-spacing: 1px;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: none;
  /* NO transform here — the shared clickRipple press-pop owns transform */
  transition: border-color .2s ease, background-color .2s ease, color .2s ease;
}
/* the glyph sits on the text baseline row, never absolutely placed (immersive's .btn-cta rule) */
.btn-action .btn-icon { display: inline-flex; align-items: center; flex: 0 0 auto; }
.btn-action svg { color: currentColor; }
@media (hover: hover) and (pointer: fine) {
  .btn-action:hover { border-color: var(--accent, #38ff38); }
}
.btn-action:active { border-color: var(--accent, #38ff38); }
.btn-action:focus-visible {
  outline: none;
  border-color: var(--accent, #38ff38);
  box-shadow: 0 0 0 2px rgba(var(--accent-rgb, 56,255,56), .7);
}

/* ---- THE reveal control — the card's ONE button.
 * The project's OUTLINE button (websites BUTTON_OUTLINE_CLASS / portal ui.tsx): pill geometry,
 * dark fill, #577757 border, green border + glow on hover. Chosen over the solid green CTA
 * (owner 2026-07-29) because this card's loudest green is already the reality's mark, and two
 * greens fought for the eye. The blink below carries the invitation the CTA's fill used to.
 * It retires the moment the values are on screen. ---- */
.reveal-row { display: flex; justify-content: center; margin-top: 16px; }
.reveal-all {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 30px;
  border: 1px solid #577757;
  border-radius: 999px;
  background: #101312;
  color: #e5e5e5;
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .08em;
  cursor: pointer;
  box-shadow: inset 0 0 0 1px rgba(87,119,87,.18), 0 10px 30px rgba(0,0,0,.30);
  /* transform is deliberately NOT transitioned: the shared press-pop animates transform too,
     and easing it over 300ms makes the button wobble as the press releases. Each state owns
     transform instantly instead. (Centring is safe — .reveal-row uses flex justify-content,
     never a transform, so press-pop has nothing to clobber.) */
  transition: border-color .2s ease, background-color .2s ease, box-shadow .3s ease;
}
@media (hover: hover) and (pointer: fine) {
  .reveal-all:hover {
    border-color: var(--accent, #38ff38);
    background: #121814;
    box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb, 56,255,56), .18), 0 0 18px rgba(var(--accent-rgb, 56,255,56), .10), 0 14px 36px rgba(0,0,0,.36);
  }
}
.reveal-all:active { transform: scale(.985); }
.reveal-all:focus-visible {
  outline: none;
  border-color: var(--accent, #38ff38);
  box-shadow: 0 0 0 2px rgba(22,22,22,.70), 0 0 0 5px rgba(var(--accent-rgb, 56,255,56), .7);
}
@media (prefers-reduced-motion: reduce) {
  .reveal-all:active { transform: none; }
}

/* The attention cue: a BLINK on the button's own border + glow — never the fill, because a
   dark button flashing green fill reads as a tab switching. Hover cancels it: once the pointer
   is on the control it has already done its job. */
.reveal-all.is-blinking { animation: revealBlink 1.6s ease-in-out infinite; }
/* The press-pop has to OUTRANK the blink. Both drive the `animation` property, and
   `.reveal-all.is-blinking` (two classes) beats the ripple's own single-class `.ripple-on`,
   so without this the button got the ripple sphere but never the press scale. Re-declaring it
   here restores the pop; the blink resumes when the 140ms pop ends. */
.reveal-all.is-blinking.ripple-on { animation: press-pop var(--press-duration, 140ms) ease-out; }
@keyframes revealBlink {
  0%, 100% {
    border-color: #577757;
    box-shadow: inset 0 0 0 1px rgba(87,119,87,.18), 0 10px 30px rgba(0,0,0,.30);
  }
  50% {
    border-color: var(--accent, #38ff38);
    box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb, 56,255,56), .30), 0 0 22px rgba(var(--accent-rgb, 56,255,56), .28), 0 10px 30px rgba(0,0,0,.30);
  }
}
@media (hover: hover) and (pointer: fine) {
  .reveal-all.is-blinking:hover { animation: none; }
}
@media (prefers-reduced-motion: reduce) {
  .reveal-all.is-blinking { animation: none; }
}

/* the masked stand-in before the reveal — says "there is something here", leaks nothing */
.val-masked { color: rgba(255,255,255,.28); letter-spacing: .12em; font-size: 13px; }

/* the caret rides the end of the value while it types, exactly one character wide */
.val.is-typing::after {
  content: "";
  display: inline-block;
  width: 1px;
  height: 1em;
  margin-left: 2px;
  vertical-align: -.12em;
  background: var(--accent, #38ff38);
  animation: caret-blink 1s steps(1, end) infinite;
}
@keyframes caret-blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }

/* ---- the QR, faded up under the details once the values have landed ---- */
.qr-slot {
  display: flex;
  justify-content: center;
  margin-top: 14px;
  opacity: 0;
  transform: translateY(6px);
  /* long enough to still be fading while the values type in — one continuous beat */
  transition: opacity .8s ease, transform .8s ease;
}
.qr-slot.is-in { opacity: 1; transform: none; }
.qr-figure {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 12px 12px 10px;
  border: 1px solid rgba(255,255,255,.12);
  border-radius: 14px;
  background: rgba(255,255,255,.04);
}
/* the code itself must stay high-contrast and unscaled-blurry — a scanner is unforgiving */
.qr-figure canvas, .qr-figure svg {
  display: block;
  width: 132px;
  height: 132px;
  image-rendering: pixelated;
  border-radius: 4px;
}
.qr-cap {
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(255,255,255,.5);
}

/* (.contact-value / .contact-value .val / .copy-btn were removed 2026-07-29 — no markup has
   emitted those classes since the row became .detail-preview and the actions became
   .btn-action. Dead selectors invite the next pass to style something invisible.) */

.loading, .error { margin: 12px 0 2px; font-size: 13px; color: rgba(229,229,229,.6); }
.error { color: #ff8a8a; }
.empty { color: rgba(255,255,255,.35); }

/* The Links popup body is the SHARED chrome now (.links-section / .info-links / .info-link*),
   built by card-chrome.mjs renderLinkRows(). The hand-rolled .link-list this card used to
   carry is gone — that was the drift that made its Links popup differ from the Reality Card's. */

@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }
