/* OpenHearth visual system.

   Two deliberate choices worth keeping in mind before editing:

   1. Headings are a serif and body is a sans. The pairing is most of why this
      reads as a community organisation's tool rather than a SaaS dashboard.
   2. A check-in that is still sitting on the phone must never look like one
      the Centre has confirmed. Green is reserved for confirmed. See .notice
      and .success below.

   Sizes: nothing that a member or staff member has to READ goes below 14px,
   and body copy is 16px. Small type was the single biggest accessibility
   problem here — Elders are a core audience.

   Touch targets: --tap (48px) is the minimum height for anything tappable. */
:root{
  --forest:#1F3D2B; --forest-deep:#152B1F; --forest-light:#2E5240;
  /* Copper darkened from #B5651D, which failed WCAG AA both as button
     background under white text (4.34) and as link colour on cream (4.09).
     This passes both: 5.64 and 5.32. */
  --copper:#9C5518; --copper-deep:#7F4412; --copper-light:#D68A4A;
  --sand:#F1EAD9; --cream:#FBF8F2; --ink:#26241D; --slate:#635B49;
  --line:#E3DCC8; --line-strong:#CFC5AB; --teal:#35605A; --white:#FFF; --danger:#A8432E;
  /* The two lines on the month-by-month chart. Not picked by eye: run
     through the dataviz validator against the white of a card — lightness,
     chroma, colour-blind separation (worst pair 10.5, target 8), ordinary
     separation (19.2, floor 15) and 3:1 against the surface all pass.
     --teal itself fails the chroma check (0.05; it reads as grey beside a
     colour), so the people line is a teal of the same family with more
     colour in it. Marks only — text on the chart wears the text colours. */
  --chart-visits:#9C5518; --chart-people:#148772;

  --tap:48px;
  --shadow-soft:0 1px 2px rgba(38,36,29,.04), 0 2px 8px rgba(38,36,29,.05);
  --radius:14px; --radius-sm:10px;

  /* The "voice" — a serif used only where the app speaks to a person
     directly: the welcome, the moment after a check-in. Everything
     informational stays in the sans. No webfont is loaded (offline, CSP),
     so this is whatever serif the handset has; the stack is ordered so
     Android (no Georgia) still lands on a real serif. */
  --font-voice:'Lora',Georgia,'Noto Serif','Times New Roman',serif;

  /* Hearth surfaces. Dark at the top, warmth rising from the bottom — the
     one place the flat palette gives way to a gradient, on purpose. */
  /* Firelight on a floor, not a copper wash: the room is green, and the
     warmth rises from below the bottom edge. Keeping the glow off-centre of
     the content means text sits on deep green almost everywhere. */
  --ember:radial-gradient(125% 62% at 50% 116%,#8A4A15 0%,#5A3A1A 26%,#23402C 58%,#152B1F 100%);
  /* The same warmth with no photograph behind it, for a strip that is on
     screen all day — every nav click lands on this, so it stays quiet. */
  --ember-strip:linear-gradient(120deg,#152B1F 0%,#1F3D2B 62%,#7F4412 100%);
  /* The same fire, close up. A full-height surface can hold the glow well
     below its bottom edge; a card 110px tall cannot, so the hearth is drawn
     nearer or it reads as a plain dark box. This is the one place in the app
     that is allowed to be the warmest thing on screen — it is the moment a
     member is being thanked for coming. */
  --ember-card:radial-gradient(135% 118% at 50% 112%,#8A4A15 0%,#6B4118 30%,#2A4530 72%,#1B3323 100%);
  --ember-text:#FBF3E7;

  /* A steady glow behind the mark on every hero screen, not moving embers —
     embers are reserved for the one moment a member is thanked. This is the
     light of the same hearth seen from the doorway: it blooms in once and
     holds still, so it never competes with someone reading a form.
     Kept compact and low-alpha on purpose: it sits right where the heading
     and mark are, over the gradient's darkest region, and .30 at its peak is
     the most it can be there without a heading in --ember-text failing WCAG
     AA against it — see the contrast test in designSystem.test.js, which
     checks this exact peak against the exact darkest stop it falls over.
     Centred in its own box (see .landing-inner::before) rather than
     positioned by viewport percentage, so it travels with the content —
     .landing vertically centres a box of a different height on every
     screen, and a glow anchored to the viewport instead of that box sat in
     empty space above short screens like the welcome beat. */
  --radiant:radial-gradient(circle,rgba(255,197,122,.30) 0%,rgba(255,170,90,.13) 45%,rgba(255,170,90,0) 72%);

  /* Photograph layer for hero surfaces. Off until the asset exists, and
     nothing depends on it: the ember gradient carries these screens on its
     own. To switch it on — drop the image at
     public/img/hero-longexposure.webp, add '/img/hero-longexposure.webp' to
     SHELL_ASSETS in public/sw-strategy.js so it works offline, then swap the
     two lines below for the two commented ones. The scrim only exists to
     keep text legible over a photograph; over the gradient alone the
     contrast is already there, so it stays off too. */
  --hero-photo:none;
  --hero-scrim:none;
  /* --hero-photo:url('/img/hero-longexposure.webp'); */
  /* --hero-scrim:linear-gradient(180deg,rgba(20,26,16,.45) 0%,rgba(20,26,16,.82) 100%); */
}
*{box-sizing:border-box;}
html,body{margin:0;padding:0;}
/* Opened from the home-screen icon, a pull down at the top of a screen does
   nothing useful (there is no address bar to refresh from) and on Garrett's
   iPhone it closed the app (Oct 6 2026). The page no longer stretches past
   its top or bottom there. In a browser tab nothing changes: pulling down
   still refreshes, and a refresh now keeps the person signed in. */
@media (display-mode: standalone){
  html,body{overscroll-behavior-y:none;}
}
body{
  /* A humanist sans on every platform, with no webfont to download — this app
     has to work offline on old handsets, and the CSP forbids external hosts. */
  font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;
  font-size:16px;
  line-height:1.5;
  background:var(--cream);
  color:var(--ink);
  -webkit-text-size-adjust:100%;
}
h1,h2,h3,h4{font-family:Georgia,'Iowan Old Style','Times New Roman',serif;color:var(--forest-deep);margin:0 0 10px;line-height:1.25;}
h1{font-size:30px;}
h2{font-size:24px;}
h3{font-size:19px;}
h4{font-size:17px;}
p{margin:0 0 12px;}
button{font-family:inherit;font-size:inherit;cursor:pointer;border:none;background:none;color:inherit;}
input,select,textarea{font-family:inherit;font-size:16px;} /* 16px also stops iOS zooming on focus */
a{color:var(--copper);}
[hidden]{display:none!important;}
:focus-visible{outline:3px solid var(--copper-light);outline-offset:2px;}

.topbar{background:var(--ember-strip);color:var(--ember-text);padding:14px 22px;display:flex;align-items:center;gap:14px;flex-wrap:wrap;}
.topbar .brand{font-size:21px;color:#fff;}
.topbar .sub{font-size:14px;color:rgba(241,234,217,.88);}
.topbar .spacer{margin-left:auto;}
.topbar button{background:rgba(255,255,255,.14);padding:0 16px;min-height:var(--tap);border-radius:var(--radius-sm);font-size:15px;font-weight:600;color:var(--sand);}
.topbar button:hover{background:rgba(255,255,255,.24);}

/* minmax(0,1fr), not 1fr — a grid track's automatic minimum is its
   content's min-content size, so a wide table (the Team table, with a role
   picker and two buttons per row, is the usual culprit) forced this column
   wider than the viewport, and with it the whole page. minmax(0,1fr) lets
   the column actually shrink to the space available, so a table too wide
   for it scrolls inside .table-wrap below instead of taking the page with
   it — see the note there for what that scroll did to .topbar. */
.shell{display:grid;grid-template-columns:232px minmax(0,1fr);min-height:calc(100vh - 76px);}
.sidebar{background:var(--forest-deep);color:var(--sand);padding:18px 12px;display:flex;flex-direction:column;gap:4px;}
.nav-item{display:flex;align-items:center;min-height:var(--tap);padding:0 14px;border-radius:var(--radius-sm);font-size:16px;font-weight:500;color:rgba(241,234,217,.82);text-align:left;width:100%;}
.nav-item:hover{background:rgba(255,255,255,.08);color:#fff;}
.nav-item.active{background:rgba(214,138,74,.28);color:#fff;}

main{padding:32px 36px 80px;max-width:1080px;}
.card{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:24px 26px;margin-bottom:22px;box-shadow:var(--shadow-soft);}
.card h3{font-size:19px;}
.row{display:flex;gap:14px;flex-wrap:wrap;align-items:center;}
.stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:14px;margin-bottom:22px;}
.stat{background:#fff;border:1px solid var(--line);border-radius:var(--radius-sm);padding:18px 20px;box-shadow:var(--shadow-soft);}
.stat .label{font-size:14px;color:var(--slate);margin-bottom:6px;}
.stat .value{font-family:Georgia,serif;font-size:32px;font-weight:700;color:var(--forest-deep);line-height:1.1;}

/* Every table on the portal is wrapped in this (see app.js). Without it, a
   table too wide for its column pushed the whole .shell grid wider than the
   viewport, so scrolling right scrolled the entire page rather than just
   the table — and .topbar, a plain block sized to the page's ORIGINAL
   width, does not grow to cover that extra scrolled distance. The result
   was a bare gap where the header's colour should be, for exactly as far
   right as the table had forced the page to grow. Scoping the scroll to
   the table itself removes the page-level overflow that caused that gap,
   rather than trying to make the header wider to chase it. */
.table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
table{width:100%;border-collapse:collapse;font-size:16px;}
th{text-align:left;font-size:14px;text-transform:uppercase;letter-spacing:.05em;color:var(--slate);padding:0 12px 10px;border-bottom:1px solid var(--line);}
td{padding:14px 12px;border-bottom:1px solid var(--line);}
tr:last-child td{border-bottom:none;}
.pill{display:inline-block;padding:4px 12px;border-radius:100px;font-size:14px;font-weight:600;background:var(--sand);color:var(--slate);}
.pill.teal{background:#E0EBE8;color:var(--teal);}
.pill.copper{background:#F3E2D2;color:var(--copper-deep);}

.btn{
  padding:0 20px;min-height:var(--tap);border-radius:var(--radius-sm);
  font-size:16px;font-weight:600;display:inline-flex;gap:8px;align-items:center;justify-content:center;
}
.btn-primary{background:var(--copper);color:#fff;}
.btn-primary:hover{background:var(--copper-deep);}
.btn-ghost{background:#fff;color:var(--forest-deep);border:1px solid var(--line-strong);}
.btn-ghost:hover{background:var(--sand);}
/* Still a full 48px target — only the label and side padding shrink, so dense
   staff tables stay readable without becoming hard to hit on a phone. */
.btn-sm{padding:0 14px;font-size:15px;min-height:var(--tap);}
.btn[disabled]{opacity:.5;cursor:default;}
/* Waiting on the Centre (busyButton in app.js): the words change, the colours
   do not, so the label keeps its contrast while it says "Signing in…". */
.btn[aria-disabled="true"],.btn-return[aria-disabled="true"]{cursor:progress;}
/* There for a screen reader, not for the eye. */
.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;}

label{font-size:15px;font-weight:600;color:var(--forest-deep);display:block;margin-bottom:6px;}
.field{margin-bottom:18px;}
input[type=text],input[type=email],input[type=password],input[type=number],input[type=tel],select,textarea{
  width:100%;padding:12px 14px;min-height:var(--tap);border-radius:var(--radius-sm);
  border:1px solid var(--line-strong);background:#fff;color:var(--ink);line-height:1.4;
}
textarea{min-height:96px;}
/* The month-by-month chart (trendCard in app.js). The SVG holds the grid and
   the two lines and stretches to whatever width it is given; its strokes do
   not scale. All text and every dot is HTML placed by percentage, so nothing
   is squashed on a phone. Grid and axis are solid hairlines a step off the
   surface; the lines are 2px; a dot is 10px with a 2px ring of the surface
   so it stays clear where the lines cross. */
.trend{position:relative;border-radius:var(--radius-sm);touch-action:pan-y;}
.trend:focus-visible{outline-color:var(--forest-deep);outline-offset:6px;}
.trend-readout{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 18px;margin-bottom:14px;color:var(--slate);font-size:16px;}
.trend-when{flex:1 0 100%;font-size:14px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--slate);}
.trend-series strong{color:var(--ink);font-size:20px;font-weight:700;}
.trend-key{display:inline-block;width:16px;height:0;margin-right:8px;border-top:3px solid;border-radius:2px;vertical-align:middle;transform:translateY(-2px);}
.trend-key.is-visits{border-color:var(--chart-visits);}
.trend-key.is-people{border-color:var(--chart-people);}
.trend-body{display:grid;grid-template-columns:40px 1fr;gap:10px;height:180px;padding-top:6px;}
.trend-y{position:relative;}
.trend-y span{position:absolute;right:0;transform:translateY(-50%);font-size:14px;color:var(--slate);font-variant-numeric:tabular-nums;line-height:1;}
.trend-plot{position:relative;margin:0 6px;cursor:crosshair;}
.trend-plot svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible;}
.trend-grid{stroke:var(--line);stroke-width:1;vector-effect:non-scaling-stroke;}
.trend-grid.is-base{stroke:var(--line-strong);}
.trend-line{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;vector-effect:non-scaling-stroke;}
.trend-line.is-visits{stroke:var(--chart-visits);}
.trend-line.is-people{stroke:var(--chart-people);}
/* The step into a month the period holds only part of: dashed, and its dot
   open. A month still under way must not read as a drop. */
.trend-line.is-part{stroke-dasharray:2 5;}
.trend-dot{position:absolute;width:10px;height:10px;border-radius:50%;box-shadow:0 0 0 2px #fff;transform:translate(-50%,-50%);pointer-events:none;}
.trend-dot.is-visits{background:var(--chart-visits);}
.trend-dot.is-people{background:var(--chart-people);}
.trend-dot.is-part{background:#fff;box-shadow:none;border:2px solid;}
.trend-dot.is-part.is-visits{border-color:var(--chart-visits);}
.trend-dot.is-part.is-people{border-color:var(--chart-people);}
.trend-dot.is-quiet{display:none;}
@media (max-width:520px){.trend-dot.is-crowd{display:none;}}
.trend-dot.is-end{display:block;}
.trend-dot.is-active{display:block;width:12px;height:12px;}
.trend-cross{position:absolute;top:0;bottom:0;width:1px;background:var(--slate);transform:translateX(-50%);pointer-events:none;}
.trend-cross[hidden]{display:none;}
.trend-x{position:relative;height:22px;margin:8px 6px 0 calc(40px + 10px + 6px);}   /* the y gutter, the gap, the plot's own margin */
.trend-xl{position:absolute;top:0;transform:translateX(-50%);font-size:14px;color:var(--slate);white-space:nowrap;line-height:1.4;}
.trend-xl.is-first{transform:none;}
.trend-xl.is-last{transform:translateX(-100%);}
.trend-xl[hidden]{display:none;}
.trend-table{margin-top:14px;border-top:1px solid var(--line);}
.trend-table summary{display:flex;align-items:center;min-height:var(--tap);font-weight:600;color:var(--copper);cursor:pointer;}
.trend-table summary:focus-visible{outline-color:var(--forest-deep);}
/* Where colours are taken away (Windows high contrast), the people line is
   dashed so the two can still be told apart. */
@media (forced-colors:active){
  .trend-line{stroke:CanvasText;}
  .trend-line.is-people{stroke-dasharray:6 5;}
  .trend-dot{background:CanvasText;forced-color-adjust:none;}
}

/* When a program runs (timePicker in app.js). Five weekday boxes, each a
   full tap target, then the two half-hour lists side by side. */
.time-picker{border:0;padding:0;margin:0 0 6px;min-width:0;}
.time-picker legend{font-size:15px;font-weight:600;color:var(--forest-deep);margin-bottom:6px;padding:0;}
.day-choices{display:flex;flex-wrap:wrap;gap:8px;}
.day-choice{display:inline-flex;align-items:center;gap:8px;min-height:var(--tap);padding:0 14px;border:1px solid var(--line-strong);border-radius:var(--radius-sm);background:#fff;font-size:16px;font-weight:600;color:var(--ink);cursor:pointer;margin:0;}
.day-choice input{width:20px;height:20px;margin:0;accent-color:var(--forest);}
.day-choice:has(input:checked){background:var(--sand);border-color:var(--forest);}
.day-choice:has(input:focus-visible){outline:3px solid var(--forest-deep);outline-offset:2px;}
.time-row .field{flex:1 1 140px;min-width:0;}
/* "On now" beside a program's name: words, not only a colour. Teal, not
   the green of a check-in the Centre has — that green means one thing. */
.pill.on-now{background:#DDEBE6;color:#1F4D45;font-size:14px;vertical-align:middle;margin-left:6px;
  /* A label, not the voice: plain sans even inside a heading set in the serif. */
  font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;font-style:normal;font-weight:700;}

/* The funder report's own dates: two fields side by side where there is
   room, stacked on a narrow phone. */
.range-fields{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
.range-fields .field{margin-bottom:12px;min-width:0;}
input[type=date]{width:100%;padding:12px 14px;min-height:var(--tap);border-radius:var(--radius-sm);border:1px solid var(--line-strong);background:#fff;color:var(--ink);line-height:1.4;}
@media (max-width:380px){.range-fields{grid-template-columns:1fr;}}
.compare{margin-top:14px;padding-top:14px;border-top:1px solid var(--line);}
.compare p{margin:4px 0 0;font-size:16px;}

/* The eye on a password field (public/password-eye.js). The button sits
   inside the field's right edge and is a full tap target of its own; the
   field leaves room so typed text never runs under it. The stroke through
   the eye shows only while the password is visible. */
.pw-wrap{position:relative;}
.pw-wrap input{padding-right:calc(var(--tap) + 6px);}
.pw-eye{position:absolute;top:0;right:0;display:inline-flex;align-items:center;justify-content:center;width:var(--tap);height:100%;min-height:var(--tap);padding:0;border:0;background:none;color:var(--slate);border-radius:var(--radius-sm);cursor:pointer;}
.pw-eye:hover{color:var(--forest-deep);}
/* The page's copper focus ring is 2.8:1 on the white of a field — under the
   3:1 a focus indicator needs. Here it is the deep green, 15:1. */
.pw-eye:focus-visible{outline-color:var(--forest-deep);outline-offset:-3px;}
.pw-eye[aria-pressed="true"]{color:var(--forest-deep);}
.pw-eye .pw-slash{display:none;}
.pw-eye[aria-pressed="true"] .pw-slash{display:inline;}
.hint{font-size:14px;color:var(--slate);margin-top:6px;line-height:1.5;}

.error{background:#FBEAE5;border:1px solid #E3A791;color:var(--danger);padding:14px 16px;border-radius:var(--radius-sm);font-size:16px;margin-bottom:16px;line-height:1.5;}
/* Deliberately not green. A check-in still sitting on the phone must not
   look like one the server has. */
.notice{background:var(--sand);border:1px solid var(--line-strong);color:var(--ink);padding:14px 16px;border-radius:var(--radius-sm);font-size:16px;margin-bottom:16px;line-height:1.5;}
.notice .hint{display:inline-block;margin-top:4px;}
.success{background:#E0EBE8;border:1px solid #93BAB2;color:var(--teal);padding:14px 16px;border-radius:var(--radius-sm);font-size:16px;margin-bottom:16px;line-height:1.5;}

/* Role picker / landing */
/* The portal's own voice. Used sparingly — the dashboard's opening line, and
   nothing that is merely a label. Dark serif on cream, so it reads as a
   sentence someone wrote rather than a field in a form. */
.lede{font-family:var(--font-voice);font-style:italic;font-weight:400;
  font-size:22px;line-height:1.35;color:var(--forest);margin:0 0 8px;}

/* Hero surfaces — the welcome, the sign-in screens, and the brief welcome
   beat after signing in. Nothing else. This is the one place the flat palette
   gives way to a gradient, on purpose: dark at the top, warmth rising from
   the bottom, so arriving at OpenHearth feels like a room with a fire in it
   rather than a login form. Everything past the front door stays flat and
   quiet, because that is where the work happens.

   .hero-surface is the name this treatment has in the design notes; .landing
   is what the six screens using it are called in index.html. Both select
   the same thing so neither name goes stale. */
.landing,.hero-surface{
  position:relative;
  min-height:100vh;
  display:flex;align-items:center;justify-content:center;
  padding:28px 20px;
  background-color:#152B1F;              /* painted first, so a slow or
                                            missing photo is never a blank */
  background-image:var(--hero-scrim,none),var(--hero-photo,none),var(--ember);
  background-size:cover;
  background-position:center;
  color:var(--ember-text);
}
/* The radiant glow — centred on the first thing inside .landing-inner,
   whatever that is: the brand mark on landing and memberEntry, the heading
   itself everywhere else. .landing vertically centres a box of a different
   height on every screen (a full role picker vs. one line of "Welcome,
   Marie"), so a glow positioned by top offset or by the inner box's own
   centre drifted away from the mark on the shorter screens, or below the
   heading, depending on how tall that screen's content was. Anchoring to
   :first-child instead means it always lands on the thing a person's eye
   goes to first, on every screen, without per-screen positioning. */
.landing-inner{position:relative;z-index:1;max-width:660px;width:100%;text-align:center;color:var(--ember-text);}
.landing-inner > :first-child{position:relative;}
.landing-inner > :first-child::before{
  content:'';
  position:absolute;z-index:-1;
  left:50%;top:50%;
  width:320px;height:320px;
  transform:translate(-50%,-50%);
  background:var(--radiant);
  opacity:0;
  animation:radiant-in 1.8s ease-out .1s both;
  pointer-events:none;
}
@keyframes radiant-in{0%{opacity:0;}100%{opacity:1;}}
.landing-inner h1{color:var(--ember-text);font-size:34px;}
/* The voice: a serif, italic, used only where the app speaks to a person
   rather than labelling something. The brand lockup is exempt — a wordmark
   is not a sentence. */
/* Form furniture on a hero. label was --forest-deep, which is the exact
   colour the sign-in screens used to be painted — those labels have been
   invisible since the day they were written, and the gradient is what finally
   made it obvious. The hint was --slate, near-invisible on warmth for the
   same reason. Both belong to the surface they sit on, not to the cream
   pages behind the front door. */
.landing label,.hero-surface label{color:var(--ember-text);}
.landing .hint,.hero-surface .hint{color:rgba(251,243,231,.92);}

.landing-inner h1:not(.brand-h1){
  font-family:var(--font-voice);font-style:italic;font-weight:400;
  letter-spacing:0;line-height:1.25;}
.hero-lede{font-size:17px;line-height:1.5;margin:0 0 26px;color:rgba(251,243,231,.92);}
.brand-mark{width:52px;height:auto;margin:0 auto 18px;color:#fff;}
.brand-mark svg{display:block;width:100%;height:auto;
  /* The mark is bottom-heavy: the crossing at its base puts its ink centre of
     mass at 54.6% of its height, so centring it by geometry leaves it sitting
     visibly low. Lifting it by that 4.6% is what makes it look centred — the
     same offset is baked into the icon PNGs. */
  transform:translateY(-4.7%);}
.landing-inner h1.brand-h1{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;font-weight:800;letter-spacing:.03em;text-transform:uppercase;}
/* ---- OpenHearth brand lockup (mark + wordmark), shared across headers ---- */
.oh-lockup{display:inline-flex;align-items:center;gap:.5em;line-height:1;}
.oh-lockup svg{width:.92em;height:auto;flex:none;color:inherit;transform:translateY(-4.7%);}
.oh-lockup .oh-word{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;font-weight:800;letter-spacing:.04em;text-transform:uppercase;white-space:nowrap;}
/* ---- A Centre's own logo (Nov 4 2026) ----
   On a white tile, so a logo drawn for paper reads on the dark door and the
   dark header. The picture keeps its own shape inside the tile. */
.centre-tile{display:inline-flex;align-items:center;justify-content:center;flex:none;background:#fff;border-radius:var(--radius-sm);box-shadow:0 1px 3px rgba(0,0,0,.18);}
.centre-tile img{display:block;height:100%;width:auto;max-width:100%;}
.centre-tile-lg{height:112px;padding:10px 14px;border-radius:var(--radius);}
.centre-tile-sm{height:40px;padding:4px 6px;}
.door-centre{text-align:center;margin:0 auto 18px;}
.door-centre .powered-by{margin:10px 0 0;font-size:14px;letter-spacing:.02em;color:rgba(251,243,231,.85);}
.centre-lockup{display:inline-flex;align-items:center;gap:12px;line-height:1.2;min-width:0;}
.centre-lockup .centre-name{font-size:17px;font-weight:700;color:#fff;}
.today-centre{display:flex;align-items:center;gap:10px;margin:0 0 14px;font-weight:600;font-size:15px;}
.landing-inner p{color:rgba(251,243,231,.92);font-size:16px;line-height:1.6;margin-bottom:30px;}
.role-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;}
.role-card{background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.18);border-radius:var(--radius);padding:24px;text-align:left;color:var(--sand);min-height:var(--tap);}
.role-card:hover{background:rgba(255,255,255,.14);border-color:var(--copper-light);}
.role-card h3{color:#fff;font-size:18px;}
.role-card p{color:rgba(251,243,231,.92);font-size:15px;margin:0;line-height:1.5;}
/* The member door when this phone already knows who it belongs to. */
#memberReturn{text-align:center;}
.welcome-back{margin-bottom:12px;}
.btn-return{display:flex;align-items:center;gap:14px;width:100%;min-height:62px;padding:0 18px;margin:0 0 12px;border-radius:var(--radius);background:var(--copper);color:#fff;text-align:left;}
.btn-return:hover{background:var(--copper-deep);}
.btn-return .avatar{flex:none;width:34px;height:34px;border-radius:50%;background:rgba(255,255,255,.18);display:inline-flex;align-items:center;justify-content:center;font-family:var(--font-voice);font-size:16px;font-weight:600;}
.btn-return-text{flex:1;}
.btn-return .who{display:block;font-size:17px;font-weight:600;line-height:1.3;}
.btn-return .line{display:block;font-size:14px;color:rgba(255,255,255,.9);}
.btn-return .arrow{flex:none;font-size:20px;color:rgba(255,255,255,.8);}
.btn-hero{width:100%;min-height:52px;border-radius:var(--radius);background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.18);color:var(--sand);font-size:16px;font-weight:600;}
.btn-hero:hover{background:rgba(255,255,255,.14);border-color:var(--copper-light);}
.hero-foot{margin-top:34px;padding-top:22px;border-top:1px solid rgba(255,255,255,.14);display:flex;flex-direction:column;gap:10px;font-size:14px;color:rgba(251,243,231,.92);}
.hero-foot .hero-link{font-size:16px;justify-content:center;}
/* A sentence with one link in it, on a hero: "New here? Create your account." */
.hero-line{text-align:center;margin:16px 0 0;font-size:16px;color:rgba(251,243,231,.92);}
/* inline-flex, not inline: min-height does nothing on an inline box, and for
   a day this link was a 24px target on the one screen a newcomer meets. */
.hero-link{display:inline-flex;align-items:center;min-height:var(--tap);padding:0 6px;font:inherit;font-weight:600;color:var(--ember-text);text-decoration:underline;}
.hero-link:hover{color:#fff;}
/* The whole row is the target, and the row is 48px: a tremor should not
   have to find a 20px box. */
.landing .consent,.hero-surface .consent{display:flex;align-items:center;gap:12px;min-height:var(--tap);padding:4px 0;font-weight:400;font-size:16px;line-height:1.5;color:var(--ember-text);margin-bottom:6px;cursor:pointer;}
.landing .consent input{width:24px;height:24px;flex:none;}
/* A heading given focus when the screen changes (see focusHeading) is a place
   for a screen reader to start, not something to draw a ring around. */
h1[tabindex="-1"]:focus,h2[tabindex="-1"]:focus{outline:none;}

/* ---- The member app ----
   Drawn to the mobile design in docs/design/. On a laptop it sits in a
   phone-shaped frame so a Centre can be shown the app; on an actual phone
   the frame goes away and the screen is the screen. Past the front door
   everything is flat, cream and quiet — the hearth surfaces (the welcome,
   the thank-you, the one featured program) are the only warm things. */
.phone-wrap{min-height:100vh;background:#DCD3BE;display:flex;justify-content:center;padding:30px 14px;}
.phone{position:relative;width:402px;max-width:100%;background:var(--cream);border-radius:28px;border:8px solid #1a1a1a;overflow:hidden;min-height:720px;display:flex;flex-direction:column;}
.phone-header{padding:20px 22px;background:var(--ember-strip);color:var(--ember-text);}
.phone-header .org{font-size:14px;color:rgba(241,234,217,.88);}
.phone-body{flex:1;padding:0;}
.screen{padding:18px 22px 22px;}
/* Section titles past the front door: a plain serif, not the italic voice. */
.screen-title{font-family:var(--font-voice);font-style:normal;font-weight:500;font-size:26px;margin:0 0 4px;color:var(--forest-deep);}
.screen-sub{font-size:15px;color:var(--slate);margin:0 0 18px;}
/* The small label above a group: "Tuesday, September 15", "Also on". */
.today-label{font-size:14px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--slate);margin:0 0 10px;}
.today-label + .today-label{margin-top:22px;}
/* The day opens in the voice — "Good morning, Marie." */
.greeting{font-family:var(--font-voice);font-style:italic;font-weight:400;font-size:29px;line-height:1.25;margin:0 0 6px;color:var(--forest);}
.greeting-sub{font-size:16px;line-height:1.55;color:var(--slate);margin:0 0 20px;}

.program-card{background:#fff;border:1px solid var(--line);border-radius:var(--radius-sm);padding:14px 16px;margin-bottom:10px;box-shadow:var(--shadow-soft);}
.program-card:hover{border-color:var(--forest-deep);}
.program-card-body{cursor:pointer;min-height:var(--tap);border-radius:var(--radius-sm);}
.program-card-body:focus-visible{outline:3px solid var(--copper-light);outline-offset:4px;}
.program-card h4{font-family:inherit;font-size:17px;font-weight:600;color:var(--ink);margin-bottom:2px;}
.program-card .meta{font-size:14px;color:var(--slate);margin-bottom:0;}
.program-card .btn{margin-top:12px;width:100%;}
/* A row in a list of programs: name and where, a small Check in beside. */
.row-card{display:flex;align-items:center;gap:14px;}
.row-card .program-card-body{flex:1;min-width:0;}
.row-card .btn{margin-top:0;width:auto;flex:none;}
/* The one warm card past the door: the program a member usually goes to, or
   the first one listed. The same hearth as the moment after a check-in. */
.hearth-card{background:var(--ember-card);color:var(--ember-text);border:none;border-radius:var(--radius);padding:20px 22px;margin-bottom:16px;}
.hearth-card h4{font-family:var(--font-voice);font-style:normal;font-weight:500;font-size:23px;color:var(--ember-text);margin-bottom:4px;}
.hearth-card .meta{font-size:15px;color:rgba(251,243,231,.92);}
.hearth-card .program-card-body:focus-visible{outline-color:var(--ember-text);}
.hearth-card .btn{margin-top:18px;}
.btn-light{background:var(--ember-text);color:var(--forest-deep);font-size:17px;font-weight:700;min-height:52px;}
.btn-light:hover{background:#fff;}
.btn-lg{width:100%;min-height:56px;font-size:18px;font-weight:700;}
.btn-block{width:100%;}
.row-link{cursor:pointer;}
/* The button inside a row that opens it (rowOpen in app.js). It reads as the
   name it replaced — the row already looks like something to press — and is
   a full tap target of its own. Its focus ring is the deep green: the page's
   copper ring is under 3:1 on the white of a card. */
.row-open{display:inline-flex;align-items:center;min-height:var(--tap);padding:0;border:0;background:none;font:inherit;font-weight:600;color:var(--ink);text-align:left;cursor:pointer;}
.row-open:hover{text-decoration:underline;}
.row-open .chev{width:auto;margin-left:8px;font-weight:400;}
.row-open:focus-visible{outline-color:var(--forest-deep);}
.row-link:hover{background:var(--sand);}
.msg-card{background:#fff;border:1px solid var(--line);border-radius:var(--radius-sm);padding:16px 18px;margin-bottom:12px;font-size:16px;line-height:1.55;box-shadow:var(--shadow-soft);}
.msg-card .time{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:14px;color:var(--slate);margin-top:8px;}

/* A program's own page: the header strip carries the name, the facts sit
   in one card, and the privacy note says what a check-in is and is not. */
.detail-header{padding:18px 22px 22px;}
.back-link{display:inline-flex;align-items:center;gap:10px;min-height:var(--tap);padding:0;font-size:15px;color:rgba(241,234,217,.88);}
.back-link:hover{color:#fff;}
.detail-title{font-family:var(--font-voice);font-style:normal;font-weight:500;font-size:26px;line-height:1.2;color:var(--ember-text);margin:0;}
.facts{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:10px 20px;margin-bottom:18px;box-shadow:var(--shadow-soft);}
.fact{display:flex;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid var(--line);}
.fact:last-child{border-bottom:none;}
.fact-k{font-size:15px;color:var(--slate);flex:none;}
.fact-v{font-size:16px;font-weight:600;color:var(--ink);text-align:right;}
.detail-desc{font-size:17px;line-height:1.6;color:var(--ink);margin:0 0 22px;}
.privacy-note{background:var(--sand);border:1px solid var(--line-strong);border-radius:var(--radius-sm);padding:14px 16px;margin-top:14px;font-size:14px;line-height:1.5;color:var(--slate);}
.privacy-note strong{display:block;font-size:15px;color:var(--forest-deep);margin-bottom:4px;}

/* Lists past the door: visits, settings, who came today. */
.list{background:#fff;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;margin-bottom:22px;box-shadow:var(--shadow-soft);}
.visit-row{display:flex;align-items:center;gap:14px;padding:14px 18px;min-height:var(--tap);border-bottom:1px solid var(--line);}
.visit-row:last-child{border-bottom:none;}
.visit-day{flex:none;width:52px;font-family:var(--font-voice);font-size:17px;font-weight:600;color:var(--forest);}
.visit-program{flex:1;min-width:0;font-size:16px;color:var(--ink);}
.visit-time{flex:none;font-size:14px;color:var(--slate);}
.row-name{display:block;font-size:16px;font-weight:600;line-height:1.3;}
.row-sub{display:block;font-size:14px;color:var(--slate);}
.visits-lede{font-size:20px;margin:0 0 18px;}
.settings-row{display:flex;align-items:center;gap:14px;width:100%;padding:16px 18px;min-height:var(--tap);border-bottom:1px solid var(--line);text-align:left;}
.settings-row:last-child{border-bottom:none;}
.settings-row:hover{background:var(--sand);}
.settings-text{flex:1;}
.settings-label{display:block;font-size:17px;font-weight:600;color:var(--ink);line-height:1.3;}
.settings-sub{display:block;font-size:14px;color:var(--slate);}
.you-card{padding:20px 22px;margin-bottom:18px;}
.you-voice{font-family:var(--font-voice);font-style:italic;font-size:20px;line-height:1.35;margin:0 0 8px;color:var(--ember-text);}
.you-sub{font-size:15px;line-height:1.55;margin:0;color:rgba(251,243,231,.92);}

/* The section bar. Sticky to the bottom of whatever is scrolling — the
   phone frame on a laptop, the screen itself on a phone. */
.tabbar{position:sticky;bottom:0;z-index:5;display:grid;grid-template-columns:repeat(4,1fr);gap:2px;padding:8px 8px calc(8px + env(safe-area-inset-bottom,0px));background:#fff;border-top:1px solid var(--line);}
.tabbar .tab{position:relative;flex-direction:column;justify-content:center;gap:4px;min-height:var(--tap);padding:6px 0;border-radius:var(--radius-sm);font-size:14px;font-weight:600;background:none;color:var(--slate);}
.tabbar .tab:hover{background:var(--cream);}
.tabbar .tab.active{background:var(--sand);color:var(--forest-deep);font-weight:700;}
/* The number beside Messages. Hidden at zero — there is no "0" badge. The
   number is the signal; the copper is only warmth. It rides the corner of
   the envelope rather than sitting in the label, so a two-digit count never
   wraps the word and the four tabs stay level. */
.tab-badge{position:absolute;top:2px;left:calc(50% + 6px);display:inline-block;min-width:24px;padding:1px 7px;border-radius:100px;font-size:16px;font-weight:700;line-height:1.35;text-align:center;background:#F3E2D2;color:var(--copper-deep);vertical-align:baseline;}
.tab-badge[hidden]{display:none;}
.tab-icon{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;font-size:18px;line-height:20px;}
.tab-icon svg{width:20px;height:auto;display:block;transform:translateY(-4.7%);}

/* Pills in a row: the funder report's period, and anything else that is a
   choice between a few words. */
.tabs{display:flex;gap:8px;margin-bottom:18px;flex-wrap:wrap;}
.tab{display:inline-flex;align-items:center;min-height:var(--tap);padding:0 16px;border-radius:100px;font-size:15px;font-weight:600;background:var(--sand);color:var(--slate);}
.tab.active{background:var(--forest-deep);color:#fff;}

/* On an actual phone the frame goes away, and the section bar is pinned to
   the screen. Sticky would not do: the frame clips its overflow to keep its
   rounded corners, and a sticky element inside a clipping box sticks to
   that box, not to the screen — so the bar sat below the fold. */
@media (max-width:520px){
  .phone-wrap{padding:0;background:var(--cream);}
  .phone{width:100%;border:none;border-radius:0;min-height:100vh;}
  .phone-body{padding-bottom:calc(80px + env(safe-area-inset-bottom,0px));}
  .tabbar{position:fixed;left:0;right:0;bottom:0;}
}

code{background:var(--sand);padding:2px 7px;border-radius:5px;font-size:15px;}

/* Narrow screens: the portal's sidebar becomes a horizontal strip so staff on
   a phone still reach every section. minmax(0,1fr), not 1fr, for the same
   reason as the desktop rule above: a single 1fr column still gets an
   automatic min-width equal to its widest content's preferred width (an
   auto-layout table is often the culprit, even one whose only row is an
   empty-state sentence), which reopened the header-gap bug at phone widths. */
/* The section menu. On a phone the sidebar used to become a strip that
   scrolled sideways — seven sections, two or three visible, the rest found
   by dragging, and a sideways scroller at the top edge of the screen is
   also where a back swipe begins. It folds into one button instead, which
   names the section you are in and opens the list beneath the header.
   Hidden above phone width, where the sidebar is simply always there. */
.nav-toggle,.nav-signout,.nav-scrim{display:none;}
.nav-toggle-bars{display:inline-flex;flex-direction:column;justify-content:center;gap:4px;width:18px;}
.nav-toggle-bars span{display:block;height:2px;border-radius:2px;background:currentColor;}
/* The way back, on every screen that has somewhere to go back to. */
.back-btn{margin-bottom:14px;}

@media (max-width:720px){
  .shell{grid-template-columns:minmax(0,1fr);}
  .topbar{position:sticky;top:0;z-index:30;padding:10px 16px;gap:10px;}
  .topbar .sub{display:none;}
  .centre-lockup .centre-name{display:none;}  /* the logo alone on a phone; the name is on Today and the Dashboard */
  .topbar #signOutBtn{display:none;}
  .topbar .nav-toggle{display:inline-flex;align-items:center;gap:10px;}
  .sidebar{display:none;}
  .sidebar[data-open="true"]{display:flex;flex-direction:column;position:fixed;left:0;right:0;top:68px;z-index:25;
    max-height:calc(100vh - 68px);overflow-y:auto;padding:10px 12px 14px;gap:4px;
    border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius);
    box-shadow:0 18px 44px rgba(38,36,29,.28);}
  .nav-item{white-space:normal;width:100%;}
  .sidebar .nav-signout{display:flex;margin-top:8px;padding-top:4px;border-top:1px solid rgba(255,255,255,.14);border-radius:0 0 var(--radius-sm) var(--radius-sm);}
  .nav-scrim[data-open="true"]{display:block;position:fixed;inset:0;z-index:20;background:rgba(21,43,31,.4);}
  main{padding:22px 18px 64px;}
  .role-grid{grid-template-columns:1fr;}
  h1{font-size:26px;}
}

/* ---------- System states ----------
   Every async action shows one of: loading, empty, error, or a confirmation.
   A blank screen is never an acceptable state — a member staring at nothing
   cannot tell whether their visit was counted. */

/* Loading: a skeleton of the shape that is coming, not a spinner, so the page
   does not jump when content lands. */
.skeleton{display:block;background:linear-gradient(90deg,var(--sand) 25%,#F7F2E6 37%,var(--sand) 63%);
  background-size:400% 100%;border-radius:8px;animation:skeleton-sweep 1.4s ease infinite;}
@keyframes skeleton-sweep{0%{background-position:100% 50%;}100%{background-position:0 50%;}}
.skeleton-line{height:16px;margin-bottom:12px;}
.skeleton-line.short{width:45%;}
.skeleton-line.medium{width:70%;}
.skeleton-card{border:1px solid var(--line);border-radius:var(--radius-sm);padding:20px;margin-bottom:12px;background:#fff;}
.loading-label{font-size:15px;color:var(--slate);margin-bottom:14px;}

/* Empty: says what would be here and what to do, never just "none". */
.empty{text-align:center;padding:40px 24px;border:1px dashed var(--line-strong);border-radius:var(--radius);background:#fff;}
.empty .empty-icon{font-size:34px;line-height:1;margin-bottom:12px;}
.empty h4{margin-bottom:6px;}
.empty p{color:var(--slate);font-size:15px;margin:0 auto;max-width:34ch;}

/* Confirmation. The tick only ever appears on .success — which means the
   Centre has the check-in. Anything still on the phone uses .notice. */
.state-row{display:flex;gap:12px;align-items:flex-start;}
.tick{flex:none;width:26px;height:26px;border-radius:50%;background:var(--teal);color:#fff;
  display:inline-flex;align-items:center;justify-content:center;font-size:15px;font-weight:700;
  animation:tick-pop .32s cubic-bezier(.2,.8,.3,1.2) both;}
@keyframes tick-pop{0%{transform:scale(.4);opacity:0;}100%{transform:scale(1);opacity:1;}}
.clock{flex:none;width:26px;height:26px;border-radius:50%;background:var(--line-strong);color:var(--ink);
  display:inline-flex;align-items:center;justify-content:center;font-size:14px;}

/* Persistent sync status. Visible whether or not anything is wrong, so a
   member never has to guess, and "on this phone" never looks like "sent". */
.syncstate{display:flex;gap:10px;align-items:center;flex-wrap:wrap;
  padding:10px 14px;border-radius:var(--radius-sm);font-size:15px;margin-bottom:16px;border:1px solid transparent;}
.syncstate .dot{flex:none;width:10px;height:10px;border-radius:50%;}
.syncstate .label{flex:1;min-width:150px;}
.syncstate.is-synced{background:#EDF3F1;border-color:#C6DAD5;color:var(--teal);}
.syncstate.is-synced .dot{background:var(--teal);}
.syncstate.is-waiting{background:var(--sand);border-color:var(--line-strong);color:var(--ink);}
.syncstate.is-waiting .dot{background:var(--copper);}
.syncstate.is-offline{background:var(--sand);border-color:var(--line-strong);color:var(--ink);}
.syncstate.is-offline .dot{background:var(--slate);}
.syncstate.is-problem{background:#FBEAE5;border-color:#E3A791;color:var(--danger);}
.syncstate.is-problem .dot{background:var(--danger);}
/* Each check-in that has not gone through, on its own line under the
   sentence, so staff can add it from what the screen says. */
.syncstate .stuck-list{flex-basis:100%;margin:0 0 0 20px;padding:0 0 0 18px;line-height:1.45;overflow-wrap:anywhere;}
.syncstate .stuck-list li + li{margin-top:4px;}

/* Staff connection chip in the top bar. */
.conn{display:inline-flex;align-items:center;gap:7px;font-size:14px;color:rgba(241,234,217,.88);}
.conn .dot{width:9px;height:9px;border-radius:50%;background:#7FB09E;}
.conn.is-offline .dot{background:var(--copper-light);}

/* The moment after a check-in. Appears under the green confirmation, never
   in place of it. Fades up over 600ms so it reads as a greeting rather than
   a status change; static under reduced-motion. */
.moment{background:var(--ember-card);color:var(--ember-text);border-radius:var(--radius);
  padding:20px 22px;margin:0 0 16px;box-shadow:var(--shadow-soft);
  animation:moment-in .6s ease-out .15s both;}
.moment-voice{font-family:var(--font-voice);font-style:italic;font-weight:400;
  font-size:23px;line-height:1.3;margin:0 0 6px;color:var(--ember-text);}
.moment-sub{font-size:16px;line-height:1.5;margin:0;color:rgba(251,243,231,.92);}
@keyframes moment-in{0%{opacity:0;transform:translateY(8px);}100%{opacity:1;transform:none;}}

/* A hero photograph is the one heavy thing this app would ever download.
   On a data-saver connection or a small old handset it is simply skipped —
   the gradient was always doing the real work. */
/* ---- The thank-you ----
   A member sees this screen every single visit, so it is the one place the
   app is allowed to stop being a form and simply be glad someone came. It
   covers the whole phone rather than sitting below a dead camera box.

   It is raised ONLY for a check-in the Centre has actually received. A
   celebration is a claim that the visit landed; a check-in still queued on
   the phone keeps the honest "saved on this phone" notice instead. That rule
   is the same one the green tick follows, and it matters more here — this
   screen is much harder to disbelieve. */
.celebrate{position:absolute;inset:0;z-index:10;
  display:flex;align-items:center;justify-content:center;
  padding:28px 22px;text-align:center;overflow:hidden;
  background-color:#152B1F;background-image:var(--ember);
  background-size:cover;background-position:center;
  color:var(--ember-text);
  animation:celebrate-in .45s ease-out both;}
@keyframes celebrate-in{from{opacity:0;}to{opacity:1;}}

/* On a real phone the thank-you owns the screen. Absolute positioning sizes
   it to .phone, which is as tall as whatever screen is underneath — so on a
   long one the message centred itself somewhere below the fold. Fixed fixes
   that. Above this width we are on a laptop looking at the demo, where
   covering the whole browser window would break the phone frame Garrett
   shows Centres, so it stays inside the frame there. */
@media (max-width:520px){
  .celebrate{position:fixed;}
}

.celebrate-inner{position:relative;z-index:2;width:100%;max-width:320px;}
.celebrate-seal{width:74px;margin:0 auto 20px;color:var(--ember-text);
  animation:seal-in .6s cubic-bezier(.2,.8,.3,1.25) .12s both;}
.celebrate-seal svg{display:block;width:100%;height:auto;}
@keyframes seal-in{from{transform:scale(.5);opacity:0;}to{transform:scale(1);opacity:1;}}

.celebrate h1{font-family:var(--font-voice);font-style:italic;font-weight:400;
  font-size:30px;line-height:1.2;margin:0 0 10px;color:var(--ember-text);
  /* Two even lines rather than a long one and an orphan. Ignored by older
     browsers, which just get the default wrap. */
  text-wrap:balance;}
.celebrate-program{font-size:19px;font-weight:600;line-height:1.35;margin:0 0 14px;color:var(--ember-text);}
.celebrate-line{font-size:17px;line-height:1.5;margin:0 0 8px;color:rgba(251,243,231,.92);}
.celebrate-note{font-size:17px;line-height:1.5;margin:0 0 20px;color:rgba(251,243,231,.92);}
.celebrate-meta{font-size:15px;line-height:1.5;margin:0 0 26px;color:rgba(251,243,231,.92);text-wrap:balance;}
.celebrate .btn{min-width:150px;justify-content:center;}

/* Embers rising, not confetti. The app is called OpenHearth; generic
   celebration graphics belong to every other app. One pass that settles
   inside five seconds, so nothing moves on screen for longer than WCAG
   2.2.2 allows without giving the member a pause control. */
.celebrate-sparks{position:absolute;inset:0;z-index:1;overflow:hidden;pointer-events:none;}
.celebrate-sparks i{position:absolute;bottom:-14px;
  width:var(--s);height:var(--s);border-radius:50%;background:var(--c);opacity:0;
  animation:spark-rise var(--d) ease-out var(--delay) 1 both;}
@keyframes spark-rise{
  0%{transform:translate3d(0,0,0) scale(.5);opacity:0;}
  15%{opacity:.85;}
  70%{opacity:.5;}
  100%{transform:translate3d(var(--dx),calc(-1 * var(--rise)),0) scale(.2);opacity:0;}
}

@media (prefers-reduced-data:reduce),(max-width:360px){
  :root{--hero-photo:none;--hero-scrim:none;}
}

@media (prefers-reduced-motion:reduce){
  .skeleton{animation:none;background:var(--sand);}
  .tick{animation:none;}
  .moment{animation:none;}
  /* The thank-you still arrives, it just arrives still. */
  .celebrate,.celebrate-seal{animation:none;}
  .celebrate-sparks{display:none;}
  /* The glow still shows — it's just already there instead of blooming in. */
  .landing-inner > :first-child::before{animation:none;opacity:1;}
}


/* ---- A figure that explains itself ----
   Funder language is jargon by default. Every number on the staff side can
   say what it counts and why it is asked for, without turning the screen
   into documentation. */
.metric{background:#fff;border:1px solid var(--line);border-radius:var(--radius-sm);box-shadow:var(--shadow-soft);margin-bottom:14px;}
.stat-row .metric{margin-bottom:0;}
.metric-head{display:flex;flex-direction:column;align-items:flex-start;gap:4px;width:100%;min-height:var(--tap);
  padding:18px 20px;text-align:left;background:none;border:none;cursor:pointer;border-radius:var(--radius-sm);}
.metric-head:hover{background:var(--cream);}
.metric-label{font-size:14px;color:var(--slate);}
.metric-value{font-family:Georgia,serif;font-size:32px;font-weight:700;color:var(--forest-deep);line-height:1.1;}
.metric-more{font-size:14px;font-weight:600;color:var(--copper);}
.metric-def{margin:0;padding:0 20px 18px;font-size:16px;line-height:1.6;color:var(--ink);max-width:62ch;}
.link-like{background:none;border:none;padding:0;font:inherit;font-weight:600;color:var(--copper);text-decoration:underline;cursor:pointer;}

/* ---- Names are doors ----
   Anywhere a member is named on the staff side, that name opens their file. */
.member-link{color:var(--copper);font-weight:600;}
.name-list{display:flex;flex-wrap:wrap;gap:10px 18px;margin:8px 0 0;line-height:1.6;}
.chev{color:var(--slate);text-align:right;width:1em;}

/* ---- The poster that goes on the door ----
   Printed by a Centre, read by whoever walks in. Big code for typing, QR for
   scanning, and a plain sentence about what checking in does and does not
   record. */
.poster{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:40px 32px;text-align:center;max-width:640px;}
.poster-brand{display:inline-flex;align-items:center;gap:10px;margin-bottom:26px;color:var(--ink);}
.poster-centre{gap:14px;}
.poster-centre-name{font-weight:800;font-size:18px;line-height:1.25;text-align:left;}
.poster-brand .oh-word{font-weight:800;letter-spacing:.04em;text-transform:uppercase;font-size:16px;}
.poster-title{font-size:34px;margin:0 0 6px;}
.poster-sub{color:var(--slate);font-size:16px;margin:0 0 26px;}
.poster-qr{margin:0 auto 22px;max-width:320px;}
.poster-qr .qr{width:100%;height:auto;display:block;}
.poster-label{font-size:16px;color:var(--slate);margin:0 0 6px;}
.poster-code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:34px;font-weight:700;
  letter-spacing:.06em;color:var(--forest-deep);margin:0 0 24px;}
.poster-foot{font-size:15px;color:var(--slate);margin:0;line-height:1.5;}

@media print{
  .topbar,.sidebar,.no-print,#portalConn{display:none !important;}
  .shell{display:block;}
  main{padding:0;max-width:none;}
  .poster{border:none;box-shadow:none;max-width:none;padding:0;}
  .poster-title{font-size:42px;}
  .poster-code{font-size:44px;}
}

/* The funder report on a phone: the headline figure explains itself, and
   how often people came back is drawn, not tabled. */
.figure-card .metric-label{font-size:14px;color:var(--slate);margin-bottom:6px;}
.figure-card .metric-value{font-family:var(--font-voice);font-style:normal;font-size:34px;font-weight:600;color:var(--forest-deep);line-height:1.05;margin-bottom:8px;}
.figure-card p{font-size:15px;line-height:1.55;color:var(--ink);margin:0;}
.freq{display:flex;flex-direction:column;gap:14px;}
.freq-row{display:flex;flex-direction:column;gap:6px;}
.freq-head{display:flex;justify-content:space-between;gap:12px;font-size:15px;}
.freq-head strong{color:var(--forest-deep);}
.freq-bar{height:10px;border-radius:100px;background:var(--sand);overflow:hidden;}
.freq-bar > span{display:block;height:100%;border-radius:100px;background:var(--copper);}
/* Finding someone at the front desk: the rows that match what was typed. */
.result-row{display:flex;align-items:center;gap:12px;width:100%;padding:14px 18px;min-height:var(--tap);border-bottom:1px solid var(--line);text-align:left;}
.result-row:last-child{border-bottom:none;}
.result-row:hover{background:var(--sand);}
.result-row.is-picked{background:var(--sand);}
.result-tick{flex:none;width:24px;height:24px;border-radius:50%;background:var(--teal);color:#fff;display:inline-flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;}
/* Everyone picked for a group check-in (Oct 29 2026). Each name is a button
   that takes its person off the list, at full tap height. */
.picked-list{margin:12px 0 4px;}
.picked-count{margin:0 0 8px;}
.picked-names{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px;}
.picked-name{display:inline-flex;align-items:center;gap:6px;min-height:var(--tap);padding:0 14px;border-radius:100px;border:1px solid var(--line);background:var(--sand);color:var(--slate);font-size:15px;font-weight:600;}
.picked-name:hover{border-color:var(--teal);}
.metric-head.is-inline{flex-direction:row;align-items:center;justify-content:space-between;gap:14px;padding:16px 20px;}
.metric-head.is-inline .metric-label{font-size:16px;color:var(--ink);font-weight:600;}

/* ---- Figures as a list ----
   The dashboard reads as one list of lines rather than a row of cards, so
   the four numbers scan top-to-bottom like the funder report does. */
.metric-list{background:#fff;border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow-soft);overflow:hidden;margin-bottom:22px;}
.metric-row + .metric-row{border-top:1px solid var(--line);}
.metric-row-head{display:flex;align-items:center;gap:16px;width:100%;min-height:var(--tap);
  padding:16px 20px;background:none;border:none;text-align:left;cursor:pointer;}
.metric-row-head:hover{background:var(--sand);}
.metric-row-label{flex:1;font-weight:600;color:var(--ink);}
.metric-row-value{font-family:Georgia,serif;font-size:24px;font-weight:700;color:var(--forest-deep);line-height:1;}
.metric-row .metric-def{padding:0 20px 18px;}

/* Daycare sign-in and sign-out (Oct 13 2026). A row per child: name and
   where they are on the left, the one thing to do on the right. */
.daycare-row{display:flex;align-items:center;gap:14px;justify-content:space-between;padding:14px 0;border-top:1px solid var(--line);flex-wrap:wrap;}
.daycare-row:first-of-type{border-top:none;}
.daycare-open-card{border-left:4px solid var(--copper);}
.daycare-open-list{list-style:none;margin:0;padding:0;}
.daycare-open{padding:12px 0;border-top:1px solid var(--line);}
.daycare-open:first-child{border-top:none;}
.daycare-open .field{margin:8px 0;}
.daycare-open textarea{width:100%;box-sizing:border-box;}
.daycare-who{flex:1 1 220px;min-width:0;}
.daycare-who .row-open{font-size:18px;}
.daycare-here .daycare-who .hint{color:var(--teal);font-weight:600;}
.daycare-gone{opacity:.92;}
.daycare-fieldset{border:none;padding:0;margin:0 0 12px;}
.daycare-fieldset legend{padding:0;margin-bottom:8px;}
.daycare-pick{display:flex;align-items:center;gap:12px;min-height:var(--tap);padding:12px 14px;border:1px solid var(--line-strong);border-radius:var(--radius-sm);margin-bottom:10px;cursor:pointer;background:#fff;}
.daycare-pick input{width:22px;height:22px;flex:none;accent-color:var(--forest);}
.daycare-pick:has(input:checked){border:2px solid var(--forest);}
.daycare-pick-other{background:var(--sand);border-style:dashed;}
.card .consent{display:flex;align-items:center;gap:12px;min-height:var(--tap);font-weight:400;cursor:pointer;}
.card .consent input{width:22px;height:22px;flex:none;accent-color:var(--forest);}
.daycare-adults,.daycare-recent{list-style:none;padding:0;margin:0 0 14px;}
.daycare-adult{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid var(--line);flex-wrap:wrap;}
.daycare-recent li{padding:6px 0;border-bottom:1px solid var(--line);font-size:15px;}
