/* ===========================================================================
   Setu — design system
   ---------------------------------------------------------------------------
   The visual language borrows from the printed marriage biodata: ruled
   label/value rows, mono field labels, hairline dividers, a formal serif for
   names. That structure does real work — because the UI already reads as a
   document, the public/private split reads as redacted fields, which is
   exactly what it is.

   Two products, one system: the palette swaps on [data-mode="connect"].
   Master specification, chapter 19.
   =========================================================================== */

/* ============ TOKENS ============
   Two products in one shell: matrimonial (alta red on warm paper)
   and Connect (botanical teal on cool paper). Theme x mode = 4 sets. */
/*
 | THE PALETTE. Eight colours, each with one job:
 |
 |   #1F020B  Black Rose      body copy and headlines
 |   #3F0B1A  Dark Maroon     footer, sidebar, dropdowns
 |   #5F1328  Wine Red        secondary headings
 |   #7A1F3D  Burgundy        branding, header            (primary)
 |   #912F4D  Claret          card borders
 |   #A83F5D  Deep Rose       action buttons              (primary)
 |   #C34F70  Blush Pink      button hover
 |   #FFF0F3  Lavender Blush  page background
 |
 | Two of those are used at full strength only where the list says. A card
 | border at solid #912F4D on every card would fence the page in, so --line
 | is that hue at low alpha and --line-strong is the colour itself, used
 | where a border is meant to be seen.
 |
 | The gradients are all one hue pair — Burgundy into Deep Rose — travelling
 | the same 135deg. Mixing hues would give each surface its own light source;
 | one ramp reused everywhere reads as a single material.
 */
:root{
  /* Nothing here is pure white, and the page itself sits a shade below the
     cards on it.

     It used to be #FFFFFF on #FFF0F3 — a full-brightness sheet, which on a
     large screen in a normally lit room is a lamp pointed at the reader.
     Every value is a few percent down from where it was; the ORDER is
     untouched, so cards still lift off the ground and the two tinted
     surfaces still sit behind them. Dark mode was never the complaint and
     is not touched. */
  --ground:#F7E7EC; --surface:#FDF7F9; --surface-2:#F2DDE4; --surface-3:#EACFD9;
  --ink:#1F020B; --body:#3F0B1A; --muted:#7A5764;
  --line:rgba(145,47,77,.20); --line-strong:#912F4D;
  --brand:#7A1F3D; --brand-deep:#5F1328; --brand-tint:#FCE7EC; --on-brand:#FFF0F3;
  /* Buttons are Deep Rose, not the branding Burgundy — the palette gives the
     two different jobs, and a call to action that matches the header stops
     looking like a call to action. */
  --action:#A83F5D; --action-hover:#C34F70;
  --gold:#A83F5D; --gold-tint:#FCE7EC;
  --grad-brand:linear-gradient(135deg, #7A1F3D 0%, #A83F5D 100%);
  --grad-brand-hover:linear-gradient(135deg, #8E2649 0%, #C34F70 100%);
  --grad-surface:linear-gradient(180deg, #F7E7EC 0%, #FDF7F9 100%);
  --ok:#2F6B57; --warn:#8A5B0E; --bad:#A02226;
  --shadow:0 1px 0 rgba(31,2,11,.04), 0 8px 24px -16px rgba(31,2,11,.26);
  /* Connect's own colour, used where the two products sit side by side. */
  --connect-brand:#1B5249;
  /* The front-page hero panel: a flat ground with a fine dot grid. Kept
     as tokens rather than literals in .home-hero because the dark value
     has to be declared in all three theme branches below.

     The ground is the same rose the globe's halo throws off — brand-tint,
     give or take — so the panel reads as an extension of the artwork
     rather than a grey box the globe happens to sit on. The dots are then
     plain ink rather than pink: a pink dot on a pink ground is a dot you
     cannot see, which is how the grid disappeared the first time. */
  --hero-ground:#F0DFE3; --globe-edge:#B98BA5; --hero-dot:rgba(58,24,32,.20);
}
:root[data-mode="connect"]{
  --ground:#EAF2EF; --surface:#F8FCFA; --surface-2:#DFEBE7; --surface-3:#D2E1DC;
  --ink:#10201D; --body:#354744; --muted:#697B77;
  --line:#D6E3DF; --line-strong:#B4C8C3;
  --brand:#1B5249; --brand-deep:#0F3A32; --brand-tint:#E3F0EC; --on-brand:#F4FBF9;
  --gold:#8A7434; --gold-tint:#F0EDDF;
  /* The button gradient, which the Connect palette never overrode — so
     every .btn on a Connect surface painted itself in the marriage side's
     wine, on the screens whose whole job is to say these are two products. */
  --grad-brand:linear-gradient(135deg, #1B5249 0%, #2F7A6C 100%);
  --grad-brand-hover:linear-gradient(135deg, #23675B 0%, #3E9384 100%);
  --shadow:0 1px 0 rgba(16,32,29,.04), 0 8px 24px -16px rgba(16,32,29,.22);
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    /* Dark is the same eight colours with the roles turned over: Black Rose
       becomes the ground, Lavender Blush becomes the text, and the two
       lightest tints carry brand and action, because Burgundy on near-black
       fails contrast outright. */
    --ground:#150109; --surface:#25060F; --surface-2:#310917; --surface-3:#3F0B1A;
    --ink:#FFF0F3; --body:#EBCDD6; --muted:#B08C99;
    --line:rgba(195,79,112,.24); --line-strong:#912F4D;
    --brand:#D9718C; --brand-deep:#EC93A9; --brand-tint:#3F0B1A; --on-brand:#1F020B;
    --action:#C34F70; --action-hover:#D9718C;
    --gold:#C34F70; --gold-tint:#3F0B1A;
    --grad-brand:linear-gradient(135deg, #912F4D 0%, #C34F70 100%);
    --grad-brand-hover:linear-gradient(135deg, #A83F5D 0%, #D9718C 100%);
    --grad-surface:linear-gradient(180deg, #1F020B 0%, #25060F 100%);
    --ok:#63B594; --warn:#D2A254; --bad:#E27C80;
    --shadow:0 1px 0 rgba(0,0,0,.3), 0 10px 28px -18px rgba(0,0,0,.85);
    --connect-brand:#6FBFAE;
    --hero-ground:#33191F; --globe-edge:#8E6A84; --hero-dot:rgba(255,255,255,.16);
  }
  :root:not([data-theme="light"])[data-mode="connect"]{
    --ground:#0D1615; --surface:#15211F; --surface-2:#1D2B28; --surface-3:#243530;
    --ink:#E7F0ED; --body:#C4D4D0; --muted:#849692;
    --line:#24352F; --line-strong:#38504A;
    --brand:#6FBFAE; --brand-deep:#8CD3C2; --brand-tint:#16302B; --on-brand:#0B1917;
    --gold:#C9AE6E; --gold-tint:#2A2718;
  }
}
:root[data-theme="dark"]{
  /* Dark is the same eight colours with the roles turned over: Black Rose
     becomes the ground, Lavender Blush becomes the text, and the two
     lightest tints carry brand and action, because Burgundy on near-black
     fails contrast outright. */
  --ground:#150109; --surface:#25060F; --surface-2:#310917; --surface-3:#3F0B1A;
  --ink:#FFF0F3; --body:#EBCDD6; --muted:#B08C99;
  --line:rgba(195,79,112,.24); --line-strong:#912F4D;
  --brand:#D9718C; --brand-deep:#EC93A9; --brand-tint:#3F0B1A; --on-brand:#1F020B;
  --action:#C34F70; --action-hover:#D9718C;
  --gold:#C34F70; --gold-tint:#3F0B1A;
  --grad-brand:linear-gradient(135deg, #912F4D 0%, #C34F70 100%);
  --grad-brand-hover:linear-gradient(135deg, #A83F5D 0%, #D9718C 100%);
  --grad-surface:linear-gradient(180deg, #1F020B 0%, #25060F 100%);
  --ok:#63B594; --warn:#D2A254; --bad:#E27C80;
  --shadow:0 1px 0 rgba(0,0,0,.3), 0 10px 28px -18px rgba(0,0,0,.85);
  --connect-brand:#6FBFAE;
  --hero-ground:#33191F; --globe-edge:#8E6A84; --hero-dot:rgba(255,255,255,.16);
}
:root[data-theme="dark"][data-mode="connect"]{
  --ground:#0D1615; --surface:#15211F; --surface-2:#1D2B28; --surface-3:#243530;
  --ink:#E7F0ED; --body:#C4D4D0; --muted:#849692;
  --line:#24352F; --line-strong:#38504A;
  --brand:#6FBFAE; --brand-deep:#8CD3C2; --brand-tint:#16302B; --on-brand:#0B1917;
  --gold:#C9AE6E; --gold-tint:#2A2718;
}

/* Native controls are drawn by the BROWSER, not by this stylesheet: the
   date field's calendar icon, select arrows, checkboxes, scrollbars, the
   picker panel itself. Without color-scheme the browser draws all of them
   for a light page, which is why the calendar icon was invisible against a
   dark form — dark glyph on a dark field.

   Declared for every theme branch the tokens above define, so the browser's
   idea of the page always matches ours. */
:root{ color-scheme:light }
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){ color-scheme:dark }
}
:root[data-theme="dark"]{ color-scheme:dark }
:root[data-theme="light"]{ color-scheme:light }

/* The calendar glyph specifically. color-scheme fixes its colour, but on a
   tinted field it can still sit at low contrast, so give it the field's own
   text colour and a target big enough to hit. */
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator{
  cursor:pointer; opacity:.75; padding:4px; border-radius:3px;
}
input[type="date"]::-webkit-calendar-picker-indicator:hover,
input[type="time"]::-webkit-calendar-picker-indicator:hover,
input[type="datetime-local"]::-webkit-calendar-picker-indicator:hover{
  opacity:1; background:var(--surface-2);
}

/* ============ TYPOGRAPHY TOKENS ============
   Declared as variables so the appearance screen has something to change.
   These are the defaults; whatever an administrator sets is emitted in the
   page head, after this file, and wins on cascade order alone — no
   !important, no specificity games.

   The Bengali stack is deliberately NOT settable. Latin display faces
   mostly have no Bengali glyphs, so letting it be swapped would render
   half the site in a fallback nobody chose. */
:root{
  --font-head:Newsreader,"Noto Serif Bengali",Georgia,serif;
  /* The same face as the headings, for everything.
     This is a deliberate change from the pairing the design system started
     with — a serif display face for headlines over a sans for body copy. One
     face throughout is a stronger, quieter look, and it is what was asked
     for. The sans is kept in the stack below the serif so anything the serif
     cannot render still lands somewhere sensible rather than on Times. */
  --font-body:Newsreader,"Noto Serif Bengali","IBM Plex Sans","Hind Siliguri",Georgia,serif;
  /* The name is now a role, not a description: this is the "small
     letterspaced label" token, and it is the same serif as everything else.
     Kept as its own token rather than folded into --font-body so those
     labels can be given a different face again in one edit. */
  --font-mono:Newsreader,"Noto Serif Bengali",Georgia,serif;
  --font-bn:"Hind Siliguri","IBM Plex Sans",sans-serif;
  --font-size:15px;
  --w-body:400;
  --w-head:500;
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; background:var(--ground); color:var(--body);
  font-family:var(--font-body);
  font-size:var(--font-size); font-weight:var(--w-body); line-height:1.6;
  transition:background .45s ease, color .45s ease;
}
:lang(bn){font-family:var(--font-bn); line-height:1.85}
h1,h2,h3,h4{font-family:var(--font-head); color:var(--ink); font-weight:var(--w-head); margin:0; text-wrap:balance; letter-spacing:-.01em}
p{margin:0}
/*
 | No underline by default.
 |
 | This line said only `color:inherit`, and that omission is why twenty-odd
 | rules further down each repeat `text-decoration:none`: every link in the
 | product — tabs, cards, navigation, the buttons that are really anchors —
 | arrived wearing the browser's default underline and had to take it off
 | one at a time. Anything that missed the memo simply kept it, which is why
 | the profile tabs and the "see what others see" line were underlined and
 | nothing else on the screen was.
 |
 | Turning it off globally is safe HERE and would not be in most codebases.
 | It is safe because this product has almost no running prose with a link
 | inside a sentence: a link that is its own paragraph, or its own tab, or
 | its own card, is identified by position and role, not by a line under it.
 | The exception is handled immediately below.
 */
a{color:inherit; text-decoration:none}

/*
 | The exception: a link INSIDE a sentence.
 |
 | There is one — the support address in the legal pages — and it has to stay
 | distinguishable from the words around it by something other than its
 | colour, because colour alone is exactly what a reader with a colour vision
 | deficiency does not have (WCAG 1.4.1). So prose keeps its underline; it
 | just wears a better one than the browser's, sitting clear of the
 | descenders instead of cutting through them.
 */
.legal-body a,
.legal-foot a{
  text-decoration:underline;
  text-decoration-thickness:1px;
  text-underline-offset:.18em;
  text-decoration-color:color-mix(in srgb, currentColor 45%, transparent);
}
.legal-body a:hover,
.legal-foot a:hover{text-decoration-color:currentColor}

/*
 | Standalone text links — one to a line, under a heading or beside a label.
 | The underline was their only "this is clickable" cue at rest, and taking
 | it away without giving anything back would leave coloured text that does
 | nothing until you happen to point at it. So it returns on hover: clean
 | while you read, unmistakable the moment you reach for it.
 */
.hd .sub a:hover,
.text-link:hover{
  text-decoration:underline;
  text-decoration-thickness:1px;
  text-underline-offset:.18em;
}
button,input,select,textarea{font:inherit; color:inherit}
:focus-visible{outline:2px solid var(--brand); outline-offset:2px; border-radius:2px}
@media (prefers-reduced-motion: reduce){*{animation-duration:.01ms !important; transition-duration:.01ms !important}}

/* ============ PRIMITIVES ============ */
.mono{font-family:var(--font-mono); font-variant-numeric:tabular-nums}
.lbl{font-family:var(--font-mono); font-size:10.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--muted)}
.serif{font-family:var(--font-head)}
.bn{font-family:var(--font-bn)}
.muted{color:var(--muted)}
.sm{font-size:13px}
.xs{font-size:12px}
.b{font-weight:600; color:var(--ink)}
.rule{height:1px; background:var(--line); border:0; margin:0}

.card{background:var(--surface); border:1px solid var(--line); border-radius:3px}
.pad{padding:18px 20px}
.stack{display:flex; flex-direction:column}
.row{display:flex; align-items:center}
.wrap{flex-wrap:wrap}
.g4{gap:4px}.g6{gap:6px}.g8{gap:8px}.g10{gap:10px}.g12{gap:12px}.g16{gap:16px}.g20{gap:20px}.g24{gap:24px}.g32{gap:32px}
.grow{flex:1}
.center{align-items:center}
.between{justify-content:space-between}

.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:7px;
  padding:9px 16px; border-radius:3px; border:1px solid transparent;
  background:var(--brand); color:var(--on-brand); font-size:13.5px; font-weight:500;
  cursor:pointer; text-decoration:none; transition:background .15s, transform .08s, border-color .15s;
}
.btn:hover{background:var(--brand-deep)}
.btn:active{transform:translateY(1px)}
.btn.ghost{background:transparent; color:var(--ink); border-color:var(--line-strong)}
.btn.ghost:hover{background:var(--surface-2); border-color:var(--brand)}
.btn.quiet{background:transparent; color:var(--muted); border-color:transparent; padding:6px 10px}
.btn.quiet:hover{color:var(--ink); background:var(--surface-2)}

/* A button that offers help rather than performing an action.
   Gold, because that is what this system already uses for advice a reader
   should notice but not obey — the .note component and the dashboard's
   verification band are the same colour for the same reason. It has to be
   VISIBLE: "Photo rules" was .quiet, which is transparent on transparent
   with muted text, and in dark mode it read as a disabled label rather than
   as the one way into the guidelines. It must not shout over the primary
   action beside it either, which is why it is a tint and not the ramp. */
.btn.hint{
  background:var(--gold-tint); color:var(--gold);
  border-color:color-mix(in srgb, var(--gold) 38%, transparent);
  font-weight:600;
}
.btn.hint:hover{
  background:var(--gold); color:var(--on-brand);
  border-color:var(--gold);
}
.btn.hint .ic-svg{opacity:.9}
.btn.sm{padding:6px 11px; font-size:12.5px}
.btn.lg{padding:12px 22px; font-size:15px}
.btn.full{width:100%}
.btn.danger{background:transparent; color:var(--bad); border-color:var(--line-strong)}
.btn.danger:hover{background:var(--bad); color:#fff; border-color:var(--bad)}
.btn[disabled]{opacity:.4; cursor:not-allowed}

.chip{display:inline-flex; align-items:center; gap:5px; padding:3px 9px; border-radius:2px;
  background:var(--surface-2); border:1px solid var(--line); font-size:11.5px; color:var(--body)}
.chip.brand{background:var(--brand-tint); border-color:transparent; color:var(--brand-deep)}
.chip.gold{background:var(--gold-tint); border-color:transparent; color:var(--gold)}
.chip.ok{background:color-mix(in srgb, var(--ok) 12%, transparent); border-color:transparent; color:var(--ok)}
.chip.bad{background:color-mix(in srgb, var(--bad) 12%, transparent); border-color:transparent; color:var(--bad)}
.chip.lock{background:transparent; border-style:dashed; color:var(--muted)}

.field{display:flex; flex-direction:column; gap:5px}
.field > label{font-family:var(--font-mono); font-size:10.5px; letter-spacing:.13em; text-transform:uppercase; color:var(--muted)}
.inp{
  padding:9px 11px; background:var(--surface); border:1px solid var(--line-strong); border-radius:3px;
  font-size:14px; width:100%; transition:border-color .15s;
}
.inp:focus{border-color:var(--brand); outline:none}
select.inp{appearance:none; background-image:linear-gradient(45deg,transparent 50%,var(--muted) 50%),linear-gradient(135deg,var(--muted) 50%,transparent 50%);
  background-position:calc(100% - 16px) 50%, calc(100% - 11px) 50%; background-size:5px 5px,5px 5px; background-repeat:no-repeat; padding-right:32px}
.hint{font-size:12px; color:var(--muted)}

/* biodata-sheet style label/value rows — the core visual idea */
.sheet{display:grid; grid-template-columns:minmax(120px,150px) 1fr; border-top:1px solid var(--line)}
.sheet > dt{padding:9px 0; border-bottom:1px solid var(--line); font-family:var(--font-mono);
  font-size:10.5px; letter-spacing:.12em; text-transform:uppercase; color:var(--muted); align-self:center}
.sheet > dd{padding:9px 0; border-bottom:1px solid var(--line); margin:0; color:var(--ink); font-size:14px}
.sheet > dd.locked{color:var(--muted); font-style:italic}

/* photo tile — blurred monogram, which is the real product default */
.ph{position:relative; overflow:hidden; background:var(--surface-2); border:1px solid var(--line);
  display:grid; place-items:center; border-radius:3px; aspect-ratio:1; color:var(--ink)}
/* The photograph, laid over the silhouette that is always drawn beneath it.
   Absolute rather than a grid child: .ph is display:grid, so two in-flow
   children would become two rows and the placeholder would push the photo
   down the tile instead of sitting behind it.

   No z-index. It needs to cover the figure and to stay UNDER two things that
   are painted after it — .ph.blur::after's hatch and .lockbadge — and plain
   DOM order already says exactly that. */
.ph-img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.ph .mono-init{font-family:var(--font-head); font-size:calc(var(--sz,72px)*.42); letter-spacing:.02em; opacity:.9}
.ph.blur .mono-init{filter:blur(9px); opacity:.55}
.ph.blur::after{content:""; position:absolute; inset:0;
  background:repeating-linear-gradient(45deg,transparent,transparent 7px,color-mix(in srgb,var(--line) 55%,transparent) 7px,color-mix(in srgb,var(--line) 55%,transparent) 8px)}
.ph .lockbadge{position:absolute; bottom:5px; left:5px; z-index:2; font-size:10px; padding:2px 6px;
  background:color-mix(in srgb,var(--surface) 88%,transparent); border:1px solid var(--line); border-radius:2px; color:var(--muted)}

/* ============ APP SHELL ============ */
/* One column. This was two — a 238px rail and the content — until the rail
   was replaced by groups in the top bar. .app-topnav is kept as a class
   because thirty templates and the layout still name it, and it now means
   the same thing as .app. */
.app{display:grid; grid-template-columns:1fr; min-height:100vh}
.mark{display:flex; align-items:center; gap:9px; text-decoration:none}
.mark .glyph{width:28px; height:28px; border-radius:3px; background:var(--brand); color:var(--on-brand);
  display:grid; place-items:center; font-family:var(--font-head); font-size:16px; transition:background .45s ease}
.mark .wordmark{font-family:var(--font-head); font-size:19px; color:var(--ink); letter-spacing:-.01em}
.mark .modetag{font-family:var(--font-mono); font-size:9.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--brand)}
.main{min-width:0; display:flex; flex-direction:column}
.view{padding:26px 22px 64px; max-width:1080px; width:100%; margin:0 auto; animation:rise .34s cubic-bezier(.2,.7,.3,1)}
@keyframes rise{from{opacity:0; transform:translateY(7px)} to{opacity:1; transform:none}}

.wall-flash{position:fixed; inset:0; z-index:999; pointer-events:none; background:var(--brand);
  opacity:0; animation:wipe .62s cubic-bezier(.4,0,.2,1)}
@keyframes wipe{0%{opacity:0; clip-path:inset(0 100% 0 0)} 35%{opacity:.92; clip-path:inset(0 0 0 0)} 100%{opacity:0; clip-path:inset(0 0 0 0)}}

/* The toast used to be a single centred pill and its rule lived here. It was
   replaced by the .toasts stack further down, but the old rule was left
   behind and went on positioning the new markup: `position:fixed; left:50%`
   put the notice's left edge in the middle of the screen, and the -50%
   translate that used to correct that was overwritten by the new animation's
   end state. So the notice sat across the middle of the page, on top of the
   form it was describing, while its own container sat empty in the corner.

   Deleted rather than adjusted. Two rules for one class, in two parts of the
   file, is how it went wrong in the first place; .toasts owns the position
   now, and the @keyframes pop that only this rule used went with it. */

/* ============ PAGE PIECES ============ */
.hd{display:flex; flex-direction:column; gap:7px; margin:0 0 24px}
.hd h1{font-size:clamp(2.1rem, 3.2vw, 2.9rem); line-height:1.08; letter-spacing:-.025em}
.hd .sub{color:var(--muted); font-size:15.5px; line-height:1.6; max-width:62ch}

.home-hero{
  display:grid; grid-template-columns:1.15fr .85fr; gap:26px 30px; align-items:center;
  background:
    radial-gradient(circle at 1px 1px, var(--hero-dot) 1.15px, transparent 1.25px) 0 0/34px 34px,
    var(--hero-ground);
  border:1px solid var(--line); border-radius:22px; padding:32px; box-shadow:var(--shadow);
  position:relative; overflow:hidden;
}
.home-hero.no-art{grid-template-columns:1fr}
.hero-copy{position:relative; z-index:1}
.hero-badge{
  display:inline-flex; align-items:center; gap:8px; padding:7px 12px; border-radius:999px;
  background:var(--surface); border:1px solid var(--line); color:var(--brand-deep); font-size:11px;
  font-family:var(--font-mono); letter-spacing:.08em; text-transform:uppercase;
}
.hero-copy h1{
  font-size:clamp(2.4rem, 4vw, 4.4rem); line-height:1.02; margin-top:12px; letter-spacing:-.04em;
  max-width:620px;
}
.hero-copy .sub{margin-top:12px; max-width:560px; font-size:1.05rem; color:var(--body)}
/* Centred, because the panel above them already is.
   .globe-panel is margin:0 auto and has been all along, so the buttons and
   the figures were the only things in this column hugging its left edge —
   one centred block with two left-aligned ones under it, which reads as a
   layout nobody finished rather than as a choice. The whole column now
   shares one axis. */
.hero-actions{display:flex; justify-content:center; gap:12px; flex-wrap:wrap; margin-top:20px}
/* ---- the three figures, as a table ----
   A flex row of stacked divs became a real table, on request. The look is
   the one this file already uses for tabular data — see .tbl, which the
   admin console is built from: a mono, letterspaced, muted column heading
   over a rule, and the value beneath it. Reusing that idiom rather than
   inventing a second one is most of what "professional" means here; the
   difference is only that these values are display type, because on a
   marketing page the figure is the point and in a console the heading is.

   Not .tbl itself. That rule is sized for dense rows of console data —
   13.5px, tight padding — and the three numbers on a hero panel are the
   opposite job. Sharing the class would have meant overriding nearly every
   declaration in it, which is a worse kind of reuse than none. */
.statstable{
  width:100%;
  /* Wide enough for three columns of display type, narrow enough that it
     does not run the width of the panel and stop reading as a group. */
  max-width:480px;
  /* auto sides: the table is centred on the same axis as the globe and the
     buttons above it. */
  margin:26px auto 0;
  border-collapse:collapse;
  /* Equal thirds regardless of what an administrator types into them: the
     columns must not resize when "500K+" becomes "1.2M+". */
  table-layout:fixed;
}
/* Centred in their columns, not just as a block.
   A table centred on the page whose contents are still ragged left reads as
   a centred thing full of left-aligned things. Each column is one heading
   over one figure, so the pair centres on the column's own axis and the
   three read as three equal units — which is what they are. */
.statstable th{
  text-align:center;
  font-family:var(--font-mono);
  font-size:10px; letter-spacing:.12em; text-transform:uppercase;
  color:var(--muted); font-weight:400;
  /* Symmetric now. The old 14px was a left indent, and any left-only
     padding pushes centred text off the centre it is measured against. */
  padding:0 10px 8px;
  border-bottom:1px solid var(--line-strong);
  /* All three headings sit ON the rule, not floating in the middle of it.
     A translated label is not the same number of lines as its neighbours —
     "Success stories" wraps to two on a phone where "Members" does not —
     and the default middle alignment leaves the short ones hovering half a
     line above the rule the long one is resting on. */
  vertical-align:bottom;
}
.statstable td{
  text-align:center;
  padding:14px 10px 0;
  font-family:var(--font-head);
  font-size:clamp(1.6rem, 2.6vw, 2.1rem);
  line-height:1;
  color:var(--ink);
  /* The figures are read as a set, so their digits line up as one. */
  font-variant-numeric:tabular-nums lining-nums;
}
/* Hairlines between the columns, and none outside them: the group is
   divided, not boxed. A border-box around three numbers on glass reads as a
   widget pasted onto the panel. */
.statstable th + th,
.statstable td + td{ border-left:1px solid var(--line) }
/* The montage. Brides and grooms who made their photograph public — the
   single strongest thing a matrimonial front page can show. */
.hero-art{position:relative; z-index:1; display:flex; flex-direction:column; gap:12px}
.hero-collage{display:grid; grid-template-columns:1fr 1fr; gap:14px}
.collage-tile{
  display:block; text-decoration:none; color:inherit; background:var(--surface);
  border:1px solid var(--line); border-radius:16px; overflow:hidden; box-shadow:var(--shadow);
  transition:transform .18s ease, border-color .18s ease;
}
.collage-tile:nth-child(odd){transform:translateY(-12px)}
.collage-tile:nth-child(even){transform:translateY(12px)}
.collage-tile:nth-child(odd):hover{transform:translateY(-16px)}
.collage-tile:nth-child(even):hover{transform:translateY(8px)}
.collage-tile:hover{border-color:var(--brand)}
.collage-tile img{display:block; width:100%; aspect-ratio:4/5; object-fit:cover; background:var(--surface-2)}
.collage-tile .cap{display:flex; flex-direction:column; gap:2px; padding:8px 10px 10px}
.collage-tile .nm{font-family:var(--font-head); font-size:15px; color:var(--ink); line-height:1.2}
.hero-art-note{font-size:11px; color:var(--muted); text-align:center; margin-top:6px}

/* The search bar runs the full width of the hero, under both columns. */
.hero-search{
  grid-column:1 / -1; position:relative; z-index:1; padding:16px 18px;
  background:var(--surface); border:1px solid var(--line); border-radius:18px; box-shadow:var(--shadow);
}
.mini-heading{font-family:var(--font-mono); font-size:10px; letter-spacing:.12em; text-transform:uppercase; color:var(--muted); margin-bottom:12px}
/* The age cell is wider than the other three. A slider needs a run to be
   draggable in; a dropdown does not, and four equal columns were sized for
   four dropdowns. */
.search-row{display:grid; grid-template-columns:1fr 1fr 1.5fr 1fr auto; gap:12px; align-items:end; margin-bottom:9px}
.search-row .btn{height:40px; padding-inline:26px}

/* ============ THE AGE RANGE ============
   Two <select>s until age-range.js reaches them, a two-handled slider after.
   .is-live is set by the script AFTER it has built the replacement, so a
   script that throws half way leaves the selects on screen rather than an
   empty cell. */
.agerange.is-live .agerange-fields{display:none}

.agerange-ui{position:relative; height:46px}

.agerange-readout{
  position:absolute; inset-block-start:0; inset-inline-start:0;
  font-size:14px; color:var(--ink); line-height:1;
  font-variant-numeric:tabular-nums lining-nums;
}

.agerange-track{
  position:absolute; inset-inline:0; inset-block-start:32px; height:4px;
  border-radius:999px;
  /* Thinned, not taken straight from the token. --line-strong is a strong
     maroon in this palette rather than the neutral grey the name suggests —
     rgb(145,47,77) against the fill's rgb(142,27,46) — so the unselected part
     of the track and the selected part were the same bar and the control had
     no visible state at all. What is wanted here is the SHAPE of the token at
     a fraction of its weight. */
  background:color-mix(in srgb, var(--line-strong) 28%, transparent);
}
.agerange-fill{
  position:absolute; inset-block:0; border-radius:inherit;
  background:var(--brand);
}

/*
 | The two inputs, stacked on the same line.
 |
 | pointer-events:none on the input and :auto on its THUMB is the whole
 | trick. Both inputs cover the full width, so whichever is on top would
 | otherwise swallow every press — including presses meant for the other
 | one's handle. Made transparent to the pointer except where the handle
 | actually is, each end stays grabbable and the bar between them belongs to
 | neither.
 */
.agerange-in{
  position:absolute; inset-inline:0; inset-block-start:22px;
  width:100%; height:24px; margin:0;
  -webkit-appearance:none; appearance:none;
  background:transparent; pointer-events:none;
}
.agerange-in:focus{outline:none}

/* The track the browser draws is not wanted — .agerange-track is the track.
   Chrome and Firefox need telling separately; there is no shared selector,
   and grouping them would make the whole rule invalid in both. */
.agerange-in::-webkit-slider-runnable-track{background:transparent; border:none; height:24px}
.agerange-in::-moz-range-track{background:transparent; border:none; height:24px}

.agerange-in::-webkit-slider-thumb{
  -webkit-appearance:none; appearance:none;
  pointer-events:auto;
  width:18px; height:18px; margin-top:3px;
  border-radius:50%; cursor:grab;
  background:var(--surface); border:2px solid var(--brand);
  box-shadow:0 1px 4px rgba(0,0,0,.28);
  transition:transform .12s ease, box-shadow .12s ease;
}
.agerange-in::-moz-range-thumb{
  pointer-events:auto;
  width:18px; height:18px;
  border-radius:50%; cursor:grab;
  background:var(--surface); border:2px solid var(--brand);
  box-shadow:0 1px 4px rgba(0,0,0,.28);
  transition:transform .12s ease, box-shadow .12s ease;
}
.agerange-in::-webkit-slider-thumb:active{cursor:grabbing; transform:scale(1.12)}
.agerange-in::-moz-range-thumb:active{cursor:grabbing; transform:scale(1.12)}

/* Keyboard focus has to be visible ON THE HANDLE. The input itself is a
   transparent full-width strip, so a ring around it would appear round the
   whole row and say nothing about which end is being moved. */
.agerange-in:focus-visible::-webkit-slider-thumb{
  box-shadow:0 0 0 4px color-mix(in srgb, var(--brand) 32%, transparent);
}
.agerange-in:focus-visible::-moz-range-thumb{
  box-shadow:0 0 0 4px color-mix(in srgb, var(--brand) 32%, transparent);
}

@media (prefers-reduced-motion: reduce){
  .agerange-in::-webkit-slider-thumb{transition:none}
  .agerange-in::-moz-range-thumb{transition:none}
}

/* ============ THE PUBLIC SEARCH PANEL, TIDIED ============
   Scoped to .hero-search on purpose. .inp and .field are the whole product's
   form primitives — admin screens, the member's own settings, registration —
   and a "make the search box look better" pass has no business changing the
   field a moderator types into. */
.hero-search .inp{
  height:40px; padding-block:0;
  border-radius:10px;
  transition:border-color .15s ease, box-shadow .15s ease;
}
.hero-search .inp:hover{border-color:var(--brand)}
/* A ring, not a hue change. The shared .inp marks focus by moving its border
   one colour, which is a single pixel of low-contrast difference and the
   thing keyboard users lose the page on. */
.hero-search .inp:focus,
.hero-search .inp:focus-visible{
  border-color:var(--brand);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--brand) 26%, transparent);
}
.hero-search .btn{border-radius:10px}
.text-link{color:var(--brand-deep); text-decoration:none; font-weight:600; font-size:13px}
.section-head{display:flex; justify-content:space-between; align-items:center; margin-bottom:12px}
.section-head > .lbl{display:block; margin-bottom:0; padding-bottom:0; border-bottom:none}

.trust-grid{display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:12px}
.trust-card{
  display:flex; align-items:flex-start; gap:12px; background:var(--surface); border:1px solid var(--line);
  border-radius:16px; padding:16px 18px; box-shadow:var(--shadow);
}
.trust-icon{width:32px; height:32px; border-radius:50%; display:grid; place-items:center; background:var(--brand-tint); color:var(--brand-deep); font-weight:700}

.process-card,.tool-card,.cat-card{background:var(--surface); border:1px solid var(--line); border-radius:16px; box-shadow:var(--shadow)}
.tool-card, .process-card{padding:18px}
.tool-card .b{display:block; margin-bottom:8px; font-size:1.05rem}
.tool-card .sm{margin-bottom:12px}

.cta-banner{
  position:relative; overflow:hidden; isolation:isolate;
  display:flex; align-items:center; justify-content:space-between; gap:20px; padding:28px 30px;
  /* Its own alignment, not the container's. It now sits inside .doorway-inner,
     which centres its text — and a kicker and a headline centred over
     left-aligned buttons reads as two banners that collided. */
  text-align:start;
  background:linear-gradient(135deg, var(--brand), var(--brand-deep)); border-radius:20px; color:var(--on-brand);
  box-shadow:var(--shadow); border:1px solid rgba(255,255,255,.1);
}
/* The lights sit behind the words, never over them. */
.cta-banner .bokeh{z-index:0}
.cta-inner,.cta-actions{position:relative; z-index:1}
.cta-banner h2{font-size:clamp(1.8rem, 2vw, 2.8rem); color:var(--on-brand); margin-top:8px}
.cta-banner .lbl{color:rgba(255,255,255,.85); letter-spacing:.12em}
.cta-actions{display:flex; gap:12px; flex-wrap:wrap}
.cta-banner .btn.ghost{background:rgba(255,255,255,.10); color:var(--on-brand); border-color:rgba(255,255,255,.28)}
.cta-banner .btn.ghost:hover{background:rgba(255,255,255,.18)}

.category-grid{gap:10px}
.cat-card{
  display:flex; align-items:center; justify-content:space-between; padding:15px 14px; text-decoration:none;
  color:var(--ink); font-weight:500; transition:transform .15s ease, border-color .15s ease;
}
.cat-card:hover{transform:translateY(-1px); border-color:var(--brand)}

.sec{margin-top:36px}
.sec > .lbl{display:block; margin-bottom:10px; padding-bottom:7px; border-bottom:1px solid var(--line)}

.grid{display:grid; gap:14px}
.g2{grid-template-columns:repeat(2,minmax(0,1fr))}
.g3{grid-template-columns:repeat(3,minmax(0,1fr))}
.g4c{grid-template-columns:repeat(4,minmax(0,1fr))}
.g6c{grid-template-columns:repeat(6,minmax(0,1fr))}
@media (max-width:900px){
  .home-hero{grid-template-columns:1fr; padding:24px}
  .collage-tile:nth-child(n){transform:none}
  .search-row{grid-template-columns:repeat(2,minmax(0,1fr))}
  .search-row .btn{grid-column:1 / -1; width:100%}
  .trust-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .g3,.g4c{grid-template-columns:repeat(2,minmax(0,1fr))} .g6c{grid-template-columns:repeat(3,minmax(0,1fr))}
  .cta-banner{flex-direction:column; align-items:flex-start}
}
@media (max-width:560px){
  .hero-copy h1{font-size:2.5rem}
  .trust-grid{grid-template-columns:1fr}
  .g2,.g3,.g4c{grid-template-columns:1fr} .g6c{grid-template-columns:repeat(2,minmax(0,1fr))}
  .hero-actions,.cta-actions{flex-direction:column; width:100%}
  .hero-actions .btn,.cta-actions .btn{width:100%}
  /* Three columns still — they are three short figures, and stacking them
     would turn the smallest block on the page into the tallest. Only the
     gutters come in, because at this width the padding is what is spending
     the room rather than the type. */
  .statstable th{padding:0 5px 8px}
  .statstable td{padding:11px 5px 0}
  .hero-collage{gap:10px}
  .search-row{grid-template-columns:1fr}
}
.pcard{background:var(--surface); border:1px solid var(--line); border-radius:3px; overflow:hidden;
  display:flex; flex-direction:column; transition:border-color .15s, transform .15s}
.pcard:hover{border-color:var(--line-strong); transform:translateY(-1px)}
.pcard .body{padding:11px 12px; display:flex; flex-direction:column; gap:5px; flex:1}
.pcard .nm{font-family:var(--font-head); font-size:16px; color:var(--ink); line-height:1.2}
.pcard .meta{font-size:12px; color:var(--muted); line-height:1.45}
.pcard .acts{display:flex; gap:6px; padding:0 12px 12px}

.note{border-left:3px solid var(--gold); background:var(--gold-tint); padding:12px 14px; border-radius:0 3px 3px 0; font-size:13.5px}
.note.brand{border-color:var(--brand); background:var(--brand-tint)}
.note.bad{border-color:var(--bad); background:color-mix(in srgb,var(--bad) 8%,transparent)}
.note .t{font-weight:600; color:var(--ink); display:block; margin-bottom:3px; font-size:12.5px}

.tbl{width:100%; border-collapse:collapse; font-size:13.5px}
.tbl th{text-align:left; font-family:var(--font-mono); font-size:10px; letter-spacing:.12em;
  text-transform:uppercase; color:var(--muted); font-weight:400; padding:8px 10px; border-bottom:1px solid var(--line-strong)}
.tbl td{padding:9px 10px; border-bottom:1px solid var(--line); color:var(--body); vertical-align:top}
.tbl tr:last-child td{border-bottom:0}
.scrollx{overflow-x:auto}

.tabs{display:flex; gap:2px; border-bottom:1px solid var(--line); margin-bottom:16px}
.tab{padding:8px 13px; font-size:13px; cursor:pointer; background:none; border:0; border-bottom:2px solid transparent; color:var(--muted)}
.tab.on{color:var(--brand-deep); border-bottom-color:var(--brand); font-weight:500}
.tab:hover{color:var(--ink)}

.step{display:flex; gap:11px; align-items:flex-start}
.step .n{flex:none; width:23px; height:23px; border-radius:50%; border:1px solid var(--brand);
  color:var(--brand); display:grid; place-items:center; font-family:var(--font-mono); font-size:11px}
.step.done .n{background:var(--brand); color:var(--on-brand)}

.plan{display:flex; flex-direction:column; border:1px solid var(--line); border-radius:3px; background:var(--surface); overflow:hidden}
.plan.feat{border-color:var(--brand); box-shadow:var(--shadow)}
.plan .ptop{padding:16px 16px 14px; border-bottom:1px solid var(--line)}
.plan .price{font-family:var(--font-head); font-size:32px; color:var(--ink); line-height:1}
.plan .per{font-size:12px; color:var(--muted)}
.plan ul{list-style:none; margin:0; padding:14px 16px; display:flex; flex-direction:column; gap:8px; font-size:13px; flex:1}
.plan li{display:flex; gap:8px; align-items:flex-start}
.plan li .tick{color:var(--ok); flex:none; font-size:12px; line-height:1.5}
.plan li .no{color:var(--muted); flex:none; font-size:12px; line-height:1.5}
.plan .pfoot{padding:0 16px 16px}
.ribbon{background:var(--brand); color:var(--on-brand); font-family:var(--font-mono);
  font-size:9.5px; letter-spacing:.14em; text-transform:uppercase; text-align:center; padding:4px}

.deck{max-width:400px; margin:0 auto; position:relative}
.deckcard{background:var(--surface); border:1px solid var(--line); border-radius:4px; overflow:hidden; box-shadow:var(--shadow)}
.deckcard .hero{aspect-ratio:4/5; position:relative}
.deckcard .hero .ph{position:absolute; inset:0; aspect-ratio:auto; border:0; border-radius:0}
.msg{max-width:74%; padding:8px 12px; border-radius:12px; font-size:13.5px; line-height:1.5}
.msg.them{background:var(--surface-2); border-bottom-left-radius:3px; align-self:flex-start}
.msg.me{background:var(--brand); color:var(--on-brand); border-bottom-right-radius:3px; align-self:flex-end}

@media (max-width:860px){
  .view{padding:20px 15px 60px}
  .hd h1{font-size:24px}
}

/* ============ TOP NAVIGATION ============
   Every page, now — the sidebar rail it replaced is gone. The four things a
   visitor browses sit inline; the deep member menu is grouped behind names
   that open on hover and on click.

   The glass is deliberately thin. It sits over a live map that is the point
   of the backdrop, and a bar opaque enough to be its own surface hides the
   thing it is floating on. */
.app-topnav{grid-template-columns:1fr}
.topnav{
  position:sticky; top:0; z-index:40;
  background:color-mix(in srgb,var(--ground) 55%,transparent);
  -webkit-backdrop-filter:blur(16px) saturate(1.35);
          backdrop-filter:blur(16px) saturate(1.35);
  border-bottom:1px solid color-mix(in srgb, var(--line) 60%, transparent);
  transition:background .45s ease, border-color .45s ease;
}
.topnav-in{
  max-width:1080px; margin:0 auto; padding:11px 22px;
  display:flex; align-items:center; gap:22px;
}
.topnav .mark{flex:none}
.topnav-menu{display:flex; align-items:center; gap:22px; flex:1; min-width:0}
.topnav-links{display:flex; align-items:center; gap:4px; flex-wrap:wrap}
.topnav-link{
  padding:7px 11px; border-radius:8px; text-decoration:none; font-size:14px;
  color:var(--body); white-space:nowrap; transition:background .18s ease, color .18s ease;
}
.topnav-link:hover{background:var(--surface-2); color:var(--ink)}
.topnav-link.on{background:var(--brand-tint); color:var(--brand-deep); font-weight:var(--w-head)}
/* ---- the navigation groups ----
   Same shape as the language menu below it, which solved this first: a
   panel under the button, shown by a class the script toggles rather than
   by :hover alone, so a tap can open it too. */
.navdrop{position:relative; display:inline-flex}
.navdrop-btn{
  display:inline-flex; align-items:center; gap:5px;
  border:0; background:transparent; cursor:pointer;
  font:inherit; font-size:14px;
}
.navdrop-btn svg{transition:transform .18s ease; opacity:.7}
.navdrop.on .navdrop-btn svg{transform:rotate(180deg)}
.navdrop.on .navdrop-btn{background:var(--surface-2); color:var(--ink)}

.navdrop-menu{
  display:none; position:absolute; top:calc(100% + 8px); inset-inline-start:0; z-index:60;
  min-width:210px; padding:6px;
  border-radius:12px;
  background:color-mix(in srgb, var(--surface) 86%, transparent);
  -webkit-backdrop-filter:blur(18px) saturate(1.3);
          backdrop-filter:blur(18px) saturate(1.3);
  border:1px solid var(--line);
  box-shadow:0 18px 44px -20px rgba(0,0,0,.5);
}
.navdrop.on .navdrop-menu{display:block}

/* The button and the menu are 8px apart. Without something spanning the gap
   the pointer leaves both on the way down and the menu closes under it — the
   close delay in the script covers the timing, this covers the geometry. */
.navdrop::after{content:''; position:absolute; top:100%; inset-inline-start:0; width:100%; height:10px}

.navdrop-item{
  display:block; padding:8px 11px; border-radius:8px;
  text-decoration:none; color:var(--body); font-size:14px; white-space:nowrap;
}
.navdrop-item:hover{background:var(--surface-2); color:var(--ink)}
.navdrop-item.on{background:var(--brand-tint); color:var(--brand-deep); font-weight:var(--w-head)}

/* The one link into Connect. Marked, because it is a door into the other
   product rather than another page of this one. */
.topnav-link.is-connect{
  display:inline-flex; align-items:center; gap:6px;
  color:var(--connect-brand);
}
.topnav-link.is-connect:hover{background:color-mix(in srgb, var(--connect-brand) 12%, transparent)}

.topnav-id{flex:none}

.topnav-actions{display:flex; align-items:center; gap:8px; margin-left:auto}
.topnav-actions form{display:flex}
.topnav-toggle{display:none; margin-left:auto}

@media (max-width:860px){
  /* The menu becomes a panel under the bar. `hidden` would fight the
     display:flex above, so it collapses to display:none and .open restores
     the column. */
  .topnav-in{flex-wrap:wrap; gap:12px; padding:10px 15px}
  .topnav-toggle{display:inline-flex}
  .topnav-menu{display:none; flex-direction:column; align-items:stretch; gap:12px; width:100%; padding-bottom:6px}
  .topnav-menu.open{display:flex}
  .topnav-links{flex-direction:column; align-items:stretch; gap:2px}
  .topnav-link{padding:9px 11px}

  /* The icons stay in the bar; only the words fold away.
     .topnav-actions is a sibling of the menu now, so order is what puts it
     back where it reads: mark, icons, burger on the first line, and the
     collapsed link column wrapping underneath. Without the order the actions
     would follow the menu in source and land under it. */
  .topnav-actions{margin-left:auto; order:2; flex-wrap:nowrap; gap:6px}
  .topnav-toggle{order:3; margin-left:0}
  .topnav-menu{order:4}

  /* The bell's panel is styled with the rest of the bell, further down —
     it has to be, because the desktop rule that anchors it to the right
     lives there and would otherwise win on source order alone. */

  /* Stacked, the groups stop floating and simply open in place — a panel
     positioned over a column that is already a panel is a panel over a
     panel, and on a narrow screen there is nowhere for it to go. */
  .navdrop{display:block}
  .navdrop-btn{width:100%; justify-content:space-between}
  .navdrop-menu{
    position:static; display:none; min-width:0; margin:2px 0 4px 12px;
    background:transparent; backdrop-filter:none; -webkit-backdrop-filter:none;
    border:0; border-inline-start:1px solid var(--line); border-radius:0;
    box-shadow:none; padding:0 0 0 8px;
  }
  .navdrop.on .navdrop-menu{display:block}
  .navdrop::after{display:none}
}
/* ============ THE DOORWAY ============
   Two products share one address, so the front page asks which one you came
   for before it sells either. The slideshow sits behind a scrim rather than
   beside the text: a photograph is atmosphere here, and atmosphere must
   never win a contrast fight with a headline. */
.doorway{
  position:relative; isolation:isolate; overflow:hidden;
  /* Full bleed. .view is a centred 1080px column with its own padding, so
     the section is pulled back out to the viewport edge rather than the
     column edge. 100% of .view, not 100vw: vw includes the scrollbar and
     would push the page sideways by its width on every desktop browser. */
  margin-left:calc(50% - 50vw); margin-right:calc(50% - 50vw);
  width:100vw; max-width:100vw;
  /* Runs UNDER the header, not below it. The bar is glass now, and glass
     over the page's own background is just a slightly different shade of
     background — the photograph has to be behind it for the effect to
     exist at all. The padding-top below puts the same distance back, so
     the headline still clears the bar. */
  margin-top:calc(-1 * var(--nav-h, 79px) - 26px);
  border-radius:0; box-shadow:none;
  /* Top-weighted, not centred. align-items:center in an 88vh block put the
     headline halfway down the screen with a band of empty photograph above
     it; the copy is the point and it should be the first thing under the
     header. */
  /* A breath of photograph above the headline, and no more than that.
     It was up to 104px on top of the header's own height, which on a
     1440×860 screen put the first word 364px down — a third of the view
     spent on empty sky before anything was said. Bounded by vh as well as
     vw, so a short laptop gives the space back rather than pushing the
     doors under the fold. */
  padding:calc(var(--nav-h, 79px) + clamp(18px, min(3.4vw, 4.5vh), 46px)) clamp(20px, 4vw, 56px) clamp(26px, 3.6vw, 52px);
  min-height:min(76vh, 660px); display:flex; align-items:flex-start;
}
.doorway-art{position:absolute; inset:0; z-index:-1; background:var(--surface-3)}
.doorway-slide{
  position:absolute; inset:0; background-size:cover; background-position:center;
  opacity:0; transition:opacity 1.1s ease-in-out;
}
.doorway-slide.on{opacity:1}
/* Two washes: one to keep the text legible over any photograph, one to keep
   the brand colour present whatever the admin uploads. */
/*
 | The scrim, thinned so the photograph is a photograph.
 |
 | It was a left-to-right wash that sat at 92% over the left third — which is
 | where the headline is, so that end of the slideshow was very nearly opaque
 | ground and the picture only appeared on the right. Two people at a wedding
 | reduced to a smear behind some type is not a slideshow; it is texture, and
 | it made the front page look busy without ever letting you see what it was
 | busy WITH.
 |
 | Now it darkens from the top and the bottom and leaves the middle band —
 | where the picture's subject almost always is — mostly clear. The type
 | keeps its contrast from its own halo (see .doorway h1.shine) and from the
 | cards' own glass, not from painting over the photograph.
 */
.doorway-scrim{
  position:absolute; inset:0;
  background:
    linear-gradient(180deg,
      color-mix(in srgb,var(--ground) 74%,transparent) 0%,
      color-mix(in srgb,var(--ground) 34%,transparent) 28%,
      color-mix(in srgb,var(--ground) 26%,transparent) 62%,
      color-mix(in srgb,var(--ground) 72%,transparent) 100%),
    linear-gradient(0deg, color-mix(in srgb,var(--brand) 12%,transparent), transparent 55%);
}
/*
 | Centred, not ragged left.
 |
 | The container was already centred, but h1 (16ch) and .sub (56ch) are much
 | narrower than the 1080 it allows — so the copy hugged the left edge while
 | the panel below ran the full width, and the block read as pushed aside
 | rather than composed. The measures stay; they are just centred within it.
 |
 | The door cards keep their own text left-aligned: a paragraph of body copy
 | centred inside a card is harder to read, and the pill would lose its
 | anchor.
 */
.doorway-inner{position:relative; z-index:1; width:100%; max-width:1080px; margin:0 auto; text-align:center}
.doorway-inner h1{
  /* Larger, and it can afford to be: the line that sat under it is
     gone, so the headline is the only thing between the navigation and
     the two doors. */
  font-size:var(--hero-h1, clamp(2.9rem, 6.4vw, 6.2rem)); line-height:1.02; margin:0 auto;
  letter-spacing:-.04em; max-width:18ch;
}
.doorway-inner .sub{margin:14px auto 0; max-width:56ch; font-size:1.05rem; color:var(--body)}
/*
 | The doors stay IN the column, level with the assurances below them.
 |
 | They used to break out of it: width:min(1680px, 96vw) with a negative
 | margin-inline computed to cancel the overhang, so the pair spanned 1382px
 | on a 1440 screen while the trustbar directly beneath sat in .doorway-inner's
 | 1080. Two stacked elements with different left edges and different right
 | edges read as a mistake however carefully each one is centred, and the
 | doors are the thing the trustbar is describing.
 |
 | Sharing the parent's measure makes the two edges the same edge by
 | construction, rather than by two calculations agreeing.
 */
.doorway-inner .doors{ width:auto; margin-inline:0 }
.doorway-inner .door{text-align:left}

/* More air above the pair. They are the choice the whole page exists to
   offer, and sitting them straight under the subtitle made them read as
   a continuation of the sentence rather than the decision. */
/*
 | Two cards at the two edges, each only as wide as its own words.
 |
 | `1fr` columns made every card exactly half the row whatever was in it, so
 | widening the row to reach the screen edges widened the CARDS — and the
 | cards are opaque glass over the slideshow. The photograph was being
 | covered by empty card.
 |
 | max-content sizes each one to its longest line instead, and
 | space-between does the pushing. The gap is now a minimum rather than a
 | fixed measure: whatever the two cards do not use, the wall between them
 | gets. The cap on .door keeps a long translation from growing a card
 | across the whole row instead of wrapping.
 */
.doors{display:grid;
       /*
        | A third, then two thirds.
        |
        | It was repeat(2, 1fr) — two equal halves — which is the right answer
        | while both cards carry a label AND a sentence, because then the two
        | of them hold the same amount of reading. They do not any more: each
        | card is one word. Equal halves gave two identical panes of glass
        | with a short word adrift in each, and said nothing about which of
        | the two doors is the larger part of the site.
        |
        | 1fr/2fr is that statement. Porichoy takes the wider track; the
        | numbers are grid's own — the free space left after the gap is split
        | three ways, one to matrimony and two to Porichoy — so the ratio
        | holds at every width without a percentage to keep in step with the
        | gap. minmax(0, …) on both, as always, so a long translation wraps
        | inside its track instead of pushing the track wider than its share.
        */
       grid-template-columns:repeat(2, minmax(0, 1fr));
       /*
        | One at each side, with the same air outside both.
        |
        | This was justify-content:center with a chosen gap, and the note here
        | argued for it: space-between hands the pair every spare pixel, so on
        | a wide screen the wall between them grows past "two choices" and into
        | "two unrelated things at opposite ends of the room".
        |
        | The owner asked for them at the sides, and asked twice, so they are
        | at the sides. The argument above is still the reason the GAP has a
        | floor rather than being left entirely to the leftover: below about a
        | thousand pixels space-between would close them up against each other
        | before the layout stacks, and the gap keeps that from happening.
        |
        | The equal outer margin is not from this rule — it comes from the
        | container, which is width:min(1680px, 96vw) centred, so 2vw of air
        | sits outside each card by construction rather than by arithmetic.
        */
       gap:clamp(28px, 4vw, 96px);
       margin-top:clamp(40px, 5.4vh, 72px);
       max-width:min(1680px, 96vw)}
/* The cap, so a long translation wraps instead of growing a card across the
   whole row. Wide enough that the English headings take two lines rather
   than three — a three-line card is taller, and height covers as much
   photograph as width does. */
/* WIDTH, not max-width — the two doors have to be the same size as each
   other, and a cap does not make them one.

   The columns are minmax(0, max-content), so each track took the width of
   its own sentence. "A marriage, with the family in the room" is a third
   longer than "Someone to see where it goes", so the matrimony card came out
   visibly wider and the pair read as a big choice next to a small one — which
   is a claim about the products, made by accident, by a string length. A cap
   could not fix it: both were under it, at different widths.

   Setting the width makes both cards exactly this, whatever is inside them,
   and the tracks follow.

   `auto` now, because the GRID sets the width: two 1fr tracks in the same
   column the trustbar uses, so each card is exactly half of it less half the
   gap, and the two edges line up with the bar below by construction.

   This replaced a chain of fixed values — 46ch/30vw, then 58ch, then 70ch —
   each one chosen to close the space between the cards while they were
   pinned to the two outer edges. That is now the gap's own job, one number,
   and the cards follow it. */
/* Shorter, and still one at each edge.

   The owner asked for the pair to be less wide without coming off the two
   sides, so the cards take a width of their own and justify-self pins each
   one to its outer edge. The tracks are still half the row each, so the left
   edge of matrimony and the right edge of dating stay on the trustbar's
   edges below — the alignment the block above is about — and everything the
   cards give up goes to the photograph between them.

   One width for both, for the reason argued above: two doors of different
   sizes is a claim about the products made by a string length. The longest
   heading here sets its own line in 210px, so 380 leaves the English on one
   line with room for a longer translation before it wraps. */
.door{ width:min(100%, 380px) }
.door-matrimony{ justify-self:start }
.door-dating{ justify-self:end }
/* Glass. Three things together, because tint alone just looks like a faded
   card: the blur behind it, a bright top edge where light would catch, and a
   soft inner highlight. --door-tint and --door-blur are set by the admin;
   the fallbacks are what you get if the settings row is missing. */
.door{
  position:relative; isolation:isolate;
  /* No minimum height. The card is the label and the title plus its
     padding, and nothing else — the floor that briefly held the old size
     left two thirds of each card empty once the paragraph came out. */
  /* No minimum height and no minimum width — the card is its two lines plus
     this padding and nothing else. The padding scales a little with the
     viewport so the inset does not look like a chip on a wide screen, but it
     stays modest: this glass sits ON the photograph, and every pixel of card
     is a pixel of slideshow nobody can see. */
  /* Slim. The card is a label and one line, and it was carrying the padding
     of something with a paragraph in it — so each door stood about twice as
     tall as it had content for, and the two of them took the middle out of
     the photograph they sit on. The gap goes with the padding: at 9px the
     label and the line read as two separate blocks rather than one label
     above its own sentence. */
  /* Small type, roomy box — the two are set separately and on purpose.

     The type came down to sit level with the trustbar row below, and the
     padding followed it down until the card was 56px and the text was
     against the edges. But the size of the LABEL and the size of the CARD
     answer different questions: how loud the words are, and how much of the
     photograph the door occupies. A door is the largest target on the front
     page and the one thing the whole product turns on, so it keeps its
     presence while its text stays quiet.

     Type is set from the appearance screen (--door-tag-size and
     --door-head-size); this padding is the card, and moving one does not
     move the other. */
  display:flex; flex-direction:column; gap:4px;
  padding:clamp(18px, 1.5vw, 24px) clamp(20px, 1.7vw, 28px);
  text-decoration:none;
  border-radius:18px; color:var(--ink);
  background:color-mix(in srgb, var(--surface) calc(var(--door-tint, 56) * 1%), transparent);
  -webkit-backdrop-filter:blur(var(--door-blur, 16px)) saturate(140%);
  backdrop-filter:blur(var(--door-blur, 16px)) saturate(140%);
  border:1px solid transparent;
  box-shadow:
    /* Softer, and lifted off the photograph rather than pressed into it. A
       heavy drop shadow under a pane of glass reads as a solid slab; it was
       also darkening the picture around the card, which is the picture this
       whole pass is trying to let through. */
    0 8px 24px -16px rgba(0,0,0,.34),
    inset 0 1px 0 color-mix(in srgb, var(--on-brand) 20%, transparent);
  transition:transform .16s ease, box-shadow .16s ease, background .16s ease;
}
/* The lit edge, now a layer of the card's own background rather than a
   pseudo-element. ::before was holding it and is needed for the moving
   border below; a background layer does the same job and costs nothing. */
/*
 | Thinner glass.
 |
 | Two layers were stacking here and only one of them was ever adjustable.
 | --door-tint is the administrator's, and it was doing its job; the 150deg
 | sheen above it added another 16% of near-white over the top-left corner
 | that no setting could reach. On a dark theme --surface is nearly black, so
 | the pair together made a card you could not see any photograph through at
 | any tint — which is the opposite of glass.
 |
 | The sheen is now a hint (7%) and stops sooner, and the tint is scaled to
 | three quarters of what the slider says. The slider still governs: it is
 | the difference between a card and a pane, and lowering it further is the
 | knob for anyone who wants more photograph still.
 */
.door{
  background:
    linear-gradient(150deg,
      color-mix(in srgb, var(--on-brand) 7%, transparent) 0%,
      transparent 30%),
    color-mix(in srgb, var(--surface) calc(var(--door-tint, 56) * 0.75%), transparent);
}
.door:hover{
  transform:translateY(-2px);
  /* Both layers, not just the tint — setting a single background here would
     drop the lit edge that now lives in the same shorthand. */
  background:
    linear-gradient(150deg,
      color-mix(in srgb, var(--on-brand) 22%, transparent) 0%,
      transparent 38%),
    color-mix(in srgb, var(--surface) calc((var(--door-tint, 56) + 8) * 1%), transparent);
  box-shadow:
    0 16px 38px -14px rgba(0,0,0,.5),
    inset 0 1px 0 color-mix(in srgb, var(--on-brand) 46%, transparent);
}

/* Safari and Firefox before 103 have no backdrop-filter. Without the blur,
   a low tint is just unreadable text on a photograph, so those browsers get
   a solid card instead of a broken one. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){
  .door{background:var(--surface)}
  .door:hover{background:var(--surface)}
}
.door-tag{
  font-family:var(--font-mono); font-size:10px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--muted);
}
.door-h{font-family:var(--font-head); font-size:25px; line-height:1.15; color:var(--ink)}
.door p{font-size:13.5px; color:var(--body); margin:0}
.door-cta{margin-top:6px; font-size:13px; font-weight:600}
/* Each door wears its own product's colour, which is the fastest way to say
   these are two different things without a paragraph explaining it. */
/* The matrimony door follows the brand colour, so the appearance screen
   reaches it like everything else. The dating door does NOT: Connect's teal
   is that product's identity, and the two doors looking alike is exactly
   what the doorway exists to prevent. It gets its own token rather than a
   literal, so it is findable. */
.door-matrimony:hover{border-color:var(--brand)}
.door-matrimony .door-cta{color:var(--brand-deep)}
.door-dating:hover{border-color:var(--connect-brand)}
.door-dating .door-cta{color:var(--connect-brand)}

/* ============ THE TWO HALVES ============ */
/* 56px of air above the heading made sense when this was the lower half of
   a scrolling home page and the gap separated two products. Each half is its
   own page now and lands directly under the header, so that margin was just
   an empty band at the top of every visit. */
.half{margin-top:8px; scroll-margin-top:70px}
.half-head{margin-bottom:22px; padding-bottom:16px; border-bottom:1px solid var(--line)}
.half-head h2{font-size:clamp(2.2rem, 3.4vw, 3.2rem); margin-top:8px; letter-spacing:-.025em; line-height:1.06}
.half-head .sub{margin-top:12px; color:var(--muted); font-size:15.5px; line-height:1.6; max-width:66ch}

/* The dating half carries the Connect palette on its own subtree, so the
   products never read as one product with a filter over it. */
.half-dating{
  --ground:#EAF2EF; --surface:#F8FCFA; --surface-2:#DFEBE7; --surface-3:#D2E1DC;
  --ink:#10201D; --body:#354744; --muted:#697B77;
  --line:#D6E3DF; --line-strong:#B4C8C3;
  --brand:#1B5249; --brand-deep:#0F3A32; --brand-tint:#E3F0EC; --on-brand:#F4FBF9;
  --grad-brand:linear-gradient(135deg, #1B5249 0%, #2F7A6C 100%);
  --grad-brand-hover:linear-gradient(135deg, #23675B 0%, #3E9384 100%);
  background:var(--ground); color:var(--body);
  border:1px solid var(--line); border-radius:22px; padding:26px;
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .half-dating{
    --ground:#0D1615; --surface:#15211F; --surface-2:#1D2B28; --surface-3:#243530;
    --ink:#E7F0ED; --body:#C4D4D0; --muted:#849692;
    --line:#24352F; --line-strong:#38504A;
    --brand:#6FBFAE; --brand-deep:#8CD3C2; --brand-tint:#16302B; --on-brand:#0B1917;
  }
}
:root[data-theme="dark"] .half-dating{
  --ground:#0D1615; --surface:#15211F; --surface-2:#1D2B28; --surface-3:#243530;
  --ink:#E7F0ED; --body:#C4D4D0; --muted:#849692;
  --line:#24352F; --line-strong:#38504A;
  --brand:#6FBFAE; --brand-deep:#8CD3C2; --brand-tint:#16302B; --on-brand:#0B1917;
}
.half-dating .half-head h2{color:var(--ink)}

.dating-panel{display:grid; grid-template-columns:1.15fr .85fr; gap:20px; align-items:start}
.dating-point{
  display:flex; align-items:flex-start; gap:12px; padding:16px 18px;
  background:var(--surface); border:1px solid var(--line); border-radius:16px;
}
.dating-wall{background:var(--surface); border-color:var(--line)}
.wall-list{margin:0; padding-left:18px; display:flex; flex-direction:column; gap:7px}
.wall-list li{font-size:13px; color:var(--body)}

/* ============ ADMIN: HERO SLIDES ============ */
.slide-card{overflow:hidden; display:flex; flex-direction:column}
.slide-card img{display:block; width:100%; aspect-ratio:16/9; object-fit:cover; background:var(--surface-2)}

@media (max-width:900px){
  /* One under the other, and each one full width.
     The desktop rule sizes the cards to their own text and caps them at
     30vw, which is right beside each other and absurd stacked — 30vw of a
     phone is a hundred pixels, and the heading would come down one word per
     line. Stacked, there is nothing to sit beside, so the cap goes. */
  .doors{grid-template-columns:1fr; justify-content:stretch}
  /* Stacked, each card is the full column, so the shared width above
     would hold them narrow in the middle of the screen. */
  /* Stacked, so the width and the edge-pinning above both come off: each
     card is the whole column again. */
  .door{width:auto; max-width:none; justify-self:stretch}
  .dating-panel{grid-template-columns:1fr}
  .doorway{min-height:0}
}
@media (max-width:560px){
  .half-dating{padding:18px}
  .dating-panel .grid.g2{grid-template-columns:1fr}
}
/* A photograph that fades every three seconds is a real accessibility
   problem for some people. Honour the request and hold on one frame. */
@media (prefers-reduced-motion: reduce){
  .doorway-slide{transition:none}
}

/* ============ BRAND MARK ============
   The bridge. Sized by the SVG itself; the tile just gives it a ground. */
/* The logo tile. The artwork carries its own background, so the tile is
   just a rounded window onto it rather than a coloured plate behind it. */
.mark .glyph{border-radius:8px; background:none; overflow:hidden; padding:0}
.mark-glyph{display:block; border-radius:6px; object-fit:cover}

/* ============ SEARCH FILTER BAR ============
   Filters belong on the results page: a global audience arrives at /search
   from country tiles, shared links and search engines, not only from the
   front page. */
.filter-bar{
  /* Six tracks for five fields: the age range holds two controls and takes
     two of them. It used to hold two short number inputs, which fitted a
     single column; two selects with their arrows and an "All" option do not,
     and they came out about 55px wide each. */
  display:grid; gap:12px; align-items:end;
  grid-template-columns:repeat(6, minmax(0,1fr)) auto auto;
}
.filter-bar .age-field{grid-column:span 2}
.filter-bar .age-field .inp{min-width:0}
.filter-bar .has-photo{white-space:nowrap; padding-bottom:9px}
@media (max-width:1080px){
  .filter-bar{grid-template-columns:repeat(3, minmax(0,1fr))}
  .filter-bar .age-field{grid-column:span 2}
  .filter-bar .has-photo{grid-column:span 3}
}
@media (max-width:640px){
  .filter-bar{grid-template-columns:1fr}
  .filter-bar .age-field,.filter-bar .has-photo{grid-column:auto}
}

/* ============ THE GLOBE ============
   Globe8.mp4, looped. The hand-built CSS sphere that used to live here is in
   the history if it is ever wanted back — it was a good approximation, but
   the reference is photographic Earth with cloud and city lights, and an
   approximation of a photograph only ever looks like a worse photograph. */
.globe-panel{
  /* Width is an admin setting; the height follows from the clip's aspect
     ratio below, so one number controls the whole panel. min() rather than a
     stack of breakpoints, so a large setting still shrinks to fit a narrow
     column instead of overflowing it. */
  position:relative; width:100%; max-width:min(var(--globe-width, 420px), 100%); margin:0 auto 16px;
  aspect-ratio:760 / 428; border-radius:18px; overflow:hidden;
  border:1px solid var(--globe-edge, var(--line)); box-shadow:var(--shadow);
  background:#05070d;   /* the clip's own black, so there is no flash before it paints */
}
.globe-video{
  display:block; width:100%; height:100%; object-fit:cover;
}
/* The clip is cool blue and this palette is warm. Rather than recolour the
   Earth — which would look like a fault — the edges are warmed so the panel
   sits inside the page instead of on top of it. */
.globe-panel-vignette{
  position:absolute; inset:0; pointer-events:none;
  background:
    radial-gradient(ellipse at 50% 50%, transparent 52%, color-mix(in srgb,var(--brand-deep) 42%,transparent) 100%),
    linear-gradient(180deg, transparent 70%, color-mix(in srgb,var(--brand-deep) 28%,transparent) 100%);
}

.globe-caption{
  text-align:center; font-size:11px; color:var(--muted);
  font-family:var(--font-mono); letter-spacing:.1em; text-transform:uppercase;
}
/* The caption doubling as the rails' "view all" link. Same idiom, made
   reachable: an underline on hover is the only thing that says a mono,
   letter-spaced caption is also a link. */
.globe-caption-link{display:block; text-decoration:none}
.globe-caption-link:hover,
.globe-caption-link:focus-visible{color:var(--brand); text-decoration:underline}

/* ============ MATRIMONY: THE STORY CAROUSELS ============
   One couple at a time, either side of the globe.

   This replaced a marquee: a tall column of portraits scrolling past
   forever. Faces going by is decoration, and what is worth showing a
   stranger on this page is a story - which needs room for words, so the
   card carries the picture, the couple, the first line or two, and the way
   to read the rest.

   The stage is a fixed height and the cards are stacked inside it, all four
   corners pinned, so one can fade out while the next fades in without the
   page under them moving. That fixed height is why every line below is
   clamped: Bangla sets taller than English and a story is as long as
   somebody's life, so the card decides how much of it fits, not the text.

   `visibility` rides along with the opacity on purpose. Transparent is not
   hidden - a card at zero opacity still takes the pointer and still holds
   its link in the tab order - and the script marks the others inert for the
   same reason from the other side.

   Hidden below the width the trio needs, rather than squeezed into
   something unreadable, and skipped entirely by the view when a side has no
   stories. */
.globe-flanked{display:flex; align-items:center; justify-content:center; gap:20px; max-width:100%}
/* .globe-panel's own margin:0 auto is what used to leave a big gap here:
   auto margins claim a flex line's leftover space for themselves BEFORE
   justify-content ever runs, so the globe was quietly pulling every spare
   pixel onto its own two sides instead of leaving it as the gap between it
   and the cards. Flanked, it keeps only its bottom margin. */
.globe-flanked > .globe-panel{margin:0 0 16px}

.storycar{
  width:212px; flex:none; display:flex; flex-direction:column; gap:9px;
  /* Dragging sideways moves the carousel; dragging up and down still
     scrolls the page, which is what a phone expects of a card like this. */
  touch-action:pan-y; user-select:none;
}
.storycar-stage{position:relative; height:296px}
.storycar-card{
  position:absolute; inset:0; display:flex; flex-direction:column; overflow:hidden;
  background:var(--surface); border:1px solid var(--line); border-radius:14px;
  box-shadow:var(--shadow);
  opacity:0; visibility:hidden; transform:translateY(7px);
  transition:opacity .5s ease, visibility .5s ease, transform .5s ease;
}
.storycar-card.is-on{opacity:1; visibility:visible; transform:none}

.storycar-art{display:block; background:var(--surface-2)}
.storycar-art img{display:block; width:100%; height:118px; object-fit:cover; -webkit-user-drag:none}

.storycar-body{display:flex; flex-direction:column; gap:5px; padding:11px 13px 13px; min-width:0}
.storycar-couple{
  font-size:12.5px; font-weight:600; color:var(--ink); letter-spacing:.01em;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.storycar-h{
  font-size:12px; line-height:1.35; color:var(--brand);
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.storycar-teaser{
  font-size:11.5px; line-height:1.55; color:var(--body);
  display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden;
}
.storycar-more{
  margin-top:2px; font-size:11px; font-weight:600; color:var(--brand); text-decoration:none;
  display:inline-flex; align-items:center; gap:4px; align-self:flex-start;
}
.storycar-more::after{content:'\2192'; transition:transform .16s ease}
.storycar-more:hover{text-decoration:underline}
.storycar-more:hover::after{transform:translateX(3px)}

/* ---- back and forward ----
   Chevrons from the icon set rather than typed arrows, and the left one is
   the same glyph turned round: see partials/ic.blade.php for why this file
   does not print arrows as text. */
.storycar-nav{display:flex; align-items:center; justify-content:center; gap:9px}
.storycar-btn{
  display:inline-flex; align-items:center; justify-content:center;
  width:27px; height:27px; padding:0; border-radius:50%; cursor:pointer;
  border:1px solid var(--line); background:var(--surface); color:var(--body);
  transition:border-color .16s ease, color .16s ease;
}
.storycar-btn:hover{border-color:var(--brand); color:var(--brand)}
.storycar-btn.is-prev .ic-svg{transform:rotate(180deg)}
.storycar-dots{display:inline-flex; align-items:center; gap:5px}
.storycar-dot{
  width:5px; height:5px; border-radius:50%; background:var(--line);
  transition:background-color .3s ease;
}
.storycar-dot.is-on{background:var(--brand)}

@media (max-width:1180px){ .storycar{display:none} }
/* No clock when motion is not wanted - the arrows still work, and the
   stories stay on the page. The script reads the same query. */
@media (prefers-reduced-motion: reduce){
  .storycar-card{transition:none}
}

/* ============ BOKEH ============
   Hexagonal lights drifting at varying depths — a CSS recreation of the
   reference footage, at roughly a two-thousandth of its weight.

   Every hexagon animates transform and opacity only. Both are composited,
   so the whole field costs the compositor a few layers and the main thread
   nothing. The blur is a static filter, applied once, never animated. */
.bokeh{
  position:absolute; inset:0; overflow:hidden; pointer-events:none;
  /* Screen keeps the lights additive over whatever is behind them, which is
     how the real footage is meant to be used — as a light source laid over
     a plate, not as a picture in its own right. */
  mix-blend-mode:screen;
}
.bokeh-hex{
  position:absolute; left:var(--left); top:var(--top);
  width:var(--size); height:var(--size);
  background:var(--colour);
  /* A true hexagon, which is the whole character of the reference: it is
     the shape of the lens aperture the footage was shot through. */
  clip-path:polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
  filter:blur(var(--blur));
  opacity:var(--opacity);
  animation:bokeh-drift var(--dur) ease-in-out infinite alternate;
  animation-delay:var(--delay);
  will-change:transform, opacity;
}
@keyframes bokeh-drift{
  from{transform:translate3d(0, 0, 0) rotate(0deg) scale(1)}
  to  {transform:translate3d(var(--drift), var(--rise), 0) rotate(var(--spin)) scale(1.14)}
}

/* Held still, it is still a warm hexagonal wash — the picture survives
   without the motion. */
@media (prefers-reduced-motion: reduce){
  .bokeh-hex{animation:none}
}

/* ============ ADMIN: DOORWAY GLASS ============ */
.glass-admin-grid{display:grid; grid-template-columns:1fr 1fr; gap:22px; align-items:start}
/* A real slide behind a real .door, so the preview is the thing itself
   rather than an approximation of it. */
.glass-preview{
  position:relative; border-radius:16px; overflow:hidden; min-height:210px;
  display:grid; place-items:center; padding:22px; background:var(--surface-3);
}
.glass-preview img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.glass-preview .door{position:relative; width:100%; max-width:300px}

input[type="range"]{
  width:100%; accent-color:var(--brand); height:26px; cursor:pointer; background:transparent;
}
@media (max-width:900px){ .glass-admin-grid{grid-template-columns:1fr} }

/* ============ ADMIN: APPEARANCE ============ */
.theme-grid{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:22px; align-items:start}
.theme-preview-wrap{position:sticky; top:78px; display:flex; flex-direction:column; gap:8px}
/* The preview scopes the same custom properties the site uses, so what it
   shows is the result applied to a smaller area rather than an imitation. */
.theme-preview{
  background:var(--ground); border:1px solid var(--line); border-radius:18px; padding:22px;
  font-family:var(--font-body); font-size:var(--font-size); font-weight:var(--w-body);
}
.theme-preview h2{font-family:var(--font-head); font-weight:var(--w-head); font-size:2rem; margin:8px 0 0}
.theme-preview .sub{color:var(--muted); margin:6px 0 16px}
.preview-doors{position:relative; border-radius:14px; overflow:hidden; padding:18px; min-height:150px}
.preview-doors img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.preview-doors .door{position:relative; max-width:none}

.pair-option{
  display:flex; align-items:flex-start; gap:10px; padding:11px 13px; cursor:pointer;
  border:1px solid var(--line); border-radius:12px; background:var(--surface);
}
.pair-option.on{border-color:var(--brand); background:var(--brand-tint)}
.pair-option .grow{display:flex; flex-direction:column; gap:2px}
.pair-name{font-size:16px; color:var(--ink); line-height:1.25}

input[type="color"]{
  width:52px; height:34px; padding:2px; cursor:pointer;
  border:1px solid var(--line); border-radius:8px; background:var(--surface);
}
@media (max-width:1000px){
  .theme-grid{grid-template-columns:1fr}
  .theme-preview-wrap{position:static}
}

/* ============ PASSWORD REVEAL ============
   The wrapper is created by script, so with JavaScript off none of this
   applies and the field is exactly the plain input it always was. */
.pw-field{position:relative; display:block}
.pw-field .inp{padding-right:42px; width:100%}
.pw-toggle{
  position:absolute; top:50%; right:6px; transform:translateY(-50%);
  display:grid; place-items:center; width:30px; height:30px;
  padding:0; border:0; border-radius:6px; cursor:pointer;
  background:transparent; color:var(--muted);
  transition:color .14s ease, background .14s ease;
}
.pw-toggle:hover{color:var(--ink); background:var(--surface-2)}
/* Keyboard users have to be able to see where they are. */
.pw-toggle:focus-visible{outline:2px solid var(--brand); outline-offset:1px; color:var(--ink)}

/* Alignment picker on the appearance screen. */
.align-option{
  display:inline-flex; align-items:center; gap:6px; padding:7px 12px; cursor:pointer;
  border:1px solid var(--line); border-radius:9px; background:var(--surface); font-size:13px;
}
.align-option.on{border-color:var(--brand); background:var(--brand-tint); color:var(--brand-deep)}

/* The language toggle in the top nav is an icon, not a word: center the glyph
   and give it a square-ish tap target. Colour follows the quiet button token. */
.topnav .lang-globe{display:inline-flex; align-items:center; justify-content:center; padding:6px; width:34px; height:34px; border-radius:8px}
.topnav .lang-globe svg{display:block}

/* ============ THE MARK, IN THE TOP NAV ============
   The rail shows the mark at 28px beside a narrow column. The landing page's
   header is taller and much wider, and at 20px the logo read as a favicon
   that had wandered into the page rather than as the site's own name. Sized
   off the header here only — the rail is unchanged. */
.topnav .mark{gap:14px}
.topnav .mark .glyph{width:60px; height:60px; border-radius:13px}
.topnav .mark-glyph{width:60px; height:60px}
.topnav .mark .wordmark{font-size:29px; line-height:1.05}
.topnav .mark .modetag{font-size:14px; letter-spacing:.2em}
/* The header grows with the mark rather than clipping it. */
.topnav-in{padding-top:9px; padding-bottom:9px}

/* The glyph opens the mark full size; the words still go home. Splitting the
   two keeps a working home link in the header — a logo that only opens a
   picture of itself is a navigation control quietly removed. */
.mark-zoom{
  padding:0; border:0; background:none; cursor:zoom-in; line-height:0;
  border-radius:10px;
}
.mark-zoom:focus-visible{outline:2px solid var(--brand); outline-offset:3px}
.mark-text{text-decoration:none; display:block}

/* The enlarged mark. Centred, because that is where the eye already is when
   something opens; the close control sits in the top right corner, which is
   where every dismissable overlay on the web puts it. */
.markbox{
  position:fixed; inset:0; z-index:120; display:none;
  align-items:center; justify-content:center;
  /* Plain rgba, not color-mix: this needs to be reliably opaque in every
     browser that renders the page, and a color-mix that degrades leaves the
     overlay see-through with the page competing behind the mark. */
  background:rgba(16,9,11,.93);
  backdrop-filter:blur(10px);
  animation:markbox-in .18s ease-out;
}
.markbox.on{display:flex}
@keyframes markbox-in{from{opacity:0} to{opacity:1}}
.markbox-art{
  display:flex; flex-direction:column; align-items:center; gap:18px;
  padding:28px;
}
.markbox-art img{
  width:min(46vw, 300px); height:auto; border-radius:26px;
  box-shadow:0 30px 80px -30px rgba(0,0,0,.9);
}
.markbox-name{
  font-family:var(--font-head); font-size:2.1rem; color:#fff; letter-spacing:-.02em;
  text-align:center; line-height:1.1;
}
.markbox-tag{
  font-family:var(--font-mono); font-size:12px; letter-spacing:.22em;
  text-transform:uppercase; color:var(--brand-deep); text-align:center;
}
.markbox-close{
  position:absolute; top:18px; right:18px;
  width:44px; height:44px; border-radius:50%;
  display:grid; place-items:center; cursor:pointer;
  background:rgba(255,255,255,.12); border:1px solid rgba(255,255,255,.28);
  color:#fff; font-size:22px; line-height:1;
}
.markbox-close:hover{background:rgba(255,255,255,.2)}
.markbox-close:focus-visible{outline:2px solid #fff; outline-offset:2px}

/* ============ LIGHT / DARK ============
   The palettes already existed for both, switched only by the operating
   system. This is the control that lets a visitor disagree with it. Three
   states, not two: "system" is the default and has to remain reachable, or
   a single click permanently opts someone out of following their own OS. */
.theme-toggle{
  display:inline-grid; place-items:center;
  width:34px; height:34px; padding:0;
}
.theme-toggle svg{display:block}
.theme-toggle .icon-dark{display:none}
:root[data-theme="dark"] .theme-toggle .icon-dark{display:block}
:root[data-theme="dark"] .theme-toggle .icon-light{display:none}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .theme-toggle .icon-dark{display:block}
  :root:not([data-theme="light"]) .theme-toggle .icon-light{display:none}
}

/* The doorway's own stats sit on a photograph, so they carry the shadow the
   matrimony panel's copies do not need. */
.doorway-stats{margin-top:30px}
.doorway-stats strong{text-shadow:0 2px 18px rgba(0,0,0,.45)}
.doorway-stats span{color:var(--body)}

@media (max-width:900px){
  .topnav .mark .glyph, .topnav .mark-glyph{width:46px; height:46px}
  .topnav .mark .wordmark{font-size:23px}
  .topnav .mark .modetag{font-size:12px}
  .markbox-art img{width:min(68vw, 240px)}
}

/* ============ HEADER: THREE PARTS ============
   Mark hard left, links centred, actions hard right. The links were in the
   same flex run as the actions, so the whole group drifted left or right
   with the width of the buttons beside it and never sat on the page's
   centre line. Now the two side groups are fixed and the middle one is the
   only thing that grows, so the links centre against the page rather than
   against whatever happens to be next to them. */
.topnav-in{justify-content:space-between}
.topnav .mark{flex:0 0 auto}
.topnav-menu{flex:1 1 auto; justify-content:center; gap:16px}
.topnav-links{flex:0 1 auto; justify-content:center}
.topnav-actions{flex:0 0 auto; margin-left:auto; display:flex; align-items:center; gap:8px}

/* ============ THE LANGUAGE MENU ============ */
.langpick{position:relative; display:inline-flex}
.langmenu{
  display:none; position:absolute; top:calc(100% + 8px); right:0; z-index:60;
  min-width:216px; max-height:min(60vh, 420px); overflow-y:auto;
  padding:6px; border-radius:12px;
  background:var(--surface); border:1px solid var(--line);
  box-shadow:0 18px 44px -20px rgba(0,0,0,.5);
}
.langmenu.on{display:block}
/* The button and the menu are separated by 8px. Without something spanning
   the gap the pointer leaves both on the way down and the menu closes under
   it — the close delay in the script covers the timing, this covers the
   geometry. */
.langpick::after{content:''; position:absolute; top:100%; right:0; width:100%; height:10px}
.langmenu-head{
  padding:7px 10px 8px; font-family:var(--font-mono); font-size:10px;
  letter-spacing:.14em; text-transform:uppercase; color:var(--muted);
  border-bottom:1px solid var(--line); margin-bottom:4px;
}
.langmenu-item{
  display:flex; align-items:baseline; justify-content:space-between; gap:10px;
  padding:8px 10px; border-radius:8px; text-decoration:none;
  color:var(--body); font-size:14px; line-height:1.3;
}
.langmenu-item:hover{background:var(--surface-2); color:var(--ink)}
.langmenu-item.on{background:var(--brand-tint); color:var(--brand-deep)}
.langmenu-name{font-weight:500}
.langmenu-note{
  font-size:10px; color:var(--muted); font-family:var(--font-mono);
  letter-spacing:.06em; white-space:nowrap; flex:none;
}
/* Right-to-left names still align to their own reading edge inside the row. */
.langmenu-item[dir="rtl"]{flex-direction:row-reverse}

@media (max-width:900px){
  /* The collapsed nav stacks, so the centring above must not apply. */
  .topnav-menu{justify-content:flex-start}
  .topnav-links{justify-content:flex-start}
  .topnav-actions{margin-left:0; flex-wrap:wrap}
  .langmenu{right:auto; left:0}
}

/* True centring for the links.
   The mark and the actions are different widths and live in different
   parents, so no amount of flex-grow puts the links on the page's centre
   line — they end up centred in whatever space is left over. Taking them out
   of flow and centring on the header itself is the only thing that actually
   does it.

   Wide screens only. Below this the three groups no longer fit on one row
   and the absolute middle would sit on top of the mark, so the flex layout
   above takes over again. */
@media (min-width:1000px){
  .topnav-in{position:relative}
  .topnav-links{
    position:absolute; left:50%; top:50%;
    transform:translate(-50%, -50%);
    margin:0; white-space:nowrap;
  }
  /* The links no longer occupy space, so the menu row is just the actions. */
  .topnav-menu{justify-content:flex-end}
}

/* ============ HEADER, EDGE TO EDGE ============
   .topnav-in was a centred 1080px column, so on anything wider the mark
   started ~72px in — half the page's own left margin plus the bar's padding
   — and read as floating rather than anchored. The doorway below it is full
   bleed; the header sitting on a narrower measure than the art underneath is
   what made the gap obvious.

   Full width with a small gutter instead: the mark sits at the left edge,
   the actions at the right, and the links stay centred on the page because
   they are positioned against this element, which is now the page. */
.topnav-in{max-width:none; padding-left:16px; padding-right:16px}

@media (max-width:900px){
  .topnav-in{padding-left:13px; padding-right:13px}
}

/* The MATRIMONY / DATING kicker over each half's heading.
   10.5px is the size of a caption, and this is the label that tells you
   which of the two products you are now reading about — the one distinction
   the whole front page is built around. Sized to be read, not squinted at.
   Scoped to .half-head so the quieter .lbl elsewhere (section heads, the
   rail, admin cards) keeps its caption size. */
.half-head > .lbl{
  font-size:15px; letter-spacing:.2em; color:var(--brand);
  display:block; margin-bottom:2px;
}
.half-dating .half-head > .lbl{color:var(--connect-brand, var(--brand))}

/* ============ THE SITE FOOTER ============
   On every page, inside .main so it sits under the content column rather
   than running behind the rail. It carries only who publishes the site —
   no member data — which is why the same footer is safe under the Connect
   palette too. */
.sitefoot{
  margin-top:auto;
  border-top:1px solid var(--line);
  background:var(--surface);
  color:var(--body);
}
/* .main is a flex column, so margin-top:auto above pins the footer to the
   bottom on pages too short to fill the screen instead of leaving it
   floating halfway up. */
.main{min-height:100vh}

.sitefoot-in{
  max-width:1180px; margin:0 auto;
  padding:44px 22px 34px;
  display:grid; gap:32px;
  grid-template-columns:minmax(0,1.6fr) minmax(0,1fr) minmax(0,1.1fr);
}

.sitefoot-mark{display:flex; align-items:center; gap:12px}
.sitefoot-mark .mark-glyph{width:44px; height:44px; border-radius:9px}
.sitefoot-name{
  font-family:var(--font-head); font-size:19px; color:var(--ink);
  letter-spacing:-.01em; line-height:1.1;
}
.sitefoot-tag{
  font-family:var(--font-mono); font-size:10.5px; letter-spacing:.2em;
  text-transform:uppercase; color:var(--gold); margin-top:3px;
}
.sitefoot-about{
  margin:16px 0 0; font-size:13px; line-height:1.65;
  color:var(--muted); max-width:52ch;
}

.sitefoot-by{
  margin:12px 0 0; font-size:12px; color:var(--muted);
}
.sitefoot-by a{color:var(--gold); text-decoration:none; font-weight:600}
.sitefoot-by a:hover{text-decoration:underline}

.sitefoot-social{display:flex; gap:9px; margin-top:18px}
.sitefoot-soc{
  width:34px; height:34px; border-radius:8px;
  display:grid; place-items:center;
  color:var(--muted); background:var(--surface-2);
  border:1px solid var(--line); text-decoration:none;
  transition:color .16s ease, border-color .16s ease, background .16s ease;
}
.sitefoot-soc:hover{color:var(--brand); border-color:var(--brand); background:var(--brand-tint)}
.sitefoot-soc:focus-visible{outline:2px solid var(--brand); outline-offset:2px}

.sitefoot-links{display:flex; flex-direction:column; gap:13px; padding-top:6px}
.sitefoot-links a{
  color:var(--body); text-decoration:none; font-size:14px;
  transition:color .16s ease;
}
.sitefoot-links a:hover{color:var(--brand); text-decoration:underline}

.sitefoot-head{
  font-family:var(--font-mono); font-size:10.5px; letter-spacing:.2em;
  text-transform:uppercase; color:var(--ink); margin-bottom:15px;
}
.sitefoot-row{
  display:flex; align-items:flex-start; gap:10px;
  margin-bottom:12px; font-size:13.5px; line-height:1.5;
  color:var(--body); text-decoration:none;
}
.sitefoot-row svg{color:var(--gold); flex:none; margin-top:2px}
a.sitefoot-row:hover{color:var(--brand)}

.sitefoot-base{
  border-top:1px solid var(--line);
  /* Full bleed: the rule and its tint run to both edges of the page, while
     the text inside stays on the same 1180px column as the rest of the
     footer. The side padding is that column's own gutter plus whatever is
     left over, so the copyright line does not move when the window is
     wider than the column. */
  padding:15px max(22px, calc((100% - 1180px) / 2 + 22px));
  display:flex; flex-wrap:wrap; gap:10px 20px;
  align-items:center; justify-content:space-between;
  font-size:11.5px; color:var(--muted);
}
.sitefoot-base strong{color:var(--body); font-weight:600}
.sitefoot-heart{color:var(--brand)}

@media (max-width:900px){
  .sitefoot-in{grid-template-columns:1fr; gap:28px; padding:32px 16px 26px}
  .sitefoot-links{flex-direction:row; flex-wrap:wrap; gap:10px 20px; padding-top:0}
  .sitefoot-base{padding:14px 16px; justify-content:flex-start}
}

/* ============ THE NAV RULE ============
   A hairline under the centred links, so the header reads as a bar with a
   navigation set into it rather than a row of words floating over the art
   below. Only where the links are actually centred — below that width they
   stack, and a rule under a stacked list is just a stray line. */
@media (min-width:1000px){
  .topnav-links{padding-bottom:9px}
  .topnav-links::after{
    content:''; position:absolute; left:0; right:0; bottom:0;
    height:1px; background:var(--line-strong); opacity:.7;
  }
}

/* ============ FAQ ACCORDION ============
   <details>/<summary>, so the browser owns open/closed state, keyboard
   handling and the screen-reader announcement, and every answer stays in
   the DOM for a crawler whether or not it is expanded. */
.faq-page{max-width:820px; margin:0 auto}
.faq-head{text-align:center; margin-bottom:30px}
.faq-head h1{font-size:clamp(2rem, 3.2vw, 2.8rem); letter-spacing:-.02em}
.faq-head .sub{margin:12px auto 0; color:var(--muted); font-size:15px; max-width:56ch; line-height:1.6}

.faq-list{display:flex; flex-direction:column; gap:10px}

.faq-item{
  background:var(--surface-2); border:1px solid transparent;
  border-radius:12px; overflow:hidden;
  transition:background .16s ease, border-color .16s ease;
}
.faq-item[open]{background:var(--surface); border-color:var(--line)}
.faq-item:hover{border-color:var(--line)}

.faq-item > summary{
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  padding:17px 20px; cursor:pointer; list-style:none;
  font-size:15.5px; color:var(--ink); line-height:1.4;
}
/* Both are needed: -webkit- for Safari, ::marker for everyone else. */
.faq-item > summary::-webkit-details-marker{display:none}
.faq-item > summary::marker{content:''}
.faq-item > summary:focus-visible{outline:2px solid var(--brand); outline-offset:-2px}

/* Drawn rather than a glyph, so the two strokes are identical and the
   horizontal one can stay put while the vertical one disappears. */
.faq-sign{position:relative; width:14px; height:14px; flex:none; opacity:.75}
.faq-sign::before,.faq-sign::after{
  content:''; position:absolute; background:currentColor; border-radius:1px;
  transition:transform .18s ease, opacity .18s ease;
}
.faq-sign::before{left:0; top:6px; width:14px; height:2px}
.faq-sign::after{left:6px; top:0; width:2px; height:14px}
.faq-item[open] .faq-sign::after{transform:scaleY(0); opacity:0}

.faq-a{
  padding:0 20px 18px; margin:0;
  font-size:14px; line-height:1.7; color:var(--muted); max-width:70ch;
}

.faq-more{
  display:flex; align-items:center; justify-content:center; gap:12px;
  flex-wrap:wrap; margin-top:32px; padding-top:24px;
  border-top:1px solid var(--line);
  font-size:14px; color:var(--muted);
}

@media (max-width:640px){
  .faq-item > summary{padding:15px 16px; font-size:14.5px}
  .faq-a{padding:0 16px 16px}
}

/* ============ THE GRADIENT LAYER ============
   One hue ramp — Burgundy into Deep Rose — at one angle, reused on every
   raised surface. Mixing hues or angles would give each element its own
   light source; a single ramp reads as one material catching the same light.

   Applied only where a surface is meant to lift off the page: the primary
   button, the header bar, the footer and the closing banner. Cards, inputs
   and panels stay flat, because a page where everything is a gradient is a
   page with no hierarchy left to spend. */

/* The call to action. Deep Rose, not the branding Burgundy — the palette
   gives them different jobs, and a button that matches the header stops
   reading as a button. */
.btn{
  background:var(--grad-brand);
  color:#FFF0F3;
  box-shadow:0 1px 2px rgba(31,2,11,.18);
}
.btn:hover{background:var(--grad-brand-hover)}
.btn:focus-visible{outline:2px solid var(--action-hover); outline-offset:2px}
/* .ghost and .quiet opt out: they were transparent before and the gradient
   above would have filled them in. */
.btn.ghost,.btn.quiet{background:transparent; box-shadow:none}
.btn.ghost:hover{background:var(--brand-tint)}
.btn.quiet:hover{background:var(--surface-2)}

/* The header. A wash rather than the full ramp — it sits behind text all
   the way across, and a strong gradient there fights every label on it. */
.topnav{
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--ground) 96%, transparent) 0%,
      color-mix(in srgb, var(--ground) 88%, transparent) 100%);
}

/* The footer, per the palette: Dark Maroon at the bottom of the page. */
.sitefoot{background:var(--grad-surface)}
.sitefoot-base{
  background:color-mix(in srgb, var(--brand) 8%, transparent);
  border-radius:0;
}

/* The closing banner already carried a brand gradient of its own; point it
   at the shared ramp so there is one definition rather than two that drift. */
.cta-banner{background:var(--grad-brand)}

/* Cards get the Claret border the palette asks for — the colour itself,
   not the low-alpha --line the rest of the page rules with. */
.card{border-color:var(--line)}
.card:hover{border-color:color-mix(in srgb, var(--line-strong) 45%, transparent)}

/* Secondary headings in Wine Red, as specified. Scoped to content headings
   so it cannot reach a card title or a form legend. */
.hd h1,.half-head h2{color:var(--ink)}
.section-head > .lbl,.sec > .lbl{color:var(--brand)}

/* ============ TERMS / PRIVACY ============ */
.legal-page{max-width:760px; margin:0 auto}
.legal-head{padding-bottom:22px; border-bottom:1px solid var(--line); margin-bottom:8px}
.legal-head h1{font-size:clamp(2rem, 3.2vw, 2.8rem); letter-spacing:-.02em; margin-top:6px}
.legal-head .sub{margin-top:12px; color:var(--muted); font-size:15.5px; line-height:1.6; max-width:62ch}
.legal-updated{
  margin-top:14px; font-family:var(--font-mono); font-size:11px;
  letter-spacing:.1em; text-transform:uppercase; color:var(--muted);
}
.legal-section{padding:26px 0; border-bottom:1px solid var(--line)}
.legal-section:last-child{border-bottom:none}
.legal-section h2{
  display:flex; align-items:baseline; gap:12px;
  font-size:1.25rem; letter-spacing:-.01em; color:var(--brand);
}
/* The number is a counter, not part of the sentence, so it is kept out of
   the heading's own text and sized like a label. */
.legal-n{
  font-family:var(--font-mono); font-size:12px; color:var(--muted);
  flex:none; min-width:20px;
}
.legal-section p{
  margin:10px 0 0 32px; font-size:14.5px; line-height:1.75;
  color:var(--body); max-width:68ch;
}
.legal-foot{
  margin-top:26px; padding-top:20px; border-top:1px solid var(--line);
}
.legal-foot a{color:var(--brand)}
@media (max-width:640px){
  .legal-section p{margin-left:0}
}

/* ============ SCROLL REVEAL ============
   One utility, used by anything that wants to arrive rather than appear.
   The class is added by script only when an IntersectionObserver exists, so
   with JavaScript off — or in a crawler — content is simply visible. That
   ordering matters: hiding first in CSS and revealing in JS means a script
   failure leaves a blank page. */
.reveal.reveal-armed{opacity:0; transform:translateY(14px)}
.reveal.reveal-armed.is-in{
  opacity:1; transform:none;
  transition:opacity .55s cubic-bezier(.22,.7,.3,1), transform .55s cubic-bezier(.22,.7,.3,1);
}
@media (prefers-reduced-motion: reduce){
  .reveal.reveal-armed,.reveal.reveal-armed.is-in{opacity:1; transform:none; transition:none}
}

/* ============ SECURITY & PRIVACY PANEL ============ */
.secpanel{margin:8px 0 34px}
.secpanel-head{text-align:center; max-width:60ch; margin:0 auto}
.secpanel-head h2{font-size:clamp(2rem, 3.4vw, 3rem); letter-spacing:-.025em; line-height:1.08}
.secpanel-head p{margin-top:12px; color:var(--muted); font-size:15px; line-height:1.65}

.secpanel-rail{color:var(--line-strong); opacity:.45; margin:26px 0 -30px}
@media (max-width:860px){ .secpanel-rail{display:none} }

.secpanel-cards{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:18px;
}
.secpanel-card{
  position:relative; padding:44px 22px 26px; text-align:center;
  background:var(--surface); border:1px solid var(--line); border-radius:18px;
  box-shadow:var(--shadow);
  transition:transform .22s cubic-bezier(.22,.7,.3,1), border-color .22s ease, box-shadow .22s ease;
}
.secpanel-card:hover{
  transform:translateY(-4px);
  border-color:color-mix(in srgb, var(--line-strong) 55%, transparent);
  box-shadow:0 18px 40px -24px rgba(31,2,11,.45);
}
/* Staggered by --d, so the three land in sequence rather than together. */
.reveal-armed .secpanel-card{opacity:0; transform:translateY(16px)}
.reveal-armed.is-in .secpanel-card{
  opacity:1; transform:none;
  transition:opacity .5s cubic-bezier(.22,.7,.3,1) var(--d,0ms),
             transform .5s cubic-bezier(.22,.7,.3,1) var(--d,0ms);
}
@media (prefers-reduced-motion: reduce){
  .reveal-armed .secpanel-card{opacity:1; transform:none}
  .secpanel-card:hover{transform:none}
}

.secpanel-num{
  position:absolute; top:-19px; left:50%; transform:translateX(-50%);
  width:38px; height:38px; border-radius:50%;
  display:grid; place-items:center;
  background:var(--surface); border:1px solid var(--line);
  font-family:var(--font-mono); font-size:13px; color:var(--muted);
  box-shadow:0 4px 12px -6px rgba(31,2,11,.3);
}

.secpanel-art{
  background:var(--grad-surface);
  border:1px solid var(--line); border-radius:14px;
  padding:14px; margin-bottom:18px;
}
.secpanel-art svg{display:block; width:100%; height:auto; color:var(--line-strong)}
/* The illustration parts read from the palette, so one drawing serves both
   themes instead of shipping a light copy and a dark copy. */
.sp-panel{fill:var(--surface); stroke:var(--line); stroke-width:1.2}
.sp-fill{fill:var(--line-strong); opacity:.55}
.sp-dim{fill:var(--surface-2); stroke:var(--line); stroke-width:1}
.sp-hide{fill:var(--surface-3); opacity:.75}
.sp-accent{fill:var(--action)}
.sp-accent-fill{fill:var(--action)}
.sp-fill-path{fill:var(--line-strong); opacity:.55}
.sp-stroke{stroke:var(--action); stroke-width:2; stroke-linecap:round; stroke-linejoin:round; fill:none}
.sp-muted-stroke{stroke:var(--muted); stroke-width:1.6; stroke-linecap:round; opacity:.7; fill:none}
.sp-dash{stroke:var(--line-strong); stroke-width:1.6; stroke-dasharray:4 5; stroke-linecap:round; fill:none; opacity:.8}

.secpanel-card h3{font-size:1.06rem; letter-spacing:-.01em; margin:0}
.secpanel-card p{margin:8px 0 0; font-size:13.5px; line-height:1.6; color:var(--muted)}

@media (max-width:860px){
  .secpanel-cards{grid-template-columns:1fr; gap:30px}
  .secpanel-card{padding-top:40px}
}

/* The legal column is 760px for readable line length; three cards in that
   width would be 230px each. The panel breaks out to its own measure and
   the prose stays narrow, which is the point of the 760 in the first place. */
.legal-page .secpanel{
  /* Margins, never a transform. The panel used to centre itself with
     translateX(-50%), which the reveal animation's own `transform:none`
     cancelled the instant it finished — leaving the whole panel sitting half
     a page to the right. Layout and animation must not share a property. */
  /* One expression, used for the width and for half of the pull-back, so
     the two cannot disagree. The earlier version derived the offset from
     50% of the parent minus 520px of viewport, and 100vw counts the
     scrollbar while the parent's width does not — which left the panel
     about 15px right of centre on every desktop. */
  --sp-w:min(1040px, calc(100vw - 44px));
  width:var(--sp-w);
  margin-left:calc((100% - var(--sp-w)) / 2);
  margin-bottom:40px;
}

/* ============ AURORA (sign-in / sign-up backdrop) ============
   Four blurred blobs on the brand ramp, drifting slowly. Only transform and
   opacity animate — both composited — so this costs the compositor a few
   layers and the main thread nothing. Fixed and pointer-inert, behind
   everything. */
.aurora{
  position:fixed; inset:0; z-index:-1; overflow:hidden; pointer-events:none;
  background:var(--grad-surface);
}
.aurora-blob{
  position:absolute; border-radius:50%;
  filter:blur(70px); opacity:.55;
  will-change:transform;
}
.aurora-1{
  width:46vw; height:46vw; left:-8vw; top:-10vw;
  background:radial-gradient(circle at 40% 40%, var(--brand) 0%, transparent 68%);
  animation:aurora-a 24s ease-in-out infinite alternate;
}
.aurora-2{
  width:40vw; height:40vw; right:-6vw; top:6vw;
  background:radial-gradient(circle at 55% 45%, var(--action) 0%, transparent 66%);
  animation:aurora-b 30s ease-in-out infinite alternate;
}
.aurora-3{
  width:52vw; height:52vw; left:18vw; bottom:-24vw;
  background:radial-gradient(circle at 50% 50%, var(--action-hover) 0%, transparent 64%);
  animation:aurora-c 27s ease-in-out infinite alternate;
}
.aurora-4{
  width:30vw; height:30vw; right:14vw; bottom:-6vw;
  background:radial-gradient(circle at 50% 50%, var(--brand-deep) 0%, transparent 70%);
  animation:aurora-a 33s ease-in-out infinite alternate-reverse;
  opacity:.4;
}
/* A faint tooth over the blur. Without it the gradients band visibly on
   8-bit displays, which is exactly where a large soft gradient shows it. */
.aurora-grain{
  position:absolute; inset:0; opacity:.5;
  background-image:radial-gradient(circle at 1px 1px, var(--hero-dot) .9px, transparent 1px);
  background-size:28px 28px;
}

@keyframes aurora-a{
  from{transform:translate3d(0,0,0) scale(1)}
  to  {transform:translate3d(6vw,4vw,0) scale(1.14)}
}
@keyframes aurora-b{
  from{transform:translate3d(0,0,0) scale(1.06)}
  to  {transform:translate3d(-7vw,6vw,0) scale(.94)}
}
@keyframes aurora-c{
  from{transform:translate3d(0,0,0) scale(1)}
  to  {transform:translate3d(-5vw,-6vw,0) scale(1.1)}
}
@media (prefers-reduced-motion: reduce){
  .aurora-blob{animation:none}
}

/* The card in front of it. Glass rather than solid, so the aurora reads
   through without ever competing with the form.

   Scoped with :has() rather than a wrapper class, so the five auth views
   need no markup change and a sixth one gets this simply by including the
   aurora partial. */
.view:has(> .aurora){
  max-width:560px; margin-left:auto; margin-right:auto; text-align:center;
}
/* .hd is a flex column, so centring its text is not enough — the items
   themselves have to be centred too. */
.view:has(> .aurora) .hd{align-items:center}
.view:has(> .aurora) .hd h1{font-size:clamp(2rem, 3.2vw, 2.6rem)}
/* The forms centre as blocks, but their contents stay left-aligned: a
   centred field label floats away from the box it belongs to, and a centred
   error message is harder to tie to the field that caused it. */
.view:has(> .aurora) form{
  margin-left:auto; margin-right:auto; width:100%; text-align:left;
}
.view:has(> .aurora) form .hint,
.view:has(> .aurora) > .hint,
.view:has(> .aurora) > .sm,
.view:has(> .aurora) > .xs{text-align:center}
.view:has(> .aurora) .btn.lg,
.view:has(> .aurora) .btn.full{width:100%}

.auth-shell{max-width:440px; margin:0 auto}
.view:has(> .aurora) .card,
.auth-shell .card{
  background:color-mix(in srgb, var(--surface) 82%, transparent);
  backdrop-filter:blur(14px) saturate(1.1);
  border-color:color-mix(in srgb, var(--line-strong) 30%, transparent);
  box-shadow:0 28px 70px -34px rgba(31,2,11,.55);
}

/* ============ LIVE RULES ============
   The hairlines carry a slow gradient travelling along them rather than a
   flat colour. One shared animation and one shared image, so every rule on
   the site moves in step instead of each having its own timing.

   Applied to rules only — the horizontal dividers that separate sections.
   NOT to card borders: a moving edge on a box you are trying to read is a
   distraction, and forty of them animating at once is a battery complaint. */
@property --rule-x{ syntax:'<length-percentage>'; inherits:false; initial-value:0%; }

.rule-live,
.topnav-links::after,
.half-head,
.legal-section,
.hd,
.section-head > .lbl,
.sec > .lbl,
.faq-more,
.legal-foot,
.sitefoot,
.sitefoot-base,
.secpanel-rail{
  --rule-img:linear-gradient(90deg,
      transparent 0%,
      color-mix(in srgb, var(--brand) 30%, transparent) 18%,
      var(--action) 50%,
      color-mix(in srgb, var(--brand) 30%, transparent) 82%,
      transparent 100%);
}

/* The rules themselves. Each is an existing 1px border, replaced by a
   gradient image on the same edge so nothing moves in the layout. */
.half-head,
.legal-section,
.faq-more,
.legal-foot,
.section-head > .lbl,
.sec > .lbl{
  border-bottom-color:transparent;
  border-image:var(--rule-img) 1;
  border-image-slice:1;
}
.sitefoot,.sitefoot-base{
  border-top-color:transparent;
  border-image:var(--rule-img) 1;
  border-image-slice:1;
}

/* The travelling highlight. background-position is cheap to animate and
   does not trigger layout; the element itself never moves. */
.topnav-links::after{
  background:var(--rule-img);
  background-size:220% 100%;
  animation:rule-run 7s linear infinite;
}
.secpanel-rail{color:var(--action); opacity:.55}
.secpanel-wire{opacity:.42}
.secpanel-wire-run{
  stroke-dasharray:60 900; stroke-dashoffset:960;
  animation:wire-run 6.5s linear infinite;
  filter:drop-shadow(0 0 3px var(--action));
}
@keyframes rule-run{
  from{background-position:200% 0}
  to  {background-position:-20% 0}
}
@keyframes wire-run{
  0%   {stroke-dashoffset:960; opacity:0}
  8%   {opacity:1}
  70%  {opacity:1}
  100% {stroke-dashoffset:0; opacity:0}
}
@media (prefers-reduced-motion: reduce){
  .topnav-links::after{animation:none}
  .secpanel-wire-run{animation:none; stroke-dasharray:none; opacity:.7}
}

/* ============ TRANSPARENT HEADER ============
   The doorway's slideshow runs full bleed underneath, so the bar reads as
   glass over the photograph instead of a solid strip cutting the top off it.
   The blur and the faint floor keep the links legible over any frame — an
   admin can upload anything, and a transparent bar over a pale photograph
   is a navigation nobody can read. */
.topnav{
  background:linear-gradient(180deg,
      color-mix(in srgb, var(--ground) 46%, transparent) 0%,
      color-mix(in srgb, var(--ground) 18%, transparent) 100%);
  backdrop-filter:blur(14px) saturate(1.15);
  -webkit-backdrop-filter:blur(14px) saturate(1.15);
  border-bottom-color:transparent;
  border-image:var(--rule-img) 1;
  border-image-slice:1;
}
/* Every page that is not the doorway has ordinary content under the bar, so
   there the glass sits on a near-solid ground and stays readable. */
/* The near-solid bar for pages without the doorway is gone. It existed when
   those pages had nothing behind the header worth showing — now every page
   carries the live map, so glass everywhere and one header rather than two.
*/

/* ============ PAGER ============ */
.pager{
  display:flex; align-items:center; justify-content:center; gap:14px;
  padding:8px 0;
}
.pager-step{
  width:38px; height:38px; border-radius:10px;
  display:grid; place-items:center;
  font-size:20px; line-height:1; text-decoration:none;
  color:var(--brand); background:var(--surface);
  border:1px solid var(--line);
  transition:background .16s ease, border-color .16s ease, color .16s ease;
}
.pager-step:hover{background:var(--brand-tint); border-color:var(--line-strong)}
.pager-step:focus-visible{outline:2px solid var(--action); outline-offset:2px}
.pager-step.is-off{color:var(--muted); opacity:.45; pointer-events:none}
.pager-count{
  font-family:var(--font-mono); font-size:11.5px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--muted);
}

/* The header's own height, declared once. The doorway subtracts it to slide
   underneath and adds it back as padding, so the two can never disagree
   about where the bar ends. Two values because the mark shrinks on narrow
   screens and the bar shrinks with it. */
:root{ --nav-h:79px }
@media (max-width:900px){ :root{ --nav-h:65px } }

/* ============ THE LIVE NETWORK MAP ============
   Full bleed, under the closing banner. Every animated property here is
   stroke-dashoffset, opacity or transform — none of them trigger layout, so
   sixteen routes and seventeen nodes cost the compositor and not the main
   thread. */
.worldmap{
  margin-left:calc(50% - 50vw); margin-right:calc(50% - 50vw);
  width:100vw; max-width:100vw;
  padding:clamp(46px, 6vw, 84px) clamp(16px, 4vw, 48px) clamp(40px, 5vw, 70px);
  /* The ground is shared with .homesec-members and is declared once, with
     that rule, further down the file. See the note there for why the two
     sections have to match. */
  border-top:1px solid transparent;
  border-image:var(--rule-img) 1; border-image-slice:1;
  overflow:hidden;
}
.worldmap-head{text-align:center; max-width:62ch; margin:0 auto 26px}
.worldmap-kicker{
  display:inline-block; margin-bottom:12px;
  padding:5px 12px; border-radius:999px;
  font-family:var(--font-mono); font-size:10.5px; letter-spacing:.2em;
  text-transform:uppercase; color:var(--action);
  background:color-mix(in srgb, var(--action) 12%, transparent);
  border:1px solid color-mix(in srgb, var(--action) 30%, transparent);
}
.worldmap-head h2{font-size:clamp(1.9rem, 3.2vw, 2.8rem); letter-spacing:-.025em; line-height:1.1}
.worldmap-head p{margin-top:12px; color:var(--muted); font-size:15px; line-height:1.65}

.worldmap-stage{position:relative; max-width:1180px; margin:0 auto}
.worldmap-svg{display:block; width:100%; height:auto}

/* The continents. A dot pattern rather than a filled shape: at this size a
   solid landmass would read as a heavy blob behind the routes, which are
   the thing worth looking at. */
.wm-dot{fill:var(--line-strong); opacity:.5}

/* The routes. Each is drawn twice: a faint standing line so the network is
   legible even between pulses, and a short bright dash that travels it. */
.wm-route{
  stroke:var(--line-strong); stroke-width:1; opacity:.28;
}
.wm-spark{
  stroke:url(#wm-arc); stroke-width:2.2; stroke-linecap:round;
  stroke-dasharray:22 1000; stroke-dashoffset:1022;
  animation:wm-fly 9s linear infinite; animation-delay:var(--d, 0s);
}
@keyframes wm-fly{
  0%   {stroke-dashoffset:1022; opacity:0}
  4%   {opacity:1}
  38%  {opacity:1}
  46%  {stroke-dashoffset:0; opacity:0}
  100% {stroke-dashoffset:0; opacity:0}
}

.wm-node-dot{fill:var(--action-hover)}
.wm-node.is-home .wm-node-dot{fill:#fff}
.wm-node-glow{opacity:.75}
.wm-node-ping{
  stroke:var(--action-hover); stroke-width:1.4;
  transform-box:fill-box; transform-origin:center;
  animation:wm-ping 3.2s ease-out infinite;
}
@keyframes wm-ping{
  0%   {transform:scale(.6); opacity:.9}
  70%  {transform:scale(3.4); opacity:0}
  100% {transform:scale(3.4); opacity:0}
}
/* Nodes arrive in the same order the routes reach them. */
.wm-node{opacity:0; animation:wm-arrive .6s ease-out forwards; animation-delay:var(--d, 0s)}
@keyframes wm-arrive{ to{opacity:1} }

.wm-label{
  position:absolute; transform:translate(-50%, -170%);
  white-space:nowrap; pointer-events:none;
  padding:4px 9px; border-radius:7px;
  font-size:11px; letter-spacing:.02em;
  color:var(--ink); background:color-mix(in srgb, var(--surface) 88%, transparent);
  border:1px solid var(--line); backdrop-filter:blur(6px);
  box-shadow:0 6px 18px -10px rgba(31,2,11,.5);
}

.worldmap-legend{
  display:flex; align-items:center; justify-content:center; gap:22px;
  margin-top:14px; flex-wrap:wrap;
  font-family:var(--font-mono); font-size:10.5px;
  letter-spacing:.16em; text-transform:uppercase; color:var(--muted);
}
.wm-live{display:inline-flex; align-items:center; gap:7px; color:var(--action)}
.wm-live i{
  width:7px; height:7px; border-radius:50%; background:var(--action);
  animation:wm-blink 2s ease-in-out infinite;
}
@keyframes wm-blink{ 0%,100%{opacity:1} 50%{opacity:.25} }

@media (prefers-reduced-motion: reduce){
  /* The network still reads — the routes are simply all lit at once and
     nothing travels or pulses. */
  .wm-spark{animation:none; stroke-dasharray:none; stroke-dashoffset:0; opacity:.8}
  .wm-node{animation:none; opacity:1}
  .wm-node-ping,.wm-live i{animation:none}
}
@media (max-width:700px){
  .wm-label{font-size:10px; padding:3px 7px}
  .worldmap-head p{font-size:14px}
}

/* ============ TRUST STRIP ============ */
.door{position:relative}

/* Smaller across the board.

   Four reassurances under the doors are a footnote, not a section: they are
   read once, on the way past, and at the old size the bar was as tall as the
   doors it was supporting. Everything shrinks together — padding, icon, both
   type sizes — because shrinking only the text leaves the icons looking
   pasted on. */
.trustbar{
  list-style:none; margin:18px 0 0; padding:9px 6px;
  display:grid; grid-template-columns:repeat(4, minmax(0,1fr));
  border-radius:16px;
  background:color-mix(in srgb, var(--surface) 62%, transparent);
  border:1px solid color-mix(in srgb, var(--line-strong) 26%, transparent);
  backdrop-filter:blur(12px) saturate(1.1);
}
.trustbar > li{
  display:block; padding:4px 12px;
  border-right:1px solid color-mix(in srgb, var(--line-strong) 20%, transparent);
}
.trustbar > li:last-child{border-right:none}
.trustbar-txt{display:flex; flex-direction:column; gap:2px; min-width:0}
.trustbar-txt strong{font-size:12px; color:var(--ink); font-weight:600; line-height:1.25}
.trustbar-txt em{font-style:normal; font-size:10.5px; color:var(--muted); line-height:1.4}

@media (max-width:900px){
  .trustbar{grid-template-columns:repeat(2, minmax(0,1fr)); gap:4px}
  .trustbar > li:nth-child(2n){border-right:none}
  .trustbar > li:nth-child(-n+2){border-bottom:1px solid color-mix(in srgb, var(--line-strong) 20%, transparent)}
}
@media (max-width:560px){
  .trustbar{grid-template-columns:1fr}
  .trustbar > li{border-right:none}
  .trustbar > li:not(:last-child){border-bottom:1px solid color-mix(in srgb, var(--line-strong) 20%, transparent)}
}

/* ============ BUTTON MOTION ============
   A sheen that crosses on hover, and a small lift. Wording untouched — the
   animation is on a pseudo-element and a transform, so no label moves or
   re-wraps. */
.btn{position:relative; overflow:hidden; isolation:isolate}
.btn::after{
  content:''; position:absolute; inset:0; z-index:-1;
  background:linear-gradient(110deg, transparent 20%,
    rgba(255,255,255,.28) 46%, rgba(255,255,255,.28) 54%, transparent 80%);
  transform:translateX(-120%);
  transition:transform .62s cubic-bezier(.22,.7,.3,1);
}
.btn:hover::after,.btn:focus-visible::after{transform:translateX(120%)}
.btn:hover{transform:translateY(-1px)}
.btn:active{transform:translateY(1px)}
.btn.ghost::after,.btn.quiet::after{display:none}
@media (prefers-reduced-motion: reduce){
  .btn::after{display:none}
  .btn:hover{transform:none}
}

/* Numbered pages. */
.pager-nums{display:flex; align-items:center; gap:5px; flex-wrap:wrap; justify-content:center}
.pager-num{
  min-width:34px; height:34px; padding:0 8px; border-radius:9px;
  display:grid; place-items:center;
  font-size:13.5px; text-decoration:none;
  color:var(--body); background:transparent; border:1px solid transparent;
  transition:background .16s ease, color .16s ease, border-color .16s ease;
}
.pager-num:hover{background:var(--surface-2); border-color:var(--line); color:var(--ink)}
.pager-num.is-on{
  background:var(--grad-brand); color:#FFF0F3; border-color:transparent; font-weight:600;
}
.pager-gap{padding:0 4px; color:var(--muted); font-size:13px}

/* ============ ICONS ============
   The typographic stand-ins these replaced took their shape from whatever
   font loaded, ignored the icon colour, and sat on the text baseline rather
   than with it. */
.ic-svg{display:block; flex:none}
.chip .ic-svg,.tick .ic-svg,.no .ic-svg,.trust-icon .ic-svg,
.sitefoot-heart .ic-svg,
.topnav-toggle .ic-svg{display:inline-block; vertical-align:-2px}
.chip{display:inline-flex; align-items:center; gap:5px}
.trust-icon,.tick,.no{display:inline-grid; place-items:center}
.sitefoot-heart{display:inline-grid; place-items:center; vertical-align:-2px}
/* ============ DOOR TAGS ============
   MATRIMONY and DATING are the one distinction the whole front page turns
   on. At 11px muted mono they were a caption; they are the label now. */
.door-tag{
  /* .door is a flex column, so an inline-block child still stretches to the
     full card width. fit-content plus align-self is what actually makes it
     a pill rather than a banner. */
  display:inline-block; width:fit-content; align-self:flex-start;
  margin-bottom:6px;
  padding:5px 11px; border-radius:999px;
  font-family:var(--font-mono);
  font-size:clamp(12px, 1.05vw, 14px);
  letter-spacing:.2em; text-transform:uppercase; font-weight:600;
  color:var(--brand-deep);
  background:color-mix(in srgb, var(--brand) 16%, transparent);
  border:1px solid color-mix(in srgb, var(--brand) 34%, transparent);
}
.door-dating .door-tag{
  color:var(--connect-brand);
  background:color-mix(in srgb, var(--connect-brand) 16%, transparent);
  border-color:color-mix(in srgb, var(--connect-brand) 36%, transparent);
}
/* On the photograph the doors sit over, a tint alone is not enough contrast
   for a small uppercase label. */
.doorway .door-tag{
  color:#FFF0F3;
  background:color-mix(in srgb, var(--brand) 62%, transparent);
  border-color:color-mix(in srgb, #FFF0F3 34%, transparent);
  backdrop-filter:blur(6px);
}
.doorway .door-dating .door-tag{
  background:color-mix(in srgb, var(--connect-brand) 62%, transparent);
}

/* ============ PRIVACY PAGE HEAD ============ */
.secpanel.is-heading{margin-top:0}
.secpanel.is-heading .secpanel-head{
  text-align:left; margin-left:0; max-width:66ch;
}
.secpanel-kicker{
  display:block; margin-bottom:6px;
  font-family:var(--font-mono);
  font-size:15px; letter-spacing:.22em; text-transform:uppercase;
  color:var(--brand); font-weight:600;
}
.secpanel.is-heading h1{
  font-size:clamp(2.2rem, 3.6vw, 3.1rem); letter-spacing:-.025em; line-height:1.06;
}
.legal-updated-alone{
  margin:0 0 26px;
  font-family:var(--font-mono); font-size:11px;
  letter-spacing:.1em; text-transform:uppercase; color:var(--muted);
}
.legal-kicker{
  display:block; margin-bottom:6px;
  font-family:var(--font-mono);
  font-size:15px; letter-spacing:.22em; text-transform:uppercase;
  color:var(--brand); font-weight:600;
}
/* The legal column starts right under the header now. */
.legal-page{padding-top:0}
.legal-head{padding-top:0}

/* ============ THE DOORWAY FACTS PANEL ============
   The three figures and the four assurances in one frame. They were two
   separate strips; seven items across a single row would have been about
   150px each, and stacking them as two rows inside one panel keeps the
   numbers large without splitting the block in two. */
.doorfacts{
  /* Glass, not a panel. At 62% surface this was an opaque slab across the
     photograph — the slideshow is the point of the doorway and the strip was
     covering a third of it. The fill is thin enough to see through and the
     blur does the legibility work instead, the same way the header does. */
  /* The panel now starts below the fold — .doorway-lead above it is sized
     to fill the first screen — so this is ordinary breathing room rather
     than the thing deciding what is visible on load. */
  margin-top:clamp(20px, 3.4vh, 44px); border-radius:16px; overflow:hidden;
  background:color-mix(in srgb, var(--surface) 22%, transparent);
  border:1px solid color-mix(in srgb, #FFF0F3 20%, transparent);
  backdrop-filter:blur(18px) saturate(1.2);
  -webkit-backdrop-filter:blur(18px) saturate(1.2);
  box-shadow:0 18px 44px -28px rgba(0,0,0,.6);
}
.doorfacts-stats{
  display:grid; grid-template-columns:repeat(3, minmax(0,1fr));
  padding:12px 8px 10px;
  border-bottom:1px solid color-mix(in srgb, #FFF0F3 18%, transparent);
}
.doorfacts-stats > div{
  display:flex; flex-direction:column; gap:2px; align-items:center;
  padding:0 14px; text-align:center;
  border-right:1px solid color-mix(in srgb, #FFF0F3 14%, transparent);
}
.doorfacts-stats > div:last-child{border-right:none}
.doorfacts-stats strong{
  font-family:var(--font-head); font-size:clamp(1.7rem, 2.6vw, 2.3rem);
  line-height:1; color:var(--ink); letter-spacing:-.02em;
}
.doorfacts-stats span{font-size:11.5px; color:var(--muted); letter-spacing:.02em}

/* The strip now sits inside the panel, so it drops its own frame. */
.doorfacts .trustbar{
  margin:0; border:none; border-radius:0;
  background:transparent; backdrop-filter:none;
  padding:10px 8px;
}

@media (max-width:760px){
  .doorfacts-stats{grid-template-columns:repeat(3, minmax(0,1fr)); padding:14px 4px 12px}
  .doorfacts-stats > div{padding:0 6px}
  .doorfacts-stats span{font-size:10.5px}
}

/* On the photograph the panel's own text carries a shadow rather than a
   heavier plate behind it — that keeps the slideshow visible through the
   glass while the numbers stay readable over a bright frame. */
.doorway .doorfacts-stats strong{text-shadow:0 2px 14px rgba(0,0,0,.55)}
.doorway .doorfacts-stats span,
.doorway .doorfacts .trustbar-txt em{color:var(--body); text-shadow:0 1px 8px rgba(0,0,0,.5)}
.doorway .doorfacts .trustbar-txt strong{text-shadow:0 1px 10px rgba(0,0,0,.55)}
.doorway .doorfacts .trustbar > li{
  border-right-color:color-mix(in srgb, #FFF0F3 12%, transparent);
}

/* Real screenshots rather than drawings. They are wider than they are tall
   and vary in height, so the frame crops to a fixed ratio and the image
   covers it — three cards the same height whatever each capture measured. */
.secpanel-art{padding:0; overflow:hidden; background:var(--surface-2)}
.secpanel-art img{
  display:block; width:100%; height:auto;
  aspect-ratio:16 / 10; object-fit:cover; object-position:top left;
}

/* The centring on .doorway-inner is for the headline and the doors. It
   cascaded into the trust strip, where each item is an icon beside a
   two-line label — centring that text detaches it from its own icon. */
.doorway .trustbar-txt{text-align:left}
.doorway .trustbar > li{justify-content:flex-start}

/* ============ THE DOORS, IN MOTION ============
   These two are the page's actual decision, so they get more than a hover
   colour: a lift, a lit edge that brightens, a sheen that crosses the card,
   and an arrow that moves off before the pointer arrives.

   The resting animation is deliberately slow and small. A card that pulses
   hard is a card people learn to ignore; this is meant to catch a glance,
   not compete with the headline above it. */
.door{
  transition:transform .28s cubic-bezier(.22,.7,.3,1),
             box-shadow .28s ease, background .28s ease, border-color .28s ease;
}
.door:hover{transform:translateY(-6px) scale(1.012)}
.door:focus-visible{outline:2px solid var(--action-hover); outline-offset:3px}

/* The sheen. A pseudo-element so nothing in the card moves with it. */
.door::after{
  content:''; position:absolute; inset:0; border-radius:inherit;
  pointer-events:none; z-index:-1; opacity:0;
  background:linear-gradient(110deg, transparent 24%,
    color-mix(in srgb, #FFF0F3 22%, transparent) 46%,
    color-mix(in srgb, #FFF0F3 22%, transparent) 54%, transparent 76%);
  transform:translateX(-120%);
  transition:transform .8s cubic-bezier(.22,.7,.3,1), opacity .2s ease;
}
.door:hover::after{opacity:1; transform:translateX(120%)}

/* ---- the living edge ----
   The border used to be transparent until the pointer arrived, and then it
   became a flat line in the product's colour. Now the colour is always
   travelling around the card and hovering only turns it up.

   How the ring is made: a pseudo-element the size of the card, filled with a
   gradient three times its width and slid sideways forever, then MASKED so
   only the outermost 1.6px survives. Two masks composited with `exclude`
   knock the middle out — the whole box minus the content box is exactly the
   border ring, and unlike `border-image` it follows the 18px radius round
   the corners instead of squaring them off. */
.door::before{
  content:''; position:absolute; inset:0; z-index:-1;
  border-radius:inherit; box-sizing:border-box; padding:1.6px;
  pointer-events:none;

  background-image:linear-gradient(100deg,
    var(--edge-1) 0%, var(--edge-2) 22%, var(--edge-3) 42%,
    var(--edge-4) 50%, var(--edge-3) 58%, var(--edge-2) 78%, var(--edge-1) 100%);
  background-size:300% 100%;

  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;
          mask-composite:exclude;

  opacity:.72;
  animation:door-edge 6s linear infinite;
  transition:opacity .28s ease;
}
.door:hover::before{ opacity:1 }

@keyframes door-edge{
  from{ background-position:0% 50% }
  to  { background-position:300% 50% }
}

/* Each door keeps its own end of the palette, the way its label does. */
.door-matrimony{
  --edge-1:#7A1F3D; --edge-2:#C34F70; --edge-3:#E9B44C; --edge-4:#FFF0F3;
}
.door-dating{
  --edge-1:#0F3A32; --edge-2:#2F7A6B; --edge-3:#6FBFA8; --edge-4:#EAF7F3;
}

/* The call to action. The arrow is its own span, so it can travel without
   dragging the words with it. */
.door-cta{
  display:inline-flex; align-items:center; gap:8px;
  margin-top:4px; font-weight:600;
}
.door-cta > span{
  display:inline-block;
  transition:transform .28s cubic-bezier(.22,.7,.3,1);
  animation:door-nudge 2.8s ease-in-out infinite;
}
.door:hover .door-cta > span{transform:translateX(6px); animation:none}
@keyframes door-nudge{
  0%,100%{transform:translateX(0)}
  50%    {transform:translateX(4px)}
}

/* A slow breath on the pill, so a still page still has something alive in
   it. Scale only — no layout, no repaint of the text. */
.door-tag{animation:door-breathe 4.2s ease-in-out infinite; transform-origin:left center}
.door:hover .door-tag{animation:none; transform:scale(1.04)}
@keyframes door-breathe{
  0%,100%{transform:scale(1)}
  50%    {transform:scale(1.035)}
}

@media (prefers-reduced-motion: reduce){
  .door:hover{transform:none}
  .door::after{display:none}
  .door::before{ animation:none; background-position:50% 50% }
  .door-cta > span,.door-tag{animation:none}
  .door:hover .door-cta > span{transform:none}
}

/* ============ THE MAP, AS BACKDROP ============
   The same network behind every page. Fixed, pointer-inert, and far quieter
   than the section version: it is atmosphere, and atmosphere must never win
   a contrast fight with the text on top of it.

   z-index -2 rather than -1, so the aurora on the auth screens still layers
   above it and the two read as one backdrop rather than fighting. */
.worldmap-bg{
  position:fixed; inset:0; z-index:-2; overflow:hidden; pointer-events:none;
  background:var(--grad-surface);
}
.worldmap-bg .worldmap-stage{
  position:absolute; inset:0; max-width:none; margin:0;
}
.worldmap-bg .worldmap-svg{
  /* The whole world, centred. `slice` filled the frame but cropped roughly a
     third of the map off the sides — a world map you cannot recognise is
     just texture. Letterboxing is fine here: the washes and the grid carry
     the space above and below it. */
  width:100%; height:100%;
}
/* The routes are the living part; the land is only there to place them.
   The first pass had those weights the wrong way round — dots at .16 against
   routes at .1 meant the network read as texture, and the one thing actually
   moving was the quietest thing on screen.
   Land down, routes up, and the travelling spark given a glow so the eye
   catches it against a dotted ground. */
/* The land dots ARE the map. Last pass dimmed them along with the page's
   dot grid — but those are different layers: the grid was competing with the
   routes, the continents are the thing the routes are drawn on. Dropping
   both left a network floating over nothing recognisable. */
.worldmap-bg .wm-dot{opacity:.42; fill:var(--action)}
.worldmap-bg .wm-route{
  opacity:.34; stroke-width:1.2;
  stroke:color-mix(in srgb, var(--action) 70%, transparent);
}
.worldmap-bg .wm-spark{
  stroke-width:2.6; opacity:1;
  filter:drop-shadow(0 0 4px var(--action-hover));
}
.worldmap-bg .wm-node-glow{opacity:.5}
.worldmap-bg .wm-node-dot{opacity:.95}
.worldmap-bg .wm-node-ping{opacity:.7}

/* The home page shows the real thing. */
body:has(.worldmap) .worldmap-bg{display:none}

/* ---- the same backdrop, in Connect's colours ----
   --action, --action-hover and --grad-surface are the matrimonial palette's;
   [data-mode="connect"] redefines --brand and the surfaces but not those, so
   the map arrived on the messenger as alta red over a green ground.

   Rebound HERE, on the backdrop itself, rather than added to the connect
   token block. Custom properties inherit, so every rule inside — the land
   dots, the routes, the travelling spark, the node pings, the second wash —
   picks the new value up from this one declaration; and because the scope is
   this element, nothing outside the backdrop changes colour. Setting them on
   :root[data-mode="connect"] would have repainted every button in Connect. */
:root[data-mode="connect"] .worldmap-bg{
  --action:var(--brand);
  --action-hover:var(--brand-deep);
  background:linear-gradient(180deg, var(--ground) 0%, var(--surface) 100%);
}

/* The aurora already paints its own ground on the auth screens; letting the
   map show through it keeps one backdrop rather than stacking two. */
.aurora{background:transparent}

@media (prefers-reduced-motion: reduce){
  .worldmap-bg .wm-spark{animation:none; stroke-dasharray:none; opacity:.3}
  .worldmap-bg .wm-node-ping{animation:none}
}

/* ============ THE BACKDROP'S OWN GROUND ============
   Under the map: the brand gradient, two slow washes, and the dot grid. This
   is what makes every page share one surface instead of each being a flat
   panel that happens to use the same palette.

   The washes animate transform only — composited, so a backdrop that is on
   every page still costs the main thread nothing. */
.wmbg-wash{
  position:absolute; border-radius:50%; filter:blur(80px); pointer-events:none;
  will-change:transform;
}
.wmbg-wash-1{
  width:58vw; height:58vw; left:-12vw; top:-16vw;
  background:radial-gradient(circle at 45% 45%, var(--brand) 0%, transparent 66%);
  opacity:.4;
  animation:wmbg-a 34s ease-in-out infinite alternate;
}
.wmbg-wash-2{
  width:50vw; height:50vw; right:-10vw; bottom:-18vw;
  background:radial-gradient(circle at 50% 50%, var(--action) 0%, transparent 64%);
  opacity:.3;
  animation:wmbg-b 41s ease-in-out infinite alternate;
}
@keyframes wmbg-a{
  from{transform:translate3d(0,0,0) scale(1)}
  to  {transform:translate3d(7vw,5vw,0) scale(1.15)}
}
@keyframes wmbg-b{
  from{transform:translate3d(0,0,0) scale(1.08)}
  to  {transform:translate3d(-6vw,-5vw,0) scale(.95)}
}

/* The dot grid, over the washes and under the map. Without it the large soft
   gradients band visibly on an 8-bit display — which is exactly where a
   full-screen wash shows it. */
.wmbg-grain{
  position:absolute; inset:0; pointer-events:none;
  background-image:radial-gradient(circle at 1px 1px, var(--hero-dot) 1px, transparent 1.15px);
  background-size:34px 34px;
  /* Lighter than it was. At .7 the grid was the loudest thing in the
     backdrop and the routes disappeared into it. */
  opacity:.28;
}

/* The map draws above those layers. */
.worldmap-bg .worldmap-stage{z-index:1}

/* The aurora carried its own copy of this grid. Two dot grids at slightly
   different pitches moiré against each other, so the auth screens keep the
   backdrop's one and the aurora contributes only its blobs. */
.aurora-grain{display:none}

@media (prefers-reduced-motion: reduce){
  .wmbg-wash{animation:none}
}

/* ============ GLASS CHROME, EVERYWHERE ============
   One header treatment on every page. The backdrop runs under all of them
   now, so the bar can be glass throughout instead of turning solid the
   moment it left the doorway.

   The tint is a floor, not a look: an admin can upload any slideshow frame,
   and a fully transparent bar over a pale photograph is a navigation nobody
   can read. Blur plus a light wash keeps the links legible over anything
   while still showing what is behind. */
.topnav{
  background:linear-gradient(180deg,
    color-mix(in srgb, var(--ground) 52%, transparent) 0%,
    color-mix(in srgb, var(--ground) 30%, transparent) 100%);
  backdrop-filter:blur(16px) saturate(1.2);
  -webkit-backdrop-filter:blur(16px) saturate(1.2);
}



/* Safari and Firefox without backdrop-filter would get a barely-tinted bar
   over a moving map, which is unreadable. They get an opaque one instead. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .topnav{background:color-mix(in srgb, var(--ground) 94%, transparent)}
  .navdrop-menu{background:var(--surface)}
}

/* ============ THE FIRST SCREEN ============
   The headline, the subtitle and the two doors fill the viewport on load;
   the figures panel begins after them and is reached by scrolling.

   Sized off the lead rather than by piling margin onto the panel: margin is
   a fixed guess about viewport height, and the guess is wrong on every
   screen that is not the one it was tuned on.

   svh, not vh: on a phone, vh is the viewport with the browser chrome
   retracted, so a 100vh block sits taller than the screen actually is and
   the doors get cut off before the user has scrolled anything. */
.doorway-lead{
  display:flex; flex-direction:column;

  /*
   | THE FIRST SCREEN IS TWO THINGS.
   |
   | The headline and the two doors, centred in the viewport, and nothing
   | else. Somebody arriving is being asked one question — which of these two
   | products did you come for — and every additional element on that screen
   | is an answer to a question they have not been asked yet.
   |
   | Reserving the height here is what pushes the assurances and the banner
   | under the fold, so they are found by scrolling rather than competing.
   | An earlier pass removed a min-height for the opposite reason: back then
   | a figures panel was ALSO trying to fit above the fold, and reserving the
   | screen for the lead left it stranded in empty photograph. The figures
   | are gone; two things fit where four did not.
   |
   | svh, not vh: on a phone vh is the height with the browser chrome hidden,
   | so a full-height first screen is taller than the screen until you scroll.
   */
  /* NOT the whole screen. A full viewport reserved for two elements left
     200px of nothing above the headline and 276px below the doors, and put
     the assurances a screen and a quarter down — far enough that they read
     as a different page rather than as the next thing.

     At 68svh the lead still owns the view it opens in, and the panel below it
     arrives at the bottom edge, which is where "there is more" is actually
     said. Briefly taken to 52svh to close the space under the slimmer cards;
     put back, because the space that wanted closing was the one BETWEEN the
     two doors, not the one beneath them. */
  min-height:calc(68svh - var(--nav-h, 79px));
  justify-content:center;
}
@supports not (height: 100svh){
  .doorway-lead{ min-height:calc(68vh - var(--nav-h, 79px)) }
}
/* The doors follow the copy directly instead of being pushed to the foot of
   the screen. A full flex gap above them read as a break between the headline
   and the choice it is asking for, which are one thought. The lead still
   fills the first screen, so the space that used to sit above the doors now
   falls below them: the facts panel keeps its place under the fold and gains
   the breathing room the doors gave up. */
/* The doors sit lower on the first screen.
   The lead is sized to fill that screen and its children stack from the top,
   so this one margin does all three things at once: the gap above the doors
   grows, the doors come down, and the dead space beneath them shrinks by the
   same amount. Tied to viewport HEIGHT, because that is what the space it is
   dividing is measured in. */
/* The gap between the headline and the doors, chosen rather than left over. */
.doorway-lead .doors{margin-top:var(--hero-doors-gap, clamp(40px, 7.5vh, 84px))}

/* The doorway no longer caps its own height — the lead decides it. */
.doorway{min-height:0}

@media (max-width:900px){
  /* Stacked doors are tall enough on their own; forcing a full screen above
     them would leave a band of empty photograph on a phone. */
  .doorway-lead{min-height:0}
  .doorway-lead .doors{margin-top:clamp(28px, 5vh, 48px)}
}

/* ===========================================================================
   THE SHINING HEADLINE
   ---------------------------------------------------------------------------
   An animated gradient across the front-page headline, with a second gradient
   travelling the other way around its outline.

   How it is built, because it is not obvious from the rules alone:

     .shine        the words themselves, filled with a gradient that is three
                   times wider than the text and slid sideways forever. The
                   fill is clipped to the glyphs, so the colour lives inside
                   the letters rather than behind them.

     .shine::before  the SAME words again, drawn behind, with no fill and a
                   thick stroke. Its own gradient runs in the opposite
                   direction. This second copy exists because -webkit-text-
                   stroke takes one flat colour — there is no way to give an
                   outline a gradient without painting it as a separate layer.

   The two gradients move at different speeds and opposite directions, which
   is what stops it reading as one flat sheet sliding past.
   =========================================================================== */

.doorway-lead h1.shine > span,
h1.shine > span,
.heroline-item{
  /* Mauve pink — the headline reads as one colour, with the shine moving
     THROUGH it rather than across a range of different ones. Four values
     because a gradient needs somewhere to travel; they are close together
     on purpose, so at a glance this is a colour and not a rainbow.

     These are the LIGHT values, and they are deep. The hero's scrim is
     built out of --ground, so in light mode the words sit on pale rose —
     and the pale mauve that works beautifully on the dark scrim was, on
     that, near-white type on a near-white wash. Dark mode keeps the pale
     ramp, just below.

     The band (--shine-lit) is the lightest of the four either way. In light
     mode that means a lighter PINK, not a white: the moment the highlight
     reaches white it is the same invisible headline again at the one moment
     the eye is following it. */
  --shine-ink:#8C2A4B;
  --shine-deep:#A63C5E;
  --shine-mid:#C05477;
  --shine-lit:#DC7A9C;

  /* Four flat colours, not a ramp.
     A gradient blends, and blending is what made the old headline read as
     one colour with a highlight travelling through it. These meet at hard
     edges instead, so the letters are cut into regions of solid colour and
     you can see exactly where one ends. */
  --blob-ink:var(--hero-blob-ink, #7A1F3D);
  --blob-rose:var(--hero-blob-rose, #C0517A);
  /* The palest of the four, and it still has to be a colour.
     It was #F3CFDC, which measures 1.19 against the light scrim — the scrim
     is built from --ground, so in light mode these words sit on pale rose,
     and a pale rose disc on it disappeared. Whenever this one covered the
     headline the headline stopped existing. 2.38 now: still clearly the
     lightest of the set, no longer the same colour as what is behind it. */
  --blob-pale:var(--hero-blob-pale, #D67F9F);
  /* Burgundy. It replaced a cyan blue, which read well but was the one
     colour in the headline that belonged to no part of the brand — a
     stranger's hue cutting across the words. Burgundy keeps the whole
     headline in one family.

     Staying in the family is the risk as well as the point: the deepest of
     the four is already a plum burgundy, and an accent beside it would
     simply merge. This one is redder and more saturated than the ink and
     lighter than it, so it still reads as its own region rather than a
     second helping of the same colour. 6.85 against the light scrim, which
     keeps it well clear of the near-invisibility that retired the gold. */
  --blob-accent:var(--hero-blob-accent, #A8143C);

  position:relative;
  display:inline-block;
  isolation:isolate;

  /*
   | ROOM FOR THE GLYPHS TO BE PAINTED IN.
   |
   | `background-clip:text` paints the background inside the element's box and
   | then clips it to the letterforms — so anything the letters do OUTSIDE
   | that box gets no colour at all and simply is not drawn. It does not look
   | like a clipped background; it looks like a broken font.
   |
   | The h1 runs at line-height 1.02, which is shorter than this face's
   | ascender-to-descender extent, so the tail of a "y" hung below the box and
   | vanished. The same happened horizontally: an inline-block is only as wide
   | as the advance widths, and a serif "r" whose terminal overhangs its own
   | advance lost the tip of it on the last letter of the line.
   |
   | Padding gives the paint somewhere to go; the matching negative margin
   | takes the space straight back, so nothing around the headline moves. The
   | bottom is the largest because descenders are the deepest overflow.
   */
  padding:.16em .10em .26em;
  margin:-.16em -.10em -.26em;

  /*
   | Three discs drifting over a solid ground, clipped to the letterforms.
   |
   | Each is placed from --mx/--my, the pointer's position over the hero as
   | a number from 0 to 100, but with a different multiplier and a different
   | direction. That difference is the whole effect: move the pointer and the
   | discs slide at three speeds, so the boundaries between colours sweep
   | across the words instead of the block of them shifting together.
   |
   | Circles measured in em, which is the only unit that keeps them round.
   | A percentage radius is resolved against the box, and the box is about
   | nine times wider than it is tall — so `ellipse 46% 190%` was not an oval
   | but a shape 92% of the headline wide, one disc covering every letter and
   | exactly one colour on screen at a time. In em they scale with the type
   | and stay circular at any size, and three of them fit across the words
   | with the ground showing between: four colours at once, which is what
   | the reference actually does.
   |
   | Hard stops at 98%: the half-percent is antialiasing, and without it the
   | edge crawls.
   */
  background-image:
    radial-gradient(circle 0.95em at calc(var(--mx) * 1.35%) calc(12% + var(--my) * 0.8%),
                    var(--blob-accent) 0 98%, transparent 98.5%),
    radial-gradient(circle 1.25em at calc(112% - var(--mx) * 1.05%) calc(var(--my) * 1.1%),
                    var(--blob-pale) 0 98%, transparent 98.5%),
    radial-gradient(circle 1.55em at calc(28% + var(--mx) * 0.55%) calc(95% - var(--my) * 0.7%),
                    var(--blob-rose) 0 98%, transparent 98.5%),
    linear-gradient(var(--blob-ink), var(--blob-ink));

  -webkit-background-clip:text;
  background-clip:text;
  color:transparent;
  -webkit-text-fill-color:transparent;

  /* The pointer jumps; the colour should not. Registered as <number> below,
     which is what makes a custom property transitionable at all. */
  transition:--mx .55s cubic-bezier(.22,.7,.3,1), --my .55s cubic-bezier(.22,.7,.3,1);

  /* Alive before anyone touches it, and on a phone where nothing ever will.
     Dropped the moment the pointer takes over — see .is-tracked. */
  animation:shine-drift 14s ease-in-out infinite alternate;
}

/*
 | --mx / --my have to be REGISTERED to animate.
 |
 | An unregistered custom property is a string as far as the engine is
 | concerned, so it flips from one value to the next with nothing in between
 | — no transition, no keyframe interpolation. Declaring the syntax makes
 | them real numbers and both start working.
 */
@property --mx{ syntax:'<number>'; inherits:true; initial-value:50 }
@property --my{ syntax:'<number>'; inherits:true; initial-value:50 }

@keyframes shine-drift{
  from{ --mx:22; --my:30 }
  to  { --mx:78; --my:72 }
}

/* Once a pointer is driving it, the idle drift would fight the cursor. */
h1.shine.is-tracked > span,
h1.shine.is-tracked > span::before{ animation:none }

/* The outline. Behind the fill (z-index -1), a little larger through the
   stroke, and running the other way. */
h1.shine > span::before{
  content:attr(data-text);
  position:absolute;
  inset:0;
  z-index:-1;
  pointer-events:none;

  /* The fill's padding, inherited rather than repeated so the two cannot
     drift apart. `inset:0` puts this box on the fill's PADDING box, so
     without the same padding its copy of the text would sit up and to the
     left of the text it is supposed to be an outline of. The stroke below
     needs the room for its own sake as well: it is drawn 2px outside every
     letter, and that is 2px more of this same overflow. */
  padding:inherit;

  -webkit-text-stroke:2px transparent;
  -webkit-text-fill-color:transparent;

  /* The same discs, deliberately out of step with the fill's. If the outline
     carried the identical stack it would be the identical colour in the same
     place, and an edge you cannot see is not an edge. The multipliers are
     inverted, so the stroke is showing a different colour to the letter it
     is drawn around at almost every point. */
  background-image:
    radial-gradient(circle 1.15em at calc(100% - var(--mx) * 1.2%) calc(100% - var(--my) * 0.9%),
                    var(--blob-pale) 0 98%, transparent 98.5%),
    radial-gradient(circle 1.4em at calc(var(--mx) * 0.75%) calc(80% - var(--my) * 0.5%),
                    var(--blob-accent) 0 98%, transparent 98.5%),
    linear-gradient(var(--blob-rose), var(--blob-rose));

  -webkit-background-clip:text;
  background-clip:text;

  animation:shine-drift 19s ease-in-out infinite alternate-reverse;
}

@keyframes shine-slide{
  from{ background-position:0% 50% }
  to{ background-position:300% 50% }
}

/* On the photograph the headline sits over, a gradient alone loses its edges
   wherever the picture behind it happens to be light. */
/*
 | NO PLATE.
 |
 | There was one here — a soft dark ellipse behind the words, so the pale
 | mauve had a ground to sit on the way it does when a browser highlights it.
 | It worked, and it read as a panel behind the headline, which is not what
 | this page wants. Removed on request.
 |
 | What is left is shadow that follows the LETTERS rather than a box: tight
 | radii only, so each glyph keeps its edge against the photograph without
 | any of it adding up to a visible shape. The wide 30px and 70px shadows
 | that used to be here are gone for the same reason the plate is — at that
 | blur they stop being an edge and become a haze.
 |
 | The trade is real and worth stating: on the brightest of the four hero
 | photographs this headline is now lower contrast than it was. That is the
 | cost of having nothing behind it.
 */
/*
 | Two shadows, both tight.
 |
 | One hairline directly under the glyphs to stop them melting into whatever
 | is immediately behind, and one small blur to give that a little softness.
 | Nothing wider: stacked drop-shadows compound, each blurring the result of
 | the last, so anything past about 10px stops reading as an edge and starts
 | reading as a cloud behind the words — which is the thing that was asked to
 | go.
 */
.doorway h1.shine > span{
  /* Light mode: a pale halo, not a dark one. The words are dark now, so a
     dark shadow has nothing to do except smudge them — but the scrim thins
     out towards the right and the photograph does show through, so the type
     still needs something separating it from whatever is back there. A
     light halo behind dark type does that job the way a dark halo does it
     behind light type. */
  filter:
    drop-shadow(0 1px 1px rgba(255,255,255,.9))
    drop-shadow(0 0 8px rgba(255,240,243,.8));
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .doorway h1.shine > span{
    filter:
      drop-shadow(0 1px 2px rgba(31,2,11,.85))
      drop-shadow(0 0 7px rgba(31,2,11,.6));
  }
  :root:not([data-theme="light"]) h1.shine > span,
  :root:not([data-theme="light"]) .heroline-item{
    --shine-ink:#D3A9BC; --shine-deep:#E2C0CE; --shine-mid:#F0DCE4; --shine-lit:#FDF4F7;
    /* Lifted. The same four colours would be four dark shapes on a dark
       scrim; what carries in light mode as deep burgundy has to arrive here
       as the pale end of the same hue. */
    --blob-ink:var(--hero-blob-ink, #B0687F); --blob-rose:var(--hero-blob-rose, #E58BA8);
    --blob-pale:var(--hero-blob-pale, #FDF4F7); --blob-accent:var(--hero-blob-accent, #CF7E94);
  }
}
:root[data-theme="dark"] .doorway h1.shine > span{
  filter:
    drop-shadow(0 1px 2px rgba(31,2,11,.85))
    drop-shadow(0 0 7px rgba(31,2,11,.6));
}
:root[data-theme="dark"] h1.shine > span,
:root[data-theme="dark"] .heroline-item{
  --shine-ink:#D3A9BC; --shine-deep:#E2C0CE; --shine-mid:#F0DCE4; --shine-lit:#FDF4F7;
  --blob-ink:var(--hero-blob-ink, #B0687F); --blob-rose:var(--hero-blob-rose, #E58BA8);
  --blob-pale:var(--hero-blob-pale, #FDF4F7); --blob-accent:var(--hero-blob-accent, #7AB7BC);
}

/* ============ THE TWO DOORS' LABELS ============
   MATRIMONY and DATING were pills — a tinted background, a border and a blur.
   Without the box they stop being a chip pinned to a card and become the
   first line of it, which is what they always were: the one distinction the
   whole page turns on. Bigger, and lit by the same gradient as the headline,
   so the front page has one idea in it rather than two. */
.door-tag,
.doorway .door-tag,
.doorway .door-dating .door-tag{
  /* Room for the caret, INSIDE the box.
     The reveal clips to inset(0) — the element's own box — so anything
     sitting at a flat 100% lands just outside it and is cut off. A caret is
     only two pixels wide, so it needs barely any, but it needs some.
     Declared HERE, in the rule that zeroes the padding, rather than beside
     the caret: this selector list carries .doorway .door-dating .door-tag,
     and a plain .door-tag elsewhere in the file cannot outrank it. */
  --caret:2px;
  padding:0;
  padding-inline-end:calc(var(--caret) + .18em);
  border:0;
  background:none;
  -webkit-backdrop-filter:none;
  backdrop-filter:none;
  margin-bottom:9px;
  /* That margin was the gap to the heading underneath. On the doorway there
     is no heading underneath any more, so it would be dead air at the foot
     of the card — but the appearance screen's preview still renders the
     pair, and there it is still doing its job. :last-child asks the
     question directly rather than guessing per page. */

  /* The headline's face AND its weight. The family already matched; the
     bold did not, and at this size a 700 serif reads as a different
     typeface entirely next to a 500 one. */
  font-family:var(--font-head);
  font-weight:var(--w-head, 500);
  /* The size comes from the appearance screen, through Theme::doorwayCss().
     The literal here is only the fallback for a page rendered without it. */
  font-size:var(--door-tag-size, clamp(18px, 2.4vw, 34px));
  letter-spacing:.22em;

  /* Through tokens rather than literal stops, because the card underneath
     flips with the theme: what reads as a dark label on pale glass is
     invisible on the dark one. Only these four values change below. */
  --tag-ink:#8E2C4E;
  --tag-deep:#A84E6E;
  --tag-mid:#C56B8B;
  --tag-lit:#E28FAC;

  background-image:linear-gradient(100deg,
    var(--tag-ink) 0%, var(--tag-deep) 30%, var(--tag-mid) 46%, var(--tag-lit) 52%,
    var(--tag-mid) 58%, var(--tag-deep) 70%, var(--tag-ink) 100%);
  background-size:300% 100%;
  -webkit-background-clip:text;
  background-clip:text;
  color:transparent;
  -webkit-text-fill-color:transparent;
}

/* Dating keeps its own end of the palette — the teal it is branded in — so
   the two doors still read as two products. */.door-dating .door-tag,
.doorway .door-dating .door-tag{
  --tag-ink:#0B2E28;
  --tag-deep:#1B5249;
  --tag-mid:#2F7A6B;
  --tag-lit:#6FBFA8;
}

/* Dark. The same shape of gradient with the range lifted — dark ends become
   mid tones and the bright band goes brighter still, so the label keeps the
   same weight on the card it is actually sitting on. */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .door-tag{
    --tag-ink:#B0687F; --tag-deep:#D9718C; --tag-mid:#EC93A9; --tag-lit:#FFE7B0;
  }
  :root:not([data-theme="light"]) .door-dating .door-tag{
    --tag-ink:#5E9C8E; --tag-deep:#7FC0AF; --tag-mid:#A5DBCB; --tag-lit:#EAF7F3;
  }
}
:root[data-theme="dark"] .door-tag{
  --tag-ink:#B0687F; --tag-deep:#D9718C; --tag-mid:#EC93A9; --tag-lit:#FFE7B0;
}
:root[data-theme="dark"] .door-dating .door-tag{
  --tag-ink:#5E9C8E; --tag-deep:#7FC0AF; --tag-mid:#A5DBCB; --tag-lit:#EAF7F3;
}

/* BOTH labels in white, and the two products told apart underneath instead.

   MATRIMONY went white first: the rose ramp was read against a slideshow
   that is a different colour every three seconds, and white is the one
   value that holds against all of them. DATING kept its teal, which left
   one label reading as a label and the other as a brand — a pair that no
   longer looked like a pair. So it goes white too, for the same reason the
   first one did.

   What carried the distinction moves down a line: each heading now takes
   its own card's edge colour. The two doors still read as two products,
   and they read as two products from the sentence the eye actually lands
   on rather than from the small caps above it.

   Set as the four tokens rather than as a flat colour, so each label keeps
   the same machinery it had — the gradient, and the caret, which paints
   from --tag-mid. `color` as well, for the reduced-motion branch further
   down: that one drops the gradient and fills from `color` instead.

   Four classes because the theme rules that set these tokens are three, and
   this has to outrank them in light, dark and system alike. */
:root .doorway .door-matrimony .door-tag,
:root .doorway .door-dating .door-tag{
  --tag-ink:#fff; --tag-deep:#fff; --tag-mid:#fff; --tag-lit:#fff;
  color:#fff;
}

/* The heading in its own card's edge colour.

   --edge-3 of the four the border animates through: gold on matrimony,
   pale teal on dating. It is the brightest of the four that is still a
   COLOUR — edge-4 is very nearly white, which is where this line started
   and would be no distinction at all.

   The tokens are declared on .door-matrimony and .door-dating and inherit
   down, so this is one rule and not two, and a card that is given a
   different edge tomorrow brings its heading with it.

   The shadow is the one the label above already carries, for the same
   reason: this type sits ON a photograph, and a mid-tone colour needs
   separating from whatever frame is behind it. */
.doorway .door .door-h{
  color:var(--edge-3);
  text-shadow:0 1px 8px rgba(0,0,0,.55);
}

/* ---- typed in, one letter at a time ----
   clip-path rather than an animated width, which is the obvious way to do
   this and the wrong one here: width changes the element's box, and the
   gradient above is sized as a PERCENTAGE of that box — so the colours would
   crawl about while the letters appeared. Clipping leaves the layout and the
   background exactly where they are and just uncovers them.

   steps(--chars) is what makes it letters rather than a wipe. The count comes
   from the template because the label is translated, and DATING is not six
   characters in Bangla. */
.door-tag{
  position:relative;

  /* Written, as the element's own value — not merely as the animation's last
     frame.

     This is the difference between `forwards` and `both` below, and it is the
     whole reason the matrimony card was once forbidden the effect. `both`
     fills BACKWARDS too, so before the animation's active phase the resting
     state is the from-keyframe: clipped to nothing. Anything that holds that
     moment — a tab opened in the background, a throttled timer, a restore
     from session, the animation not applying at all — leaves the visitor
     looking at a card with no word on it.

     Declaring the finished state here inverts the failure: what survives when
     nothing animates is the written word, which is what the card is for. */
  clip-path:inset(0 0 0 0);

  animation:
    shine-slide 8s linear infinite,
    /* Once, and it stays. `forwards` holds the last frame, so the finished
       word is the resting state — not a moment between two erasures. The
       caret below is what keeps moving. */
    door-type var(--reveal, 1s) steps(var(--chars, 9)) 1 forwards;
}

/*
 | It types itself in, one letter at a time, and then it is simply the label.
 |
 | It looped once — type, hold, unwrite, type again — and the two words the
 | whole page exists to offer spent part of every cycle half missing. A
 | heading that erases itself is a heading you cannot finish reading. The
 | caret carries the movement instead, which is what a caret is for.
 |
 | One step per letter, and nothing to compute: the keyframe now runs from
 | empty to written with no holds in it, so the duration IS the reveal and
 | steps(--chars) is exactly one step per letter. (An earlier version buried
 | the reveal inside a longer cycle, which meant the step count had to be a
 | multiple of the letter count and the two numbers had to be kept in step by
 | hand in the template.)
 */
@keyframes door-type{
  from { clip-path:inset(0 100% 0 0) }
  to   { clip-path:inset(0 0 0 0) }
}

/*
 | The caret.
 |
 | A text cursor, because that is what the effect is claiming: something is
 | typing these two words. A round dot said the opposite — a full stop, or a
 | status light — and the letters appearing beside it had nothing to do with
 | it. This is the thing doing the writing, so it is shaped like one.
 |
 | It rides the reveal exactly, sits at the end of the finished word while it
 | is held, and rides back as the word is deleted. Same clock, same steps, so
 | it is never anywhere but at the edge of the text.
 |
 | And it blinks, on its own timer that owes nothing to the typing. A caret
 | that only blinks while idle has to know when idle is; one that blinks
 | throughout is simply a cursor, which is what every text field on earth
 | has already taught everyone to read.
 */
.door-tag::after{
  content:"";
  position:absolute;

  /*
   | Centred on the line, not hung from the top of it.
   |
   | It was pinned at a small offset from the box's top edge, which assumed
   | the glyphs started there. They do not: this is a serif at a large size
   | with .22em of letter-spacing, so between the top of the element and the
   | cap-height there is a slab of ascent the letters never use — and the
   | caret sat up in it, floating above the word it was supposed to be
   | writing. Half the box, less half the caret, puts it on the same middle
   | as the text at any font size.
   */
  inset-block-start:50%;
  margin-block-start:calc(var(--caret-h, .92em) / -2);
  height:var(--caret-h, .92em);

  inset-inline-start:0;
  width:var(--caret, 2px);
  border-radius:1px;
  background:var(--tag-mid);

  animation:
    door-sweep calc(var(--reveal, 1s) * 2) steps(var(--chars, 9)) infinite,
    door-blink 1.06s steps(1, end) infinite;
}
/*
 | Out to the end of the word and back, for ever, one letter per step.
 |
 | The first half of the first pass IS the reveal — same duration, same step
 | count — so the caret appears to be writing the word. After that the word
 | is finished and the caret simply paces it, which is the only thing still
 | moving up there.
 |
 | steps() applies between EACH adjacent pair of keyframes, not across the
 | whole run, so steps(--chars) over 0→50→100 gives one step per letter in
 | each direction rather than half a step.
 |
 | It stops one caret-width short of the edge, which is where the padding on
 | .door-tag put the room. Travelling to a flat 100% is what pushed it
 | outside the reveal's clip and cut it off.
 */
@keyframes door-sweep{
  0%   { inset-inline-start:0 }
  50%  { inset-inline-start:calc(100% - var(--caret, 2px)) }
  100% { inset-inline-start:0 }
}
/* steps(1, end) rather than a fade: a caret is on or it is off. Fading one
   in and out reads as a pulsing light, not as a cursor. */
@keyframes door-blink{
  0%, 55%   { opacity:1 }
  55.01%, 100% { opacity:0 }
}

/* Both labels run the same clock. They are given the SAME --reveal in the
   template and each steps through its own letter count inside it, so the
   longer word simply types a little faster and the two start and finish
   together. */

/* The card headings grow with the labels; the pair has to stay a pair.

   Down from clamp(25px, 2.9vw, 40px). At 40px on a wide screen this line was
   competing with the h1 directly above it — two display-sized lines in the
   same eyeful, and the one that matters is the headline. It is a subtitle to
   its own label now, which is what it reads as. */
.door-h{ font-size:var(--door-head-size, clamp(17px, 1.55vw, 21px)) }

.doorway .door-tag{ filter:drop-shadow(0 1px 6px rgba(0,0,0,.5)) }

/* Nothing here is information — it is decoration on text that reads perfectly
   well standing still — so it all stops for anyone who asked for less motion,
   and the colour falls back to something solid rather than to nothing. */
@media (prefers-reduced-motion: reduce){
  h1.shine > span,
  h1.shine > span::before,
  .door-tag{
    animation:none;
    background-image:none;
    color:var(--ink);
    -webkit-text-fill-color:currentColor;
  }
  h1.shine > span::before{ content:none }

  /* The labels used to be light because they were pills on a photograph.
     Without the pill they sit on the card itself, so they take the same
     brand colour the rest of the card's text does. */
  .door-tag{ color:var(--brand-deep); clip-path:none }
  .door-dating .door-tag{ color:var(--connect-brand) }
  .door-tag::after{ content:none }
}

/* ============ THE OCTAGON ============
   A profile picture is not a circle here. The mark, the door cards and the
   hero all lean on straight-cut corners, and a round avatar was the one
   element in the product that did not belong to it.

   clip-path, not a border-radius trick: eight real corners, and the same
   shape whatever is inside — a photograph, an initial, or nothing yet. The
   percentages are the standard regular-octagon cut (1 - 1/(1+√2) ≈ 29.3%,
   pulled in slightly so the flats read as flats at small sizes). */
.oct{
  --oct-size:40px;
  position:relative; flex:none;
  display:inline-flex; align-items:center; justify-content:center;
  width:var(--oct-size); height:var(--oct-size);
  overflow:hidden;
  background:var(--brand-tint);
  color:var(--brand-deep);
  /* Round. It was an eight-sided cut to echo the mark, but a face is the one
     thing on a page everybody already knows the shape of — a portrait with
     corners reads as a cropping mistake before it reads as a motif.
     border-radius rather than a circular clip-path, so a focus ring and the
     lifted-edge shadow still draw around it; clip-path cuts those off. */
  border-radius:50%;
}
.oct img{ width:100%; height:100%; object-fit:cover; display:block }
.oct-i{
  font-family:var(--font-head, inherit);
  font-size:calc(var(--oct-size) * .42); line-height:1; font-weight:600;
}
.oct-sm{ --oct-size:40px }

/* In the header, smaller than the mark.
   It was 60px, matched to the logo so the bar had two bookends of equal
   weight. That held while the empty state was a single letter on a pale
   tint. It stopped holding once the placeholder became a filled silhouette:
   at 60px the figure reads as a dark disc parked beside the controls rather
   than as a portrait, and it outweighs everything around it. 42px is the
   size at which a face is still a face and the row still balances. */
.topnav .oct-sm{ --oct-size:42px }
.oct-lg{ --oct-size:132px }

/* Empty is a shape waiting for a picture, not a broken image. */
.oct.is-empty{
  background:color-mix(in srgb, var(--brand) 14%, transparent);
  border:0;
}

/* The one in the header is a control, so it has to say so on hover and take
   a focus ring — and a clipped element cannot show an outline, because the
   outline is outside the clip. The ring goes on the wrapper instead. */
.oct-link{
  display:inline-flex; border-radius:6px;
  transition:transform .16s ease, box-shadow .16s ease;
}
.oct-link:hover{ transform:translateY(-1px) }
.oct-link:focus-visible{
  outline:2px solid var(--action-hover); outline-offset:3px;
}

/* ============ PHOTO GUIDELINES ============ */
.phg{
  margin-top:6px; padding:16px;
  border:1px solid var(--line); border-radius:14px;
  background:var(--surface-2);
}
.phg-h{ margin:0; font-size:16px }
.phg-sub{ margin:4px 0 14px; font-size:13px; color:var(--muted); line-height:1.6 }

.phg-cols{ display:grid; grid-template-columns:auto 1fr; gap:18px }
@media (max-width:620px){ .phg-cols{ grid-template-columns:1fr } }

.phg-col-h{
  display:inline-flex; align-items:center; gap:6px;
  font-size:11.5px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
}
.phg-col-h.is-yes{ color:#2E8B57 }
.phg-col-h.is-no{ color:var(--warn, #B4552F) }

.phg-set{ display:flex; flex-wrap:wrap; gap:12px; margin-top:10px }
.phg-ex{ margin:0; width:64px; text-align:center }
.phg-ex figcaption{ margin-top:5px; font-size:10.5px; color:var(--muted); line-height:1.3 }

.phg-art{
  display:block; width:64px; height:64px; overflow:hidden;
  background:var(--surface-3);
  /* Round, like every other portrait in the product now. */
  border-radius:50%;
}
.phg-art img{width:100%; height:100%; object-fit:cover; display:block}
/* The rejected ones carry a ring in the warning colour rather than a tint
   over the picture. These are photographs now, and washing one in orange
   makes it harder to see the very thing the example is pointing at — the
   turned head, the four other people. The ring sits outside the image and
   says the same thing without touching it.

   A shadow, not a border: the octagon is a clip-path, and a border would be
   clipped away with the corners. */
.phg-ex.is-no .phg-art{
  filter:drop-shadow(0 0 0 transparent);
  box-shadow:0 0 0 2px color-mix(in srgb, var(--warn, #B4552F) 65%, transparent);
}

.phg-rules-h{ margin:16px 0 8px; font-size:13px }
.phg-rules{ margin:0; padding:0; list-style:none; display:grid; gap:7px }
.phg-rules li{
  display:flex; align-items:baseline; justify-content:space-between; gap:12px;
  font-size:12.5px; line-height:1.55; color:var(--body);
}
.phg-by{
  flex:none; font-style:normal; font-size:10px; letter-spacing:.04em;
  padding:2px 7px; border-radius:99px;
  color:var(--muted); background:color-mix(in srgb, var(--muted) 12%, transparent);
}
.phg-by.is-auto{
  color:#2E8B57; background:color-mix(in srgb, #2E8B57 12%, transparent);
}
.phg-note{ margin:12px 0 0; font-size:11.5px; color:var(--muted); line-height:1.55 }

.phg-current{ display:flex; align-items:flex-start; gap:16px; flex-wrap:wrap }
.phg-current-b{ flex:1; min-width:220px; display:grid; gap:8px; align-content:start }

/* ---- the "what others see" preview ----
   A key and its value on one line, with the key fixed so the two columns of
   the comparison line up across the gap between the cards. A hidden field is
   said in words and set in italic muted type: it must not read as a value,
   because "Not shown" in the same weight as "Dhaka" is exactly the confusion
   this page exists to remove. */
.prev-row{
  display:flex; align-items:baseline; gap:10px;
  padding:5px 0;
  border-bottom:1px solid color-mix(in srgb, var(--line) 55%, transparent);
  font-size:13px;
}
.prev-row:last-child{border-bottom:0}
.prev-k{flex:0 0 42%; color:var(--muted); font-size:12px}
.prev-v{flex:1; min-width:0; color:var(--ink); overflow-wrap:anywhere}
.prev-v.is-hidden-field{color:var(--muted); font-style:italic}
.prev-ph{flex:none; display:block; width:96px}

/* ---- the guidelines sheet ----
   A dialog over the page, not a panel beside it: it appears at the moment
   somebody is choosing a file, and the point is that it is read before the
   choice rather than after it. */
.phg-sheet{
  position:fixed; inset:0; z-index:120;
  display:flex; align-items:center; justify-content:center;
  padding:24px;
  background:color-mix(in srgb, #1F020B 62%, transparent);
  -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px);
  overflow-y:auto;
}
.phg-sheet[hidden]{display:none}
.phg-sheet-in{
  position:relative;
  width:min(660px, 100%);
  max-height:calc(100vh - 48px); overflow-y:auto;
  padding:22px 22px 18px;
  border-radius:16px;
  background:var(--surface);
  box-shadow:0 30px 70px -30px rgba(0,0,0,.6);
}
/* The sheet already IS the panel, so the guidelines block inside it drops its
   own frame rather than drawing a second one a few pixels in. */
.phg-sheet-in .phg{margin:0; border:0; background:transparent; padding:0}

.phg-x{
  position:absolute; inset-block-start:12px; inset-inline-end:12px;
  display:inline-flex; align-items:center; justify-content:center;
  width:36px; height:36px;
  border:0; border-radius:50%;
  background:var(--surface-2); color:var(--muted);
  cursor:pointer;
}
.phg-x:hover{background:var(--surface-3); color:var(--ink)}

.phg-sheet-act{display:flex; justify-content:flex-end; margin-top:16px}

/* ---- the doorway's own scroll reveal ----
   The generic .reveal lifts an element 14px. These two are the full width of
   the page and arrive under a screen that has just been given entirely to the
   headline and the doors, so they get a longer travel and a slower curve:
   the point is that they are FELT to arrive from below rather than simply
   being found already there.

   Staggered inside the trustbar, so the four assurances land one after
   another instead of as a single slab. */
.doorfacts.reveal-armed,
.cta-banner.reveal-armed{ opacity:0; transform:translateY(46px) }
.doorfacts.reveal-armed.is-in,
.cta-banner.reveal-armed.is-in{
  opacity:1; transform:none;
  transition:opacity .72s cubic-bezier(.22,.72,.28,1),
             transform .72s cubic-bezier(.22,.72,.28,1);
}

.doorfacts.reveal-armed .trustbar > li{ opacity:0; transform:translateY(22px) }
.doorfacts.reveal-armed.is-in .trustbar > li{
  opacity:1; transform:none;
  transition:opacity .5s cubic-bezier(.22,.72,.28,1) var(--d, 0ms),
             transform .5s cubic-bezier(.22,.72,.28,1) var(--d, 0ms);
}
.doorfacts .trustbar > li:nth-child(1){ --d:120ms }
.doorfacts .trustbar > li:nth-child(2){ --d:200ms }
.doorfacts .trustbar > li:nth-child(3){ --d:280ms }
.doorfacts .trustbar > li:nth-child(4){ --d:360ms }

@media (prefers-reduced-motion: reduce){
  .doorfacts.reveal-armed, .cta-banner.reveal-armed,
  .doorfacts.reveal-armed .trustbar > li{ opacity:1; transform:none; transition:none }
}

/* ============ THE STAFF CONSOLE ============
   A sidebar and the screen beside it. The rest of the site is one column on
   purpose — a member has few places to be — but a console is somewhere you
   work for an hour, moving between a queue and a setting and back, and it
   needs its own map visible the whole time.

   `start` on the grid, not `stretch`: the nav is as tall as its items, and a
   stretched column would draw its border the full height of whatever long
   table happens to be next to it. */
.adm{
  display:grid;
  grid-template-columns:232px minmax(0, 1fr);
  gap:clamp(20px, 2.4vw, 40px);
  align-items:start;
}

/* Sticky, and only as far as the header. It stays with you down a long
   moderation queue, which is the screen this exists for. */
.adm-nav{
  position:sticky; inset-block-start:calc(var(--nav-h, 79px) + 14px);
  display:flex; flex-direction:column; gap:18px;
}

.adm-nav-group{display:flex; flex-direction:column; gap:2px}
.adm-nav-h{
  padding:0 10px 6px;
  font-size:10.5px; font-weight:600; letter-spacing:.13em; text-transform:uppercase;
  color:var(--muted); opacity:.75;
}

.adm-nav-item{
  display:flex; align-items:center; gap:10px;
  padding:8px 10px;
  border-radius:9px;
  color:var(--body); font-size:14px; text-decoration:none;
  /* The left edge is where the active mark goes. Transparent here so the
     items do not shift by three pixels when one lights up. */
  border-inline-start:2.5px solid transparent;
  transition:background .15s ease, color .15s ease, border-color .15s ease;
}
.adm-nav-item:hover{background:var(--surface-2); color:var(--ink)}
.adm-nav-item:focus-visible{outline:2px solid var(--action-hover); outline-offset:2px}

.adm-nav-item.is-on{
  background:var(--surface-3);
  color:var(--ink);
  border-inline-start-color:var(--brand);
  font-weight:500;
}
.adm-nav-ic{display:inline-flex; flex:none; color:var(--muted)}
.adm-nav-item.is-on .adm-nav-ic{color:var(--brand)}
.adm-nav-t{flex:1; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}

/* The queue count. Only drawn when there is one, so an empty queue is a
   quiet row rather than a zero asking to be read. */
.adm-nav-n{
  flex:none; min-width:20px; padding:0 6px;
  display:inline-flex; align-items:center; justify-content:center;
  height:19px; border-radius:10px;
  background:var(--brand); color:var(--on-brand);
  font-size:11px; font-weight:600; line-height:1;
}

.adm-main{min-width:0}

/* Under 900px the sidebar becomes a scrolling strip above the screen. A
   232px column on a phone is most of the phone; the same links laid out
   horizontally keep the console navigable without taking the width the
   actual work needs. */
@media (max-width:900px){
  .adm{grid-template-columns:1fr; gap:18px}
  .adm-nav{
    position:static;
    flex-direction:row; gap:10px;
    overflow-x:auto; overscroll-behavior-x:contain;
    padding-bottom:6px;
    border-bottom:1px solid var(--line);
  }
  .adm-nav-group{flex-direction:row; gap:4px; flex:none}
  /* The group headings are a vertical device. Across a strip they would be
     three more things to scroll past. */
  .adm-nav-h{display:none}
  .adm-nav-item{
    border-inline-start:0; border-block-end:2.5px solid transparent;
    border-radius:8px 8px 0 0; white-space:nowrap;
  }
  .adm-nav-item.is-on{border-block-end-color:var(--brand)}
}

/* ---- the console's identity block ---- */
.adm-me{
  display:flex; align-items:center; gap:10px;
  padding:10px; margin-bottom:2px;
  border-radius:11px;
  background:var(--surface-2);
  border:1px solid var(--line);
}
.adm-me-b{display:flex; flex-direction:column; min-width:0; gap:1px}
.adm-me-b strong{
  font-size:13.5px; color:var(--ink); font-weight:600;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.adm-me-b em{font-style:normal; font-size:11px; color:var(--muted)}

/* ---- the queue cards ----
   An empty queue and a full one are not the same object with a different
   number in it. Empty is grey, flat and inert; full is tinted, lifted, and a
   link straight to the screen where the work is done. Reading the page has to
   be quicker than reading the numbers. */
.adm-queues{
  display:grid; gap:12px;
  grid-template-columns:repeat(auto-fit, minmax(190px, 1fr));
}
.adm-q{
  display:flex; flex-direction:column; gap:3px;
  padding:15px 16px 13px;
  border:1px solid var(--line); border-radius:13px;
  background:var(--surface);
  color:inherit; text-decoration:none;
  transition:transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.adm-q-label{font-size:11px; letter-spacing:.09em; text-transform:uppercase; color:var(--muted)}
.adm-q-n{font-family:var(--font-head); font-size:30px; line-height:1.05; color:var(--ink)}
.adm-q-note{font-size:11.5px; color:var(--muted); line-height:1.45}

/* Nothing to do. Deliberately the quietest thing on the screen. */
.adm-q.is-clear{background:transparent; border-style:dashed}
.adm-q.is-clear .adm-q-n{color:var(--muted); opacity:.55}

/* Work waiting. */
.adm-q.is-warn{
  border-color:color-mix(in srgb, var(--brand) 45%, transparent);
  background:color-mix(in srgb, var(--brand) 7%, var(--surface));
}
.adm-q.is-warn .adm-q-n{color:var(--brand-deep)}

/* Should have been zero. */
.adm-q.is-bad{
  border-color:var(--bad, #B4304A);
  background:color-mix(in srgb, var(--bad, #B4304A) 9%, var(--surface));
}
.adm-q.is-bad .adm-q-n{color:var(--bad, #B4304A)}

a.adm-q:hover{transform:translateY(-2px); box-shadow:0 10px 24px -18px rgba(0,0,0,.5)}
a.adm-q:focus-visible{outline:2px solid var(--action-hover); outline-offset:2px}

/* ---- the two panels under the queues ---- */
.adm-split{
  display:grid; gap:clamp(18px, 2.4vw, 34px);
  grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));
}
.adm-split .lbl{display:block; margin-bottom:10px}

.adm-stats{display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:12px}
.adm-stat{
  padding:12px 14px; border-radius:11px;
  background:var(--surface-2); border:1px solid var(--line);
  display:flex; flex-direction:column; gap:2px;
}
.adm-stat-n{font-family:var(--font-head); font-size:22px; line-height:1.1; color:var(--ink)}
.adm-stat-l{font-size:11.5px; color:var(--muted)}

.adm-act{
  display:grid; grid-template-columns:1fr auto;
  gap:2px 12px; align-items:baseline;
  padding:8px 0;
  border-bottom:1px solid color-mix(in srgb, var(--line) 60%, transparent);
  font-size:13px;
}
.adm-act:last-child{border-bottom:0}
.adm-act-who{
  grid-column:1; min-width:0; color:var(--ink);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.adm-act-who em{font-style:normal; font-size:10.5px; color:var(--muted); margin-inline-start:6px}
.adm-act-what{grid-column:1; font-size:12px; color:var(--muted)}
.adm-act time{grid-column:2; grid-row:1 / span 2; font-size:11.5px; color:var(--muted); white-space:nowrap}

/* ============ THE MEMBER DASHBOARD ============
   The first screen after signing in, and the only one most members see every
   day. Built out of the same eight colours as everything else — no new hues,
   no second light source — but at a larger scale than the rest of the member
   area, because this page is read at a glance and the others are read.

   The one structural rule: a block appears only when it has something to
   say. An empty statistic is a zero; an empty BAND is a strip of colour
   demanding attention for nothing, so the templates omit those instead. */

.dash-hero{
  display:flex; flex-wrap:wrap; gap:20px;
  align-items:center; justify-content:space-between;
  padding:26px 28px; margin-bottom:18px;
  border:1px solid var(--line); border-radius:14px;
  background:var(--grad-surface);
  position:relative; overflow:hidden;
}
/* A single soft bloom in the corner, drawn from the brand ramp rather than
   an image — it survives both themes and costs no request. */
.dash-hero::after{
  content:''; position:absolute; inset-inline-end:-70px; top:-90px;
  width:280px; height:280px; border-radius:50%;
  background:radial-gradient(circle, var(--brand-tint) 0%, transparent 70%);
  pointer-events:none;
}
.dash-hero-who{display:flex; align-items:center; gap:18px; min-width:0; position:relative}

.dash-face{
  flex:none; width:74px; height:74px; border-radius:50%;
  display:grid; place-items:center; overflow:hidden;
  font-family:var(--font-head); font-size:30px; line-height:1;
  background:var(--grad-brand); color:var(--on-brand);
  border:2px solid var(--surface);
  box-shadow:0 0 0 1px var(--line), var(--shadow);
}
.dash-face img{width:100%; height:100%; object-fit:cover; display:block}

.dash-hero-t{min-width:0}
.dash-hero-t h1{
  font-size:clamp(1.6rem, 2.6vw, 2.3rem); line-height:1.12;
  letter-spacing:-.02em; margin:0; color:var(--ink);
}
.dash-hero-t .sub{margin:6px 0 0; font-size:13.5px; color:var(--muted); line-height:1.5}

.dash-visits{
  flex:none; position:relative;
  display:grid; grid-template-columns:auto 1fr; gap:1px 12px;
  align-items:center; padding:13px 20px;
  border:1px solid var(--line-strong); border-radius:12px;
  background:var(--surface); text-decoration:none;
  transition:border-color .16s ease, transform .16s ease;
}
.dash-visits:hover{border-color:var(--brand); transform:translateY(-1px)}
.dash-visits .ic-svg{grid-row:1 / span 2; color:var(--gold)}
.dash-visits-n{font-family:var(--font-head); font-size:26px; line-height:1; color:var(--ink)}
.dash-visits-l{font-size:11.5px; color:var(--muted)}

.dash-note{margin:0 0 18px}

/* ---- the bands: one sentence and one decision, across the full width ---- */
.dash-band{
  display:flex; align-items:center; gap:16px; flex-wrap:wrap;
  padding:16px 20px; margin-bottom:18px;
  border:1px solid var(--line); border-inline-start:3px solid var(--gold);
  border-radius:0 12px 12px 0; background:var(--surface);
  text-decoration:none; color:inherit;
}
a.dash-band{transition:border-color .16s ease, background .16s ease}
a.dash-band:hover{background:var(--surface-2)}
.dash-band.is-ask{border-inline-start-color:var(--brand)}
.dash-band-i{
  flex:none; width:42px; height:42px; border-radius:11px;
  display:grid; place-items:center;
  background:var(--gold-tint); color:var(--gold);
}
.dash-band.is-ask .dash-band-i{background:var(--brand-tint); color:var(--brand)}
.dash-band-t{flex:1; min-width:190px}
.dash-band-t .b{color:var(--ink); font-size:14.5px}
.dash-band-t p{margin:3px 0 0; line-height:1.5}

/* ---- the numbers ---- */
.dash-stats{
  display:grid; gap:12px; margin-bottom:12px;
  grid-template-columns:repeat(4, minmax(0,1fr));
}
.dash-stat{
  position:relative;
  display:grid; grid-template-columns:auto 1fr auto; gap:2px 12px;
  align-items:center; padding:16px 18px;
  border:1px solid var(--line); border-radius:12px;
  background:var(--surface); text-decoration:none; color:inherit;
  transition:border-color .16s ease, transform .16s ease;
}
a.dash-stat:hover{border-color:var(--line-strong); transform:translateY(-1px)}
/* Something is actually waiting. The border does the work, not a red dot:
   the whole tile is the link, so the whole tile should read as live. */
.dash-stat.is-live{border-color:var(--brand); background:var(--brand-tint)}
.dash-stat-i{
  grid-row:1 / span 2; flex:none;
  width:38px; height:38px; border-radius:10px;
  display:grid; place-items:center;
  background:var(--gold-tint); color:var(--gold);
}
.dash-stat.is-live .dash-stat-i{background:var(--surface); color:var(--brand)}
.dash-stat-n{font-family:var(--font-head); font-size:25px; line-height:1.1; color:var(--ink)}
.dash-stat-l{font-size:12px; color:var(--muted); grid-column:2}
.dash-stat > .ic-svg{grid-row:1 / span 2; grid-column:3; color:var(--muted)}
a.dash-stat:hover > .ic-svg{color:var(--brand)}

.dash-stat.is-wide .dash-stat-i{background:var(--brand-tint); color:var(--brand)}
.dash-stat-x{
  grid-column:2; grid-row:3;
  display:inline-flex; align-items:center; gap:4px; margin-top:4px;
  font-size:11.5px; color:var(--brand); text-decoration:none; font-weight:600;
}
.dash-stat-x:hover{text-decoration:underline}

/* ---- what to do next ---- */
.dash-acts{
  display:grid; gap:12px; margin-top:12px;
  grid-template-columns:repeat(3, minmax(0,1fr));
}
.dash-act{
  display:grid; grid-template-columns:auto 1fr auto; gap:0 13px;
  align-items:center; padding:15px 17px;
  border:1px solid var(--line); border-radius:12px;
  background:var(--surface); text-decoration:none; color:inherit;
  transition:border-color .16s ease, transform .16s ease;
}
.dash-act:hover{border-color:var(--line-strong); transform:translateY(-1px)}
.dash-act-i{
  flex:none; width:40px; height:40px; border-radius:10px;
  display:grid; place-items:center;
  background:var(--surface-2); color:var(--brand);
}
.dash-act-t{display:flex; flex-direction:column; gap:2px; min-width:0}
.dash-act-t .b{color:var(--ink); font-size:14px}
.dash-act-t .sm{font-size:12px; line-height:1.45}
.dash-act > .ic-svg{color:var(--muted)}
.dash-act:hover > .ic-svg{color:var(--brand)}

/* The one card that is selling something. Given the brand ramp so it reads
   as the page's single loudest element and nothing else has to compete. */
.dash-act.is-lead{background:var(--grad-brand); border-color:transparent; color:var(--on-brand)}
.dash-act.is-lead:hover{background:var(--grad-brand-hover)}
.dash-act.is-lead .dash-act-i{background:rgba(255,255,255,.18); color:var(--on-brand)}
.dash-act.is-lead .dash-act-t .b,
.dash-act.is-lead .dash-act-t .sm,
.dash-act.is-lead > .ic-svg{color:var(--on-brand)}
.dash-act.is-lead .dash-act-t .sm{opacity:.86}

.dash-bar{height:6px; background:var(--surface-2); border-radius:3px; overflow:hidden}
.dash-bar span{display:block; height:100%; background:var(--grad-brand)}

/* ---- a row of suggestions ---- */
.dash-row{margin-top:38px}
.dash-row-h{
  display:flex; align-items:flex-end; justify-content:space-between;
  gap:16px; flex-wrap:wrap; margin-bottom:16px;
  padding-bottom:11px; border-bottom:1px solid var(--line);
}
.dash-row-h h2{
  display:flex; align-items:center; gap:9px; margin:0;
  font-size:clamp(1.25rem, 2vw, 1.6rem); letter-spacing:-.02em; color:var(--ink);
}
.dash-row-h h2 .ic-svg{color:var(--gold)}
.dash-row-h p{margin:5px 0 0; line-height:1.5}

@media (max-width:980px){
  .dash-stats{grid-template-columns:repeat(2, minmax(0,1fr))}
  .dash-acts{grid-template-columns:1fr}
}
@media (max-width:620px){
  .dash-hero{padding:20px; gap:16px}
  .dash-face{width:60px; height:60px; font-size:25px}
  .dash-visits{width:100%; justify-content:flex-start}
  .dash-stats{grid-template-columns:1fr}
}

/* ============ THE SUGGESTION CARD ============
   The photograph is the card: the picture fills the panel and the facts sit
   over its lower half under a gradient. That is a deliberate break from
   .pcard, which is the biodata-sheet treatment and stays the right shape for
   a dense list of search results. This one appears three or four at a time
   on a page somebody is browsing rather than scanning.

   Nothing here decides what to show. Every line is drawn only if the
   serialiser put that key in the payload, so a card with no work line is a
   member who keeps their profession private — the card must never fill that
   gap with an invitation to pay, because no plan on this site overrides
   another member's privacy setting. */

.mcard-grid{display:grid; gap:16px; grid-template-columns:repeat(3, minmax(0,1fr))}
.mcard-grid.is-four{grid-template-columns:repeat(4, minmax(0,1fr))}

.mcard{
  position:relative; display:flex; flex-direction:column;
  border:1px solid var(--line); border-radius:14px; overflow:hidden;
  background:var(--surface); box-shadow:var(--shadow);
  transition:border-color .16s ease, transform .16s ease, box-shadow .16s ease;
}
.mcard:hover{border-color:var(--line-strong); transform:translateY(-2px)}

.mcard-art{
  position:relative; aspect-ratio:4/5; overflow:hidden;
  background:var(--grad-brand);
  display:grid; place-items:center;
}
.mcard-art img{width:100%; height:100%; object-fit:cover; display:block}
.mcard-initial{
  font-family:var(--font-head); font-size:64px; line-height:1;
  color:var(--on-brand); opacity:.5;
}
/* The gradient the text sits on. Always drawn, so a light photograph and a
   dark one give the caption the same contrast. */
.mcard-veil{
  position:absolute; inset:auto 0 0 0; height:72%;
  background:linear-gradient(to top, rgba(31,2,11,.92) 0%, rgba(31,2,11,.66) 42%, transparent 100%);
  pointer-events:none;
}
/* A veiled photograph is veiled on the SERVER — this class only says so on
   the surface, and adds nothing a reader could switch off. */
.mcard-art.is-veiled img{transform:scale(1.04)}

.mcard-flag{
  position:absolute; top:11px; inset-inline-start:11px;
  display:inline-flex; align-items:center; gap:5px;
  padding:5px 10px; border-radius:999px;
  font-size:10.5px; font-weight:600; letter-spacing:.01em;
  background:rgba(31,2,11,.62); color:#FFF0F3;
  backdrop-filter:blur(6px); border:1px solid rgba(255,240,243,.22);
}
.mcard-flag.is-nid{background:var(--brand); border-color:transparent}

.mcard-rail{
  position:absolute; top:11px; inset-inline-end:11px; z-index:2;
  display:flex; flex-direction:column; gap:8px;
}
.mcard-rail form{display:contents}
.mcard-act{
  width:34px; height:34px; border-radius:50%;
  display:grid; place-items:center; cursor:pointer;
  background:rgba(31,2,11,.55); color:#FFF0F3;
  border:1px solid rgba(255,240,243,.24);
  backdrop-filter:blur(6px); text-decoration:none;
  transition:background .16s ease, border-color .16s ease, transform .16s ease;
}
.mcard-act:hover{background:var(--brand); border-color:transparent; transform:scale(1.06)}
.mcard-act.is-warm:hover{background:var(--gold)}
.mcard-act:focus-visible{outline:2px solid #FFF0F3; outline-offset:2px}

/* The caption carries its OWN gradient as well as sitting on the veil.
   The veil is a fixed fraction of the picture; this one is exactly as tall
   as the text, so a card with an extra line — a country, a job title —
   cannot outgrow its own background and end up as pale grey lettering on a
   pale photograph. That is what happened first, and only on the light
   pictures, which is the worst way for it to fail: fine in review, unreadable
   for whoever happens to have a photo taken outdoors. */
.mcard-body{
  position:absolute; inset:auto 0 0 0; z-index:1;
  display:flex; flex-direction:column; gap:7px;
  padding:34px 14px 15px;
  background:linear-gradient(to top,
    rgba(31,2,11,.95) 0%, rgba(31,2,11,.9) 46%,
    rgba(31,2,11,.72) 78%, rgba(31,2,11,0) 100%);
}
.mcard-top{display:flex; align-items:center; gap:8px; flex-wrap:wrap}
.mcard-name{
  font-family:var(--font-head); font-size:17px; line-height:1.15;
  color:#FFF0F3; text-decoration:none; letter-spacing:-.01em;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:100%;
}
.mcard-name:hover{text-decoration:underline}
.mcard-score{
  display:inline-flex; align-items:center; gap:4px; flex:none;
  padding:3px 9px; border-radius:999px;
  font-family:var(--font-mono); font-size:10.5px;
  background:var(--gold); color:#FFF0F3;
}

.mcard-line{
  display:flex; align-items:center; gap:7px;
  font-size:12.5px; color:rgba(255,240,243,.92); line-height:1.35;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.mcard-line .ic-svg{flex:none; opacity:.8}
.mcard-dim{display:inline-flex; align-items:center; gap:5px; color:rgba(255,240,243,.62)}

.mcard-facts{display:flex; flex-wrap:wrap; gap:5px; margin-top:1px}
.mcard-fact{
  display:inline-flex; align-items:center; gap:4px;
  padding:3px 8px; border-radius:999px;
  font-size:10.5px; color:#FFF0F3;
  background:rgba(255,240,243,.14); border:1px solid rgba(255,240,243,.2);
}
.mcard-fact .ic-svg{opacity:.75}

.mcard-foot{
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  margin-top:5px; padding-top:11px;
  border-top:1px solid rgba(255,240,243,.18);
}
.mcard-marital{display:flex; align-items:center; gap:7px; min-width:0}
.mcard-disc{
  flex:none; width:32px; height:32px; border-radius:50%;
  display:grid; place-items:center;
  font-family:var(--font-mono); font-size:10.5px;
  color:#FFF0F3; background:rgba(255,240,243,.14);
  border:1px solid rgba(255,240,243,.34);
}
.mcard-marital-t{
  font-size:10.5px; color:rgba(255,240,243,.78); line-height:1.25; max-width:9ch;
}
.mcard-cta{flex:1; min-width:132px}
.mcard-cta .btn{gap:6px}

@media (max-width:1040px){
  .mcard-grid.is-four{grid-template-columns:repeat(3, minmax(0,1fr))}
}
@media (max-width:880px){
  .mcard-grid,.mcard-grid.is-four{grid-template-columns:repeat(2, minmax(0,1fr))}
  .mcard-marital-t{display:none}
}
@media (max-width:520px){
  .mcard-grid,.mcard-grid.is-four{grid-template-columns:1fr}
  .mcard-marital-t{display:block}
}

/* ============ FOOTER — COLUMNS AND THE CHECKOUT STRIP ============
   The footer grew from three blocks to five: the brand, three named link
   columns, and the contact details. Declared after the original rules rather
   than edited into them, so the earlier block stays readable as the base
   layout and this is plainly the widening.

   .sitefoot-links keeps its class name and loses its own heading — the
   heading now belongs to the column that wraps it, because there are three
   of them and an unlabelled list of links is a puzzle. */
.sitefoot-in{
  grid-template-columns:minmax(0,1.7fr) repeat(3, minmax(0,.72fr)) minmax(0,1.2fr);
  gap:32px 26px;
}
.sitefoot-col{min-width:0}
.sitefoot-col .sitefoot-links{flex-direction:column; gap:11px; padding-top:0}
.sitefoot-col .sitefoot-links a{font-size:13.5px}

/* Was a bottom margin on the heading alone; the columns need it to sit on
   the same baseline as the contact block's heading. */
.sitefoot-col .sitefoot-head{margin-bottom:14px}

.sitefoot-pay{
  /* Full bleed to match .sitefoot-base below it, so the two rules that close
     the footer are the same length. */
  padding:16px max(22px, calc((100% - 1180px) / 2 + 22px));
  border-top:1px solid var(--line);
  display:flex; align-items:center; gap:12px 20px; flex-wrap:wrap;
}
.sitefoot-pay-h{
  display:inline-flex; align-items:center; gap:7px;
  font-family:var(--font-mono); font-size:10.5px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--muted);
}
.sitefoot-pay-h svg{color:var(--ok)}
.sitefoot-rails{display:flex; flex-wrap:wrap; gap:8px}
.sitefoot-rail{
  padding:5px 11px; border-radius:6px;
  font-size:11.5px; color:var(--body);
  background:var(--surface-2); border:1px solid var(--line);
}

@media (max-width:1080px){
  .sitefoot-in{grid-template-columns:minmax(0,1.4fr) repeat(2, minmax(0,1fr))}
}
@media (max-width:900px){
  /* The base rule already collapses to one column at this width; the link
     lists go back to wrapping rows so three headings do not become three
     tall stacks on a phone. */
  .sitefoot-in{grid-template-columns:1fr}
  .sitefoot-col .sitefoot-links{flex-direction:row; flex-wrap:wrap; gap:10px 20px}
  .sitefoot-pay{padding:14px 16px}
}

/* ============ THE BLANK AVATAR ============
   The silhouette that stands in for a missing photograph, drawn by
   partials/avatar-blank. One rule set for every place it appears, because it
   is the same drawing at four sizes and it should not drift into four.

   It fills its container rather than sitting in it: these holders are already
   circles with their own background, and a figure floating in the middle with
   air around it reads as a missing icon rather than as a person. The head
   starts near the top of the frame and the shoulders run off the bottom
   edge, which is how a real portrait crops. */
.avblank{
  display:block; width:100%; height:100%;
  /* Slightly down from full strength: it is a placeholder, and at full
     opacity it competes with the real photographs beside it. */
  opacity:.82;
}

/* The photo tile. Its background is surface-2, so the figure takes the
   muted ink rather than the brand — a rose-coloured person on a rose tile
   is a shape you have to work to see. */
.ph .avblank{color:var(--muted); opacity:.55}
/* Blurred tiles blur the stand-in too, so a hidden photograph and a missing
   one do not announce which is which at a glance. */
.ph.blur .avblank{filter:blur(7px); opacity:.4}

/* The header chip and the dashboard hero both sit on the brand ramp, so the
   figure is drawn in the colour that ramp is meant to carry.

   In the header it is INSET rather than bled to the edges. A head-and-
   shoulders that touches the rim of a 42px circle has no silhouette left to
   read — it is just a filled circle — so it sits at three quarters of the
   chip with the tint showing around it, which is what makes it read as a
   portrait. Raised opacity too: at .55 over the near-transparent empty-state
   background it was barely there in dark mode. */
.oct .avblank{width:74%; height:74%; color:var(--brand-deep); opacity:.78}
.oct-i{display:inline-flex; align-items:center; justify-content:center; width:100%; height:100%}
.dash-face .avblank{color:var(--on-brand); opacity:.62}

/* On a suggestion card the figure is the whole panel, so it is scaled down
   and centred rather than cropped — at card size a bled-off silhouette
   fills the frame and stops reading as a placeholder at all. */
.mcard-initial{display:grid; place-items:center; width:100%; height:100%}
.mcard-initial .avblank{
  width:46%; height:auto; margin-top:-14%;
  color:var(--on-brand); opacity:.42;
}

/* ============ THE ACCOUNT MENU ============
   The member's own face, opening onto the two things they eventually come
   looking for — their settings and the way out — plus the profile editor the
   face used to link to directly.

   It reuses .navdrop wholesale: same open-on-hover, open-on-click, close-on-
   Escape handler, same panel. Only two things differ, and both are because
   this one is the last item in the bar rather than one of a row in the
   middle. */

/* Anchored to the RIGHT edge. The shared rule starts the panel at the
   button's leading edge, which for the last control in the header puts a
   210px menu off the side of the window. */
.navdrop-acct .navdrop-menu{inset-inline-start:auto; inset-inline-end:0; min-width:232px}
.navdrop-acct::after{inset-inline-start:auto; inset-inline-end:0}

/* The trigger is a portrait, not a word: no padding, no hover fill, and none
   of the chevron rotation the group buttons carry. */
.oct-btn{
  padding:0; border:0; background:transparent; cursor:pointer;
  display:inline-flex; border-radius:50%;
  transition:box-shadow .16s ease;
}
.oct-btn:hover{box-shadow:0 0 0 2px var(--brand-tint)}
.oct-btn:focus-visible{outline:2px solid var(--brand); outline-offset:2px}
.navdrop-acct.on .navdrop-btn{background:transparent}
.navdrop-acct.on .oct-btn{box-shadow:0 0 0 2px var(--brand)}

/* Who you are signed in as, above the actions. The profile id was previously
   only a title attribute — true, and invisible to anyone not hovering. */
.navdrop-head{
  display:flex; flex-direction:column; gap:2px;
  padding:8px 11px 9px; margin-bottom:4px;
  border-bottom:1px solid var(--line);
}
.navdrop-head strong{
  font-size:13.5px; color:var(--ink); font-weight:600;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.navdrop-head .mono{font-size:11px; color:var(--muted); letter-spacing:.04em}

/* Log out is a form, so its control is a button where the others are links.
   Given the same box so the menu does not have one row that sits differently
   from the rest. */
.navdrop-form{margin:0}
button.navdrop-item{
  width:100%; text-align:start; border:0; background:transparent;
  cursor:pointer; font:inherit; font-size:14px;
}
.navdrop-item.is-out{color:var(--muted); margin-top:2px; border-top:1px solid var(--line); border-radius:0 0 8px 8px}
.navdrop-item.is-out:hover{color:var(--bad); background:color-mix(in srgb, var(--bad) 9%, transparent)}

/* ============ THE UNREAD COUNT ============
   On the one link in the bar that can be owed something.

   Brand rather than the usual alarm red: nothing here has gone wrong, and a
   red pip on a matrimonial site's header reads as a warning rather than as
   "somebody wrote to you". It sits inline in the link so the two move
   together and the bar does not reflow when the number appears. */
.topnav-link{display:inline-flex; align-items:center; gap:7px}
.topnav-count{
  display:inline-grid; place-items:center;
  min-width:18px; height:18px; padding:0 5px;
  border-radius:9px;
  background:var(--brand); color:var(--on-brand);
  font-family:var(--font-mono); font-size:10.5px; line-height:1;
  font-weight:600; letter-spacing:0;
}
/* On the active link the bar has already tinted the row, so a solid brand
   pill on top of it loses its edge. */
.topnav-link.on .topnav-count{background:var(--brand-deep)}

/* ============ SEARCH: THE FILTER PANEL ============
   Nine controls that used to be one undifferentiated column, in three named
   groups that open and close.

   <details>/<summary>, so the browser owns the open state, the keyboard and
   the screen-reader announcement — the same reasoning as the FAQ accordion,
   and the same reason neither of them needs a line of JavaScript. */

.searchpage{
  display:grid; gap:20px; align-items:start;
  grid-template-columns:264px minmax(0,1fr);
}

.sfilter{
  position:sticky; top:78px;
  background:var(--surface); border:1px solid var(--line); border-radius:12px;
  overflow:hidden;
}

.sfilter-head{
  display:flex; align-items:center; gap:9px;
  padding:13px 15px;
  border-bottom:1px solid var(--line);
}
.sfilter-head .lbl{margin-inline-end:auto}

/* How many filters are on, so a panel scrolled past still says it is doing
   something. */
.sfilter-on{
  display:grid; place-items:center;
  min-width:19px; height:19px; padding:0 6px; border-radius:10px;
  background:var(--brand); color:var(--on-brand);
  font-family:var(--font-mono); font-size:10.5px; font-weight:600;
}
.sfilter-clear{
  font-size:12px; color:var(--muted); text-decoration:none;
  border-bottom:1px solid transparent;
}
.sfilter-clear:hover{color:var(--bad); border-bottom-color:currentColor}

/* ---- a group ---- */
.sfilter-grp{border-bottom:1px solid var(--line)}
.sfilter-grp > summary{
  display:flex; align-items:center; gap:8px;
  padding:12px 15px; cursor:pointer; list-style:none;
  font-size:13.5px; color:var(--ink); font-weight:500;
  transition:background .14s ease;
}
.sfilter-grp > summary::-webkit-details-marker{display:none}
.sfilter-grp > summary:hover{background:var(--surface-2)}
.sfilter-grp > summary:focus-visible{outline:2px solid var(--brand); outline-offset:-2px}

/* The chevron is drawn here rather than shipped as an icon, so it can rotate
   off the open state without any script. */
.sfilter-grp > summary::after{
  content:''; margin-inline-start:auto; flex:none;
  width:7px; height:7px;
  border-inline-end:1.8px solid var(--muted); border-block-end:1.8px solid var(--muted);
  transform:rotate(45deg) translate(-2px,-2px);
  transition:transform .18s ease;
}
.sfilter-grp[open] > summary::after{transform:rotate(-135deg) translate(-2px,-2px)}
.sfilter-grp[open] > summary{color:var(--brand-deep)}

/* How many of THIS group's filters are set — the reason a collapsed group is
   still readable. */
.sfilter-n{
  display:grid; place-items:center;
  min-width:17px; height:17px; padding:0 5px; border-radius:9px;
  background:var(--brand-tint); color:var(--brand-deep);
  font-family:var(--font-mono); font-size:10px;
}

.sfilter-body{
  display:flex; flex-direction:column; gap:11px;
  padding:2px 15px 15px;
}

.sfilter-check{
  display:flex; align-items:center; gap:9px;
  font-size:13px; color:var(--body); cursor:pointer;
}

/* ---- the foot ---- */
/* Apply and the total together, because the total is the answer to the
   question the controls above just asked. It used to live at the top of the
   other column, where you could not see it and the control you were changing
   at the same time. */
.sfilter-foot{
  display:flex; flex-direction:column; align-items:center; gap:7px;
  padding:14px 15px;
  background:var(--surface-2);
}
.sfilter-total{font-size:12px; color:var(--muted)}

@media (max-width:900px){
  .searchpage{grid-template-columns:1fr}
  /* Not sticky when it is stacked above the results: a panel pinned to the
     top of a phone screen is a panel covering what it filtered. */
  .sfilter{position:static}
}

/* ============ SEARCH: THE FILTER BAR ============
   Replaces the column the rules further up describe. Same three groups, same
   <details>, laid along a bar under the heading instead of down the side.

   Why the move: a 264px column spent a sixth of every screen on controls
   that are touched once and then read past, and it pushed the results — the
   thing the page is for — into a narrow well beside it. Along the top the
   same controls cost one row, the cards get the whole width, and the bar
   stays put while the results scroll under it.

   Declared after the column rules rather than instead of them: the shared
   pieces (.sfilter-grp, .sfilter-body, .sfilter-n, .sfilter-check) are the
   same in both, and only the frame around them changes. */

.searchpage{
  display:block;                  /* was a two-column grid */
}

.sfilter{
  position:sticky;
  /* Under the header, not under the top of the window. 78px is the same
     offset the column used, and the same one .topnav reserves. */
  top:78px; z-index:30;
  display:flex; align-items:center; flex-wrap:wrap; gap:8px;
  margin-bottom:20px; padding:9px 12px;
  border:1px solid var(--line); border-radius:12px;
  /* Slightly translucent, so the cards passing beneath it read as passing
     beneath it rather than disappearing into a solid slab. */
  background:color-mix(in srgb, var(--surface) 92%, transparent);
  -webkit-backdrop-filter:blur(14px) saturate(1.2);
          backdrop-filter:blur(14px) saturate(1.2);
  box-shadow:var(--shadow);
  overflow:visible;               /* the panels hang out of it */
}

.sfilter-lbl{
  padding-inline:4px 6px;
  border-inline-end:1px solid var(--line);
  margin-inline-end:4px; align-self:stretch;
  display:flex; align-items:center;
}

/* ---- a group, now a chip ---- */
.sfilter .sfilter-grp{
  position:relative;              /* the panel hangs from this */
  border-bottom:0;                /* the column drew a rule under each */
}
.sfilter .sfilter-grp > summary{
  padding:8px 13px; border-radius:9px;
  border:1px solid transparent;
  font-size:13px; white-space:nowrap;
}
.sfilter .sfilter-grp > summary:hover{background:var(--surface-2)}
.sfilter .sfilter-grp[open] > summary{
  background:var(--brand-tint); border-color:var(--brand);
  color:var(--brand-deep);
}
/* A chip whose filter is set stays marked even when the panel is shut. */
.sfilter .sfilter-grp:has(.sfilter-n) > summary{
  border-color:color-mix(in srgb, var(--brand) 45%, transparent);
}

/* ---- the panel, hanging under its chip ---- */
.sfilter .sfilter-body{
  position:absolute; z-index:40;
  top:calc(100% + 7px); inset-inline-start:0;
  width:max(272px, 100%);
  padding:14px;
  border:1px solid var(--line); border-radius:12px;
  background:var(--surface);
  box-shadow:0 20px 48px -22px rgba(0,0,0,.55);
}
/* The last chip's panel would run off the right edge of the bar. */
.sfilter .sfilter-grp:last-of-type .sfilter-body{
  inset-inline-start:auto; inset-inline-end:0;
}

/* ---- the tail ---- */
.sfilter-tail{
  display:flex; align-items:center; gap:10px;
  margin-inline-start:auto;
}
.sfilter-total{font-size:12.5px; color:var(--muted); white-space:nowrap}

@media (max-width:900px){
  /* Not sticky on a phone: a bar pinned to the top of a small screen is a
     bar covering the results it just filtered. The chips wrap instead. */
  .sfilter{position:static; padding:10px}
  .sfilter-lbl{display:none}
  .sfilter-tail{margin-inline-start:0; width:100%; justify-content:space-between}
  /* Full width rather than hanging: at this size a popover anchored to a
     chip lands half off the screen. */
  .sfilter .sfilter-body{position:static; width:auto; box-shadow:none; margin-top:8px}
  .sfilter .sfilter-grp{width:100%}
  .sfilter .sfilter-grp > summary{width:100%}
}

/* ---- the results, now that they have the whole width ---- */
/* auto-fill rather than a fixed three: the grid was sized for the column
   that used to sit beside it, and would have left four cards' worth of space
   showing three. */
.searchpage .mcard-grid{
  grid-template-columns:repeat(auto-fill, minmax(232px, 1fr));
}

/* Saving a search is an action ABOUT the filters, not one of them — so it
   sits at the far end rather than starting at the same edge as the bar,
   where it read as a fourth chip that had come loose. */
.searchsave{justify-content:flex-end; margin-bottom:14px}
@media (max-width:620px){ .searchsave{justify-content:flex-start} }

/* ============ THE CORNER NOTICES ============
   What just happened, stacked in the bottom-right, rather than a full-width
   slab at the top of the document.

   Bottom-right and not top: the header is already the busiest strip on every
   page, and a notice landing on top of the navigation covers the controls
   somebody is most likely to reach for next. The bottom corner is out of the
   way of both the header and the content column, and it is where every
   product people already use puts a confirmation.

   Fixed, so it never reflows the page. The old block was in the document
   flow — "Saved." pushed the entire screen down by its own height the moment
   it appeared, which on a long page moved the thing you had just been
   looking at. */

.toasts{
  position:fixed; z-index:200;
  /* Pinned to the START edge — the left, in both languages this ships in.
     The forms it reports on are centred, so a notice on either side clears
     them; the left is where it was asked for. */
  inset-block-end:20px; inset-inline-start:20px;
  display:flex; flex-direction:column; gap:10px;
  /* The column is only as wide as it needs to be, and never wider than the
     screen it is pinned to. */
  width:min(380px, calc(100vw - 32px));
  /* The gaps between notices must not swallow clicks on what is underneath. */
  pointer-events:none;
}

.toast{
  pointer-events:auto;
  display:grid; grid-template-columns:auto 1fr auto; gap:11px;
  align-items:start;
  padding:13px 13px 13px 14px;
  border:1px solid var(--line);
  border-inline-start:3px solid var(--brand);
  border-radius:12px;
  background:color-mix(in srgb, var(--surface) 96%, transparent);
  -webkit-backdrop-filter:blur(16px) saturate(1.25);
          backdrop-filter:blur(16px) saturate(1.25);
  box-shadow:0 18px 44px -18px rgba(0,0,0,.5), 0 2px 8px -4px rgba(0,0,0,.25);
  font-size:13.5px; line-height:1.55; color:var(--body);
  animation:toast-in .26s cubic-bezier(.2,.8,.3,1) both;
}

/* In from the side it is pinned to, not up from the bottom: it reads as
   arriving from off-screen rather than growing out of the page. */
@keyframes toast-in{
  from{ opacity:0; transform:translateX(-14px) scale(.98) }
  to  { opacity:1; transform:none }
}

.toast.is-going{
  opacity:0; transform:translateX(-14px);
  transition:opacity .22s ease, transform .22s ease;
}

@media (prefers-reduced-motion: reduce){
  .toast{animation:none}
  .toast.is-going{transition:none}
}

.toast-ic{
  flex:none; width:26px; height:26px; border-radius:50%;
  display:grid; place-items:center;
  background:var(--brand-tint); color:var(--brand-deep);
}

.toast-b{margin:0; min-width:0}
.toast-b strong{display:block; color:var(--ink); font-weight:600; margin-bottom:4px}
.toast-b ul{margin:0; padding-inline-start:16px}
.toast-b li{margin-top:2px}

.toast-x{
  flex:none; width:24px; height:24px; margin:-2px -2px 0 0;
  border:0; border-radius:7px; background:transparent; cursor:pointer;
  display:grid; place-items:center; color:var(--muted);
  transition:background .14s ease, color .14s ease;
}
.toast-x:hover{background:var(--surface-2); color:var(--ink)}
.toast-x:focus-visible{outline:2px solid var(--brand); outline-offset:1px}

/* A success is green-edged and leaves on its own; a problem is red-edged and
   stays until it is dismissed. The colour is the only hint of which, so it
   carries an icon too — a border tint alone is not a message. */
.toast.is-ok{border-inline-start-color:var(--ok)}
.toast.is-ok .toast-ic{
  background:color-mix(in srgb, var(--ok) 14%, transparent); color:var(--ok);
}
.toast.is-bad{border-inline-start-color:var(--bad)}
.toast.is-bad .toast-ic{
  background:color-mix(in srgb, var(--bad) 14%, transparent); color:var(--bad);
}
.toast.is-bad .toast-b strong{color:var(--bad)}

@media (max-width:620px){
  /* Across the bottom, above the thumb rather than under it. */
  .toasts{
    inset-inline:12px; inset-block-end:12px;
    width:auto;
  }
}

/* ============ THE BELL ============
   A [data-drop] menu like the account one, so the open/close behaviour comes
   from nav.js and nothing here scripts anything. What is styled here is the
   button, the count on it, and the lines inside — which are shared with the
   full notifications page, so every rule below works in both places. */

.bell-btn{
  position:relative;
  display:inline-flex; align-items:center; justify-content:center;
  width:34px; height:34px;
  border:1px solid transparent; border-radius:9px;
  background:transparent; color:var(--muted); cursor:pointer;
  transition:color .16s ease, background .16s ease, border-color .16s ease;
}
.bell-btn:hover{ color:var(--brand-deep); background:var(--brand-tint) }
.bell-btn:focus-visible{ outline:2px solid var(--action-hover); outline-offset:2px }

/* The count rides the bell's top-right corner. min-width rather than a fixed
   size so "9+" is not squeezed, and tabular figures so 1 and 9 are the same
   width and the badge does not twitch as the number changes. */
.bell-dot{
  position:absolute; top:-1px; inset-inline-end:-1px;
  min-width:17px; height:17px; padding:0 4px;
  display:grid; place-items:center;
  border-radius:999px;
  background:var(--bad); color:#fff;
  font-size:10.5px; font-weight:700; line-height:1;
  font-variant-numeric:tabular-nums;
  border:2px solid var(--surface);
}

/* The panel opens LEFTWARD, from its own right edge.

   .navdrop-menu anchors at inset-inline-start:0, which is right for a menu
   under a word on the left of the bar and wrong for this one: the bell sits
   near the right edge, so a 360px panel hung off its left corner ran past the
   window and the text was cut down the middle. The account menu beside it
   already solved this; the bell simply had not inherited it.

   The ::after is the strip that spans the 8px gap between button and menu, so
   the pointer does not leave both on the way down. It has to move with the
   panel or it covers the wrong side. */
.navdrop-bell .navdrop-menu{ inset-inline-start:auto; inset-inline-end:0 }
.navdrop-bell::after{ inset-inline-start:auto; inset-inline-end:0 }

.navdrop-menu-bell{ width:min(360px, calc(100vw - 28px)); padding:6px }

/* ---- one line, in the menu and on the page ---- */

.notif-line{
  display:grid; grid-template-columns:26px 1fr auto; gap:10px;
  align-items:start;
  padding:9px 10px;
  border-radius:8px;
  color:var(--ink); text-decoration:none;
  font-size:13px; line-height:1.5;
}
.notif-line:hover{ background:var(--surface-2) }
.notif-line:focus-visible{ outline:2px solid var(--action-hover); outline-offset:-2px }

/* Unread is carried by weight and a start rail, not by colour alone — the
   two states have to be tellable apart without relying on hue. */
.notif-line.is-new{ background:var(--brand-tint) }
.notif-line.is-new .notif-txt{ font-weight:600; color:var(--ink) }
.notif-line.is-new .notif-ic{ box-shadow:inset 0 0 0 1px var(--brand) }

.notif-ic{
  width:26px; height:26px; border-radius:7px;
  display:grid; place-items:center;
  background:var(--surface-2); color:var(--muted);
}
.notif-ic-interest, .notif-ic-match{ color:var(--brand) }
.notif-ic-call{ color:var(--bad) }
.notif-ic-message{ color:var(--brand-deep) }

.notif-txt{ min-width:0; color:var(--body) }

.notif-at{
  font-size:11px; color:var(--faint, var(--muted));
  white-space:nowrap; font-variant-numeric:tabular-nums;
  padding-top:2px;
}

.notif-empty{ margin:0; padding:14px 12px; font-size:12.5px; color:var(--muted) }

.notif-foot{
  display:flex; align-items:center; gap:4px;
  margin-top:4px; padding-top:4px;
  border-top:1px solid var(--line);
}
.notif-foot .navdrop-item{ flex:1; text-align:center }
.notif-foot .navdrop-form{ flex:1; margin:0 }
.notif-foot .is-quiet{ width:100%; color:var(--muted); font-size:12px }

/* On the page the lines are a list rather than a menu, so they get rules
   between them instead of the menu's own padding. */
.notif-page{ padding:6px }
.notif-page .notif-line{ border-radius:6px }
.notif-page .notif-line + .notif-line{
  border-top:1px solid color-mix(in srgb, var(--line) 55%, transparent);
  border-radius:0;
}

/* 860px, matching the breakpoint the rest of the bar collapses at — an
   earlier draft of these rules used 620 and left a 240px band where the nav
   had folded but the bell still thought it had a desktop to sit in. */
@media (max-width:860px){
  /* On a phone the panel is pinned to the WINDOW, not to the bell.

     The bell is only ~180px from the right edge there, so anchoring the panel
     to its own right corner still hangs 360px of it off the screen. Both
     insets, and the width follows from them — which is why the width rule has
     to be released as well, or a fixed 360px wins over the pair of insets and
     the right edge goes back over the side.

     .navdrop-bell drops to position:static so `absolute` resolves against the
     bar rather than the button. This block sits AFTER the desktop rules
     deliberately: same specificity, and source order is what decides. */
  .navdrop-bell{ position:static }
  .navdrop-bell .navdrop-menu{
    position:absolute; top:calc(100% + 6px);
    inset-inline-start:12px; inset-inline-end:12px;
    background:var(--surface);
    border:1px solid var(--line); border-radius:12px;
    box-shadow:0 18px 44px -20px rgba(0,0,0,.5);
    padding:6px;
  }
  .navdrop-menu-bell{ width:auto }

  /* The relative time is the first thing to go: the sentence is what the
     line is for, and three columns do not fit a phone. The full moment is
     still on the <time> element for anything that reads it. */
  .notif-at{ display:none }
  .notif-line{ grid-template-columns:26px 1fr }
}


/* =========================================================================
   THE BIODATA SHEET

   A biodata is a document. It is printed, folded, handed across a table,
   photographed and forwarded on WhatsApp. So the sheet carries its own
   paper colours rather than the theme's: in dark mode a themed sheet was
   cream-on-charcoal on the screen and black-on-white on the paper, and a
   preview that does not match what comes out of the printer is not a
   preview. Only the accent stays themed, so `Theme::css()` still reaches it.

   Four layouts share these bones. The form has offered the choice since the
   maker was written and stored it on the draft; nothing ever read it back,
   so every biodata came out identical whichever one was picked.
   ========================================================================= */
.bsheet{
  --p-bg:#fffdfb;
  --p-ink:#161113;
  --p-soft:#5c5257;
  --p-line:#ded6da;
  --p-rule:#ab9ea6;

  /* Fixed, not var(--brand), for the same reason the paper is fixed. In dark
     mode the brand is a light pink and the accent landed on white paper at
     roughly no contrast at all. The sheet does not follow the theme. */
  --p-accent:#6d2438;

  background:var(--p-bg);
  color:var(--p-ink);
  border:1px solid var(--p-line);
  border-radius:14px;
  padding:34px 36px;
  font-size:14px;
  line-height:1.55;
  box-shadow:0 1px 3px rgba(0,0,0,.10), 0 14px 38px rgba(0,0,0,.10);
}

/*
   The site sets an explicit colour on headings — `h1,h2,h3,h4{color:var(--ink)}`
   — and in dark mode --ink is a near-white pink. The sheet paints its own
   white paper underneath, so the member's name came out pale pink on cream
   and was very nearly not there. Nothing inside the sheet inherits the
   theme's ink; the rules below re-state the few that are deliberately soft.
*/
.bsheet *{ color:inherit }

.bsheet-hd{
  display:flex; gap:20px; align-items:center;
  padding-bottom:16px; margin-bottom:20px;
  border-bottom:1px solid var(--p-rule);
}
.bsheet-id{ min-width:0 }
.bsheet-name{ margin:0; font-size:26px; line-height:1.2; font-weight:600; letter-spacing:-.01em }
.bsheet-kicker{
  margin:5px 0 0; font-family:var(--font-mono); font-size:10.5px;
  letter-spacing:.18em; text-transform:uppercase; color:var(--p-soft);
}

/* Portrait proportions, not a square. Every biodata photograph anybody has
   ever pasted onto one of these is a passport photograph. */
.bsheet-photo{
  flex:none; width:104px; height:132px; object-fit:cover;
  border-radius:6px; border:1px solid var(--p-line); background:#f0ebed;
}

.bsheet-grp + .bsheet-grp{ margin-top:20px }
.bsheet-grp > h3{
  margin:0 0 9px; font-family:var(--font-mono); font-weight:600;
  font-size:10.5px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--p-accent);
}

/* No column gap: the label cell and the value cell each draw their own
   bottom rule, and an eighteen-pixel gap between them broke every line in
   the sheet into two floating dashes. The cells touch and the space between
   the words is padding instead, so one rule runs the width of the row. */
.bsheet-rows{ margin:0; display:grid; grid-template-columns:170px 1fr; gap:0 }
.bsheet-rows dt{ color:var(--p-soft); padding-inline-end:18px }
.bsheet-rows dd{ margin:0; padding-inline-start:18px }
.bsheet-rows dt,
.bsheet-rows dd{ padding-block:6px; border-bottom:1px solid var(--p-line) }
.bsheet-prose{ margin:0 0 10px; white-space:pre-line }
.bsheet-prose:last-child{ margin-bottom:0 }
.bsheet-traditional .bsheet-prose{ text-align:center }
.bsheet-formal .bsheet-prose{ margin-top:8px }

/* The last row in the sheet already has the footer's rule below it; two
   lines twelve pixels apart read as a mistake. */
.bsheet-grp:last-of-type .bsheet-rows > :nth-last-child(-n+2){ border-bottom:0 }

.bsheet-ft{
  display:flex; justify-content:space-between; align-items:center;
  gap:14px; flex-wrap:wrap;
  margin-top:26px; padding-top:12px; border-top:1px solid var(--p-rule);
}
.bsheet-brand{ display:flex; align-items:center; gap:9px }
.bsheet-mark{ width:26px; height:26px; flex:none; border-radius:5px }
.bsheet-brand-name{
  font-size:13px; font-weight:600; letter-spacing:-.01em; line-height:1.15;
  color:var(--p-ink);
}
.bsheet-brand-url,
.bsheet-ref{
  font-family:var(--font-mono); font-size:9.5px; letter-spacing:.12em;
  text-transform:uppercase; color:var(--p-soft);
}
.bsheet-brand-url{ margin-top:1px }
.bsheet-traditional .bsheet-ft,
.bsheet-formal .bsheet-ft{ justify-content:center; text-align:center }
.bsheet-traditional .bsheet-ref,
.bsheet-formal .bsheet-ref{ width:100%; order:2 }

/* --- traditional: serif, centred, the double rule of a wedding card ------ */
.bsheet-traditional{ font-family:Newsreader,'Noto Serif Bengali',serif }
.bsheet-traditional .bsheet-hd{
  flex-direction:column; text-align:center; gap:13px;
  border-bottom:3px double var(--p-rule);
}
.bsheet-traditional .bsheet-name{ font-size:29px; font-weight:500; letter-spacing:0 }
.bsheet-traditional .bsheet-grp > h3{
  font-family:inherit; font-size:13px; letter-spacing:.1em;
  text-align:center; color:var(--p-ink); font-weight:600;
}

/* --- modern: sans, left-aligned, an accent rule instead of a box --------- */
.bsheet-modern .bsheet-hd{ border-bottom:0; padding-bottom:4px }
.bsheet-modern .bsheet-name{ font-size:31px; font-weight:700; letter-spacing:-.02em }
.bsheet-modern .bsheet-photo{ border-radius:10px }
.bsheet-modern .bsheet-grp > h3{
  border-left:3px solid var(--p-accent); padding-left:10px; margin-left:-13px;
}
.bsheet-modern .bsheet-rows dt,
.bsheet-modern .bsheet-rows dd{ border-bottom:1px dotted var(--p-line) }

/* --- formal: a ruled frame, the way an office document is set ------------ */
.bsheet-formal{ font-family:Newsreader,'Noto Serif Bengali',serif; padding:12px; border-radius:4px }
.bsheet-formal .bsheet-frame{ border:1px solid var(--p-rule); padding:26px 28px }
.bsheet-formal .bsheet-hd{ flex-direction:column; text-align:center; gap:12px }
.bsheet-formal .bsheet-name{ font-size:25px; font-weight:500; letter-spacing:.02em }
.bsheet-formal .bsheet-grp > h3{
  background:var(--p-rule); color:var(--p-bg); font-family:var(--font-mono);
  padding:4px 10px; letter-spacing:.14em;
}
.bsheet-formal .bsheet-photo{ border-radius:0 }

/* --- compact: the whole thing on one page, nothing dropped --------------- */
.bsheet-compact{ font-size:12.5px; padding:26px 28px }
.bsheet-compact .bsheet-name{ font-size:21px }
.bsheet-compact .bsheet-photo{ width:80px; height:100px }
.bsheet-compact .bsheet-hd{ padding-bottom:12px; margin-bottom:14px; gap:16px }
.bsheet-compact .bsheet-grp + .bsheet-grp{ margin-top:13px }
.bsheet-compact .bsheet-grp > h3{ margin-bottom:5px }
.bsheet-compact .bsheet-rows{ grid-template-columns:140px 1fr }
.bsheet-compact .bsheet-rows dt{ padding-inline-end:12px }
.bsheet-compact .bsheet-rows dd{ padding-inline-start:12px }
.bsheet-compact .bsheet-rows dt,
.bsheet-compact .bsheet-rows dd{ padding-block:3.5px }

/*
   The preview page: sheet on the left, controls on the right.

   Named areas rather than source order, because the two have to swap on a
   narrow screen. One column put the controls after the sheet, and the sheet
   is a full page long — so on a phone the download button sat below the
   entire biodata, off the bottom of the screen, and looked like it was not
   there at all. The tools come first when there is one column; the
   invitation to make a profile stays after the sheet either way, which is
   where it belongs.
*/
.bio-stage{
  display:grid;
  grid-template-columns:minmax(0,1fr) 290px;
  grid-template-areas:"sheet tools" "sheet after";
  grid-template-rows:auto 1fr;
  gap:22px; align-items:start;
}
.bio-stage > .bsheet{ grid-area:sheet }
.bio-tools{ grid-area:tools; position:sticky; top:78px }
.bio-after{ grid-area:after }

@media (max-width:900px){
  .bio-stage{
    grid-template-columns:1fr;
    grid-template-areas:"tools" "sheet" "after";
    grid-template-rows:auto;
  }
  .bio-tools{ position:static }
}

/* The photograph's own controls, which only mean anything once there is one. */
.bio-photo-off .bio-photo-drop{ display:none }

/* =========================================================================
   PRINT

   The download button has always called window.print(), and there has never
   been a print stylesheet, so what came out of the printer was the web page:
   the navigation, the language menu, the footer, the dark background, and
   the download button itself sitting beside the biodata. This is the file
   people were handing to families.

   Everything but the sheet is removed, the layout's columns are flattened,
   and the page is set to A4.
   ========================================================================= */
@media print{
  /*
   | margin:0, and the paper's margin becomes padding on the sheet instead.
   |
   | This is the only way to be rid of the browser's own header and footer.
   | Chrome and Edge draw them INSIDE the page margin — the document title
   | and the date across the top, the URL and "1/2" across the bottom — and
   | no CSS switches them off. With no margin there is nowhere to draw them
   | and they are dropped.
   |
   | What people were handing to families had "Free biodata maker" and
   | today's date printed over it, and the site's address underneath. None of
   | it is theirs, and none of it was ever meant to be on the page.
   */
  @page{ size:A4; margin:0 }

  html, body{ background:#fff !important; color:#000 }

  /* The chrome. `.noprint` is the hook for anything page-specific. */
  .topnav, .setupbar, .sitefoot, .flash, .note, .btn, .bio-tools, .bio-after, .noprint{
    display:none !important;
  }

  /* The layout is a grid with a rail and a max-width content column; on
     paper it is one full-bleed block. */
  .app, .app-topnav, .main, .view, .bio-stage{
    display:block !important;
    grid-template-columns:none !important;
    max-width:none !important;
    width:auto !important;
    margin:0 !important;
    padding:0 !important;
    gap:0 !important;
    /* Both .app and .main are min-height:100vh, which prints as a forced
       blank second page, and .view animates in on load. */
    min-height:0 !important;
    animation:none !important;
  }

  /* The sheet's printed geometry is defined ONCE, on .bsheet-metrics below,
     and this only switches it on. See the comment there for why. */
  .bsheet{
    border:0; border-radius:0; box-shadow:none;
    margin:0; max-width:none;
    font-size:11pt;
    /* The margin the page no longer has. Same 13mm as before, moved inside
       so the browser has no strip to print its own furniture in. */
    padding:13mm;
  }
  /* The formal layout draws a ruled frame, which has to sit inside the
     paper margin rather than replace it. */
  .bsheet-formal{ padding:13mm }
  .bsheet-formal .bsheet-frame{ padding:18px 20px }

  /* Browsers drop background colours when printing unless told not to, and
     the formal layout's heading bands are backgrounds. */
  .bsheet, .bsheet *{ -webkit-print-color-adjust:exact; print-color-adjust:exact }

  /* A group split across a page break reads as two half-answers. */
  .bsheet-grp, .bsheet-hd{ break-inside:avoid; page-break-inside:avoid }

  /* Print stylesheets conventionally append every href after its link text.
     On a biodata that is noise. */
  a[href]::after{ content:none !important }
}

/* =========================================================================
   THE SAME GEOMETRY, ON SCREEN, FOR ONE MEASUREMENT

   The fit in partials/biodata-tools has to know how tall the sheet will be
   ON PAPER, and it runs in beforeprint — where the document is still in
   SCREEN media. @media print has not been applied to anything yet. So the
   first version measured a 14px sheet with 34px padding and scaled for a
   page that was about to be laid out at 11pt with 13mm, and the formal
   layout went over by exactly that difference.

   This class is every declaration from the print block above that changes
   how tall the sheet is. The script adds it, measures, removes it. The two
   must stay in step, which is why they are next to each other: a change to
   the print block that is not made here is a fit that is quietly wrong
   again, and nothing will fail to tell you.
   ========================================================================= */
.bsheet.bsheet-metrics{
  border:0; border-radius:0; box-shadow:none;
  margin:0; max-width:none;
  font-size:11pt;
  padding:13mm;
  /* The paper itself, so the measurement is of a page rather than of
     whatever width the screen happens to give it. */
  width:210mm;
  box-sizing:border-box;
}
.bsheet-formal.bsheet-metrics{ padding:13mm }
.bsheet-formal.bsheet-metrics .bsheet-frame{ padding:18px 20px }

/* ============ THE SIGN-IN STAGE ============
   A dark field of drifting light, the form on one side of it and what this
   place promises on the other. See resources/views/auth/login.blade.php and
   partials/ribbons.

   It is dark under BOTH palettes, which is the one place on this site that
   is true, and it is deliberate rather than an oversight: luminous ribbons
   need something to be luminous against, and the ground they are drawn on is
   the brand's own darkest claret rather than a neutral black. The page
   around it — header, footer — still turns over with the theme.

   So the stage carries its own small palette. Everything inside it reads
   from these and not from --ink/--body/--surface, which are the colours of a
   page this section is not on. */
.signin{
  --stage-0:#1A040C; --stage-1:#3D0F21; --stage-2:#0E0207;
  --rb-warm:#D9718C; --rb-lit:#FFE3EA; --rb-gold:#F0C088;
  --rb-cool:#2F7A6B; --rb-cool-lit:#8FD8C4;
  --stage-ink:#FFF0F3; --stage-body:#E7C9D3; --stage-muted:#AE8A97;
  --stage-line:rgba(255,240,243,.17);
  /* The colour the panel actually paints. The notch in the field borders
     has to be exactly this and not a guess at it, so it is a token both
     of them read rather than two values that drift apart. */
  --stage-plate:#190711;

  position:relative; isolation:isolate; overflow:hidden;
  /* The header is the only thing above it, so the stage is the rest of the
     screen. svh rather than vh: on a phone vh is the height with the browser
     chrome retracted, which is not the height anything is being read at. */
  min-height:calc(100vh - 80px);
  min-height:calc(100svh - 80px);
  background:
    radial-gradient(100% 70% at 34% 88%, var(--stage-1) 0%, transparent 64%),
    radial-gradient(80% 60% at 82% 22%, #2A0714 0%, transparent 70%),
    linear-gradient(160deg, var(--stage-0) 0%, var(--stage-2) 100%);
  color:var(--stage-body);
}
/* Full bleed. Scoped with :has() rather than a wrapper class, in the same
   way the aurora screens are, so the view needs no markup change. */
.view:has(> .signin){padding:0; max-width:none}

/* ---- the drifting light ---- */
/*
 | mix-blend-mode is on the STROKES, not on this box.
 |
 | It was on the container, which is inset:0 of the sign-in stage — so the
 | compositor had to read back the whole viewport's backdrop and re-blend it
 | every frame, while six blurred strokes and a 54px-blurred wash moved
 | underneath. That is the single most expensive thing on the sign-in screen
 | and it was paying for the appearance of six lines.
 |
 | Blending each stroke instead gives the same look — screen over the wash
 | is what makes them read as light — for the area the strokes actually
 | occupy rather than for the whole page.
 */
.ribbons{position:absolute; inset:0; overflow:hidden; pointer-events:none; z-index:0}
.ribbons-svg{mix-blend-mode:screen}
.ribbons-svg{position:absolute; inset:0; width:100%; height:100%; display:block}

/* The wash the ribbons sit in. Six strokes, however bright, are six strokes;
   the reference has a broad body of warm light under them that they read as
   the bright edges of. One blurred radial, drifting on its own clock. */
.ribbons::after{
  content:""; position:absolute;
  left:6%; right:-12%; bottom:-20%; height:78%;
  background:radial-gradient(58% 100% at 48% 62%,
    color-mix(in srgb, var(--rb-warm) 62%, transparent) 0%,
    color-mix(in srgb, var(--rb-gold) 28%, transparent) 42%,
    transparent 72%);
  filter:blur(54px); opacity:.5;
  animation:rb-wash 34s ease-in-out infinite alternate;
}
/*
 | Translate only. No scale.
 |
 | This drifted AND grew — scale(1) to scale(1.14) — on an element carrying
 | filter:blur(54px). A blurred element that only moves is a finished texture
 | the compositor slides about; one that changes size has to be blurred again,
 | at 54px, every frame it is on screen. The same frame the sticky header is
 | separately re-blurring the whole thing through its own backdrop-filter.
 |
 | The travel is widened to carry the motion the scale was adding. Over
 | thirty-four seconds nobody can tell the difference, and the wash stops
 | being re-rasterised.
 */
@keyframes rb-wash{
  from{transform:translate3d(-7%,5%,0)}
  to  {transform:translate3d(8%,-6%,0)}
}

/* Each ribbon twice from one path: a wide, heavily blurred halo and a thin
   bright core over it. One stroke on its own reads as a line however soft it
   is; the pair reads as light. */
.rb{fill:none; stroke-linecap:round}
.rb-halo{stroke:url(#rb-warm); stroke-width:52; opacity:.55; filter:blur(30px)}
.rb-core{
  stroke:url(#rb-warm); stroke-width:2.6; opacity:1;
  filter:blur(.8px)
         drop-shadow(0 0 7px var(--rb-lit))
         drop-shadow(0 0 22px var(--rb-warm));
}
.rb-cool.rb-halo{stroke:url(#rb-cool); opacity:.30}
.rb-cool.rb-core{
  stroke:url(#rb-cool); opacity:.9;
  filter:blur(.8px)
         drop-shadow(0 0 7px var(--rb-cool-lit))
         drop-shadow(0 0 20px var(--rb-cool));
}

/* Three bands on three clocks, at durations that do not divide into each
   other, so the field never visibly returns to a frame you have seen. Only
   transform animates. */
.rb-band{will-change:transform; transform-origin:50% 70%}
.rb-band-1{animation:rb-a 38s ease-in-out infinite alternate}
.rb-band-2{animation:rb-b 47s ease-in-out infinite alternate}
.rb-band-3{animation:rb-c 59s ease-in-out infinite alternate}
/*
 | Translate only, for the reason given on rb-wash above.
 |
 | Each band holds two blurred strokes — a 30px halo and a core carrying a
 | blur plus two drop-shadows, which are three more blur passes. scaleY made
 | every one of those redraw on every frame. Eighteen blur passes, sixty
 | times a second, under a header blurring the result again.
 |
 | The vertical travel is opened up to replace the stretch. These run for
 | twenty-odd seconds a cycle; what they read as is drift, and drift is what
 | is left.
 */
@keyframes rb-a{
  from{transform:translate3d(-3%,3%,0)}
  to  {transform:translate3d(4%,-5%,0)}
}
@keyframes rb-b{
  from{transform:translate3d(3%,-4%,0)}
  to  {transform:translate3d(-4%,4.5%,0)}
}
@keyframes rb-c{
  from{transform:translate3d(-2%,4%,0)}
  to  {transform:translate3d(5%,-3.5%,0)}
}

.rb-sparks i{
  position:absolute; left:var(--x); top:var(--y);
  width:var(--s); height:var(--s); border-radius:50%;
  background:var(--rb-gold); opacity:0;
  box-shadow:0 0 12px 3px color-mix(in srgb, var(--rb-gold) 55%, transparent);
  animation:rb-spark 7.5s ease-in-out infinite;
  animation-delay:var(--d);
}
@keyframes rb-spark{
  0%,100%{opacity:0; transform:translate3d(0,7px,0)}
  46%    {opacity:.85; transform:translate3d(0,-7px,0)}
}

/* ---- the two halves ---- */
.signin-grid{
  position:relative; z-index:1;
  min-height:inherit;
  display:grid; align-items:center;
  /* The reference puts the form in the left 40% and the promise in the rest.
     A minimum on the first track rather than a percentage alone, so the
     panel never gets squeezed narrower than its own fields. */
  grid-template-columns:minmax(340px, 40%) minmax(0, 1fr);
  gap:clamp(24px, 5vw, 88px);
  max-width:1680px; margin:0 auto;
  padding:clamp(16px, 3vh, 48px) clamp(20px, 5vw, 70px);
}

/* ---- the panel ---- */
.signin-panel{
  width:100%; max-width:430px;
  padding:clamp(18px, 2.4vh, 30px) clamp(20px, 2.4vw, 32px);
  border-radius:18px;
  border:1px solid var(--stage-line);
  /* Mostly opaque, not a 5% tint. Two reasons, and the second is the one
     that matters: a form has to be readable over whatever the ribbons are
     doing behind it, and the notch cut into each field's border is a small
     plate of this colour — over a near-transparent panel that plate showed
     as a dark chip on the border, which is what it was. At 88% the light
     behind the panel still moves through it and the notch is invisible. */
  background:color-mix(in srgb, var(--stage-plate) 82%, transparent);
  -webkit-backdrop-filter:blur(18px) saturate(1.15);
  backdrop-filter:blur(18px) saturate(1.15);
  box-shadow:0 40px 90px -50px rgba(0,0,0,.9);
  animation:signin-panel .7s cubic-bezier(.2,.7,.3,1) backwards;
}
@keyframes signin-panel{
  from{opacity:0; transform:translateY(18px) scale(.985)}
  to  {opacity:1; transform:none}
}

/*
 | The order things arrive in.
 |
 | Every element in the panel carries --i, its place in the sequence, and
 | this one rule turns that into a delay. The blade is then a column of
 | numbers rather than ten animation-delay declarations here, and re-ordering
 | the panel is re-numbering it.
 |
 | `backwards` matters: without it each element is painted in its final
 | position for the length of its own delay and then jumps back to the start
 | of its animation, which is the whole panel flashing into place and then
 | assembling itself.
 |
 | The step is 100ms, not the reference's half-second. The reference is a
 | showpiece with nobody waiting to type; here the last control has to be
 | usable, and a password field that arrives four seconds after the page is a
 | form that looks broken to anyone who is just trying to log in.
 */
.signin-panel > .signin-brand,
.signin-panel > .signin-h,
.signin-panel > .signin-sub,
.signin-panel > .signin-or,
.signin-panel > .signin-alt,
.signin-panel > .signin-code,
.signin-panel > .signin-foot,
.signin-form > .signin-field,
.signin-form > .signin-err,
.signin-form > .signin-go{
  animation:signin-in .56s cubic-bezier(.2,.7,.3,1) backwards;
  animation-delay:calc(.2s + var(--i, 0) * .1s);
}
@keyframes signin-in{
  from{opacity:0; transform:translateY(14px)}
  to  {opacity:1; transform:none}
}

.signin-brand{display:flex; align-items:center; gap:11px; margin-bottom:clamp(12px, 2vh, 22px)}
.signin-brand .mark-glyph{width:clamp(28px, 3.6vh, 34px); height:clamp(28px, 3.6vh, 34px)}
.signin-brand b{
  display:block; font-family:var(--font-head); font-weight:var(--w-head,500);
  font-size:19px; line-height:1.1; color:var(--stage-ink); letter-spacing:-.01em;
}
.signin-brand em{
  display:block; margin-top:2px; font-style:normal;
  font-family:var(--font-mono); font-size:9.5px; letter-spacing:.22em;
  text-transform:uppercase; color:var(--rb-warm);
}

.signin-h{
  margin:0; font-family:var(--font-head); font-weight:var(--w-head,500);
  font-size:clamp(1.4rem, 2.2vh + .5vw, 2.05rem); line-height:1.14;
  color:var(--stage-ink); letter-spacing:-.015em;
}
.signin-sub{
  margin:clamp(6px, 1.2vh, 10px) 0 clamp(12px, 2.4vh, 26px);
  font-size:clamp(12.5px, 1.7vh, 13.5px); line-height:1.55;
  color:var(--stage-muted); max-width:34ch;
}

.signin-form{display:block}

/*
 | The field, with its label on the box's own top edge.
 |
 | That is the reference's shape, and it is also the one that survives a
 | narrow column: a label above a box can end up a line away from it, a label
 | cut into its border cannot. The notch is the label's own background, set
 | to the stage colour underneath — there is no way to cut a hole in a
 | border, so it is covered instead.
 */
.signin-field{position:relative; margin-top:clamp(13px, 2.1vh, 20px)}
.signin-field > label{
  position:absolute; left:16px; top:0; z-index:1;
  transform:translateY(-50%);
  padding:0 6px;
  /* Opaque, and the panel's own colour: it is covering a border, and a
     translucent chip covers nothing. Same token as the panel above, so the
     two cannot drift. */
  background:var(--stage-plate);
  font-family:var(--font-mono); font-size:9.5px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--stage-muted);
  pointer-events:none;
}
.signin-inp,
.signin-panel .inp{
  width:100%; height:clamp(42px, 5.4vh, 48px); padding:0 18px;
  border-radius:999px;
  border:1px solid var(--stage-line);
  background:rgba(255,240,243,.035);
  color:var(--stage-ink); font-size:14px;
  transition:border-color .18s ease, background .18s ease, box-shadow .18s ease;
}
.signin-inp::placeholder,
.signin-panel .inp::placeholder{color:color-mix(in srgb, var(--stage-muted) 70%, transparent)}
.signin-inp:hover,
.signin-panel .inp:hover{border-color:color-mix(in srgb, var(--rb-warm) 45%, transparent)}
.signin-inp:focus,
.signin-panel .inp:focus{
  outline:none;
  border-color:var(--rb-warm);
  background:rgba(255,240,243,.07);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--rb-warm) 22%, transparent);
}
/* The reveal toggle is added by the layout's script, which wraps the input
   in .pw-field. Its own rule colours it for a light card. */
.signin-field .pw-field{display:block}
.signin-field .pw-toggle{color:var(--stage-muted); background:none; border:0}
.signin-field .pw-toggle:hover{color:var(--stage-ink); background:rgba(255,240,243,.08)}

.signin-err{display:block; margin:7px 0 0 18px; font-size:12px; color:#F09A9D}

.signin-go{
  display:block; width:100%; margin-top:clamp(14px, 2.3vh, 22px);
  height:clamp(44px, 5.6vh, 50px); padding:0 20px; border:0; border-radius:999px;
  background:var(--grad-brand); background:linear-gradient(100deg, #A83F5D 0%, #D9718C 55%, #C34F70 100%);
  color:#FFF0F3; font-family:var(--font-head); font-size:15.5px; font-weight:var(--w-head,500);
  cursor:pointer;
  box-shadow:0 14px 34px -16px rgba(195,79,112,.85);
  transition:transform .16s ease, box-shadow .16s ease, filter .16s ease;
}
.signin-go:hover{transform:translateY(-1px); filter:brightness(1.07); box-shadow:0 18px 40px -16px rgba(195,79,112,.95)}
.signin-go:active{transform:none}
.signin-go:focus-visible{outline:2px solid var(--stage-ink); outline-offset:3px}

/* A rule with a word sitting in it. The line is two flex children rather
   than a border on the text, so it re-centres itself on any word length —
   "or" is four letters in some of the locales this ships in. */
.signin-or{
  display:flex; align-items:center; gap:14px;
  margin:clamp(13px, 2.2vh, 22px) 0 clamp(11px, 1.8vh, 18px);
  font-family:var(--font-mono); font-size:10px; letter-spacing:.2em;
  text-transform:uppercase; color:var(--stage-muted);
}
.signin-or::before,
.signin-or::after{content:""; flex:1; height:1px; background:var(--stage-line)}

.signin-alt{
  display:flex; width:100%; align-items:center; justify-content:center; gap:10px;
  height:clamp(40px, 5vh, 46px); padding:0 18px;
  border:1px solid var(--stage-line); border-radius:999px;
  background:rgba(255,240,243,.035);
  color:var(--stage-ink); font-size:13.5px; text-decoration:none;
  cursor:pointer;
  transition:border-color .18s ease, background .18s ease;
}
.signin-alt:hover{border-color:color-mix(in srgb, var(--rb-warm) 55%, transparent); background:rgba(255,240,243,.07)}
.signin-alt:focus-visible{outline:2px solid var(--rb-warm); outline-offset:2px}

/* Code login. Closed it is one line of text, open it is the form it always
   was — two identifier boxes standing open on one screen is a question about
   which one to fill in. */
.signin-code{margin-top:clamp(7px, 1.3vh, 14px)}
.signin-code > summary{
  list-style:none; cursor:pointer;
  padding:clamp(3px, .7vh, 6px) 2px; font-size:13px; color:var(--stage-muted);
  text-align:center; text-decoration:underline; text-underline-offset:3px;
  transition:color .16s ease;
}
.signin-code > summary::-webkit-details-marker{display:none}
.signin-code > summary:hover{color:var(--stage-ink)}
.signin-code > summary:focus-visible{outline:2px solid var(--rb-warm); outline-offset:2px; border-radius:6px}
.signin-code[open] > summary{color:var(--stage-ink)}
.signin-code .signin-alt{margin-top:16px}
/* It arrives with the panel; opening it later must not replay that. */
.signin-code .signin-field{animation:none}
.signin-hint{margin:10px 0 0; font-size:11.5px; line-height:1.55; color:var(--stage-muted); text-align:center}

.signin-foot{
  display:flex; flex-wrap:wrap; gap:8px 16px;
  align-items:center; justify-content:space-between;
  margin-top:clamp(13px, 2.2vh, 26px); padding-top:clamp(10px, 1.7vh, 18px);
  border-top:1px solid var(--stage-line);
  font-size:12.5px; color:var(--stage-muted);
}
.signin-foot a{color:var(--stage-ink); text-decoration:none; font-weight:600}
.signin-foot a:hover{color:var(--rb-warm); text-decoration:underline}
.signin-foot > a{font-weight:400; text-decoration:underline; text-underline-offset:3px; color:var(--stage-muted)}

/* ---- what we promise, one at a time ---- */
.signin-say{position:relative; min-height:270px; padding-inline:clamp(0px, 2vw, 30px)}
.signin-say-item{
  position:absolute; inset:0 0 auto 0;
  opacity:0;
  animation:signin-say 26s cubic-bezier(.2,.7,.3,1) infinite;
  /* 26s / four items. Each is on screen for about five of its six and a
     half, and the delay is what puts them in a queue rather than on top of
     each other. */
  animation-delay:calc(var(--n) * 6.5s + 1.5s);
}
.signin-say-item h2{
  margin:0; font-family:var(--font-head); font-weight:400;
  font-size:clamp(2rem, 4vw, 3.4rem); line-height:1.08;
  letter-spacing:-.02em; color:var(--stage-ink);
  text-shadow:0 2px 24px rgba(0,0,0,.55);
}
.signin-say-item p{
  margin:18px 0 0; max-width:30ch;
  font-size:clamp(14px, 1.2vw, 17px); line-height:1.6;
  color:var(--stage-body);
  text-shadow:0 1px 14px rgba(0,0,0,.6);
}
@keyframes signin-say{
  0%  {opacity:0; transform:translateY(22px)}
  3%  {opacity:1; transform:none}
  22% {opacity:1; transform:none}
  25% {opacity:0; transform:translateY(-16px)}
  100%{opacity:0; transform:translateY(22px)}
}

.signin-dots{position:absolute; left:clamp(0px, 2vw, 30px); bottom:0; display:flex; gap:7px}
.signin-dots i{
  width:6px; height:6px; border-radius:999px;
  background:var(--stage-muted); opacity:.45;
  animation:signin-dot 26s linear infinite;
  animation-delay:calc(var(--n) * 6.5s + 1.5s);
}
@keyframes signin-dot{
  0%  {width:6px;  opacity:.45; background:var(--stage-muted)}
  3%  {width:22px; opacity:1;   background:var(--rb-warm)}
  22% {width:22px; opacity:1;   background:var(--rb-warm)}
  25% {width:6px;  opacity:.45; background:var(--stage-muted)}
  100%{width:6px;  opacity:.45; background:var(--stage-muted)}
}

/* ---- narrower ---- */
@media (max-width:1080px){
  .signin-grid{grid-template-columns:minmax(320px, 46%) minmax(0, 1fr); gap:32px}
  .signin-say{min-height:230px}
}
/* One column. The promise goes above the form rather than away: it is the
   reason to sign in, and a phone is where most people will. It loses the
   rotation's tall headline size and keeps the rotation. */
@media (max-width:820px){
  .signin{min-height:0}
  .signin-grid{
    grid-template-columns:1fr; gap:26px;
    justify-items:center; padding:26px 18px 44px;
  }
  .signin-panel{max-width:460px; order:2}
  .signin-say{order:1; width:100%; max-width:460px; min-height:150px; text-align:center; padding:0}
  .signin-say-item h2{font-size:clamp(1.5rem, 6vw, 2rem)}
  .signin-say-item p{margin-top:10px; max-width:none; font-size:13.5px}
  .signin-dots{left:50%; transform:translateX(-50%)}
}
@media (max-width:420px){
  .signin-panel{padding:22px 18px}
  .signin-foot{justify-content:center; text-align:center}
}

/* Nothing on this stage is information — the light is decoration and the
   sequence is a flourish on a form that reads perfectly well standing
   still. So all of it stops, and the rotation resolves to the first message
   rather than to an empty column. */
@media (prefers-reduced-motion: reduce){
  .rb-band,
  .rb-sparks i,
  .signin-panel,
  .signin-panel > *,
  .signin-form > *,
  .signin-dots i{animation:none}
  .signin-say-item{animation:none; opacity:0}
  .signin-say-item[style*="--n:0"]{opacity:1}
  .signin-dots i[style*="--n:0"]{width:22px; opacity:1; background:var(--rb-warm)}
}

/* ---- the same stage, carrying a long form ----
   Registration is the sign-in screen's other half and it gets the same
   stage, but it cannot get the same panel: eight fields do not fit in one
   screen the way two do. So the stage stops trying to be exactly one screen
   tall, the panel widens enough for a country select and a phone number to
   share a line, and the promise beside it sticks to the top of the window
   rather than sitting halfway down a column the reader has scrolled past. */
.signin--tall{min-height:0}
.signin--tall .signin-grid{align-items:start}
/* Wide enough for two fields on a line. Step one has seven of them, and
   in one column that is a screen and a half however tightly it is set —
   the panel has to get wider before the form can get shorter. */
.signin--tall .signin-panel{max-width:640px}
/* The promise keeps the rest. It is a headline and two lines; it does not
   need half the window, and the form does. */
.signin--tall .signin-grid{grid-template-columns:minmax(360px, 48%) minmax(0, 1fr)}
/* A field that will not share its line: the mobile row carries a country
   select and a number, and the consent note is a paragraph. */
.signin-span2{grid-column:1 / -1}
.signin--tall .signin-say{position:sticky; top:104px; align-self:start}
/* Every child, rather than the login screen's named list: this form has a
   step label, a consent note and a terms row that screen does not, and a
   panel where half the parts arrive and half are simply there reads as a
   fault. The form element itself is excluded — it animates its own children,
   and a block that fades in while its contents fade in is two animations
   over the same pixels.

   Sixteen elements at the login screen's tenth of a second each is the last
   one arriving nearly two seconds in. The sequence is the same; the step is
   what has to give on a form this long. */
.signin--tall .signin-panel > *:not(.signin-form),
.signin--tall .signin-form > *{
  animation:signin-in .56s cubic-bezier(.2,.7,.3,1) backwards;
  animation-delay:calc(.18s + var(--i, 0) * .055s);
}

.signin-lbl{
  display:block; margin-bottom:9px;
  font-family:var(--font-mono); font-size:10px; letter-spacing:.2em;
  text-transform:uppercase; color:var(--rb-warm);
}

/* Selects, as pills. appearance:none takes the platform control off and the
   chevron has to come back as a background image — inline SVG rather than a
   file, because it is eleven pixels of artwork and a request is a request.
   The dark ground is on the option list too: without it a dark select opens
   a white menu on Windows. */
select.signin-inp,
.signin-panel select.inp{
  appearance:none; -webkit-appearance:none;
  padding-right:38px; cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23AE8A97' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right 16px center;
  background-size:11px 8px;
}
select.signin-inp option,
.signin-panel select.inp option{background:var(--stage-plate); color:var(--stage-ink)}

/* A country select and a phone number on one line. Both flexible, neither
   fixed, and min-width:0 on each — without it the select refuses to go below
   its longest country name and squeezes the number into a slot too narrow to
   read what you typed. */
.signin-row{display:flex; gap:8px}
.signin-row > .signin-inp:first-child{flex:0 1 210px; min-width:0}
.signin-row > .signin-inp:last-child{flex:1 1 130px; min-width:0}

/* The address Google already confirmed, shown rather than asked for: a box
   invites a correction that would break the very thing it confirmed. */
.signin-static{
  display:flex; align-items:center;
  height:clamp(42px, 5.4vh, 48px); margin:0; padding:0 18px;
  border:1px dashed var(--stage-line); border-radius:999px;
  background:rgba(255,240,243,.03); color:var(--stage-body); font-size:14px;
}

.signin-note{
  margin-top:12px; padding:12px 14px;
  border:1px solid color-mix(in srgb, var(--rb-warm) 40%, transparent);
  border-radius:12px;
  background:color-mix(in srgb, var(--rb-warm) 12%, transparent);
  font-size:12.5px; line-height:1.55; color:var(--stage-body);
}
.signin-note b{display:block; margin-bottom:3px; color:var(--stage-ink); font-weight:600}

/* The two documents are linked, not just named, so the row is text with
   links in it and the box has to sit on the first line of that text rather
   than in the middle of the block. */
.signin-check{
  display:flex; align-items:flex-start; gap:10px;
  margin-top:clamp(14px, 2.3vh, 22px);
  font-size:12.5px; line-height:1.55; color:var(--stage-body);
}
.signin-check input{
  flex:none; width:16px; height:16px; margin-top:1px;
  accent-color:var(--rb-warm); cursor:pointer;
}
.signin-check a{color:var(--stage-ink); text-decoration:underline; text-underline-offset:2px}
.signin-check a:hover{color:var(--rb-warm)}

/* The file input's own button is a platform control and cannot be styled
   past its edges, so the pill is the wrapper and the button inside it gets
   the stage's colours. */
.signin-panel input[type="file"],
.signin-file{
  width:100%; height:auto; padding:11px 16px;
  border:1px dashed var(--stage-line); border-radius:14px;
  background:rgba(255,240,243,.03); color:var(--stage-body); font-size:13px;
}
.signin-panel input[type="file"]::file-selector-button,
.signin-file::file-selector-button{
  margin-right:12px; padding:7px 14px;
  border:1px solid var(--stage-line); border-radius:999px;
  background:rgba(255,240,243,.06); color:var(--stage-ink);
  font:inherit; cursor:pointer;
}
.signin-panel input[type="file"]::file-selector-button:hover,
.signin-file::file-selector-button:hover{border-color:var(--rb-warm)}

/* Step two is a two-up form at 660px and does not belong in a 520px column
   beside anything. Same stage, one wide panel, no promise column: by then
   the member is half way through and is being asked to finish, not to be
   convinced. */
.signin--wide .signin-grid{grid-template-columns:minmax(0, 1fr); justify-items:center}
.signin--wide .signin-panel{max-width:680px}
.signin-two{display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:0 16px}
@media (max-width:560px){
  .signin-two{grid-template-columns:1fr}
  .signin-row{flex-wrap:wrap}
  .signin-row > .signin-inp:first-child{flex:1 1 100%}
}

/* ============ THE HERO'S ROTATING LINE ============
   Four claims under the headline, one at a time, each rising out of the line
   below and leaving through the one above.

   Bottom to top rather than the side-to-side the reference site uses, on
   request — and it is the better axis here anyway: the headline sits
   directly above and a claim crossing the screen horizontally would be a
   second thing travelling through the same eyeful in a different direction.

   The box is one line tall with overflow hidden, so the words are genuinely
   revealed by a slot rather than fading in place. --shine-ink and its three
   companions are the headline's own tokens, reached by adding this class to
   the three rules that declare them: one ramp for both, and light and dark
   both follow without a second set of values. */
.heroline{
  position:relative; overflow:hidden;
  height:1.7em;
  margin:clamp(10px, 1.8vh, 20px) 0 0;
  text-align:center;
  font-family:var(--font-head);
  font-weight:var(--w-head, 500);
  font-size:clamp(1.02rem, 2.2vw, 1.6rem);
  line-height:1.7;
  letter-spacing:-.01em;
}
.heroline-item{
  position:absolute; inset:0;
  display:block;
  opacity:0;
  /* Four claims, five and a half seconds each. Long enough to read a short
     line twice, which is what somebody who looks up mid-sentence needs. */
  animation:heroline 22s cubic-bezier(.2,.7,.3,1) infinite;
  animation-delay:calc(var(--n) * 5.5s);

  background-image:linear-gradient(100deg,
    var(--shine-ink) 0%, var(--shine-deep) 28%, var(--shine-mid) 44%,
    var(--shine-lit) 52%, var(--shine-mid) 60%, var(--shine-deep) 76%,
    var(--shine-ink) 100%);
  -webkit-background-clip:text; background-clip:text;
  color:transparent; -webkit-text-fill-color:transparent;
  /* The scrim under the hero is a photograph, and the ramp above is a
     colour rather than a white — so the line needs the same separation from
     the picture that the headline gets. */
  filter:drop-shadow(0 1px 9px rgba(0,0,0,.5));
}
@keyframes heroline{
  0%  {opacity:0; transform:translateY(108%)}
  4%  {opacity:1; transform:none}
  21% {opacity:1; transform:none}
  25% {opacity:0; transform:translateY(-108%)}
  100%{opacity:0; transform:translateY(108%)}
}
/* Two lines of Bengali or Urdu at this size need the room; the slot grows
   rather than clipping the second one. */
@media (max-width:620px){
  .heroline{height:auto; min-height:2.6em}
  .heroline-item{position:relative; inset:auto}
  .heroline-item:not(:first-child){margin-top:-2.6em}
}

/* It is decoration on four claims that read perfectly well standing still,
   so it stops, and it resolves to the first of them rather than to an empty
   line. */
@media (prefers-reduced-motion: reduce){
  .heroline-item{animation:none; opacity:0}
  .heroline-item[style*="--n:0"]{opacity:1}
}

/* ---- the payment marks in the footer ----
   Sized by height, not width: these are other people's wordmarks at other
   people's aspect ratios, and a fixed width would squash a long one and
   stretch a square one. The plate behind them is the same chip the names
   sit in, so a strip that is half artwork and half type still reads as one
   row. */
.sitefoot-logo{
  /* The same plate as the named marks beside it, so a strip that is part
     artwork and part type still reads as one row. Sized by height with a
     minimum width, not by width: these are other people's marks at other
     people's aspect ratios, and a fixed width would squash a long one and
     stretch a square one. */
  display:inline-flex; box-sizing:border-box;
  height:30px; min-width:62px; width:auto;
  padding:6px 12px; border-radius:6px;
  background:#FFFFFF; border:1px solid rgba(16,20,26,.12);
  box-shadow:0 1px 2px rgba(16,20,26,.06);
  object-fit:contain;
}
@media (max-width:900px){
  .sitefoot-logo{height:26px; min-width:54px; padding:5px 10px}
}

/* ---- step one, tightened until it arrives whole ----
   Pairing the fields got this form most of the way onto one screen; these
   are the last forty pixels. They are scoped to --tall rather than edited
   into the values above, because the sign-in panel already fits and tighter
   spacing there would only cost it air it can afford. */
.signin--tall .signin-grid{padding-block:clamp(12px, 2.2vh, 36px)}
.signin--tall .signin-brand{margin-bottom:clamp(10px, 1.7vh, 18px)}
.signin--tall .signin-sub{margin-bottom:clamp(10px, 1.8vh, 20px)}
.signin--tall .signin-panel > .signin-hint{margin-top:7px}
.signin--tall .signin-or{margin:clamp(10px, 1.7vh, 18px) 0 clamp(8px, 1.3vh, 14px)}
.signin--tall .signin-check{margin-top:clamp(10px, 1.7vh, 18px)}
.signin--tall .signin-go{margin-top:clamp(11px, 1.9vh, 20px)}
.signin--tall .signin-foot{
  margin-top:clamp(10px, 1.7vh, 18px);
  padding-top:clamp(8px, 1.3vh, 14px);
}
/* Two lines of hint under the Google button at 640px, not three. */
.signin--tall .signin-panel > .signin-hint{font-size:11px; line-height:1.5}

/* Wider still. Every line this panel does not have to wrap is a line of
   height it does not spend, and at 1280 the subtitle and the Google note
   were each costing a line to a column that was narrower than it needed to
   be. The promise beside it is a headline and two lines; it does not need
   half the window. */
.signin--tall .signin-panel{max-width:690px}
.signin--tall .signin-grid{grid-template-columns:minmax(420px, 55%) minmax(0, 1fr)}

/* A laptop screen, where the whole form still has to arrive at once.
   Nothing is removed — the type comes down a step and the fields lose the
   air they can spare, which is what a 720px window has to trade to show
   thirteen controls and a consent line without scrolling. */
@media (max-height:820px){
  .signin--tall .signin-brand{margin-bottom:10px}
  .signin--tall .signin-brand .mark-glyph{width:28px; height:28px}
  .signin--tall .signin-h{font-size:clamp(1.25rem, 2.6vh, 1.6rem)}
  .signin--tall .signin-sub{margin:5px 0 10px; font-size:12px}
  .signin--tall .signin-panel > .signin-hint{margin-top:6px; font-size:10.5px; line-height:1.45}
  .signin--tall .signin-or{margin:10px 0 8px}
  .signin--tall .signin-field{margin-top:11px}
  /* Not the photograph. That control is a dashed box with the browser's
     own button inside it, and a 40px cap squashes both back into a plain
     file field — this rule outranks the one that styles it. */
  .signin--tall .signin-inp:not([type="file"]),
  .signin--tall .signin-panel .inp:not([type="file"]){height:40px}
  .signin--tall .signin-alt{height:40px}
  .signin--tall .signin-go{height:42px; margin-top:12px}
  .signin--tall .signin-check{margin-top:11px; font-size:12px}
  .signin--tall .signin-foot{margin-top:11px; padding-top:9px}
}

/* ---- two corrections to the widening above ----

   1. The stacking rule at 820px is `.signin-grid`, one class; the widening
      is `.signin--tall .signin-grid`, two. So the widening went on winning
      below the breakpoint and a phone got a grid with a 420px minimum in a
      400px window — the panel collapsed to a strip. It has to be restated
      inside the media query, at the same weight.

   2. Step two carries BOTH --tall and --wide, and the two rules weigh the
      same, so the later one won: the one-panel screen was being given the
      split. Written as a compound selector here rather than moved, because
      "wide beats tall" is a fact about the pair and belongs where you can
      see both names. */
@media (max-width:820px){
  .signin--tall .signin-grid{grid-template-columns:minmax(0, 1fr)}
  .signin--tall .signin-panel{max-width:520px}
}
.signin--wide.signin--tall .signin-grid{grid-template-columns:minmax(0, 1fr)}
.signin--wide.signin--tall .signin-panel{max-width:720px}

/* ============ THE PAYMENT STRIP ============
   The marks a member can actually pay with, as marks rather than as a row
   of grey chips.

   What is drawn here is each brand's NAME in each brand's COLOUR on a white
   plate — not a redrawing of its logo. That distinction is the whole rule of
   this block. Naming a payment method you accept is ordinary and expected;
   hand-copying a trademark, badly, into a stylesheet is neither, and every
   one of these companies publishes exact artwork with exact rules about how
   it may be set. So: no approximations. When the real file is dropped into
   public/img/pay the footer swaps to it on its own (see the partial) and
   these plates stop being drawn at all.

   White under both palettes on purpose. These colours are all specified
   against white, and a payment strip is one of the few places on a dark page
   that is SUPPOSED to look like a row of cards. */
.sitefoot-rail{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:62px; height:30px; padding:0 11px;
  border-radius:6px;
  background:#FFFFFF;
  border:1px solid rgba(16,20,26,.12);
  box-shadow:0 1px 2px rgba(16,20,26,.06);
  /* A sans, deliberately: the site is set in a serif throughout, and a
     payment mark set in the body face reads as a word in a sentence rather
     than as a mark. This is the one strip that should look borrowed. */
  font-family:"IBM Plex Sans","Hind Siliguri",system-ui,sans-serif;
  font-size:11.5px; font-weight:700; letter-spacing:.005em; line-height:1;
  color:#1B2430; white-space:nowrap;
}

/* Each brand's own colour, from its published guidelines. */
.sitefoot-rail[data-pay="visa"]{color:#1434CB; font-style:italic; letter-spacing:.02em}
.sitefoot-rail[data-pay="mastercard"]{color:#EB001B}
.sitefoot-rail[data-pay="amex"]{color:#006FCF; letter-spacing:.06em; font-size:9.5px; text-transform:uppercase}
.sitefoot-rail[data-pay="stripe"]{color:#635BFF}
.sitefoot-rail[data-pay="paypal"]{color:#003087}
.sitefoot-rail[data-pay="bkash"]{color:#E2136E}
.sitefoot-rail[data-pay="nagad"]{color:#EE1C25}
.sitefoot-rail[data-pay="rocket"]{color:#8C3494}
.sitefoot-rail[data-pay="applepay"]{color:#000000}

/* The lock and its label sit beside the plates, so they take the footer's
   own muted colour rather than competing with them. */
.sitefoot-pay-h{align-self:center}

@media (max-width:900px){
  .sitefoot-rail{height:26px; min-width:54px; padding:0 9px; font-size:10.5px}
  .sitefoot-rail[data-pay="amex"]{font-size:8.5px}
}

/* A field the country-scope script has switched off. Semantic, and it has
   to beat the display on .field and .signin-field, which are flex. */
[hidden]{display:none !important}

/* ============ HEARTS (the live-network section) ============
   Outlined hearts and bokeh drifting up behind the map. See
   partials/hearts for what this is and why it is not the clip.

   Every mark animates transform and opacity only — both composited — so a
   field of twenty-nine costs the compositor a few layers and the main
   thread nothing.

   --heart-ink is the one number that decides whether this is atmosphere or
   interference. It is deliberately low: the section carries a headline,
   three lines of prose and a map, and all of them have to be read straight
   through it. */
.hearts{
  --heart-ink:var(--action);
  --heart-op:.22;
  --dust-op:.28;

  position:absolute; inset:0; z-index:0;
  overflow:hidden; pointer-events:none;
}
/* Dark keeps the same marks a little brighter: the ground under them goes
   from a pale rose to near-black, and .22 that reads as a whisper on light
   is invisible on that. */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .hearts{--heart-op:.30; --dust-op:.34}
}
:root[data-theme="dark"] .hearts{--heart-op:.30; --dust-op:.34}

/*
 | The same marks behind every page.
 |
 | Fixed at z-index -1, which puts them over the world map's backdrop (-2)
 | and under everything in the document flow. Anything with a ground of its
 | own — the live-network section, the sign-in stage, every card — paints
 | over them, which is the right outcome: this is air between the content,
 | not a texture on it.
 |
 | Much fainter than the section layer, and it has to be. That one sits
 | behind a headline and three lines; this one sits behind every screen in
 | the product, including a form somebody is typing into and a thread
 | somebody is reading.
 */
.hearts-bg{position:fixed; z-index:-1; --heart-op:.10; --dust-op:.12}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .hearts-bg{--heart-op:.16; --dust-op:.18}
}
:root[data-theme="dark"] .hearts-bg{--heart-op:.16; --dust-op:.18}

/* The section has to hold them, and its own contents have to sit over
   them. .worldmap already clips its overflow; this gives it something to
   position against. */
.worldmap{position:relative; isolation:isolate}
.worldmap > .worldmap-head,
.worldmap > .worldmap-stage{position:relative; z-index:1}

.hearts-field i,
.hearts-dust i{
  position:absolute;
  left:var(--x);
  bottom:-12%;
  width:var(--s); height:var(--s);
  background:var(--heart-ink);
  opacity:0;
  /*
   | No will-change here, deliberately.
   |
   | It reads as the right hint and is the opposite of one at this count. An
   | element running a transform/opacity animation is promoted to its own
   | compositor layer by the browser anyway, for exactly as long as the
   | animation runs; will-change asks for that layer to be created up front
   | and HELD. With twenty-nine of these fixed behind every page — and
   | fifty-eight on the front page, where the world map brings its own set —
   | that is fifty-eight retained layers and their backing stores, for the
   | life of the tab, for decoration.
   |
   | The MDN warning about not leaving will-change on indefinitely is about
   | precisely this case. Removing it changes nothing about how the
   | animation runs and gives the memory back.
   */
  animation:heart-rise var(--t, 24s) linear infinite;
  animation-delay:var(--d, 0s);
}

/* The heart, once. An outline rather than a solid: the reference's marks
   are drawn, not filled, and a field of solid hearts at this size reads as
   confetti. The mask carries the shape and the background carries the
   colour, so the palette still owns it. */
.hearts-field i{
  --heart-shape:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 20.6s-7.3-4.6-9.4-9C.7 7.9 2.6 4.1 6.3 4.1c2 0 3.4 1 5.7 3.3 2.3-2.3 3.7-3.3 5.7-3.3 3.7 0 5.6 3.8 3.7 7.5-2.1 4.4-9.4 9-9.4 9Z' fill='none' stroke='%23000' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E");
  -webkit-mask-image:var(--heart-shape);
  mask-image:var(--heart-shape);
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-size:contain;     mask-size:contain;
  --peak:var(--heart-op);
}

.hearts-dust i{
  border-radius:50%;
  --peak:var(--dust-op);
}

/*
 | Up and slightly across, appearing and gone before either edge.
 |
 | The travel is 128vh rather than 100: a mark that starts exactly at the
 | bottom edge and ends exactly at the top is visible at both, and the fade
 | has to do all the work of hiding that. Overshooting at both ends means
 | the fade is decoration rather than concealment, and a slow tab that drops
 | a frame does not show a heart snapping out of existence.
 |
 | The sway is a translateX on the same timeline rather than a second
 | animation, so the two cannot drift out of phase.
 */
@keyframes heart-rise{
  0%   {opacity:0;           transform:translate3d(0, 0, 0) scale(.9)}
  12%  {opacity:var(--peak); transform:translate3d(6px, -15vh, 0) scale(1)}
  50%  {opacity:var(--peak); transform:translate3d(-8px, -64vh, 0) scale(1.04)}
  88%  {opacity:var(--peak); transform:translate3d(5px, -112vh, 0) scale(1)}
  100% {opacity:0;           transform:translate3d(0, -128vh, 0) scale(.94)}
}

/* Below the fold of a phone this section is mostly the map, and a field of
   twenty-nine animated marks behind a screen that small is a lot of
   compositing for atmosphere nobody is looking at. Half of them go. */
@media (max-width:700px){
  .hearts-field i:nth-child(2n),
  .hearts-dust i:nth-child(2n){display:none}
  .hearts{--heart-op:.18; --dust-op:.22}
}

/* Decoration on a section that reads perfectly well standing still.

   Frozen rather than removed, and frozen SCATTERED: a negative delay with
   the animation paused renders each mark at the point its own start would
   have reached, so they stand still up the height of the section instead of
   stacked in a row at the bottom edge where every one of them begins. The
   extra three seconds keeps the marks whose delay is 0 off the frame where
   opacity is still 0. */
@media (prefers-reduced-motion: reduce){
  .hearts-field i,
  .hearts-dust i{
    animation-delay:calc(-1 * (var(--d) + 3s));
    animation-play-state:paused;
  }
  .hearts{--heart-op:.14; --dust-op:.16}
}

/* ---- the typed box, first on the filter bar ----
   The three menus can only offer what we thought to list; this takes
   anything. It is drawn as one unit with the magnifier inside rather than as
   another .inp, so it reads as the thing you type into rather than as a
   fourth filter that happens to be a text field. */
.sfilter-q{
  display:flex; align-items:center; gap:7px;
  padding:0 11px; border-radius:9px;
  border:1px solid var(--line-strong);
  background:var(--surface);
  color:var(--muted);
  transition:border-color .15s, box-shadow .15s;
}
.sfilter-q:focus-within{
  border-color:var(--brand);
  box-shadow:0 0 0 3px var(--brand-tint);
}
.sfilter-q svg{flex:none}
.sfilter-q input{
  width:190px; padding:8px 0;
  border:0; background:transparent; outline:none;
  font-family:var(--font-body); font-size:13px; color:var(--ink);
}
.sfilter-q input::placeholder{color:var(--muted)}

/* The substitution notice, said before the cards it explains. */
.searchwide{margin-bottom:16px}
.searchwide a.chip:hover{background:var(--brand); color:var(--on-brand)}

@media (max-width:900px){
  /* On the bar's phone layout the chips already go full width; the box
     should too, rather than sitting at a fixed 190px beside them. */
  .sfilter-q{width:100%}
  .sfilter-q input{width:100%}
}

/* ============ SEARCH: THE BAR SAYS WHAT IT IS DOING ============
   A shut chip used to read "Who they are" beside a count. The count told you
   something was on; it never told you what, so the one thing a filter is for
   took a click to see. The chip carries its own answer now, the options
   inside are pills rather than a stack of identical selects, and the match
   count — the number the whole page turns on — is set in the same serif as a
   name instead of twelve-point grey text at the far end of the bar. */

/* ---- the value on a shut chip ---- */
.sfilter-val{
  font-weight:500; color:var(--brand-deep);
  max-width:15ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* Once a chip carries its value, its own name is the quieter half. */
.sfilter .sfilter-grp > summary:has(.sfilter-val) > span:first-child{color:var(--muted)}
.sfilter .sfilter-grp > summary:has(.sfilter-val){
  border-color:color-mix(in srgb, var(--brand) 45%, transparent);
  background:var(--surface);
}

/* ---- a labelled row of choices inside a panel ---- */
.sfilter-set{display:flex; flex-direction:column; gap:7px}
.sfilter-pills{display:flex; flex-wrap:wrap; gap:6px}

/* Sharp, like every other chip in the system — the soft radii belong to the
   floating surfaces, not to the controls sitting on them. */
.sfilter-pill{
  padding:5px 11px; border-radius:2px;
  border:1px solid var(--line); background:var(--surface);
  font-size:12.5px; color:var(--body); text-decoration:none;
  transition:border-color .15s, background .15s, color .15s;
}
.sfilter-pill:hover{border-color:var(--line-strong)}
.sfilter-pill.is-on{background:var(--brand); border-color:var(--brand); color:var(--on-brand)}
.sfilter-pill:focus-visible{outline:2px solid var(--brand); outline-offset:2px}

/* ---- the count ---- */
.sfilter-count{display:flex; align-items:baseline; gap:6px; white-space:nowrap}
.sfilter-count b{
  font-family:var(--font-head); font-size:27px; font-weight:500;
  color:var(--ink); line-height:1; font-variant-numeric:tabular-nums;
}

/* ---- what is switched on, as a second row of the bar ----
   Full width, so it wraps below everything else, and pulled out to the bar's
   own edges so its rule runs the whole way across. */
.sfilter-on-row{
  flex-basis:100%;
  display:flex; align-items:center; flex-wrap:wrap; gap:6px;
  margin:9px -12px -9px; padding:10px 12px 11px;
  border-top:1px solid var(--line);
}
.sfilter-onpill{
  display:inline-flex; align-items:center; gap:6px;
  padding:4px 5px 4px 9px; border-radius:2px;
  background:var(--brand-tint); border:1px solid var(--line);
  font-size:12.5px; color:var(--brand-deep);
}
.sfilter-onpill .lbl{font-size:9.5px; letter-spacing:.1em}
.sfilter-onpill > a{
  display:grid; place-items:center; width:15px; height:15px;
  color:var(--muted); text-decoration:none; border-radius:2px;
}
.sfilter-onpill > a:hover{color:var(--bad); background:var(--surface)}

@media (max-width:900px){
  .sfilter-on-row{margin:9px -10px -10px; padding:10px}
  .sfilter-val{max-width:none}
}

/* ============ PRIVACY: THE LEDGER ============
   One table where there used to be a preview, a checklist and three more
   cards. Every field is a row and every audience a column, so "who can see my
   employer" is answered on the same line as the word employer.

   Density is the point. The old screen ran past two screenfuls on a laptop
   for eighteen facts, because each fact was a card, a checkbox row and a
   preview line in three different places. One line each, label and value on
   the same line, and the whole thing fits above the fold. */

.pledger{
  border:1px solid var(--line-strong); border-radius:3px;
  background:var(--surface); overflow:hidden;
}

/* One grid shared by every row, so the three columns line up down the whole
   table without a <table>. 74px is wide enough for the switch and for the
   word ALWAYS at 10.5px; the tick column needs less. */
.pledger-head,.pledger-row{
  display:grid; grid-template-columns:minmax(0,1fr) 74px 62px; gap:10px;
  align-items:center; padding:6px 14px;
}
.pledger-head{
  padding:9px 14px; background:var(--surface-2);
  border-bottom:1px solid var(--line);
}
.pledger-head > .lbl:nth-child(n+2){text-align:center}

.pledger-sec{
  padding:9px 14px 5px; background:var(--surface);
  border-top:1px solid var(--line);
}
.pledger-sec .lbl{color:var(--brand)}
.pledger-sec + .pledger-row{border-top:0}

.pledger-row{border-top:1px solid var(--line); transition:background .12s}
.pledger-row:hover{background:var(--surface-2)}

/* Label and value on ONE line — the single biggest saving on the page. The
   value is the quieter half because the row is about the label. */
.pledger-f{
  display:flex; align-items:baseline; gap:9px; min-width:0;
  font-size:13.5px; color:var(--ink);
}
.pledger-f > em{
  font-style:normal; font-size:12.5px; color:var(--muted);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* Struck through when the switch is off: the member can still read what they
   are withholding, which is the only way to decide whether to. */
.pledger-f > em.is-off{text-decoration:line-through; opacity:.75}

.pledger-w{display:flex; justify-content:center; margin:0}
.pledger-w:not(form){font-size:10.5px; letter-spacing:.12em; text-transform:uppercase; color:var(--muted)}
.pledger-y{display:flex; justify-content:center; color:var(--ok)}

/* The never-public tier is one row, not five: both columns read the same for
   every field in it, so five rows would be five copies of one fact. */
.pledger-row.is-block{align-items:center}
.pledger-row.is-block .pledger-f{flex-wrap:wrap; gap:5px; padding:3px 0}

.pledger-foot{
  padding:10px 14px; background:var(--surface-2);
  border-top:1px solid var(--line); font-size:13px; color:var(--body);
}

/* ---- the switch ----
   Drawn here rather than as a styled checkbox: each one is its own little
   form posting one field and one value, so there is no input to style. */
.sw{
  position:relative; flex:none; width:34px; height:19px; padding:0;
  border-radius:10px; border:1px solid var(--line-strong);
  background:var(--surface-2); cursor:pointer;
  transition:background .15s, border-color .15s;
}
.sw > span{
  position:absolute; top:2px; left:2px;
  width:13px; height:13px; border-radius:50%;
  background:var(--muted); transition:left .15s, background .15s;
}
.sw.is-on{background:var(--brand); border-color:var(--brand)}
.sw.is-on > span{left:17px; background:var(--on-brand)}
.sw:focus-visible{outline:2px solid var(--brand); outline-offset:2px}

/* ---- the two access decisions ---- */
.pdec{margin:0}
.pdec.is-on{border-color:var(--brand)}

/* ---- a blocked number, with the way to unblock it ---- */
.phid{gap:7px; padding-inline-end:5px}
.phid form{display:flex; margin:0}
.phid button{
  display:grid; place-items:center; width:15px; height:15px;
  padding:0; border:0; background:transparent; color:var(--muted); border-radius:2px;
}
.phid button:hover{color:var(--bad); background:var(--surface)}

@media (max-width:640px){
  /* The value drops under its label rather than fighting it for a phone's
     width; the two right-hand columns stay where they are, because they are
     the answer the table exists to give. */
  .pledger-head,.pledger-row{grid-template-columns:minmax(0,1fr) 60px 44px; gap:8px; padding:7px 11px}
  .pledger-f{flex-direction:column; align-items:flex-start; gap:1px}
  .pledger-f > em{max-width:100%}
}

/* The blocked-number list is always in the document, so a number added
   without a page reload has somewhere to land. Empty, it takes no room. */
.phid-list{margin-top:12px}
.phid-note{margin-top:9px}
.phid-list.is-empty,.phid-note.is-empty{display:none}

/* ============ REQUESTS: ONE LIST OF PEOPLE WAITING ============
   Interests and access requests were two pages of the same card at 78px with
   three lines of meta. One row now: face, who, what they asked, the answer. */

.qrow{
  display:grid; grid-template-columns:52px minmax(0,1fr) auto; gap:14px;
  align-items:center; padding:11px 14px;
  background:var(--surface); border:1px solid var(--line); border-radius:3px;
  transition:border-color .15s;
}
.qrow:hover{border-color:color-mix(in srgb, var(--line-strong) 45%, transparent)}

.qrow-ph{width:52px}
.qrow-who{display:flex; flex-direction:column; gap:2px; min-width:0}

.qrow-name{
  font-family:var(--font-head); font-size:16.5px; color:var(--ink); text-decoration:none;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.qrow-name:hover{color:var(--brand)}

.qrow-meta{
  font-size:12px; color:var(--muted);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* What they actually want, which is the reason the row exists and was
   previously a paragraph under the name in the same weight as everything
   else. */
.qrow-ask{font-size:13px; color:var(--body)}
.qrow-ask > time{color:var(--muted); font-size:12px}

.qrow-act{display:flex; align-items:center; gap:7px; flex:none}
.qrow-act form{margin:0}

/* The count on a tab. Only the tab that is a job wears it at full strength —
   a number on a record is decoration. */
.tab-n{
  display:inline-grid; place-items:center; min-width:17px; height:17px;
  padding:0 5px; margin-inline-start:5px; border-radius:9px;
  background:var(--surface-2); color:var(--muted);
  font-size:11px; font-variant-numeric:tabular-nums; vertical-align:1px;
}
.tab.on .tab-n{background:var(--brand-tint); color:var(--brand-deep)}
.tab-n.is-live{background:var(--brand); color:var(--on-brand)}

@media (max-width:640px){
  /* The buttons drop under the person rather than squeezing the name to
     nothing beside them. */
  .qrow{grid-template-columns:44px minmax(0,1fr); gap:11px; padding:11px}
  .qrow-ph{width:44px}
  .qrow-act{grid-column:1/-1; justify-content:flex-end}
}

/* ---- Dating, in the marriage side's bar ----
   The one link here that leads out of this product, so it carries the other
   product's colour and a dot before its name. Both are quiet: it is a door,
   not an advertisement, and the bar it sits in belongs to the marriage side.

   The dot is drawn rather than shipped as an icon — it is a full stop in
   Connect's green, which is the whole idea. */
.topnav-link.is-dating{color:var(--connect-brand)}
.topnav-link.is-dating::before{
  content:''; flex:none;
  width:6px; height:6px; border-radius:50%;
  background:var(--connect-brand);
  opacity:.7;
}
.topnav-link.is-dating:hover{
  background:color-mix(in srgb, var(--connect-brand) 10%, transparent);
  color:var(--connect-brand);
}
.topnav-link.is-dating:hover::before{opacity:1}
.topnav-link.is-dating.on{
  background:color-mix(in srgb, var(--connect-brand) 14%, transparent);
  color:var(--connect-brand);
}

/* ============ POROCHOY: THE DOOR ============
   Was four boxes of two lines, a card of three bullets and a paragraph. The
   promises are still made; their explanations moved to the safety page, and
   what stands in their place is the product — five cards, which is the whole
   day's set. "Five good ones beats five hundred" stops being a sentence and
   becomes the layout: nobody has to be told where the scroll stops. */

.half-dating{
  /* The grid replaces the old two-column .dating-panel entirely. */
  display:flex; flex-direction:column; gap:18px;
}

/* Name and the way in on one line: the button sits where the eye already is
   rather than under everything it has to read first. */
.dating-top{
  display:flex; align-items:flex-end; justify-content:space-between;
  gap:24px; flex-wrap:wrap;
}
.dating-top .half-head{margin:0}

.dating-caption{
  display:flex; align-items:center; justify-content:space-between;
  gap:16px; flex-wrap:wrap;
}
.dating-caption .sm{color:var(--body)}

/* ---- the day's five ---- */
.dating-deck{display:grid; grid-template-columns:repeat(5, minmax(0,1fr)); gap:14px}

.dcard{
  background:var(--surface); border:1px solid var(--line); border-radius:16px;
  overflow:hidden; display:flex; flex-direction:column;
}
/* No hover lift and no cursor change: these are examples, and a card that
   looks pressable but is not is worse than one that plainly is not. */
.dcard-ph{border-bottom:1px solid var(--line)}
.dcard-ph .ph{border:0; border-radius:0; aspect-ratio:.92}

.dcard-body{display:flex; flex-direction:column; gap:5px; padding:12px 13px}
.dcard-name{font-family:var(--font-head); font-size:17px; color:var(--ink)}
.dcard-city{font-size:12.5px; color:var(--muted)}
.dcard-body .chip{align-self:flex-start; margin-top:3px}

/* ---- the three promises the cards do not already show ---- */
.dating-foot{
  display:flex; align-items:center; flex-wrap:wrap; gap:10px 22px;
  padding-top:16px; border-top:1px solid var(--line);
}
.dating-promise{
  display:inline-flex; align-items:center; gap:7px;
  font-size:13px; color:var(--body);
}
.dating-promise .ic-svg{color:var(--brand); flex:none}
.dating-wall-link{margin-inline-start:auto}

@media (max-width:900px){
  /* A row of five on a phone is five slivers. They scroll instead, which is
     also the honest shape: a set you move through one at a time. */
  .dating-deck{
    display:flex; gap:12px;
    overflow-x:auto; scroll-snap-type:x mandatory;
    margin-inline:-26px; padding-inline:26px; padding-bottom:4px;
  }
  .dcard{flex:0 0 62%; scroll-snap-align:start}
  .dating-top{align-items:flex-start}
  .dating-wall-link{margin-inline-start:0}
}

/* ============ THE TWO CLOSING SECTIONS ============
   Members, then couples, between the world map and the footer.

   Both are full bleed by the same means the map above them uses — the
   50%/50vw margin trick — so the bottom of the page reads as a stack of
   bands rather than as a column that stops and then starts again. They are
   the last two things on the page and they answer the same question in two
   tenses: who is here, and how it ended for the people who left. */
.homesec{
  margin-left:calc(50% - 50vw); margin-right:calc(50% - 50vw);
  width:100vw; max-width:100vw;
  padding:clamp(46px, 6vw, 82px) clamp(16px, 4vw, 48px);
  border-top:1px solid transparent;
  border-image:var(--rule-img) 1; border-image-slice:1;
}
/* Three bands, two grounds.

   The members band was the flat page surface — var(--ground), nothing on it.
   That was chosen when it sat BELOW the map; now it sits above it, and a flat
   band leading into the map's lit one made the map look like the only section
   on the page anyone had designed. Asked for, and right.

   So it takes the map's ground — and takes it from the same declaration
   rather than a copy of it. One rule, both selectors: these two are meant to
   be identical, the seam is visible the moment they are not, and two copies
   of a four-line gradient is how they stop being identical. .worldmap's own
   block no longer sets a background; it points here.

   What keeps them two sections rather than one long one is the rule between
   them: .homesec and .worldmap both carry border-top with var(--rule-img),
   and that line is now doing the work the change of ground used to.

   Stories keeps the lighter 14%. It is the last band before the footer and
   reads as the quiet end of the page, which is a different job. */
.worldmap,
.homesec-members{
  background:
    radial-gradient(ellipse 70% 60% at 50% 0%,
      color-mix(in srgb, var(--brand) 22%, transparent) 0%, transparent 70%),
    var(--grad-surface);
}
.homesec-stories{
  background:
    radial-gradient(ellipse 70% 60% at 50% 0%,
      color-mix(in srgb, var(--brand) 14%, transparent) 0%, transparent 70%),
    var(--grad-surface);
}
.homesec-inner{max-width:1180px; margin:0 auto}

.homesec-head{text-align:center; max-width:64ch; margin:0 auto clamp(24px, 3vw, 38px)}
.homesec-head .lbl{display:block; margin-bottom:9px; color:var(--brand)}
.homesec-head h2{
  font-size:clamp(1.85rem, 3vw, 2.6rem); line-height:1.1; letter-spacing:-.025em;
}
.homesec-sub{margin:12px auto 0; font-size:15px; line-height:1.6; color:var(--body)}

.homesec-foot{
  display:flex; align-items:center; justify-content:center;
  flex-wrap:wrap; gap:10px 14px;
  margin-top:clamp(22px, 2.6vw, 34px);
}
.homesec-note{font-size:12.5px; color:var(--muted)}

/* ---- the members row ---- */
/* Four across, and .pcard is the same component the search results and the
   matrimony page use. One card, three surfaces: a second "but nicer on the
   front page" version is how two components drift apart. */
.memberrow{
  display:grid; gap:14px;
  grid-template-columns:repeat(4, minmax(0, 1fr));
}
@media (max-width:1040px){ .memberrow{grid-template-columns:repeat(3, minmax(0, 1fr))} }
@media (max-width:820px){  .memberrow{grid-template-columns:repeat(2, minmax(0, 1fr))} }
@media (max-width:460px){  .memberrow{grid-template-columns:1fr} }

/* ---- the stories row ---- */
.storyrow{
  display:grid; gap:16px;
  grid-template-columns:repeat(3, minmax(0, 1fr));
}
@media (max-width:940px){ .storyrow{grid-template-columns:repeat(2, minmax(0, 1fr))} }
@media (max-width:640px){ .storyrow{grid-template-columns:1fr} }

.scard{
  background:var(--surface); border:1px solid var(--line);
  border-radius:14px; overflow:hidden;
  transition:transform .16s ease, border-color .16s ease, box-shadow .16s ease;
}
.scard:hover{
  transform:translateY(-2px); border-color:var(--line-strong);
  box-shadow:0 14px 32px -22px rgba(0,0,0,.45);
}
/* The whole card is the link, so there is one target rather than a heading
   that works and a picture that does not. Everything inside is a span for
   that reason — an <a> may not contain a heading element. */
.scard-link{
  display:flex; flex-direction:column; height:100%;
  text-decoration:none; color:inherit;
}
.scard-art{display:block; background:var(--surface-2)}
.scard-art img{display:block; width:100%; height:auto; aspect-ratio:4/3; object-fit:cover}

.scard-body{display:flex; flex-direction:column; gap:7px; padding:15px 16px 17px; flex:1}
.scard-couple{
  font-family:var(--font-mono); font-size:10.5px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--brand);
}
.scard-h{
  font-family:var(--font-head); font-size:19px; line-height:1.25; color:var(--ink);
  letter-spacing:-.01em;
}
.scard-teaser{font-size:13.5px; line-height:1.6; color:var(--body)}
.scard-meta{
  display:flex; flex-wrap:wrap; gap:4px 12px;
  font-size:11.5px; color:var(--muted); margin-top:auto; padding-top:6px;
}
/* The affordance. Not a button: the card is already the button, and a button
   inside a link is two targets for one destination. */
.scard-more{
  font-size:13px; font-weight:600; color:var(--brand-deep);
  display:inline-flex; align-items:center; gap:5px;
}
.scard-more::after{content:'→'; transition:transform .16s ease}
.scard-link:hover .scard-more::after{transform:translateX(3px)}

/* ============ ONE STORY, IN FULL ============
   A reading column. 62ch, because this is the only page on the site that is
   genuinely prose and the measure is the whole difference between text you
   read and text you scan. */
.storypage{max-width:62ch; margin:0 auto}
.storypage-head{margin-bottom:26px}
.storypage-back{display:inline-block; margin-bottom:18px}
.storypage-head .lbl{display:block; margin-bottom:8px; color:var(--brand)}
.storypage-head h1{
  font-size:clamp(1.9rem, 3.4vw, 2.7rem); line-height:1.12; letter-spacing:-.025em;
}
.storypage-facts{
  display:flex; flex-wrap:wrap; gap:6px 18px;
  margin-top:14px; font-size:12.5px; color:var(--muted);
}
.storypage-facts > span{display:inline-flex; align-items:center; gap:6px}

.storypage-art{
  margin:0 0 28px; border-radius:16px; overflow:hidden;
  border:1px solid var(--line); background:var(--surface-2);
}
.storypage-art img{display:block; width:100%; height:auto}

/* Bigger than body copy and looser than it. Somebody is reading a few hundred
   words of somebody else's marriage; 17px at 1.75 is the size that stops
   being a UI and starts being a page. */
.storypage-body p{
  font-size:17px; line-height:1.75; color:var(--ink); margin:0;
}
.storypage-consent{
  margin:26px 0 0; padding-top:16px; border-top:1px solid var(--line);
  font-size:12.5px; color:var(--muted);
}
.storypage-cta{display:flex; flex-wrap:wrap; gap:10px; margin-top:22px}

/* The two at the foot break the reading column: they are cards, and a card
   squeezed into 62ch is a card that looks like a mistake. */
.storypage-more{max-width:1180px; margin-inline:auto}

/* The password label and its way out, on one line.
   main's sign-in stage has one label per field and nothing beside it; the
   reset link has to live next to the box it is about, so the label becomes a
   row. Only this field uses it, which is why it is a modifier rather than a
   change to .signin-field label. */
.signin-label-row{display:flex; align-items:baseline; justify-content:space-between; gap:12px}
/* --stage-muted, not --on-brand.

   --on-brand is the ink that goes ON the brand colour, and on this theme it
   is a near-black — so the first version of this link was dark type on the
   dark stage and effectively invisible. The sign-in stage is dark whatever
   the theme, and it carries its own three tokens for exactly that reason;
   this is the same colour the "or" divider and the footer link already use
   on the same card. */
.signin-forgot{
  font-size:12px; text-decoration:none;
  color:var(--stage-muted);
  transition:color .15s;
}
.signin-forgot:hover{color:var(--stage-ink); text-decoration:underline}

/* ============ EACH DOOR IS ITS OWN NAME ============
   Both cards carry their word, set to the width of the card, and nothing
   else. MATRIMONY and PORICHOY are a pair and are now built as one: same
   size rule, same weight, same tracking, same type-in, same caret.

   It was asymmetric before — Porichoy a small label above an explaining
   line, Matrimony the whole card — on the reasoning that the bigger door is
   the one most visitors came through. That was reversed on the owner's
   instruction: the two doors read as two products, and a product whose name
   is set in 11px above a subtitle does not read as the equal of one set in
   56px. The explaining line went with it; see home.blade.php.

   Three things have to happen together for "fill the box" to survive
   fourteen languages, and they are all here rather than spread about. */

/* The card measures itself, so the type can be a share of it. cqi is 1% of
   this container's inline size, which is exactly what "as wide as the card"
   means — and it keeps working when the cards stack full-width on a phone,
   which a vw-based size would not.

   inline-size containment only, never `paint`: paint containment would make
   this a backdrop root and the glass would stop seeing the photograph
   through it. */
.doorway .door-matrimony,
.doorway .door-dating{ container-type:inline-size }

.doorway .door-matrimony .door-tag,
.doorway .door-dating .door-tag{
  /*
   | Centred in the card, and only as wide as the word.
   |
   | It was width:100% — the whole card — which centred nothing and was
   | invisible while both labels happened to fill their cards. They do not:
   | the two share ONE font size, the smaller of the pair, so the shorter
   | word is narrower than its card and was sitting against the left edge
   | with the space all on the right.
   |
   | Shrinking the box rather than adding text-align:center, because the box
   | is what the caret and the reveal are measured against. door-sweep runs
   | inset-inline-start from 0 to 100% OF THIS ELEMENT, and door-type clips
   | it: with a full-width box and centred text the caret would start a
   | couple of inches to the left of the first letter and the reveal would
   | uncover empty card before reaching the word. fit-content keeps the box
   | on the word, so both still track the letters exactly.
   |
   | padding-inline-end already reserves the caret's own width, so it stays
   | inside the box and inside the clip.
   */
  width:fit-content;
  /* fit-content has no ceiling of its own, and the size above is computed
     from a character count rather than measured — so a word whose glyphs are
     wider than the average the formula assumes can come out a pixel or two
     past the card. Bangla "পরিচয়" does exactly that. */
  max-width:100%;
  align-self:center;
  /* Nothing follows it, so the gap to what followed it is dead air. */
  margin-bottom:0;

  font-weight:700;

  /*
   | The size is computed, not chosen, because the word is translated.
   |
   |   96cqi          the card's width, less a little air at the end
   |   var(--chars)   the letters in THIS language's word — the template
   |                  already measures it for the type-in animation
   |   0.84           average advance per letter in em, for this face at
   |                  this tracking: ~0.76 of glyph plus the .08em below
   |
   | So MATRIMONY (9) lands near 56px on a half-width card and fills it;
   | বিয়ে (4) would compute to twice that and is held at the ceiling
   | instead, because a four-letter word blown up to the full width of a
   | card is a shout, not a label. The floor is legibility on a narrow
   | phone.
   */
  font-size:clamp(20px, calc(96cqi / (var(--chars, 9) * 0.84)), 58px);

  /* A literal ceiling, not --door-tag-size, and so the appearance screen's
     label-size slider does not reach either door on the front page. That was
     already true of Matrimony and is now true of Porichoy as well: a label
     asked to be exactly as wide as its card cannot also be whatever number a
     slider says. The slider still sets the labels everywhere else, including
     its own preview, which is where its effect is judged. */

  /* .22em is a caption's tracking. At this size it is a gap between
     letters you can park a car in — and the 0.84 above is measured with
     this number, so the two move together. */
  letter-spacing:.08em;

  /*
   | A caret in proportion to the word it is writing.
   |
   | The base label declares --caret:2px, which is a text cursor beside 11px
   | of type. Beside 56px it is a hairline — visibly not the thing writing
   | these letters, which is the one job it has. An em keeps it a cursor at
   | any size the clamp above lands on; the 2px floor is for the narrow-phone
   | end of that clamp, where a proportional caret would round away to
   | nothing.
   |
   | .door-tag's padding-inline-end is calc(var(--caret) + .18em), so the room
   | reserved for it grows from the same number and the sweep still stops
   | inside the reveal's clip rather than being cut off by it.
   */
  --caret:max(2px, .045em);
}

/* ============ THE DOORS BREATHE, AND LIGHT UP UNDER THE POINTER ============

   Two cards, one clock, and the same everything: same keyframes, same
   duration, same easing, and deliberately NO per-door delay. A stagger is
   the obvious way to make two floating cards look less mechanical and it is
   wrong here — these two are a pair, and a pair that rises and falls out of
   step reads as one card lagging the other rather than as two moving
   together. "Exactly the same" was the instruction and it is also the
   better-looking answer.

   Colour is the one thing that stays different, as it already was: the
   travelling edge takes each product's own --edge ramp. The MOTION is what
   had to match, not the palette — the doorway exists to say these are two
   different things.

   Nothing here moves the cards' position in the layout. translateY is a
   paint-time transform on an absolutely-normal grid item: .door-matrimony is
   still justify-self:start and .door-dating still justify-self:end, and
   neither reserves or gives up a pixel of space while it floats. */
.doorway .door{
  animation:door-float 3.6s ease-in-out infinite;
}

@keyframes door-float{
  0%,100%{ transform:translateY(0) }
  50%    { transform:translateY(-7px) }
}

/*
 | Under the pointer: the float stops and the card turns white.
 |
 | animation:none is load-bearing, not tidiness. A running animation beats
 | every ordinary transform declaration on the same element, so while
 | door-float is going, .door:hover's own translateY/scale cannot be seen at
 | all. Switching the animation off is what hands the transform back to the
 | hover rule — and it is the same idiom this file already uses on the CTA
 | arrow a few hundred lines up.
 |
 | The card is caught rather than merely lit: it stops moving the moment you
 | reach it, which is what makes the lift underneath read as a response to
 | the pointer instead of a coincidence in the float's cycle.
 |
 | A literal white, not a token. --on-brand is "the colour that goes ON the
 | brand" and is #1F020B in the dark theme — nearly black. Asked for white,
 | and white is what it has to be in both themes, so the value is written
 | out.
 */
.doorway .door:hover,
.doorway .door:focus-visible{
  animation:none;
  background:#FFF;
  /* The glass is redundant behind an opaque card, and the inset highlight
     was mixed from --on-brand, which in the dark theme would draw a nearly
     black line across the top of a white one. */
  box-shadow:
    0 18px 40px -14px rgba(0,0,0,.45),
    inset 0 1px 0 rgba(255,255,255,.9);
}

/*
 | And the name goes back to being its own colour.
 |
 | Both labels are forced to #fff further up, and for a good reason: they sit
 | on a photograph, where a mid-tone would disappear. On a white card that
 | same rule paints white on white — the word vanishes exactly when the
 | pointer is on it.
 |
 | So this does not invent a hover colour. It restores the ramp each label
 | already has everywhere else in the product: the brand's rose for
 | Matrimony, Connect's teal for Porichoy. The card turning white is what
 | lets the door finally show its own colour, which is the distinction the
 | whole doorway is built to make.
 |
 | Five classes, because the white it is undoing is set through :root plus
 | three of them and has to be outranked in light, dark and system alike.
 */
:root .doorway .door-matrimony:hover .door-tag,
:root .doorway .door-matrimony:focus-visible .door-tag{
  --tag-ink:#8E2C4E; --tag-deep:#A84E6E; --tag-mid:#C56B8B; --tag-lit:#E28FAC;
  color:var(--brand-deep);
}
:root .doorway .door-dating:hover .door-tag,
:root .doorway .door-dating:focus-visible .door-tag{
  --tag-ink:#0B2E28; --tag-deep:#1B5249; --tag-mid:#2F7A6B; --tag-lit:#6FBFA8;
  color:var(--connect-brand);
}

/* The shadow under the type was separating it from a photograph. There is no
   photograph under it now, and a dark blur under coloured text on white is
   just grime. */
.doorway .door:hover .door-tag,
.doorway .door:focus-visible .door-tag{ filter:none }

/*
 | And the word does NOT re-type itself every time the pointer leaves.
 |
 | `.door:hover .door-tag{animation:none}` a few thousand lines up is older
 | than this pass and was harmless then: Matrimony had no type-in at all and
 | Porichoy's label was 11px. Now both are the card, and that rule changes
 | animation-name from a list to `none` on hover and back again on the way
 | out — which by specification restarts every animation in it. The result is
 | a 45px word that re-types itself each time the pointer crosses a door,
 | which is a page that looks broken rather than alive.
 |
 | The cure is not another `animation:` value; any DIFFERENT value restarts
 | it just the same. It is to restate the animation EXACTLY as .door-tag
 | already computes it, so hovering changes nothing about it and there is no
 | state change to restart from. Keep this in step with the .door-tag rule it
 | mirrors.
 |
 | transform:none goes with it: that rule also scales the label 1.04, and the
 | card underneath is already scaling on hover. Two scales on the same words
 | is a wobble, and on Matrimony the label IS the card, so it would grow past
 | its own edges.
 */
.doorway .door:hover .door-tag,
.doorway .door:focus-visible .door-tag{
  animation:
    shine-slide 8s linear infinite,
    door-type var(--reveal, 1s) steps(var(--chars, 9)) 1 forwards;
  transform:none;
}

/*
 | Reduced motion, declared HERE rather than with the other reduced-motion
 | rules a few thousand lines up.
 |
 | Media queries add no specificity. `.door{animation:none}` in that earlier
 | block is one class against the two of `.doorway .door` above, and even at
 | equal weight the later rule wins — so the float would have survived the
 | setting entirely. Same selector, after the rule it is switching off, is
 | the only version of this that works.
 |
 | The white and the colour change stay: a card that lights up under the
 | pointer is feedback, not decoration, and nobody asked for less of that.
 */
@media (prefers-reduced-motion: reduce){
  .doorway .door{ animation:none }
}

/* ============ THE BAR OVER THE SIGN-IN STAGE ============
   .topnav is sticky and carries backdrop-filter: blur(16px) saturate(1.2).
   That is the right effect over ordinary content and the wrong one here.

   A blurred sticky bar has to re-sample and re-blur whatever passes beneath
   it on every scroll frame. On this screen what passes beneath it is the
   most expensive thing on the site: six 30px-blurred halo strokes, six cores
   each carrying a blur and two drop-shadows, a 54px-blurred wash and a
   screen blend. Blurring a blur, sixty times a second, for the length of a
   418px scroll — and the stage fills the top of the page, so the whole of
   that scroll happens with it under the bar. That is the "laggy at the
   start" the owner reported.

   The stage is dark whatever the theme and carries its own palette, so a
   solid bar in its own darkest token reads as part of it rather than as
   glass over it. Nothing is lost and the per-frame cost goes to zero. */
.signin-page .topnav{
  background:color-mix(in srgb, #1A040C 92%, transparent);
  -webkit-backdrop-filter:none;
          backdrop-filter:none;
  border-bottom-color:rgba(255,240,243,.14);
}

/* ============ THE WALL, ON THE PORICHOY OPT-IN ============
   Two sides and a line between them. The screen is a privacy contract and
   the thing being decided is whether the separation is real, which is a
   shape before it is a sentence — so it is drawn once here and then proved
   clause by clause in the cards below.

   The page runs under [data-mode="connect"], so --brand and --surface are
   already this product's palette and nothing here names a colour. */
.wallsplit{
  display:grid;
  grid-template-columns:1fr auto 1fr;
  align-items:stretch;
  gap:0;
  max-width:64ch;
  margin:0 0 22px;
  border:1px solid var(--line);
  border-radius:16px;
  overflow:hidden;
  background:var(--surface);
}
.wallsplit-side{
  display:flex; flex-direction:column; gap:7px;
  padding:18px 20px;
  min-width:0;
}
.wallsplit-side p{margin:0; font-size:13.5px; line-height:1.6; color:var(--body)}
.wallsplit-side .lbl{color:var(--muted)}

/* This side is the one they are joining, so it is the one that is lit. */
.wallsplit-side.is-here{
  background:color-mix(in srgb, var(--brand) 9%, var(--surface));
}
.wallsplit-side.is-here .lbl{color:var(--brand)}
.wallsplit-side.is-here p{color:var(--ink)}

/* The rule, and the gutter it lives in.
   It was a 1px column with the label absolutely positioned over it, which
   put the rotated words on top of the sentences either side of them. The
   column has a real width now: the line is centred in it, the word sits on
   the line, and neither touches the copy. The gutter carries the plain
   surface so that it reads as the gap rather than as part of either side. */
.wallsplit-rule{
  position:relative;
  width:38px;
  background:var(--surface);
  display:grid;
  place-items:center;
}
.wallsplit-rule::before{
  content:""; position:absolute; top:14px; bottom:14px; left:50%;
  width:1px; transform:translateX(-50%);
  /* Dashed rather than solid: every card on this page already has a solid
     border, so a solid line here would read as one more edge. */
  background:repeating-linear-gradient(
    to bottom,
    var(--line-strong) 0 5px,
    transparent 5px 11px
  );
}
.wallsplit-word{
  position:relative;
  writing-mode:vertical-rl;
  transform:rotate(180deg);
  white-space:nowrap;
  padding:9px 2px;
  background:var(--surface);
  font-family:var(--font-mono);
  font-size:9px; letter-spacing:.18em; text-transform:uppercase;
  color:var(--muted);
}

/* Stacked, the wall is horizontal and the word lies down with it. Same
   three elements, turned through ninety degrees. */
@media (max-width:640px){
  .wallsplit{grid-template-columns:1fr}
  .wallsplit-rule{
    width:auto; height:38px;
  }
  .wallsplit-rule::before{
    top:50%; bottom:auto; left:14px; right:14px;
    width:auto; height:1px; transform:translateY(-50%);
    background:repeating-linear-gradient(
      to right,
      var(--line-strong) 0 5px,
      transparent 5px 11px
    );
  }
  .wallsplit-word{
    writing-mode:horizontal-tb;
    transform:none;
    padding:2px 10px;
  }
}

/* ---- the join step, merged onto the public Porichoy page ---- */
/* The four promises in full, under the wall. Two columns on a desktop: they
   are four short cards and a single column of them pushes the join step,
   which is the point of the page, off the bottom of a laptop screen. */
.dating-promises{
  display:grid; gap:12px;
  grid-template-columns:repeat(2, minmax(0, 1fr));
  margin-top:22px;
}
@media (max-width:760px){ .dating-promises{grid-template-columns:1fr} }

/* Everything below this rule is about the reader rather than the product,
   so it is separated from the marketing by a line rather than by spacing
   alone — the page changes subject here. */
.dating-join{
  display:flex; flex-direction:column; gap:14px;
  max-width:64ch;
  margin-top:26px; padding-top:24px;
  border-top:1px solid var(--line);
  /* The top CTA links to #join, so give the anchor somewhere to land that
     is not underneath the sticky bar. */
  scroll-margin-top:78px;
}

/* One page, and no trailing blank one.
   The sheet is scaled to fit by the script in partials/biodata-tools; this is
   the other half of the promise. A block that is exactly the height of the
   paper still spills a hairline onto a second sheet on some printers, and an
   empty page is as confusing as a split one. */
@media print{
  .bsheet, .bsheet-frame{ break-inside:avoid; page-break-inside:avoid }
  .bsheet{ break-after:avoid; page-break-after:avoid }
}

/* ============ THE PUBLIC SEARCH PAGE ============
   Results and filters side by side, replacing a bar of six controls across
   the top. In a row every question looked the same size and the first result
   started below the fold; in a column the panel can be grouped and the people
   start where somebody who came to look at people expects them. */
.searchpage{
  display:grid;
  grid-template-columns:minmax(0,1fr) 320px;
  gap:clamp(16px, 2vw, 26px);
  align-items:start;
}
.searchpage-results{display:flex; flex-direction:column; gap:14px; min-width:0}

/* Sticky, so the filters are reachable from anywhere down a long results
   list without scrolling back. The offset clears the sticky top navigation;
   max-height plus its own scroll keeps a panel taller than the viewport from
   becoming unreachable at its foot. */
.searchpage-filters{
  position:sticky; top:84px;
  max-height:calc(100vh - 104px); overflow-y:auto;
}
.sfilters{display:flex; flex-direction:column; gap:16px}
.sfilter-set{border:0; padding:0; margin:0}
.sfilter-set legend{
  padding:0; margin-bottom:7px;
  font-family:var(--font-mono); font-size:10.5px; letter-spacing:.13em;
  text-transform:uppercase; color:var(--muted);
}

/* The radio is the control and the span is the pill. The input is taken out
   of the flow rather than display:none — a hidden input is not focusable, and
   the whole point of a radio group is that it is reachable by keyboard. */
.sfilter-radio{display:inline-flex; cursor:pointer}
.sfilter-radio input{position:absolute; opacity:0; width:0; height:0}
.sfilter-radio input:checked + .sfilter-pill{
  background:var(--brand); border-color:var(--brand); color:var(--on-brand);
}
.sfilter-radio input:focus-visible + .sfilter-pill{outline:2px solid var(--brand); outline-offset:2px}

.sfilter-actions{display:flex; gap:8px; align-items:center}
.sfilter-actions .btn:last-child{margin-inline-start:auto}

/* ---- the wide card ----
   The same .mcard, laid on its side: photograph left, everything else right.
   A four-up grid of suggestions wants the portrait; a single column of
   results wants the row, because a row can carry the location, the work and
   the facts on their own lines without becoming a tall card with three
   words in it. */
.mcard.is-wide{flex-direction:row; align-items:stretch}
.mcard.is-wide .mcard-art{
  /* The portrait's fixed 4/5 becomes "as tall as the body beside it". */
  aspect-ratio:auto;
  width:clamp(140px, 32%, 220px);
  flex:none;
}

/*
 | The body comes OUT of the photograph.
 |
 | In the portrait card the body is position:absolute over the bottom of the
 | picture, on a dark gradient — that is what "the photograph is the card"
 | means, and it is why every colour in it is a hard-coded #FFF0F3 rather
 | than a token. Beside the picture there is no gradient to be legible
 | against, so the same values would be near-white text on the page surface.
 |
 | Put back through the theme's own tokens rather than a second set of
 | literals, so the wide card is readable in both themes for the same reason
 | everything else is.
 */
.mcard.is-wide .mcard-body{
  position:static; flex:1; min-width:0;
  background:none;
  padding:14px 16px;
  justify-content:center;
}
.mcard.is-wide .mcard-name{color:var(--ink)}
.mcard.is-wide .mcard-line{color:var(--body)}
.mcard.is-wide .mcard-dim{color:var(--muted)}
.mcard.is-wide .mcard-fact{
  color:var(--body); background:var(--surface-2); border-color:var(--line);
}
.mcard.is-wide .mcard-foot{border-top-color:var(--line)}
.mcard.is-wide .mcard-marital-t{color:var(--body)}
.mcard.is-wide .mcard-disc{
  color:var(--brand-deep); background:var(--brand-tint); border-color:var(--line);
}

@media (max-width:900px){
  .searchpage{grid-template-columns:1fr}
  /* Filters above the results once the two columns stack. They are used
     before the list is read, and a panel below a page of cards is a panel
     nobody scrolls back up to. */
  .searchpage-filters{order:-1; position:static; max-height:none; overflow:visible}
}
@media (max-width:560px){
  /* Below this the photograph and the body are each too narrow to be worth
     the split, so the card stands back up. */
  .mcard.is-wide{flex-direction:column}
  .mcard.is-wide .mcard-art{width:auto; aspect-ratio:4/5}
}

/* ============ PAYING BY HAND ============
   The bKash page a member reads with their phone in the other hand, and the
   admin queue behind it. The brand colour is bKash's own #E2136E, used the
   way a payment method is always shown — as the mark of whose rails these
   are — and confined to the card so nothing else on the site changes. */
.paywrap{display:flex; flex-direction:column; gap:16px; max-width:620px}

.paycard{
  --bkash:#E2136E;
  border:1px solid var(--line); border-radius:16px; overflow:hidden;
  background:var(--surface);
}
.paycard-head{
  display:flex; align-items:center; gap:12px;
  padding:14px 18px;
  background:var(--bkash); color:#fff;
}
/* The wordmark set in type, for when no official SVG has been added. This
   deliberately does not imitate the logo's own lettering: a drawing of
   somebody else's mark is worse than plain type saying the same word. */
.paycard-word{font-size:20px; font-weight:700; letter-spacing:-.02em}
.paycard-logo{display:block}
.paycard-kind{
  margin-inline-start:auto;
  font-family:var(--font-mono); font-size:10px; letter-spacing:.14em;
  text-transform:uppercase; opacity:.85;
}

.paycard-grid{
  display:grid; gap:1px;
  grid-template-columns:repeat(3, minmax(0,1fr));
  background:var(--line);
}
@media (max-width:560px){ .paycard-grid{grid-template-columns:1fr} }
.payfield{display:flex; flex-direction:column; gap:6px; padding:14px 16px; background:var(--surface)}

/* The three values are buttons because copying them is the point. Reading a
   number off one screen while typing it into another is where a digit gets
   lost and the money goes to a stranger. */
.paycopy{
  display:flex; flex-direction:column; align-items:flex-start; gap:3px;
  padding:0; border:0; background:none; cursor:pointer; text-align:left;
  color:inherit; font:inherit;
}
.paybig{font-size:20px; font-weight:600; letter-spacing:-.01em; color:var(--ink)}
.paycopy-hint{font-size:10.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--muted)}
.paycopy:hover .paycopy-hint{color:var(--brand)}
.paycopy.is-copied .paycopy-hint{color:var(--ok, #2F7A6B)}
.paycopy.is-copied .paycopy-hint::after{content:' ✓'}

.paysteps{margin:0; padding:14px 18px 16px 34px; display:flex; flex-direction:column; gap:7px}
.paysteps li{font-size:13.5px; line-height:1.55; color:var(--body)}
.paycard-foot{margin:0; padding:0 18px 16px}

/* The state banner. Three colours for three different pieces of news, and
   the waiting one is deliberately not an error: the member has done their
   part and is being told so. */
.paystate{
  display:flex; flex-direction:column; gap:8px; align-items:flex-start;
  padding:16px 18px; border-radius:14px; border:1px solid var(--line);
}
.paystate-t{font-family:var(--font-head); font-size:17px; color:var(--ink)}
.paystate p{margin:0; font-size:13.5px; line-height:1.6; color:var(--body)}
.paystate.is-waiting{background:color-mix(in srgb, var(--brand) 8%, var(--surface)); border-color:color-mix(in srgb, var(--brand) 28%, transparent)}
.paystate.is-done{background:color-mix(in srgb, #2F7A6B 10%, var(--surface)); border-color:color-mix(in srgb, #2F7A6B 34%, transparent)}
.paystate.is-refused{background:color-mix(in srgb, #A8324B 9%, var(--surface)); border-color:color-mix(in srgb, #A8324B 32%, transparent)}
.payref-row{display:flex; align-items:baseline; gap:10px}

.paysubmit{max-width:none}
.payrow + .payrow{margin-top:10px}

/* ============ THE PROFILE PAGE ============
   A band, a strip of sections, and facts in a grid with a mark against
   each. It replaced a two-column table of label-and-value rows, which held
   the same information and asked the reader to find it.

   Built mobile-first because that is where it is read: on a phone the band
   is a stack, the facts are two narrow columns, and the two actions leave
   the flow entirely for a bar pinned to the bottom, where a thumb already
   is. From 900px up the band becomes a row with the actions beside the
   person they act on, and the bar is not rendered at all.

   Nothing here hides a field. Anything the viewer may not see is absent
   from the payload, so the card it would have been in does not render -
   there is nothing in the HTML for a curious reader to find. */
.pfpage{max-width:1080px; margin:0 auto}

/* ---- the band ---- */
.pfhead{
  display:flex; flex-direction:column; gap:16px; align-items:center; text-align:center;
  padding:22px 18px; border-radius:20px; border:1px solid var(--line);
  background:
    radial-gradient(circle at 1px 1px, var(--hero-dot) 1.15px, transparent 1.25px) 0 0/30px 30px,
    var(--hero-ground);
  box-shadow:var(--shadow);
}
.pfhead-photo{flex:none; width:132px}
/* .ph fills its container rather than reading the `size` it is passed,
   so the width above is what actually decides how big the photograph
   is. Handed nothing, it grew to fill half the band. */
.pfhead-b{display:flex; flex-direction:column; gap:7px; align-items:center; min-width:0}
.pfhead-name{
  font-size:clamp(1.5rem, 4.2vw, 2rem); line-height:1.1; letter-spacing:-.02em; margin:0;
  overflow-wrap:anywhere;
}
/* An opaque id is not a name and should not be set like one: mono, spaced,
   so it reads as a reference rather than as somebody called TBC_1904. */
.pfhead-name.is-id{
  font-family:var(--font-mono); font-size:clamp(1.15rem, 3.4vw, 1.5rem); letter-spacing:.04em;
}
.pfhead-id{font-family:var(--font-mono); font-size:11.5px; letter-spacing:.08em; color:var(--muted)}

/* The scan line. Separators are drawn between the spans rather than typed
   into the text, so a profile missing its city never prints a stray bar. */
.pfhead-meta{
  display:flex; flex-wrap:wrap; justify-content:center; gap:6px 0; margin:2px 0 0;
  font-size:13.5px; color:var(--body);
}
.pfhead-meta span{padding:0 10px; position:relative}
/* The divider trails each item rather than leading the next one. On a narrow
   screen this line wraps, and a leading rule puts a bar at the START of the
   second line - which reads as a missing word. Trailing, the bar ends the
   line instead, which is what it means. */
.pfhead-meta span:not(:last-child)::after{
  content:''; position:absolute; right:0; top:50%; transform:translateY(-50%);
  width:1px; height:12px; background:color-mix(in srgb, var(--muted) 55%, transparent);
}
.pfhead-meta span:first-child{padding-left:0}
.pfhead-meta span:last-child{padding-right:0}
.pfhead-chips{display:flex; flex-wrap:wrap; justify-content:center; gap:6px; margin-top:3px}
.pfhead-note{margin:4px 0 0; font-size:12.5px; color:var(--muted)}
/* On a phone these are in the bar pinned to the bottom instead. Printed
   in both places they were the same two buttons twice on one screen. */
.pfhead-do{display:none; flex-direction:column; gap:8px; width:100%; max-width:280px}

/* ---- the section strip ----
   Scrolls sideways on a narrow screen rather than wrapping onto three
   lines. Written by the script from the panels that exist, so a viewer who
   is shown three sections gets three tabs. */
.pftabs{
  display:flex; gap:7px; margin:18px 0 14px; padding-bottom:4px;
  overflow-x:auto; scrollbar-width:none;
}
.pftabs::-webkit-scrollbar{display:none}
.pftab{
  flex:none; padding:8px 15px; border-radius:999px; cursor:pointer; white-space:nowrap;
  border:1px solid var(--line); background:var(--surface); color:var(--muted);
  font-size:13px; font-weight:600; transition:color .16s ease, border-color .16s ease, background-color .16s ease;
}
.pftab:hover{color:var(--ink); border-color:var(--brand)}
.pftab.is-on{background:var(--brand-tint); border-color:transparent; color:var(--brand-deep)}

/* Only once the script has run. Without it every panel is simply visible,
   one after another, which is a perfectly good profile page. */
.pfbody{display:flex; flex-direction:column; gap:14px}
.pfbody.is-tabbed > [data-panel]{display:none}
.pfbody.is-tabbed > [data-panel].is-on{display:block}

/* ---- a card of facts ---- */
.pfcard{
  background:var(--surface); border:1px solid var(--line); border-radius:16px;
  padding:16px 16px 17px; box-shadow:var(--shadow);
}
.pfcard-h{
  display:flex; align-items:center; gap:9px; margin:0 0 13px;
  font-size:15px; letter-spacing:-.01em;
}
.pfcard-h-ic{
  display:inline-flex; align-items:center; justify-content:center; flex:none;
  width:28px; height:28px; border-radius:50%;
  background:var(--brand-tint); color:var(--brand-deep);
}
.pfcard-foot{margin:13px 0 0; font-size:12.5px; color:var(--muted)}
.pfsay{
  margin:0 0 10px; font-size:15px; line-height:1.5; color:var(--ink); font-style:italic;
}
.pfprose{margin:0; font-size:14px; line-height:1.7; color:var(--body); white-space:pre-line}

.pfacts{display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:14px 12px}
.pfact{display:flex; gap:9px; align-items:flex-start; min-width:0}
.pfact-ic{
  display:inline-flex; align-items:center; justify-content:center; flex:none;
  width:26px; height:26px; border-radius:50%; margin-top:1px;
  background:var(--surface-2); color:var(--brand); border:1px solid var(--line);
}
.pfact-b{display:flex; flex-direction:column; gap:2px; min-width:0}
.pfact-k{font-size:11.5px; color:var(--muted); letter-spacing:.01em}
.pfact-v{font-size:13.5px; line-height:1.45; color:var(--ink); font-weight:600; overflow-wrap:anywhere}

/* ---- what is behind the wall ----
   A list of field NAMES. The answers are not in the payload at all, and
   naming what exists is what makes somebody ask for it. */
.pfcard.is-locked{border-style:dashed}
.pflocked{display:flex; flex-wrap:wrap; gap:7px}
.pflocked-i{
  display:inline-flex; align-items:center; gap:6px; padding:6px 11px; border-radius:999px;
  border:1px dashed var(--line); color:var(--muted); font-size:12.5px;
}

/* ---- the bar on a phone ---- */
.pfbar{
  position:fixed; left:0; right:0; bottom:0; z-index:40;
  display:grid; grid-template-columns:1fr 1fr; gap:10px;
  padding:11px 14px calc(11px + env(safe-area-inset-bottom));
  background:color-mix(in srgb, var(--surface) 94%, transparent);
  border-top:1px solid var(--line); backdrop-filter:blur(9px);
}
/* Room under the last card, so the bar never covers the end of the page. */
.pfpage{padding-bottom:78px}

@media (min-width:900px){
  .pfhead{flex-direction:row; align-items:center; text-align:left; gap:22px; padding:24px 26px}
  .pfhead-b{align-items:flex-start; flex:1}
  .pfhead-meta{justify-content:flex-start}
  .pfhead-meta span:first-child{padding-left:0}
  .pfhead-chips{justify-content:flex-start}
  .pfhead-do{display:flex; width:auto; max-width:none; flex:none; min-width:200px}
  .pfacts{grid-template-columns:repeat(3, minmax(0, 1fr)); gap:16px 18px}
  .pfcard{padding:20px 22px 22px}
  .pfbar{display:none}
  .pfpage{padding-bottom:0}
}

/* ============ THE PROFILE HUB ============
   A member's own profile as one screen: how far along it is, then the six
   forms as cards rather than as a row of tabs above a field.

   Two columns from 760px and one below it. The cards are links with a
   count in them, so they are sized for a thumb on a phone and read as a
   list of jobs rather than as navigation. */
/* Grid rather than a flex row, so the buttons can sit beside the figure on
   a wide screen while staying AFTER the bar and the note in the document -
   which is the order it reads in on a phone. */
.hubtop{display:grid; grid-template-columns:1fr; gap:12px}
.hubtop-head{display:flex; flex-direction:column; gap:3px}
.hubtop-n{font-family:var(--font-head); font-size:26px; color:var(--ink); line-height:1}
.hubtop-do{display:flex; gap:8px; flex-wrap:wrap}
@media (min-width:760px){
  .hubtop{grid-template-columns:1fr auto; align-items:start}
  .hubtop-do{grid-column:2; grid-row:1; justify-self:end}
  .hubtop-bar,
  .hubtop-note{grid-column:1 / -1}
}

.hubgrid{display:grid; grid-template-columns:1fr; gap:12px}
@media (min-width:760px){ .hubgrid{grid-template-columns:repeat(2, minmax(0, 1fr))} }

.hubcard{
  display:flex; align-items:center; gap:13px; padding:15px 16px; text-decoration:none;
  background:var(--surface); border:1px solid var(--line); border-radius:14px;
  box-shadow:var(--shadow); transition:border-color .16s ease, transform .16s ease;
}
.hubcard:hover{border-color:var(--brand); transform:translateY(-1px)}
.hubcard-ic{
  display:inline-flex; align-items:center; justify-content:center; flex:none;
  width:38px; height:38px; border-radius:11px;
  background:var(--brand-tint); color:var(--brand-deep);
}
.hubcard-b{display:flex; flex-direction:column; gap:2px; min-width:0; flex:1}
.hubcard-t{font-size:14.5px; font-weight:600; color:var(--ink)}
.hubcard-s{font-size:12px; line-height:1.45; color:var(--muted)}

/* A count, not a tick, while there is anything left: "1 of 2" says there is
   something to do and a tick says the opposite. The tick is for when it is
   actually finished. */
.hubcard-n{
  flex:none; min-width:34px; text-align:center; padding:4px 8px; border-radius:999px;
  font-family:var(--font-mono); font-size:11.5px; letter-spacing:.02em;
  background:var(--surface-2); color:var(--muted); border:1px solid var(--line);
}
.hubcard-n.is-done{
  background:color-mix(in srgb, var(--ok) 12%, transparent); border-color:transparent; color:var(--ok);
  display:inline-flex; align-items:center; justify-content:center;
}
.hubcard-go{flex:none; color:var(--muted); display:inline-flex; align-items:center}
.hubcard:hover .hubcard-go{color:var(--brand)}
