/* ============================================================================
   Little Fitrah — Admin Portal shell & responsive layer
   ============================================================================

   WHY THIS FILE EXISTS

   The portal is ten static pages that each style themselves with Tailwind
   utility classes from the Play CDN. That works on a desktop and falls apart on
   a phone: the dashboard sidebar is a fixed 224px of a 375px screen, every data
   table is wider than the viewport, modals are centred boxes that overflow the
   fold, and tap targets are 12px text links.

   This stylesheet adds the parts a utility-class-per-element approach cannot
   express — an app shell, a phone breakpoint, and one set of component looks
   shared by every page — without touching any page's behaviour. Nothing here
   knows what a child or an activity is; it only decides how things look.

   SPECIFICITY NOTE — read before editing

   The Tailwind Play CDN generates its stylesheet at runtime and *appends* it to
   <head>, so it always sits after this file in document order and wins every
   specificity tie. Rules that have to override a utility class already on the
   element therefore carry !important. Rules for elements this file owns
   outright (everything prefixed .lf-) do not need it, and do not have it.

   BREAKPOINT

   767px and below is "phone". The portal has no tablet-specific design; between
   768px and 1024px it uses the desktop layout, which fits.
   ============================================================================ */

/* ---------------------------------------------------------------------------
   0. The hidden attribute
   ---------------------------------------------------------------------------
   The browser's own [hidden] rule lives in the UA stylesheet, which every author
   rule outranks. So `el.hidden = true` on anything carrying a class that sets
   display — .lf-note is display:flex — does nothing at all, silently: the
   attribute is set, the aria tree updates, and the box stays on screen.

   Found on 2026-09-08 driving the gender backfill notice, which announced "1 still
   to answer" after the last answer had been given. !important because a plain
   [hidden] ties on specificity with any single class and would then be decided by
   source order, which is not a thing to leave to whoever edits this file next.
   --------------------------------------------------------------------------- */

[hidden] { display: none !important; }

/* ---------------------------------------------------------------------------
   1. Type
   ---------------------------------------------------------------------------
   Plus Jakarta Sans for interface text, Fraunces for display — page titles, the
   wordmark, and the big dashboard numbers. Fraunces carries a SOFT axis, which
   is what keeps a serif from reading as corporate in a preschool product.
   Both are requested from each page's <head> with display=swap and fall back to
   the previous system stack, so a blocked or slow font request costs nothing but
   the typeface.
   --------------------------------------------------------------------------- */
:root {
  /* Brand — CONTEXT.md § 7 Design system. Single source for the shell. */
  --lf-gold: #C5A059;
  --lf-gold-deep: #A9843C;
  --lf-gold-light: #D9BE86;
  --lf-cream: #F9F6EE;
  --lf-cream-deep: #F2ECDF;
  --lf-earth: #4A3E3D;
  --lf-slate: #6E655F;
  --lf-sage: #7CA982;
  --lf-teal: #5B9EA0;
  --lf-periwinkle: #8993C4;
  --lf-rose: #DB8C83;
  --lf-rose-deep: #B4655C;
  --lf-amber: #E5B25D;
  --lf-mauve: #C48B96;
  --lf-purple: #A38BB8;

  /* THE LOGO'S OWN TWO, sampled from Reference/image/original.png (T-158). The wordmark
     is set in six hues and the portal was using one of them; these are the two that had
     no token at all. `--lf-blush` is the deep rose of the L and the F, `--lf-peach` the
     apricot of the a. */
  --lf-blush: #B07C85;
  --lf-peach: #F5B38A;

  /* A READABLE PARTNER FOR EVERY HUE, and the reason the set exists: the hues above are
     chosen to sit *on* cream at 14-20% as a wash, and not one of them passes contrast as
     text. `--lf-peach` on white is 1.8:1. So anything that colours a LABEL uses the
     `-deep` token and anything that colours a WASH uses the plain one; `--lf-accent` /
     `--lf-accent-deep` below is that pairing made into one switch.
     These started as the numbers `.lf-pill--teal`, `--sage`, `--purple` and `--amber` have
     always carried inline, and three of them have been DARKENED to get there: measured
     against cream, `var(--lf-teal-deep)` was 4.48:1, `#9A6F1D` 4.17:1 and `#B06A34` 3.92:1, all short
     of WCAG AA 4.5 for the 12px labels they colour. They are 4.61, 4.63 and 4.62 now. The
     four pill rules are pointed at these tokens below rather than left holding their own
     copies -- one value, one name, or the next person darkens one and not the other, which
     is D-69 with different numbers.

     AND THE NUMBERS ABOVE ARE MEASURED AGAINST THE WASH, NOT AGAINST CREAM. I got this
     wrong first: I sized these against `--lf-cream` and reported 4.6:1, but almost every
     one of them colours a label sitting on its OWN hue at 14-22%, which is darker than
     cream -- so the real figures were 3.96 to 4.38 and eight of the nine were still short
     of AA. Solved against the strongest wash each token actually appears on, they are
     4.58-4.69 now. Only `--lf-periwinkle-deep` needed nothing. The lesson is the cheap
     one: contrast is a property of a PAIR, and the pair is the label and the thing
     directly behind it -- never the page background two layers down.
     T-159 finished the sweep: `.lf-tbtn--*`, `.lf-days--*`, `.lf-out--good`, `.lf-enrol__days`
     and the four print overrides all read these tokens now, so those three colours appear in
     no declaration outside the three lines below (and in this sentence, which is why a
     grep for them is not the check -- the check is that nothing but a token DECLARES one).
     `--lf-amber-deep` moved to `#8A6420` rather than the other way
     round: that was `.lf-days--soon`'s own inline value and it measures 4.95:1 on cream
     against the 4.63 of the one I first wrote, so unifying UPWARDS raised every amber label
     instead of lowering one. A de-duplication that quietly picks the weaker of two colours
     is a contrast regression wearing a tidy-up's clothes. */
  --lf-gold-text: #7F6228;
  --lf-teal-deep: #386E6F;
  --lf-sage-deep: #486F4D;
  --lf-purple-deep: #74598B;
  --lf-amber-deep: #86611F;
  --lf-periwinkle-deep: #565FA0;
  --lf-mauve-deep: #8E5565;
  --lf-blush-deep: #86565F;
  --lf-peach-deep: #985B2D;

  /* THE ROTATION. Eight accents, in the order the wordmark reads them, so a page that
     wants "the next colour" asks for a number rather than picking a hue. Colour then
     means something -- the same subject is the same colour on every screen -- instead of
     being decoration that drifts page by page. */
  --lf-accent: var(--lf-gold);
  --lf-accent-deep: var(--lf-gold-text);

  /* One surface colour, named. `.lf-rail` reached for `var(--lf-surface, #fff)` and the
     fallback was the only reason it painted — a fallback is a token that does not exist
     wearing a disguise, and the next rule to use it without one silently loses its
     declaration. Which is exactly what happened to `--lf-earth-brown` and
     `--lf-warm-slate`: eleven colours on the child report, dropped (D-71). */
  --lf-surface: #fff;

  --lf-line: rgba(197, 160, 89, .18);
  --lf-line-soft: rgba(197, 160, 89, .10);

  --lf-font: 'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --lf-display: 'Fraunces', 'Iowan Old Style', Georgia, serif;

  /* Elevation. Warm-tinted rather than neutral grey — a grey shadow over cream
     reads as dirt. */
  --lf-shadow-sm: 0 1px 2px rgba(74, 62, 61, .04), 0 2px 8px -4px rgba(74, 62, 61, .08);
  --lf-shadow: 0 1px 2px rgba(74, 62, 61, .04), 0 10px 28px -14px rgba(74, 62, 61, .16);
  --lf-shadow-lg: 0 2px 4px rgba(74, 62, 61, .04), 0 24px 56px -20px rgba(74, 62, 61, .28);

  --lf-r-sm: 12px;
  --lf-r: 16px;
  --lf-r-lg: 22px;
  --lf-r-xl: 28px;

  /* Shell metrics. The page offsets are derived from these, so changing the bar
     height here moves every page's content with it. */
  --lf-topbar: 56px;
  --lf-tabbar: 60px;
  --lf-pad: 16px;
  --lf-safe-t: env(safe-area-inset-top, 0px);
  --lf-safe-b: env(safe-area-inset-bottom, 0px);

  /* iOS-ish easing: fast out, long settle. Used for every shell transition so
     the drawer, the sheets and the tab bar all feel like one surface. */
  --lf-ease: cubic-bezier(.32, .72, 0, 1);
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  font-family: var(--lf-font) !important;
  /* A single very soft warm bloom behind the cream. Flat cream over a whole
     phone screen looks like an unstyled page; this reads as paper. */
  background-color: var(--lf-cream) !important;
  background-image:
    radial-gradient(1200px 520px at 12% -8%, rgba(197, 160, 89, .10), transparent 62%),
    radial-gradient(900px 460px at 100% 0%, rgba(139, 158, 160, .07), transparent 60%);
  background-attachment: fixed;
  background-repeat: no-repeat;
  color: var(--lf-earth);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  -webkit-tap-highlight-color: transparent;
}

h1, h2, h3 { letter-spacing: -.011em; }

/* ---------------------------------------------------------------------------
   2. Replacements for the dead @apply block
   ---------------------------------------------------------------------------
   index.html carried a <style> block that defined .card, .nav-link,
   .btn-primary, .btn-secondary and .badge with Tailwind @apply. The Play CDN
   only processes @apply inside <style type="text/tailwindcss">, so that block
   was inert CSS the browser dropped: the dashboard cards had no background, and
   the Google sign-in button was unstyled text on cream. These are the same
   classes, written as real CSS, and improved rather than merely restored.
   --------------------------------------------------------------------------- */
.card {
  background: #fff;
  border: 1px solid var(--lf-line-soft);
  border-radius: var(--lf-r-lg);
  padding: 24px;
  box-shadow: var(--lf-shadow-sm);
}

.badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .01em;
}

/* ---------------------------------------------------------------------------
   2b. The accent rotation (T-158)
   ---------------------------------------------------------------------------
   One class sets both halves of a hue. Put it on any container and everything
   inside that reads `--lf-accent` (a wash) or `--lf-accent-deep` (a label)
   follows. Nested wins over outer, which is how a section can carry a tone and
   a card inside it can still carry its own.
   --------------------------------------------------------------------------- */
.lf-accent--1 { --lf-accent: var(--lf-gold);       --lf-accent-deep: var(--lf-gold-text); }
.lf-accent--2 { --lf-accent: var(--lf-teal);       --lf-accent-deep: var(--lf-teal-deep); }
.lf-accent--3 { --lf-accent: var(--lf-sage);       --lf-accent-deep: var(--lf-sage-deep); }
.lf-accent--4 { --lf-accent: var(--lf-periwinkle); --lf-accent-deep: var(--lf-periwinkle-deep); }
.lf-accent--5 { --lf-accent: var(--lf-blush);      --lf-accent-deep: var(--lf-blush-deep); }
.lf-accent--6 { --lf-accent: var(--lf-purple);     --lf-accent-deep: var(--lf-purple-deep); }
.lf-accent--7 { --lf-accent: var(--lf-mauve);      --lf-accent-deep: var(--lf-mauve-deep); }
.lf-accent--8 { --lf-accent: var(--lf-peach);      --lf-accent-deep: var(--lf-peach-deep); }

.nav-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-radius: 14px;
  color: var(--lf-earth);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  transition: background-color .18s ease, color .18s ease;
}
.nav-link:hover { background: color-mix(in srgb, var(--lf-accent) 12%, transparent); }
.nav-link.active {
  background: color-mix(in srgb, var(--lf-accent) 18%, transparent);
  color: var(--lf-accent-deep);
  font-weight: 600;
}
/* THE ICON IS ALWAYS IN ITS SUBJECT'S COLOUR, not only when active -- that is what makes
   a rail of thirteen links scannable rather than a list. The LABEL stays `--lf-earth`
   until the link is current, because thirteen coloured labels is a ransom note. */
.nav-link .lf-i { flex: 0 0 auto; color: var(--lf-accent-deep); opacity: .85; }
.nav-link:hover .lf-i,
.nav-link.active .lf-i { opacity: 1; }

/* A HUE PER SUBJECT, KEYED TO THE ICON NAME (T-158). `shell.js` passes the icon through
   as `data-tone`, so the colour is decided here in the design system rather than in the
   navigation list -- and the same subject is the same colour for every role that sees it.
   Anything unmapped simply stays gold, which is the `:root` default. */
.nav-link[data-tone="children"], .lf-qa[data-tone="children"]     { --lf-accent: var(--lf-blush);      --lf-accent-deep: var(--lf-blush-deep); }
.nav-link[data-tone="users"], .lf-qa[data-tone="users"]        { --lf-accent: var(--lf-periwinkle); --lf-accent-deep: var(--lf-periwinkle-deep); }
.nav-link[data-tone="centers"], .lf-qa[data-tone="centers"]      { --lf-accent: var(--lf-sage);       --lf-accent-deep: var(--lf-sage-deep); }
.nav-link[data-tone="layers"], .lf-qa[data-tone="layers"]       { --lf-accent: var(--lf-purple);     --lf-accent-deep: var(--lf-purple-deep); }
.nav-link[data-tone="target"], .lf-qa[data-tone="target"]       { --lf-accent: var(--lf-teal);       --lf-accent-deep: var(--lf-teal-deep); }
.nav-link[data-tone="reports"], .lf-qa[data-tone="reports"]      { --lf-accent: var(--lf-teal);       --lf-accent-deep: var(--lf-teal-deep); }
.nav-link[data-tone="link"], .lf-qa[data-tone="link"]         { --lf-accent: var(--lf-mauve);      --lf-accent-deep: var(--lf-mauve-deep); }
.nav-link[data-tone="group"], .lf-qa[data-tone="group"]        { --lf-accent: var(--lf-periwinkle); --lf-accent-deep: var(--lf-periwinkle-deep); }
.nav-link[data-tone="calendarDays"], .lf-qa[data-tone="calendarDays"] { --lf-accent: var(--lf-peach);      --lf-accent-deep: var(--lf-peach-deep); }
.nav-link[data-tone="calendarUp"], .lf-qa[data-tone="calendarUp"]   { --lf-accent: var(--lf-peach);      --lf-accent-deep: var(--lf-peach-deep); }
.nav-link[data-tone="history"], .lf-qa[data-tone="history"]      { --lf-accent: var(--lf-mauve);      --lf-accent-deep: var(--lf-mauve-deep); }
.nav-link[data-tone="photos"], .lf-qa[data-tone="photos"]       { --lf-accent: var(--lf-sage);       --lf-accent-deep: var(--lf-sage-deep); }
.nav-link[data-tone="archive"], .lf-qa[data-tone="archive"]      { --lf-accent: var(--lf-peach);      --lf-accent-deep: var(--lf-peach-deep); }
/* T-275: the parent's own glyphs. Today was gold as `dashboard` and gets peach; the record
   keeps the teal it had as `reports`; the report's sprout takes the growing green. Home
   stays gold, as `dashboard` did. */
.nav-link[data-tone="sun"], .lf-qa[data-tone="sun"]          { --lf-accent: var(--lf-peach);      --lf-accent-deep: var(--lf-peach-deep); }
.nav-link[data-tone="sprout"], .lf-qa[data-tone="sprout"]       { --lf-accent: var(--lf-sage);       --lf-accent-deep: var(--lf-sage-deep); }
.nav-link[data-tone="book"], .lf-qa[data-tone="book"]         { --lf-accent: var(--lf-teal);       --lf-accent-deep: var(--lf-teal-deep); }

/* ---------------------------------------------------------------------------
   3. Buttons
   ---------------------------------------------------------------------------
   .lf-btn is the portal's one button. Every page previously repeated the same
   eight utility classes per button and drifted — some rounded-xl, some
   rounded-lg, three different paddings. Height is floored at 44px so a phone
   tap lands, which is the actual reason this exists.
   --------------------------------------------------------------------------- */
.lf-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 20px;
  border: 1px solid transparent;
  border-radius: 14px;
  font-family: var(--lf-font);
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition: transform .12s var(--lf-ease), box-shadow .18s ease,
              background-color .18s ease, border-color .18s ease, opacity .18s ease;
  -webkit-user-select: none;
  user-select: none;
}
.lf-btn:active { transform: scale(.975); }
.lf-btn:focus-visible {
  outline: 2px solid var(--lf-gold);
  outline-offset: 2px;
}
.lf-btn:disabled,
.lf-btn[disabled] {
  opacity: .45;
  cursor: not-allowed;
  transform: none;
}

.lf-btn--primary {
  /* A flat fill at this size looks cheap; the gradient is what makes gold read
     as metal rather than mustard. */
  background: linear-gradient(160deg, #D2B173 0%, var(--lf-gold) 52%, #B58F45 100%);
  color: #fff;
  box-shadow: 0 1px 1px rgba(74, 62, 61, .06), 0 8px 18px -10px rgba(169, 132, 60, .70);
}
.lf-btn--primary:hover { background: linear-gradient(160deg, #DBBC83 0%, #CBA862 52%, #BE9850 100%); }

.lf-btn--secondary {
  background: #fff;
  border-color: var(--lf-line);
  color: var(--lf-gold-deep);
  box-shadow: var(--lf-shadow-sm);
}
.lf-btn--secondary:hover { background: rgba(197, 160, 89, .07); border-color: rgba(197, 160, 89, .38); }

.lf-btn--ghost { background: transparent; color: var(--lf-slate); }
.lf-btn--ghost:hover { background: rgba(197, 160, 89, .10); color: var(--lf-earth); }

.lf-btn--danger { background: rgba(219, 140, 131, .14); color: var(--lf-rose-deep); }
.lf-btn--danger:hover { background: rgba(219, 140, 131, .22); }

/* --destructive is filled, not tinted. A permanent delete is the one action in the
   portal that cannot be undone, and it should not look like the tinted "deactivate"
   buttons that can be. */
.lf-btn--destructive {
  background: linear-gradient(160deg, #C97F76 0%, #B4655C 60%, #9E544C 100%);
  color: #fff;
  box-shadow: 0 1px 1px rgba(74, 62, 61, .06), 0 8px 18px -10px rgba(180, 101, 92, .7);
}
.lf-btn--destructive:hover { background: linear-gradient(160deg, #D08B82 0%, #BC6E65 60%, #A85C54 100%); }

.lf-btn--sm { min-height: 38px; padding: 0 14px; font-size: 13px; border-radius: 12px; }
.lf-btn--block { width: 100%; }

/* Legacy names still used by index.html markup. */
.btn-primary {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 46px; padding: 0 20px; border: 0; border-radius: 14px;
  background: linear-gradient(160deg, #D2B173 0%, var(--lf-gold) 52%, #B58F45 100%);
  color: #fff; font-family: var(--lf-font); font-size: 15px; font-weight: 600;
  cursor: pointer;
  box-shadow: 0 1px 1px rgba(74, 62, 61, .06), 0 10px 22px -12px rgba(169, 132, 60, .8);
  transition: transform .12s var(--lf-ease), background-color .18s ease;
}
.btn-primary:active { transform: scale(.975); }
.btn-secondary {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px; padding: 0 18px; border: 1px solid var(--lf-line);
  border-radius: 14px; background: #fff; color: var(--lf-gold-deep);
  font-family: var(--lf-font); font-size: 14px; font-weight: 600; cursor: pointer;
}

/* ---------------------------------------------------------------------------
   4. Page chrome
   --------------------------------------------------------------------------- */
/* THE MEASURE, AND WHICH PAGE GETS WHICH.
   A page is read or it is worked. A parent reading about their child, a teacher
   filling a form, a psychologist writing a paragraph -- those are read, and at
   1121px a card holding the words "Blocks - Art - Books" is mostly empty cream.
   They take `--narrow`. A page that is a table of every child takes the default
   or `--wide`, because there the width is the point. Before 2026-09-12 only
   three pages had said which they were and the rest defaulted to wide by
   omission, which is how the portal ended up looking like a phone stretched. */
.lf-page { max-width: 1152px; margin: 0 auto; }
.lf-page--wide { max-width: 1280px; }
.lf-page--narrow { max-width: 896px; }
.lf-page--full { max-width: none; }

.lf-page-head { margin-bottom: 22px; }

.lf-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 8px;
  color: var(--lf-slate);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  transition: color .18s ease;
}
.lf-back:hover { color: var(--lf-gold-deep); }
/* A BACK LINK NOBODY CAN SEE IS NOT A BACK LINK (T-167). `.lf-back` is 13px of slate with
   no affordance, which is fine inside a signed-in shell that has a rail beside it and
   wrong on a standalone public page reached from a phone -- it was reported as missing.
   This modifier gives it a surface and the 44px CONTEXT.md 7 requires, without touching
   the plain `.lf-back` the other pages use. */
.lf-back--btn {
  min-height: 44px; padding: 0 16px; margin-bottom: 14px;
  background: var(--lf-surface); border: 1px solid var(--lf-line);
  border-radius: 999px; font-size: 14px; font-weight: 600; color: var(--lf-earth);
}
.lf-back--btn:hover { background: var(--lf-cream-deep); border-color: var(--lf-gold); }

.lf-title {
  font-family: var(--lf-display);
  font-optical-sizing: auto;
  font-weight: 600;
  font-size: 30px;
  line-height: 1.14;
  letter-spacing: -.015em;
  color: var(--lf-earth);
  margin: 0;
}
.lf-title__note {
  display: inline-block;
  margin-left: 10px;
  font-family: var(--lf-font);
  font-size: 13px;
  font-weight: 500;
  color: var(--lf-slate);
  vertical-align: middle;
}

.lf-sub {
  max-width: 62ch;
  margin: 8px 0 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--lf-slate);
}
.lf-sub a { color: var(--lf-teal); text-decoration: none; border-bottom: 1px solid rgba(91, 158, 160, .35); }
.lf-sub a:hover { border-bottom-color: var(--lf-teal); }

.lf-page-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 16px; }

/* Section heading used above a list or a panel. */
.lf-h2 {
  font-family: var(--lf-display);
  font-weight: 600;
  font-size: 19px;
  letter-spacing: -.01em;
  color: var(--lf-earth);
  margin: 0;
}

.lf-panel {
  background: #fff;
  border: 1px solid var(--lf-line-soft);
  border-radius: var(--lf-r-lg);
  box-shadow: var(--lf-shadow-sm);
}
.lf-panel--pad { padding: 20px; }
.lf-panel--clip { overflow: hidden; }
.lf-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--lf-line-soft);
}

/* A callout — the "Before uploading" note on the curriculum page. */
.lf-note {
  display: flex;
  gap: 12px;
  padding: 14px 16px;
  background: #fff;
  border: 1px solid var(--lf-line-soft);
  border-left: 4px solid var(--lf-gold);
  border-radius: var(--lf-r);
  box-shadow: var(--lf-shadow-sm);
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--lf-slate);
}
.lf-note b, .lf-note strong { color: var(--lf-gold-deep); }
/* .lf-note is a row: an icon beside a line of text. --stack is the other shape — a
   heading over a paragraph — which flex would otherwise sit side by side. */
.lf-note--stack { display: block; }
.lf-note a { color: var(--lf-teal); text-decoration: none; border-bottom: 1px solid rgba(91, 158, 160, .35); }

/* ---------------------------------------------------------------------------
   5. Form controls
   ---------------------------------------------------------------------------
   16px on phones is not a style choice: iOS Safari zooms the whole page when a
   focused input's text is smaller than that, and the zoom does not undo itself.
   Every date and select in the planning grids used 12–14px.
   --------------------------------------------------------------------------- */
.lf-field { display: block; }
.lf-label {
  display: block;
  margin-bottom: 6px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--lf-slate);
}
.lf-input {
  width: 100%;
  min-height: 44px;
  padding: 10px 14px;
  background: var(--lf-cream);
  border: 1px solid var(--lf-line);
  border-radius: 14px;
  font-family: var(--lf-font);
  font-size: 15px;
  color: var(--lf-earth);
  transition: border-color .18s ease, box-shadow .18s ease, background-color .18s ease;
  appearance: none;
}
.lf-input:focus {
  outline: none;
  background: #fff;
  border-color: var(--lf-gold);
  box-shadow: 0 0 0 4px rgba(197, 160, 89, .18);
}
.lf-input::placeholder { color: rgba(110, 101, 95, .55); }

/* A disabled field must look disabled.
   Buttons had a :disabled rule from the start; inputs never did, so a locked field was
   pixel-identical to an editable one and the only clue was that typing did nothing. Four
   places rely on this: the email on a linked user (frozen once an account has signed in),
   and the primary key on Centers, Activities and Metrics when editing rather than
   creating. */
.lf-input:disabled,
.lf-input[disabled] {
  background: rgba(110, 101, 95, .07);
  border-color: rgba(110, 101, 95, .16);
  border-style: dashed;
  color: var(--lf-slate);
  cursor: not-allowed;
  box-shadow: none;
}
.lf-input:disabled::placeholder { color: rgba(110, 101, 95, .4); }
/* The label above a disabled field, so the state reads before the input is reached. */
.lf-field:has(.lf-input:disabled) > .lf-label { opacity: .65; }
.lf-input--mono { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 14px; }
.lf-hint { margin: 6px 0 0; font-size: 12px; line-height: 1.55; color: var(--lf-slate); }

/* Extend, in the enrolment actions cell.
   Gold-deep rather than the brand gold: #C5A059 on white fails contrast at 12px, which is
   the same reason the pills use deep variants for their labels. Amber would read as a
   warning, and extending an enrolment is good news. */
.lf-a-warn { color: var(--lf-gold-deep); }

/* How long is left on an enrolment, next to the last day.
   Three states and no fourth: running, ending soon, already over. The soon state is amber
   and never red - a fortnight's notice is a prompt to ring a parent, not an alarm. */
.lf-days { display: inline-block; margin-left: 8px; padding: 1px 8px; border-radius: 999px;
           font-size: 11px; font-weight: 600; white-space: nowrap; }
.lf-days--ok   { background: rgba(124, 169, 130, .16); color: var(--lf-sage-deep); }
.lf-days--soon { background: rgba(229, 178, 93, .22);  color: var(--lf-amber-deep); }
.lf-days--over { background: rgba(110, 101, 95, .14);  color: var(--lf-slate); }
.lf-days--none { color: var(--lf-slate); font-weight: 500; }
.lf-err { margin: 0; font-size: 13px; font-weight: 500; color: var(--lf-rose-deep); }

/* A second line inside a table cell — the child's gender under their name.
   Not .lf-hint: that is a paragraph style with a top margin, and this sits inline under a
   name in a cell that becomes a record-card heading below 768px. `display:block` is what
   puts it on its own line in both layouts. */
.lf-cell-sub {
  display: block;
  margin-top: 3px;
  font-family: var(--lf-font);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: .01em;
  color: var(--lf-slate);
}

/* A control small enough to live inside a table row.
   .lf-input is 44px tall and full width, which is right for a form and wrong for a select
   sitting under a name in a list. This keeps the 44px TAP target on a phone — where the
   row becomes a card and there is room — and shrinks it on the desktop table, where the
   pointer does not need one. */
.lf-input--inline {
  width: auto;
  min-height: 32px;
  margin-top: 4px;
  padding: 4px 30px 4px 10px;
  border-radius: 10px;
  font-size: 12.5px;
  background-position: right 9px center;
}
@media (max-width: 767px) {
  .lf-input--inline { min-height: 44px; padding: 8px 34px 8px 12px; font-size: 14px; }
}

select.lf-input {
  /* Native arrow is inconsistent and ugly at this radius. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%236E655F' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  padding-right: 38px;
}

/* Checkbox that reads as a control rather than a browser default. */
.lf-check {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  font-size: 14px;
  color: var(--lf-slate);
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
}
.lf-check input[type="checkbox"] {
  width: 20px; height: 20px; flex: 0 0 auto;
  accent-color: var(--lf-gold);
  cursor: pointer;
}

/* A row of filters / parameters. Wraps on desktop, stacks on a phone. */
.lf-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
}
.lf-filters > .lf-field { min-width: 160px; }
.lf-filters > .lf-field--grow { flex: 1 1 220px; }
/* ---------------------------------------------------------------------------
   The character counter (charcount.js)
   ---------------------------------------------------------------------------
   Quiet until it matters. A counter shouting from character one is noise on a field most
   people never fill; the colour arrives in the last 25 characters, which is about a
   sentence, and turns rose only when a value is genuinely over — which can happen to text
   written before the ceiling existed, because V20's CHECK is NOT VALID.
   --------------------------------------------------------------------------- */
.lf-count {
  margin: 4px 0 0;
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;   /* so the number does not jitter as it counts down */
  color: var(--lf-slate);
  opacity: .75;
}
.lf-count--near { color: var(--lf-gold-deep); opacity: 1; font-weight: 600; }
.lf-count--over { color: var(--lf-rose-deep); opacity: 1; font-weight: 600; }

/* Bottom-aligned is right when every item is a label over a control, and wrong the
   moment one of them carries a hint underneath: the hint makes that field taller,
   so its control rises and stops lining up with its neighbours. The teacher pages
   explain backdating under the day picker, and that is what this is for. */
.lf-filters--top { align-items: flex-start; }
/* A hint has no natural width, so inside a shrink-to-fit `.lf-params` it decides
   the panel's: the teacher pages' one-line note about backdating dragged the card
   out to 859px and the hug was gone. Capped so it wraps under its own control. */
.lf-filters .lf-field > .lf-hint { max-width: 30ch; }

/* Two fields that belong together — From and To, domain and order. A row at every
   width; the phone block only adjusts how it shares the space. */
.lf-filters__pair { display: flex; align-items: flex-end; gap: 12px; }
.lf-filters__pair > .lf-field { min-width: 0; }

/* ---------------------------------------------------------------------------
   6. Pills / status
   --------------------------------------------------------------------------- */
.lf-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .01em;
  white-space: nowrap;
}
.lf-pill--gold { background: rgba(197, 160, 89, .16); color: var(--lf-gold-deep); }
.lf-pill--teal { background: rgba(91, 158, 160, .16); color: var(--lf-teal-deep); }
.lf-pill--sage { background: rgba(124, 169, 130, .18); color: var(--lf-sage-deep); }
.lf-pill--rose { background: rgba(219, 140, 131, .18); color: var(--lf-rose-deep); }
.lf-pill--mute { background: rgba(110, 101, 95, .12); color: var(--lf-slate); }
.lf-pill--purple { background: rgba(163, 139, 184, .20); color: var(--lf-purple-deep); }
.lf-pill--amber { background: rgba(229, 178, 93, .22); color: var(--lf-amber-deep); }

.lf-mono { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12px; }

/* The age badge on a scoring form (T-208): the child's age on the score date and the band
   whose wording the rungs are in. Gold, because the band is the brand's one gold fact on the
   page; the text is --lf-gold-deep on the gold wash, the same pair as .lf-pill--gold. */
.lf-agebadge-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin-bottom: 12px; }
.lf-agebadge {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 12px; border-radius: 999px;
  background: rgba(197, 160, 89, .16); border: 1px solid rgba(197, 160, 89, .45);
  color: var(--lf-gold-deep); font-size: 12.5px; font-weight: 600; letter-spacing: .01em;
}
.lf-agebadge__band { padding-left: 8px; border-left: 1px solid rgba(169, 132, 60, .4); font-weight: 700; }
.lf-agebadge-note { margin: 0; font-size: 12.5px; color: var(--lf-slate); }

/* For long unbroken values — a UUID, a metadata blob. Unlike word-break:break-all
   this only breaks a word that genuinely cannot fit, so ordinary prose beside it
   still wraps at spaces. */
.lf-wrap-any { overflow-wrap: anywhere; word-break: normal; }

/* ---------------------------------------------------------------------------
   6b. Toolbars, code, day lists
   --------------------------------------------------------------------------- */

/* A heading or a checkbox on the left, a count or a search box on the right. */
.lf-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}
.lf-toolbar__count { font-size: 13px; color: var(--lf-slate); }
.lf-toolbar__search { width: 240px; }

.lf-code {
  padding: 1px 6px;
  border-radius: 6px;
  background: rgba(197, 160, 89, .14);
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: .9em;
}

/* One day of a child's plan: the date, then its activities. */
.lf-day { margin-bottom: 16px; }
.lf-day:last-child { margin-bottom: 0; }
.lf-day__date {
  margin-bottom: 6px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--lf-gold-deep);
}
.lf-day__row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px solid var(--lf-line-soft);
  font-size: 13.5px;
}
.lf-day__row:last-child { border-bottom: 0; }
.lf-day__name { color: var(--lf-earth); }

/* ---------------------------------------------------------------------------
   6c. Disclosure, split layout, master-data lists, dense tables
   --------------------------------------------------------------------------- */

/* <details> used for the bulk-upload panel. */
.lf-details {
  margin-bottom: 22px;
  padding: 14px 16px;
  background: #fff;
  border: 1px solid var(--lf-line-soft);
  border-radius: var(--lf-r-lg);
  box-shadow: var(--lf-shadow-sm);
}
.lf-details summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 32px;
  font-size: 14px;
  font-weight: 600;
  color: var(--lf-teal-deep);
  cursor: pointer;
  list-style: none;
}
.lf-details summary::-webkit-details-marker { display: none; }
.lf-details summary::after {
  content: '+';
  font-size: 20px;
  font-weight: 500;
  line-height: 1;
  color: var(--lf-slate);
}
.lf-details[open] summary::after { content: '\2013'; }

/* A fixed sidebar list beside a detail pane. */
.lf-split {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}

/* The activity picker. */
.lf-actlist__item {
  display: block;
  width: 100%;
  padding: 12px 16px;
  text-align: left;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--lf-line-soft);
  font-family: var(--lf-font);
  color: var(--lf-earth);
  cursor: pointer;
  transition: background-color .15s ease;
}
.lf-actlist__item:last-child { border-bottom: 0; }
.lf-actlist__item:hover { background: rgba(197, 160, 89, .06); }
.lf-actlist__item.is-active { background: rgba(197, 160, 89, .12); }
.lf-actlist__item.is-off { opacity: .5; }
.lf-actlist__id { display: block; font-size: 11px; color: var(--lf-gold-deep); }
.lf-actlist__name { display: block; margin-top: 1px; font-size: 14px; font-weight: 600; }
.lf-actlist__meta { display: block; margin-top: 2px; font-size: 11.5px; color: var(--lf-slate); }

/* A metric card and its five rungs. */
.lf-metric {
  margin-bottom: 12px;
  border: 1px solid var(--lf-line);
  border-radius: var(--lf-r);
  overflow: hidden;
}
.lf-metric.is-off { opacity: .5; }
.lf-metric__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.lf-metric__bar { padding: 12px 16px; background: rgba(197, 160, 89, .05); }
.lf-metric__name { margin-left: 8px; font-size: 14px; font-weight: 600; }

.lf-rungs { margin: 0; padding: 0; list-style: none; }
.lf-rungs li {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 9px 16px;
  border-top: 1px solid var(--lf-line-soft);
  font-size: 13.5px;
  line-height: 1.5;
}
.lf-rung {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 25px;
  height: 25px;
  border-radius: 50%;
  background: linear-gradient(160deg, #D2B173, var(--lf-gold));
  color: #fff;
  font-size: 11.5px;
  font-weight: 700;
}
.lf-rungrow { display: flex; align-items: center; gap: 10px; }
/* The step editor's character counter and its refusal state (SPRINT_PLAN AI-12).
   Both are declared HERE rather than as utility classes on the element, because a class a
   page uses and no stylesheet defines renders as nothing and says nothing — `.lf-h3` is
   doing exactly that on the scoring page today (T-111), and one of those per sprint is the
   rate at which a design system stops being one.

   `.lf-rungrow` is a flex row, so the input has to be told to take the slack or the counter
   pushes it to its intrinsic width. */
.lf-rungrow > .lf-input { flex: 1 1 auto; min-width: 0; }
.lf-rungrow__count {
  flex: 0 0 auto;
  min-width: 48px;
  text-align: right;
  font-variant-numeric: tabular-nums;   /* so the number does not jitter while typing */
  white-space: nowrap;
}
/* A negative count is kept rather than clamped at zero: it says how many characters to cut,
   which is the only number that helps at that moment. Coloured so it reads as a problem. */
.lf-rungrow__count--over { color: var(--lf-rose-deep); font-weight: 600; }
/* !important because it overrides `.lf-input`'s own border, and the Tailwind Play CDN
   appends its stylesheet after this file and wins every specificity tie. */
.lf-input--bad {
  border-color: var(--lf-rose-deep) !important;
  background: rgba(219, 140, 131, .06) !important;
}

/* =========================================================================
   The activity <-> value assignment board (SPRINT_PLAN section AJ)

   Every name below is unique in this file. Checked, because `.lf-rungs` and
   `.lf-rung` were each declared twice for unrelated components and the later
   pair won on every page for weeks (D-69).
   ========================================================================= */

/* --- the direction switch -------------------------------------------------
   A segmented control and not a dropdown: there are exactly two choices, both
   are one word, and a dropdown hides half the answer behind a tap. */
.lf-seg {
  display: inline-flex;
  padding: 3px;
  border-radius: 12px;
  background: var(--lf-cream-deep);
  border: 1px solid var(--lf-line-soft);
}
.lf-seg__btn {
  min-height: 38px;
  padding: 0 16px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--lf-slate);
  font-family: var(--lf-font);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background .14s ease, color .14s ease;
}
.lf-seg__btn:hover { color: var(--lf-earth); }

/* --- age-band completeness (T-207) ------------------------------------------
   One dot per band, L1 to L3, filled when that band's five rungs are all there.
   Three dots and not a fraction, because which band is short is the question. */
.lf-banddots { display: inline-flex; gap: 3px; vertical-align: middle; margin: 0 6px; }
.lf-banddot {
  width: 8px; height: 8px; border-radius: 50%;
  border: 1.5px solid var(--lf-rose-deep);
  background: transparent;
}
.lf-banddot.is-on { border-color: var(--lf-sage-deep); background: var(--lf-sage-deep); }
.lf-bandbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
/* The read view's band switch (T-250) puts the ages under each L-number: on one line, three
   of them do not fit a 375 px card. */
.lf-seg__btn--age { display: inline-flex; flex-direction: column; align-items: center; justify-content: center; line-height: 1.2; padding: 4px 16px; }
.lf-seg__age { font-size: 11px; font-weight: 500; }
.lf-seg__btn.is-on {
  background: #fff;
  color: var(--lf-gold-deep);
  box-shadow: 0 1px 3px rgba(74, 62, 61, .10);
}

/* --- the coverage strip ---------------------------------------------------
   The only figure on this page whose answer changes what an admin does next.
   A button, not a card, because it filters. */
.lf-coverage { display: flex; flex-wrap: wrap; gap: 10px; margin: 14px 0 4px; }
.lf-coverage__item {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  min-height: 44px;
  padding: 0 15px;
  border-radius: 12px;
  border: 1px solid var(--lf-line-soft);
  background: #fff;
  font-family: var(--lf-font);
  font-size: 13px;
  color: var(--lf-slate);
  cursor: pointer;
  transition: border-color .14s ease, background .14s ease;
}
.lf-coverage__item:hover { border-color: var(--lf-gold); }
.lf-coverage__item.is-on { border-color: var(--lf-gold-deep); background: rgba(197, 160, 89, .12); }
.lf-coverage__n { font-size: 19px; font-weight: 700; color: var(--lf-gold-deep); }
/* Nothing left to do reads as calm rather than as an alarm with a zero in it. */
.lf-coverage__item.is-clear .lf-coverage__n { color: var(--lf-sage); }

/* --- the chip board ------------------------------------------------------- */
.lf-board__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}
.lf-board__title { font-size: 19px; }
.lf-board__dirty {
  display: inline-block;
  margin-top: 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--lf-gold-deep);
}

.lf-chipgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: 10px;
}
/* A whole card is the target, not a checkbox inside it. Forty of these get tapped
   in a sitting and a 20dp hit area in the corner is the classic list mistake. */
.lf-chipopt {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr);
  align-items: start;
  gap: 11px;
  min-height: 62px;
  padding: 12px 14px;
  position: relative;   /* the is-changed dot is absolute against this */
  border-radius: 14px;
  border: 1px solid var(--lf-line-soft);
  background: #fff;
  text-align: left;
  font-family: var(--lf-font);
  cursor: pointer;
  transition: border-color .14s ease, background .14s ease, box-shadow .14s ease;
}
.lf-chipopt:hover { border-color: var(--lf-gold); }
.lf-chipopt.is-on {
  border-color: var(--lf-gold-deep);
  background: rgba(197, 160, 89, .12);
  box-shadow: inset 0 0 0 1px var(--lf-gold-deep);
}
/* Pending, not saved. A dashed edge and a dot, so the state survives being
   printed, screenshotted, or read by somebody who cannot separate the two
   golds — colour alone is never the only carrier of a state on this page. */
.lf-chipopt.is-changed { border-style: dashed; border-color: var(--lf-gold-deep); }
/* `position` belongs in the block above and is here only because the dot needs it.
   Kept adjacent deliberately: check-portal-css.py allows a restatement its own
   reader can see, and fails one that has other components in between. */
.lf-chipopt.is-changed::after {
  content: '';
  position: absolute;
  top: 9px; right: 9px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--lf-gold-deep);
}
.lf-chipopt__box {
  width: 22px; height: 22px;
  margin-top: 1px;
  border-radius: 7px;
  border: 1.5px solid var(--lf-line);
  background: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 800; color: transparent;
  transition: background .14s ease, border-color .14s ease, color .14s ease;
}
.lf-chipopt.is-on .lf-chipopt__box {
  background: var(--lf-gold-deep);
  border-color: var(--lf-gold-deep);
  color: #fff;
}
.lf-chipopt__id {
  display: block;
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 10.5px;
  letter-spacing: .04em;
  color: var(--lf-gold-deep);
}
.lf-chipopt__name {
  display: block;
  margin-top: 2px;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--lf-earth);
}
.lf-chipopt__meta { display: block; margin-top: 3px; font-size: 11.5px; color: var(--lf-slate); }

/* --- the read-only rows on the two master pages -------------------------- */
.lf-linkrow { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin-top: 10px; }
.lf-linkrow__none { font-size: 12.5px; color: var(--lf-slate); }


/* Inline row actions — the text links that used to be 12px hit targets. */
.lf-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.lf-tbtn {
  min-height: 34px;
  padding: 0 12px;
  border: 0;
  border-radius: 10px;
  background: rgba(197, 160, 89, .13);
  color: var(--lf-gold-deep);
  font-family: var(--lf-font);
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color .16s ease, transform .12s var(--lf-ease);
}
.lf-tbtn:active { transform: scale(.96); }
.lf-tbtn--info { background: rgba(91, 158, 160, .14); color: var(--lf-teal-deep); }
.lf-tbtn--danger { background: rgba(219, 140, 131, .15); color: var(--lf-rose-deep); }
.lf-tbtn--good { background: rgba(124, 169, 130, .17); color: var(--lf-sage-deep); }
/* --wipe is the permanent delete. Deliberately quieter than --danger at rest: it must be
   findable but must not compete with Deactivate, which is the right answer far more
   often. The weight arrives in the dialog, on the filled button. */
.lf-tbtn--wipe {
  background: transparent;
  border: 1px solid rgba(180, 101, 92, .35);
  color: var(--lf-rose-deep);
}
.lf-tbtn--wipe:hover { background: rgba(180, 101, 92, .1); }

/* Small print under a panel — the psychologist-exclusion note. */
.lf-fineprint {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--lf-line-soft);
  font-size: 12.5px;
  line-height: 1.65;
  color: var(--lf-slate);
}
.lf-fineprint b { color: var(--lf-earth); }

.lf-out--good { color: var(--lf-sage-deep); font-weight: 500; }
.lf-out--bad { color: var(--lf-rose-deep); font-weight: 500; }

/* A dense read-only table that stays a table. Five narrow columns of one-line
   values are more use scrolled sideways than folded into cards. */
.lf-scroller {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  border: 1px solid var(--lf-line-soft);
  border-radius: var(--lf-r);
}
.lf-mini { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.lf-mini th {
  padding: 9px 10px;
  background: #FBF9F3;
  text-align: left;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--lf-slate);
  white-space: nowrap;
}
.lf-mini td { padding: 7px 10px; border-top: 1px solid var(--lf-line-soft); vertical-align: top; }
.lf-mini tr.is-personalised { background: rgba(91, 158, 160, .06); }

.lf-swipe-hint { display: none; }

/* ---------------------------------------------------------------------------
   6d. Planning grids, the upload widget, toasts
   --------------------------------------------------------------------------- */

/* The From/To/Go card above a planning grid. inline-block so it hugs its own
   controls on a desktop rather than stretching a near-empty panel edge to edge. */
.lf-params { display: inline-block; margin-bottom: 20px; }

.lf-gridbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 12px;
}

/* --- upload widget -------------------------------------------------------- */

/* Three upload cards side by side, one per sheet. minmax(0,1fr) rather than 1fr
   is load-bearing: 1fr floors each track at the item min-content, and a native
   file input reports a ~340px min-content, so the grid grew wider than a phone
   viewport and the whole page scrolled sideways. */
.lf-uploads {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: 12px;
}

.lf-upload__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 14px;
}
.lf-upload__intro { min-width: 0; }
.lf-upload__row { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; }
.lf-upload__out:not(:empty) { margin-top: 16px; }
/* Add / Replace, for the one sheet uploaded in a mode (T-321). */
.lf-upload__modes { display: flex; flex-direction: column; gap: 2px; margin-bottom: 10px; }
.lf-upload__modes input[type="radio"] { width: 20px; height: 20px; flex: 0 0 auto; accent-color: var(--lf-gold); }
.lf-upload__tally { display: flex; flex-wrap: wrap; gap: 8px; }

/* The native file input, made to match everything else. */
.lf-file {
  flex: 1 1 220px;
  min-width: 0;
  max-width: 100%;
  font-family: var(--lf-font);
  font-size: 13.5px;
  color: var(--lf-slate);
}
.lf-file::file-selector-button {
  margin-right: 12px;
  min-height: 38px;
  padding: 0 16px;
  border: 0;
  border-radius: 12px;
  background: rgba(197, 160, 89, .15);
  color: var(--lf-gold-deep);
  font-family: var(--lf-font);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.lf-file::file-selector-button:hover { background: rgba(197, 160, 89, .25); }

.lf-upload__errors {
  margin-top: 14px;
  border: 1px solid rgba(219, 140, 131, .3);
  border-radius: var(--lf-r);
  overflow: hidden;
}
.lf-upload__errors table { width: 100%; border-collapse: collapse; font-size: 13px; }
.lf-upload__errors th {
  padding: 8px 12px;
  background: rgba(219, 140, 131, .1);
  color: var(--lf-rose-deep);
  text-align: left;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.lf-upload__errors td { padding: 8px 12px; border-top: 1px solid rgba(219, 140, 131, .2); }

.lf-toast {
  padding: 10px 14px;
  border: 1px solid transparent;
  border-radius: var(--lf-r-sm);
  font-size: 13.5px;
  line-height: 1.5;
}
.lf-toast--bad {
  background: rgba(219, 140, 131, .13);
  border-color: rgba(219, 140, 131, .3);
  color: var(--lf-rose-deep);
}
.lf-toast--info {
  background: rgba(197, 160, 89, .11);
  border-color: rgba(197, 160, 89, .3);
  color: var(--lf-gold-deep);
}

/* ---------------------------------------------------------------------------
   6e. Assignments — who may see which child
   ---------------------------------------------------------------------------
   Grouped by child rather than listed flat. The question the screen answers is
   "who can see this child", and the child is also the unit an admin revokes
   within, so the child is the container.
   --------------------------------------------------------------------------- */
.lf-assign {
  margin-bottom: 14px;
  background: #fff;
  border: 1px solid var(--lf-line-soft);
  border-radius: var(--lf-r-lg);
  box-shadow: var(--lf-shadow-sm);
  overflow: hidden;
}
.lf-assign:last-child { margin-bottom: 0; }
/* A child who has left: dimmed, because every row inside it is already inert. */
.lf-assign.is-left { opacity: .78; }

.lf-assign__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  padding: 15px 18px;
  background: rgba(197, 160, 89, .05);
  border-bottom: 1px solid var(--lf-line-soft);
}
.lf-assign__child {
  margin: 0;
  font-family: var(--lf-display);
  font-weight: 600;
  font-size: 18px;
  letter-spacing: -.012em;
  color: var(--lf-earth);
}
.lf-assign__tags { display: flex; flex-wrap: wrap; gap: 8px; }

.lf-assign__note {
  margin: 0;
  padding: 11px 18px;
  background: rgba(219, 140, 131, .08);
  border-bottom: 1px solid var(--lf-line-soft);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--lf-rose-deep);
}

.lf-assign__rows { padding: 0; }

/* One assigned person. A dot in the role colour on the left rail so a mixed list
   is scannable by role without reading the pills. */
.lf-arow {
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr) auto;
  align-items: start;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--lf-line-soft);
}
.lf-arow:last-child { border-bottom: 0; }
.lf-arow.is-revoked { opacity: .55; }
.lf-arow__dot {
  width: 9px;
  height: 9px;
  margin-top: 6px;
  border-radius: 50%;
  background: var(--tone, var(--lf-gold));
}
.lf-arow__who { min-width: 0; }
.lf-arow__name {
  margin: 0;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--lf-earth);
}
.lf-arow__mail {
  margin: 2px 0 0;
  font-size: 12.5px;
  color: var(--lf-slate);
  overflow-wrap: anywhere;
}
.lf-arow__tags { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 7px; }
.lf-arow__since { font-size: 11.5px; color: rgba(110, 101, 95, .8); }
.lf-arow__act { display: flex; align-items: center; gap: 8px; }
.lf-arow__hint { font-size: 11.5px; font-style: italic; color: rgba(110, 101, 95, .75); }

.lf-stats--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.lf-toast--good {
  display: inline-block;
  background: rgba(124, 169, 130, .15);
  border-color: rgba(124, 169, 130, .35);
  color: var(--lf-sage-deep);
}

/* ---------------------------------------------------------------------------
   6f. Permanent-delete dialog (admin/js/purge-dialog.js)
   ---------------------------------------------------------------------------
   .lf-modal is a self-contained overlay: the dialog injects its own markup, so
   unlike the page modals it cannot rely on Tailwind utilities being present on
   an element the page authored. It also carries .lf-sheet, which is what turns
   it into a bottom sheet on a phone.
   --------------------------------------------------------------------------- */
.lf-modal {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(46, 38, 37, .42);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  animation: lf-fade .2s ease both;
}
.lf-modal__panel {
  width: 100%;
  max-width: 460px;
  padding: 24px;
  background: #fff;
  border-radius: var(--lf-r-xl);
  box-shadow: var(--lf-shadow-lg);
}

.lf-purge__subject {
  margin: 6px 0 16px;
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 13px;
  color: var(--lf-gold-deep);
  overflow-wrap: anywhere;
}
.lf-purge__body { min-height: 64px; }
.lf-purge__lead { margin: 0; font-size: 14px; font-weight: 600; color: var(--lf-earth); }
.lf-purge__hint { margin: 10px 0 0; font-size: 12.5px; line-height: 1.6; color: var(--lf-slate); }
.lf-purge__list {
  margin: 10px 0 0;
  padding: 0 0 0 18px;
  font-size: 13px;
  line-height: 1.75;
  color: var(--lf-earth);
}
.lf-purge__block {
  padding: 14px 16px;
  background: rgba(219, 140, 131, .09);
  border: 1px solid rgba(219, 140, 131, .28);
  border-radius: var(--lf-r);
}
.lf-purge__block .lf-purge__lead { color: var(--lf-rose-deep); }
.lf-purge__ok {
  padding: 14px 16px;
  background: rgba(124, 169, 130, .09);
  border: 1px solid rgba(124, 169, 130, .28);
  border-radius: var(--lf-r);
}
/* The second tier, shown under a refusal when a forced delete is on offer.
   Visually heavier than the refusal above it and hatched rather than flat, because it is
   the only control in the portal that destroys records somebody made. The gap above is
   generous on purpose: it must not read as part of the explanation it follows. */
.lf-purge__force {
  margin-top: 18px;
  padding: 16px;
  border: 1px solid rgba(180, 101, 92, .45);
  border-radius: var(--lf-r);
  background:
    repeating-linear-gradient(45deg,
      rgba(180, 101, 92, .07) 0 10px,
      rgba(180, 101, 92, .12) 10px 20px);
}
.lf-purge__force .lf-purge__lead { color: var(--lf-rose-deep); }
.lf-purge__force .lf-label { margin-top: 14px; color: var(--lf-rose-deep); }
.lf-purge__force .lf-input { background: #fff; border-color: rgba(180, 101, 92, .4); }
.lf-purge__force .lf-input:focus {
  border-color: var(--lf-rose-deep);
  box-shadow: 0 0 0 4px rgba(180, 101, 92, .18);
}
.lf-purge__hint--danger { color: var(--lf-rose-deep); font-weight: 500; }

.lf-purge__actions { display: flex; gap: 10px; margin-top: 20px; }
.lf-purge__actions > .lf-btn { flex: 1 1 0; }

/* ---------------------------------------------------------------------------
   6g. Settings
   ---------------------------------------------------------------------------
   A stack of titled cards, one concern each — the shape a person already knows
   from every phone settings screen, so nothing here has to be learned.
   --------------------------------------------------------------------------- */
.lf-set {
  margin-bottom: 16px;
  background: #fff;
  border: 1px solid var(--lf-line-soft);
  border-radius: var(--lf-r-lg);
  box-shadow: var(--lf-shadow-sm);
  overflow: hidden;
}
.lf-set:last-child { margin-bottom: 0; }
.lf-set__head {
  padding: 15px 20px;
  background: rgba(197, 160, 89, .05);
  border-bottom: 1px solid var(--lf-line-soft);
}
.lf-set__body { padding: 20px; }

/* Who is signed in. The avatar is an initial, not an image: Little Fitrah stores no
   images anywhere, by hard constraint, and a gravatar would be a third party learning
   the staff list of a playhouse. */
.lf-who { display: flex; align-items: center; gap: 16px; margin-bottom: 16px; }
.lf-avatar {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  border-radius: 18px;
  background: linear-gradient(155deg, #D2B173, var(--lf-gold));
  color: #fff;
  font-family: var(--lf-display);
  font-size: 22px;
  font-weight: 600;
  box-shadow: 0 8px 18px -10px rgba(169, 132, 60, .8);
}
.lf-avatar--mark {
  background: linear-gradient(155deg, rgba(197, 160, 89, .2), rgba(197, 160, 89, .08));
  color: var(--lf-gold);
  box-shadow: none;
}
.lf-who__text { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
.lf-who__name {
  margin: 0;
  font-family: var(--lf-display);
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -.012em;
  color: var(--lf-earth);
}
.lf-who__mail { margin: 0; font-size: 13px; color: var(--lf-slate); overflow-wrap: anywhere; }

/* A picked-one-of-several row. Radio semantics, but sized and shaped like a settings
   row rather than a form control — the tick is the state, and the whole row is the
   target. */
.lf-choices { display: flex; flex-direction: column; gap: 10px; margin-bottom: 14px; }
.lf-choice {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  width: 100%;
  min-height: 60px;
  padding: 0 18px;
  background: var(--lf-cream);
  border: 1px solid var(--lf-line);
  border-radius: var(--lf-r);
  font-family: var(--lf-font);
  text-align: left;
  cursor: pointer;
  transition: background-color .16s ease, border-color .16s ease, transform .12s var(--lf-ease);
}
.lf-choice:hover { border-color: rgba(197, 160, 89, .45); }
.lf-choice:active { transform: scale(.99); }
.lf-choice.is-on {
  background: rgba(197, 160, 89, .13);
  border-color: rgba(197, 160, 89, .55);
}
.lf-choice__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.lf-choice__name { font-size: 15px; font-weight: 600; color: var(--lf-earth); }
.lf-choice__sub { font-size: 11.5px; letter-spacing: .06em; color: var(--lf-slate); }
.lf-choice__tick {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--lf-gold);
  color: #fff;
  opacity: 0;
  transform: scale(.7);
  transition: opacity .18s ease, transform .22s var(--lf-ease);
}
.lf-choice.is-on .lf-choice__tick { opacity: 1; transform: none; }

/* ============================================================================
   7. THE APP SHELL — injected by admin/js/shell.js
   ============================================================================
   Three surfaces, all phone-only: a top bar, a slide-in drawer holding the full
   navigation, and a bottom tab bar for the four destinations an admin actually
   returns to. Before this, a sub-page's only route anywhere was a "← Dashboard"
   link, so moving from Children to Users cost two page loads.
   ============================================================================ */
.lf-shell { display: none; }

.lf-topbar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  height: calc(var(--lf-topbar) + var(--lf-safe-t));
  padding: var(--lf-safe-t) 6px 0;
  display: flex;
  align-items: center;
  gap: 4px;
  background: rgba(249, 246, 238, .78);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid transparent;
  transition: border-color .25s ease, background-color .25s ease;
}
.lf-topbar.is-scrolled {
  background: rgba(249, 246, 238, .92);
  border-bottom-color: var(--lf-line);
}

.lf-iconbtn {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-radius: 14px;
  color: var(--lf-earth);
  cursor: pointer;
  transition: background-color .18s ease, transform .12s var(--lf-ease);
}
.lf-iconbtn:active { transform: scale(.92); background: rgba(197, 160, 89, .14); }

/* The bar title only appears once the page's own large title has scrolled
   away, so the phone never shows the same heading twice. */
.lf-topbar__title {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--lf-display);
  font-weight: 600;
  font-size: 16px;
  letter-spacing: -.01em;
  color: var(--lf-earth);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .22s ease, transform .22s var(--lf-ease);
}
.lf-topbar.is-scrolled .lf-topbar__title { opacity: 1; transform: none; }

.lf-topbar__brand {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--lf-gold);
  transition: opacity .22s ease;
}
.lf-topbar.is-scrolled .lf-topbar__brand { opacity: 0; }

/* The page must not scroll behind an open drawer — on a phone the drawer is the
   whole surface, and a document sliding underneath it reads as a bug. */
body.lf-drawer-open { overflow: hidden; }

.lf-dlink:focus-visible,
.lf-tab:focus-visible,
.lf-iconbtn:focus-visible {
  outline: 2px solid var(--lf-gold);
  outline-offset: -2px;
  border-radius: 14px;
}

/* --- scrim ---------------------------------------------------------------- */
.lf-scrim {
  position: fixed;
  inset: 0;
  z-index: 70;
  background: rgba(46, 38, 37, .38);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  opacity: 0;
  pointer-events: none;
  transition: opacity .28s ease;
}
body.lf-drawer-open .lf-scrim { opacity: 1; pointer-events: auto; }

/* --- drawer --------------------------------------------------------------- */
.lf-drawer {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 80;
  width: min(86vw, 320px);
  display: flex;
  flex-direction: column;
  background: #fff;
  border-right: 1px solid var(--lf-line-soft);
  box-shadow: var(--lf-shadow-lg);
  transform: translate3d(-102%, 0, 0);
  /* visibility is transitioned as well as transform so a closed drawer is out of
     the tab order and out of the accessibility tree. Discrete transitions flip at
     the end when hiding and at the start when showing, which is exactly the
     behaviour wanted here: the slide-out still plays. */
  visibility: hidden;
  transition: transform .34s var(--lf-ease), visibility .34s var(--lf-ease);
  overscroll-behavior: contain;
}
.lf-drawer:focus { outline: none; }
body.lf-drawer-open .lf-drawer { transform: translate3d(0, 0, 0); visibility: visible; }

.lf-drawer__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: calc(var(--lf-safe-t) + 16px) 16px 16px;
  border-bottom: 1px solid var(--lf-line-soft);
}
.lf-drawer__mark {
  width: 42px; height: 42px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 14px;
  background: linear-gradient(155deg, rgba(197, 160, 89, .20), rgba(197, 160, 89, .08));
  color: var(--lf-gold);
}
.lf-drawer__name {
  font-family: var(--lf-display);
  font-weight: 600;
  font-size: 17px;
  color: var(--lf-gold-deep);
  line-height: 1.2;
}
.lf-drawer__role { font-size: 12px; color: var(--lf-slate); }
.lf-drawer__close { margin-left: auto; }

.lf-drawer__nav {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 12px 10px calc(24px + var(--lf-safe-b));
}
.lf-drawer__group {
  margin: 18px 0 6px;
  padding: 0 12px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(110, 101, 95, .72);
}
.lf-drawer__group:first-child { margin-top: 4px; }

.lf-dlink {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 48px;
  padding: 0 12px;
  border-radius: 14px;
  color: var(--lf-earth);
  font-size: 15px;
  font-weight: 500;
  text-decoration: none;
  transition: background-color .16s ease;
}
.lf-dlink:active { background: rgba(197, 160, 89, .14); }
.lf-dlink .lf-i { flex: 0 0 auto; color: var(--lf-slate); transition: color .16s ease; }
.lf-dlink.is-active {
  background: rgba(197, 160, 89, .15);
  color: var(--lf-gold-deep);
  font-weight: 600;
}
.lf-dlink.is-active .lf-i { color: var(--lf-gold); }
.lf-dlink__chev { margin-left: auto; color: rgba(110, 101, 95, .4); }

.lf-drawer__foot {
  padding: 14px 16px calc(16px + var(--lf-safe-b));
  border-top: 1px solid var(--lf-line-soft);
  background: var(--lf-cream);
}
.lf-drawer__user { font-size: 13px; font-weight: 600; color: var(--lf-earth); }
.lf-drawer__mail {
  font-size: 12px; color: var(--lf-slate);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lf-drawer__site {
  display: block; margin-top: 10px;
  font-size: 13px; font-weight: 600; color: var(--lf-earth); text-decoration: none;
}
.lf-drawer__site:hover { text-decoration: underline; }
#lf-signout {
  margin-top: 12px;
  width: 100%;
  min-height: 44px;
  background: rgba(219, 140, 131, .12);
  border: 0;
  border-radius: 14px;
  color: var(--lf-rose-deep);
  font-family: var(--lf-font);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

/* --- bottom tab bar ------------------------------------------------------- */
.lf-tabbar {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 60;
  height: calc(var(--lf-tabbar) + var(--lf-safe-b));
  padding-bottom: var(--lf-safe-b);
  display: flex;
  align-items: stretch;
  background: rgba(255, 255, 255, .86);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-top: 1px solid var(--lf-line-soft);
  box-shadow: 0 -8px 24px -18px rgba(74, 62, 61, .5);
}
.lf-tab {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 6px 2px 4px;
  background: none;
  border: 0;
  color: var(--lf-slate);
  font-family: var(--lf-font);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .01em;
  text-decoration: none;
  cursor: pointer;
  position: relative;
  transition: color .18s ease;
}
.lf-tab span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lf-tab .lf-i { transition: transform .22s var(--lf-ease); }
.lf-tab:active .lf-i { transform: scale(.86); }
.lf-tab.is-active { color: var(--lf-gold-deep); }
.lf-tab.is-active .lf-i { color: var(--lf-gold); }
/* The indicator is a short bar at the top edge — it reads as "this pane" rather
   than decorating the icon, and survives long labels. */
.lf-tab.is-active::before {
  content: '';
  position: absolute;
  top: 0;
  width: 26px;
  height: 3px;
  border-radius: 0 0 3px 3px;
  background: var(--lf-gold);
}

/* ============================================================================
   7b. Dashboard & sign-in
   ============================================================================ */

/* --- boot splash ---------------------------------------------------------- */
#lf-boot {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  background: var(--lf-cream);
  transition: opacity .38s ease, visibility .38s ease;
}
#lf-boot.is-done { opacity: 0; visibility: hidden; }
.lf-boot__mark {
  color: var(--lf-gold);
  animation: lf-breathe 1.7s ease-in-out infinite;
}
@keyframes lf-breathe {
  0%, 100% { opacity: .32; transform: scale(.94) }
  50%      { opacity: 1;   transform: scale(1) }
}

/* --- sign-in -------------------------------------------------------------- */
.lf-login {
  position: relative;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(28px + var(--lf-safe-t)) 20px calc(28px + var(--lf-safe-b));
}
/* FOUR CORNERS OF THE WORDMARK'S PALETTE, very faint (T-158). The sign-in screen was
   cream and one gold; it is the first thing anybody sees and it looked like a utility.
   These are washes at 13-16% behind everything, blurred across half the viewport each,
   so the screen reads as coloured light rather than as coloured shapes -- which is the
   whole difference between this and a childish page. No layout, no hit area, no cost:
   a single painted pseudo-element behind a `position: relative` parent. */
.lf-login::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(38rem 26rem at 12% 6%,  color-mix(in srgb, var(--lf-periwinkle) 16%, transparent), transparent 70%),
    radial-gradient(34rem 24rem at 88% 10%, color-mix(in srgb, var(--lf-blush) 15%, transparent), transparent 70%),
    radial-gradient(40rem 28rem at 84% 94%, color-mix(in srgb, var(--lf-teal) 13%, transparent), transparent 70%),
    radial-gradient(30rem 22rem at 6% 90%,  color-mix(in srgb, var(--lf-peach) 17%, transparent), transparent 70%);
}
.lf-login.hidden { display: none; }

/* `hidden` IS OURS NOW, NOT TAILWIND'S (T-167).
   `#app` on index.html carries `class="hidden"` and nothing in this stylesheet said what
   that meant -- the only rule was Tailwind's, from the CDN. So a slow, blocked or
   still-evaluating CDN left the dashboard laid out UNDER the sign-in card, and scrolling
   the sign-in page showed it: stat tiles, "Today's activities", the lot. Reported from a
   phone on a weak connection. `.lf-login.hidden` was already first-party, which is why
   only half the screen leaked and the bug looked cosmetic.
   A visibility gate must not depend on a third-party script arriving. `!important` here
   because Tailwind's utilities are also single-class and load after this file. */
.hidden { display: none !important; }
/* AND THE SAME THING AGAIN, QUALIFIED BY THE ID (T-167).
   `.hidden` above is class-level, and so is Tailwind's `.flex` sitting on the same
   element -- `#app class="hidden flex ..."`. Measured in a browser, the generic rule did
   not win: the CDN emits its utilities inside `@layer`, and layered vs unlayered
   precedence for `!important` is reversed, so an unlayered important class rule is the
   WEAKEST important declaration on the page rather than the strongest. An id selector is
   not in that argument at all.
   This is the rule that actually keeps the dashboard off the sign-in screen, and it is
   deliberately not clever: a visibility gate is the wrong place to be clever. */
#app.hidden { display: none !important; }
.lf-login__inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 372px;
  text-align: center;
  animation: lf-rise .55s var(--lf-ease) both;
}
.lf-login__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 78px;
  height: 78px;
  margin-bottom: 20px;
  border-radius: 26px;
  background: linear-gradient(155deg, #fff 0%, #FBF6E9 100%);
  border: 1px solid var(--lf-line);
  color: var(--lf-gold);
  /* The lift under the mark picks up two of the wordmark's hues instead of gold alone,
     so the logo sits in coloured light rather than on a yellow smudge. */
  box-shadow:
    0 1px 2px rgba(74, 62, 61, .05),
    0 14px 30px -20px color-mix(in srgb, var(--lf-periwinkle) 55%, transparent),
    0 18px 40px -22px color-mix(in srgb, var(--lf-blush) 45%, transparent);
}
.lf-login__name {
  margin: 0;
  font-family: var(--lf-display);
  font-weight: 600;
  font-size: 33px;
  line-height: 1.1;
  letter-spacing: -.02em;
  color: var(--lf-gold-deep);
}
/* THE WORDMARK IN ITS OWN SIX COLOURS, in the order the logo reads them. Guarded by
   @supports and never the other way round: the rule that makes the text transparent
   lives INSIDE the query, so a browser that cannot clip a background to text never
   applies it and keeps the solid gold above. Getting that backwards paints an invisible
   heading, which is the one failure mode worth being careful about here. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .lf-login__name {
    background: linear-gradient(96deg,
      var(--lf-blush) 0%, var(--lf-gold) 17%, var(--lf-teal) 36%,
      var(--lf-periwinkle) 56%, var(--lf-purple) 75%, var(--lf-rose) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
  }
}
.lf-login__role {
  margin: 6px 0 26px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--lf-slate);
}
.lf-login__card {
  padding: 20px;
  background: #fff;
  border: 1px solid var(--lf-line-soft);
  border-radius: var(--lf-r-xl);
  box-shadow: var(--lf-shadow);
}

/* Google button treatment: white surface, four-colour mark. The previous version
   tinted every path with currentColor on a gold fill, which rendered the mark as
   a flat white blob. */
.lf-gbtn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  width: 100%;
  min-height: 52px;
  padding: 0 18px;
  background: #fff;
  border: 1px solid rgba(74, 62, 61, .16);
  border-radius: 16px;
  font-family: var(--lf-font);
  font-size: 15.5px;
  font-weight: 600;
  color: #3C4043;
  cursor: pointer;
  transition: transform .12s var(--lf-ease), box-shadow .2s ease, background-color .2s ease;
  box-shadow: 0 1px 2px rgba(74, 62, 61, .06);
}
.lf-gbtn:hover { background: #FCFBF7; box-shadow: 0 2px 10px -4px rgba(74, 62, 61, .2); }
.lf-gbtn:active { transform: scale(.982); }
.lf-gbtn__g { flex: 0 0 auto; }

.lf-login__note {
  margin: 16px 0 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--lf-slate);
}
/* --- the "why no averages" page ------------------------------------------
   Two language buttons under the article. Only this page has them: it is the
   one page in the portal a stranger reads, arriving from a pasted link with no
   saved preference and no Settings screen to reach.
   ------------------------------------------------------------------------- */
.lf-why__lang {
  display: flex; flex-wrap: wrap; gap: 10px; justify-content: center;
  max-width: 760px; margin: 4px auto 0;
  padding: 0 var(--lf-pad, 16px) 8px;
}
/* 44px, not 40. CONTEXT.md § 7 holds every tap target in this portal to 44px, and the
   first version of this page shipped at 40 — measured in a browser at 375px, which is
   the only way anybody would have noticed. This page is read on a phone, by a stranger,
   from a pasted link. */
.lf-why__lang .lf-btn { min-height: 44px; }
.lf-why__lang .is-on {
  border-color: var(--lf-gold);
  background: var(--lf-cream-deep);
  color: var(--lf-gold-deep);
}

/* --- the desktop rail -----------------------------------------------------
   Seventeen of eighteen pages had NO navigation above 767px: `.lf-shell` is
   the phone shell and is hidden there, and only `index.html` carried a sidebar
   of its own. See the long note in shell.js.

   Everything here reuses the dashboard's own `.nav-link`, `.lf-navgroup` and
   `.lf-sidebar__*` classes, so this is not a second visual language — it is the
   sidebar that already existed, given to the pages that never got it.
   ------------------------------------------------------------------------- */
:root { --lf-rail: 248px; --lf-tourbar-h: 40px; }

.lf-rail { display: none; }

/* ---------------------------------------------------------------------------
   The desktop layer
   ---------------------------------------------------------------------------
   WHAT WAS WRONG
   This stylesheet is phone-first and, above 767px, was never finished. Nothing
   here was badly designed; it was simply un-designed at that width, and the
   result reads as cheap. Measured on the parent's Today page at 1440px:

     the day picker            1079px wide
     six cards, each           1121px wide
     "Blocks - Art - Books"    alone in a 1121px box

   A control that wide stops looking like a date and starts looking like a text
   field, and the eye has to cross the window to get from a label to its value.

   WHY THERE IS ALMOST NO NEW VISUAL LANGUAGE BELOW
   Because the answers were already in this file, written for the mass-planning
   pages and never applied to the eleven pages built later: `.lf-params` ("hugs
   its own controls on a desktop rather than stretching a near-empty panel edge
   to edge"), `.lf-filters`, `.lf-row2`, `.lf-scroller`, `.lf-page--narrow`.
   Inventing a second look would have left this portal with two, which is the
   actual road to ugly. This block finishes the one that exists.
   --------------------------------------------------------------------------- */
@media (min-width: 768px) {

  /* --- controls stop stretching ------------------------------------------- */
  /* Capped by TYPE, not by page: the natural width of a control is a property of
     what it holds — a date is ten characters wherever it appears. They are
     maxima, so a narrow column still shrinks them.

     Scoped to `.lf-field > .lf-input`, meaning "a control with a label above it
     in a stacked form". That is deliberate and load-bearing: the value inputs in
     the metric modal (`.lf-rungrow`) and the cells of the planning grids are
     sized by their row and must keep filling it. They are not in a `.lf-field`,
     so this cannot reach them. */
  .lf-field > .lf-input { max-width: 560px; }
  .lf-field > .lf-input[type="date"],
  .lf-field > .lf-input[type="month"],
  .lf-field > .lf-input[type="time"] { max-width: 210px; }
  .lf-field > .lf-input[type="number"] { max-width: 160px; }
  .lf-field > select.lf-input { max-width: 380px; }
  .lf-field > textarea.lf-input { max-width: 680px; }
  /* The one deliberate exception: a control that really is the width of its
     panel. Nothing uses it yet; it exists so the next page that needs it does
     not reach for an inline style. */
  .lf-field > .lf-input--full { max-width: none; }

  /* --- the page header gets room ------------------------------------------ */
  .lf-page-head { margin-bottom: 26px; }

  /* --- a wide table scrolls instead of being crushed ----------------------- */
  /* These tables carry Tailwind's `w-full`, which forces eight columns of a
     child's enrolment into whatever is left beside the rail. Measured on the
     Children page at 1440px: the table wants 1382px and is given 1119, so the
     Extend / Deactivate / Delete group wraps to three lines and the last column
     is clipped at the edge. A table that has run out of room should say so by
     scrolling, which is what `.lf-scroller` was written for.

     No magic number. `max-content` is whatever this table actually needs, and
     `min-width: 100%` stops a short one (Centers has four columns) from sitting
     in a puddle on the left. The 1400px ceiling is for the Audit log, whose
     detail column has no natural limit and would otherwise push the table to
     several thousand pixels rather than wrap a sentence; min-width still beats
     max-width in CSS, so a genuinely wide window fills as before.

     TWO CLASSES ON THE WRAPPER, ON PURPOSE. Tailwind comes from the Play CDN and
     injects its sheet into the head at runtime, so `.w-full` lands AFTER this
     file and wins any tie. `.lf-cards.lf-tablewrap` scores (0,2,1) against its
     (0,1,0) and settles it by weight rather than by `!important`, which would
     leave nothing for a real exception to use later. */
  .lf-cards.lf-tablewrap > table { width: max-content; min-width: 100%; max-width: 1400px; }
}

@media (min-width: 1024px) {
  /* A 30px title is right above a 375px column and timid above a 1100px one.
     The step is small on purpose — this is a nursery's portal, not a landing
     page, and the display face is already doing the work. */
  .lf-title { font-size: 34px; }
  .lf-sub { font-size: 14.5px; }
}

/* A table that is allowed to keep its own width and scroll inside the card.
   Borrows `.lf-scroller`'s behaviour rather than restating it, and adds the
   rounded clip so the scrolled edge does not run over the panel's corner. */
.lf-tablewrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  border-radius: inherit;

  /* A scroll nobody can see is the same shape of bug as a check that reports
     success without looking: the Extend / Deactivate / Delete group sits off the
     right edge and the page gives no sign it is there.

     Four layers, the CSS-only scroll shadow. The two `local` gradients are white
     covers pinned to the CONTENT, so they slide away as it scrolls; the two
     `scroll` radials are pinned to the ELEMENT and are revealed underneath. The
     result is a soft edge that appears only on the side there is more to see,
     with no JavaScript and nothing to keep in sync on resize.

     The cover colour must match the panel (#fff) exactly or the shadow reads as
     a stripe. */
  background:
    linear-gradient(to right, #fff 42%, rgba(255, 255, 255, 0)) left center / 36px 100% no-repeat local,
    linear-gradient(to left,  #fff 42%, rgba(255, 255, 255, 0)) right center / 36px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%,    rgba(62, 47, 32, .11), rgba(62, 47, 32, 0)) left center / 15px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgba(62, 47, 32, .11), rgba(62, 47, 32, 0)) right center / 15px 100% no-repeat;
}
/* Below 768px `.lf-cards` folds every row into its own card, so there is nothing
   to scroll and the shadows would sit over the first and last card for no reason. */
@media (max-width: 767px) { .lf-tablewrap { background: none; } }

@media (min-width: 768px) {
  .lf-rail {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 40;
    width: var(--lf-rail);
    display: flex;
    flex-direction: column;
    padding: 24px 12px 18px;
    background: var(--lf-surface, #fff);
    border-right: 1px solid var(--lf-line);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  /* The content moves over rather than shrinking into a narrower measure: every
     page already centres itself inside `.lf-page`, so padding the body keeps
     that centring true relative to the space left. */
  body.lf-has-rail { padding-left: calc(var(--lf-rail) + 28px) !important; padding-right: 28px !important; }

  .lf-rail__brand { display: block; padding: 0 14px; margin-bottom: 26px; text-decoration: none; }
  .lf-rail__nav { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; }
  .lf-rail__foot {
    margin-top: auto;
    padding: 14px 14px 0;
    border-top: 1px solid var(--lf-line-soft);
  }
  .lf-rail__user { margin: 0; font-size: 12.5px; font-weight: 600; color: var(--lf-earth);
                   overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .lf-rail__mail { margin: 2px 0 8px; font-size: 11.5px; color: var(--lf-slate);
                   overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .lf-rail__site { display: block; margin-bottom: 8px; font-size: 12px; font-weight: 600;
                   color: var(--lf-earth); text-decoration: none; }
  .lf-rail__site:hover { text-decoration: underline; }
  .lf-rail__foot button {
    padding: 0; border: 0; background: none; cursor: pointer;
    font-family: var(--lf-font); font-size: 12px; font-weight: 600; color: var(--lf-rose-deep);
  }
  .lf-rail__foot button:hover { text-decoration: underline; }

  /* The tour banner is `position: sticky` inside the body, so with a rail it would
     start at the rail's edge. Pulled back across it: the banner is the one element
     that must span the whole window. */
  body.lf-has-rail .lf-tourbar {
    margin-left: calc((var(--lf-rail) + 28px) * -1);
    margin-right: -28px;
    padding-left: calc(var(--lf-rail) + 42px);
  }

  /* And the rail starts BELOW it. The banner spans the whole window on purpose — it is
     the one element on the tour that may never be missed — and it is z-index 60 against
     the rail's 40, so without this it sits squarely on the wordmark. Found in a
     screenshot: the rail read "Fitrah" with "Little" underneath the banner. */
  /* `--lf-tourbar-h` is set from the banner's measured height by shell.js, so a
     sentence that wraps in one language does not clip the wordmark in it. The 50px
     fallback is only for the instant before that runs. */
  body.lf-has-tourbar .lf-rail { top: var(--lf-tourbar-h, 50px); }
}

/* The dashboard sidebar's link list (T-259). `index.html` keeps its own `<aside>` inside
   a `flex h-screen overflow-hidden` layout, and that aside had no scroll of its own: the
   ADMIN list is about 25 links, and everything below "Menu Belajar" was simply cut off at
   1366 x 768. Only the list scrolls here, so the wordmark stays at the top and sign-out at
   the bottom; the rail on every other page scrolls whole, as before.

   The thin scrollbar is shared with `.lf-rail` so the two sidebars look like one. */
.lf-rail,
.lf-navscroll {
  scrollbar-width: thin;
  scrollbar-color: var(--lf-line) transparent;
}
.lf-rail::-webkit-scrollbar,
.lf-navscroll::-webkit-scrollbar { width: 6px; }
.lf-rail::-webkit-scrollbar-thumb,
.lf-navscroll::-webkit-scrollbar-thumb { background: var(--lf-line); border-radius: 3px; }

/* The fade is `.lf-tablewrap`'s scroll shadow turned on its side: a white cover pinned to
   the CONTENT slides away at the end of the list, revealing a shadow pinned to the
   ELEMENT. So the bottom edge is soft only while there is more below. The cover must be
   the aside's #fff exactly or it reads as a stripe. */
.lf-navscroll {
  background:
    linear-gradient(to top, #fff 42%, rgba(255, 255, 255, 0)) center bottom / 100% 36px no-repeat local,
    radial-gradient(farthest-side at 50% 100%, rgba(62, 47, 32, .13), rgba(62, 47, 32, 0)) center bottom / 100% 16px no-repeat;
}

/* --- "What is this?" ------------------------------------------------------
   A small round button after a heading, and one shared panel.

   THE 44px RULE, AND WHY THIS ONE IS 30px
   CONTEXT.md § 7 holds every tap target to 44px. This button is 30px of ink
   with a 44px hit area supplied by a `::before` overlay: at 44px of visible
   circle it competes with the heading it belongs to, and a help control that
   draws the eye more than the card title is a worse outcome than a slightly
   subtle one. The touch target is the full 44px, which is what the rule is
   actually protecting.

   The panel is ONE element for the whole document, moved to whichever button
   is open. Twenty panels rendered inline would be twenty copies of the same
   markup inside subtrees that get replaced on every fetch.
   ------------------------------------------------------------------------- */
.lf-helpbtn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px;
  margin-left: 7px;
  vertical-align: middle;
  padding: 0;
  border: 1px solid var(--lf-line);
  border-radius: 50%;
  background: transparent;
  color: var(--lf-gold-deep);
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
/* The hit area, invisible, centred, and at least 44px in both directions. */
.lf-helpbtn::before {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
}
.lf-helpbtn:hover { background: var(--lf-cream-deep); border-color: var(--lf-gold-light); }
.lf-helpbtn:focus-visible { outline: 2px solid var(--lf-gold); outline-offset: 2px; }
.lf-helpbtn[aria-expanded="true"] {
  background: var(--lf-gold); border-color: var(--lf-gold); color: #fff;
}
/* Inside a heading the button must not inherit a display face or a huge line-height. */
.lf-h1 .lf-helpbtn, .lf-h2 .lf-helpbtn, .lf-title .lf-helpbtn, h1 .lf-helpbtn,
h2 .lf-helpbtn, h3 .lf-helpbtn, .lf-label .lf-helpbtn {
  font-family: var(--lf-font);
  line-height: 1;
}
/* On an all-caps micro-label the 30px circle would tower over the text. */
.lf-label .lf-helpbtn, .lf-parentscore__section .lf-helpbtn {
  width: 24px; height: 24px; margin-left: 6px;
}
.lf-label .lf-helpbtn svg, .lf-parentscore__section .lf-helpbtn svg {
  width: 13px; height: 13px;
}

/* A bare anchor for a control that belongs to something with no heading of its own —
   the spreadsheet button, for instance. Aligns with the button beside it. */
.lf-helphost { display: inline-flex; align-items: center; gap: 2px; }
.lf-toolbar .lf-helphost { flex: 0 1 auto; min-width: 0; }
.lf-toolbar .lf-helphost .lf-btn { flex: 1 1 auto; }
.lf-helphost .lf-helpbtn { margin-left: 2px; }

.lf-help {
  position: absolute;
  z-index: 80;
  max-width: calc(100vw - 24px);
  padding: 14px 16px 16px;
  border: 1px solid var(--lf-line);
  border-radius: var(--lf-r-lg, 18px);
  background: #fff;
  box-shadow: 0 18px 40px rgba(74, 62, 61, .16), 0 2px 6px rgba(74, 62, 61, .06);
  font-family: var(--lf-font);
}
.lf-help[hidden] { display: none; }
.lf-help__head {
  display: flex; align-items: flex-start; gap: 10px;
  margin-bottom: 8px;
}
.lf-help__title {
  flex: 1 1 auto; margin: 0;
  font-family: var(--lf-display);
  font-size: 15px; color: var(--lf-earth);
}
.lf-help__x {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; margin: -4px -6px 0 0;
  border: 0; border-radius: 50%;
  background: transparent; color: var(--lf-slate); cursor: pointer;
}
.lf-help__x:hover { background: var(--lf-cream-deep); }
.lf-help__x:focus-visible { outline: 2px solid var(--lf-gold); outline-offset: 2px; }
.lf-help__body {
  margin: 0;
  font-size: 13.5px; line-height: 1.65;
  color: var(--lf-earth);
}
.lf-help__body b { color: var(--lf-gold-deep); }

/* Below the breakpoint it is a bottom sheet, like every other modal in the portal.
   A popover anchored to a heading halfway down a phone screen either runs off the
   edge or covers the very thing it is explaining. */
@media (max-width: 767px) {
  .lf-help.is-sheet {
    position: fixed;
    left: 0; right: 0; bottom: 0; top: auto;
    width: auto !important;
    max-width: none;
    max-height: 82vh;
    overflow-y: auto;
    padding-bottom: calc(20px + env(safe-area-inset-bottom));
    border-radius: 22px 22px 0 0;
    border-bottom: 0;
    animation: lf-help-up .2s ease-out;
  }
  .lf-help__body { font-size: 14.5px; }
}
@keyframes lf-help-up { from { transform: translateY(14px); opacity: .6 } to { transform: none; opacity: 1 } }
@media (prefers-reduced-motion: reduce) {
  .lf-help.is-sheet { animation: none; }
}
/* The sheet is modal, so the page behind it stops scrolling under the thumb. */
body.lf-help-open { overflow: hidden; }

/* --- the parent tour ------------------------------------------------------
   Two pieces: the way in, on the sign-in card, and the banner that rides above
   every page of the tour once it has started.

   The banner is `position: sticky` rather than `fixed` so it needs no body
   padding on any of the pages it appears over, and it is declared OUTSIDE the
   767px block on purpose — the phone shell is hidden on a laptop, and a laptop
   is where a link pasted into WhatsApp usually gets opened.
   ------------------------------------------------------------------------- */
.lf-login__tour {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--lf-line-soft);
}
.lf-tourbtn {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%; min-height: 44px; padding: 0 18px;
  border: 1px solid var(--lf-line);
  border-radius: 14px;
  background: var(--lf-cream);
  color: var(--lf-gold-deep);
  font-family: var(--lf-font);
  font-size: 14.5px; font-weight: 600; text-decoration: none;
  transition: background .16s ease, border-color .16s ease;
}
.lf-tourbtn:hover { background: var(--lf-cream-deep); border-color: var(--lf-gold-light); }
.lf-tourbtn:focus-visible { outline: 2px solid var(--lf-gold); outline-offset: 2px; }
.lf-login__note--tour { margin-top: 10px; }

.lf-tourbar {
  position: sticky; top: 0; z-index: 60;
  display: flex; align-items: center; gap: 10px;
  padding: 9px 14px;
  padding-top: calc(9px + env(safe-area-inset-top));
  background: var(--lf-earth);
  color: #F7F1E4;
  font-family: var(--lf-font);
  font-size: 12.5px; line-height: 1.45;
}
.lf-tourbar__dot {
  flex: none; width: 8px; height: 8px; border-radius: 50%;
  background: var(--lf-amber);
}
.lf-tourbar__text { flex: 1 1 auto; font-weight: 600; }
.lf-tourbar__exit {
  flex: none; min-height: 32px; padding: 0 12px;
  border: 1px solid rgba(247, 241, 228, .35); border-radius: 10px;
  background: transparent; color: #F7F1E4;
  font-family: var(--lf-font); font-size: 12.5px; font-weight: 600;
  cursor: pointer;
}
.lf-tourbar__exit:hover { background: rgba(247, 241, 228, .12); }
.lf-tourbar__exit:focus-visible { outline: 2px solid var(--lf-amber); outline-offset: 2px; }

/* The phone shell's own top bar is `fixed` at z-index 60, so on a phone a
   banner stuck to `top: 0` would sit underneath it. Below the breakpoint it
   sticks to the bottom edge of that bar instead — pushed clear rather than
   hidden, because this is the one element on the tour that may never be
   scrolled away or covered.

   `--lf-pad` is body padding on a phone, so the negative margins are what let
   the banner still reach both edges. Same trick on the desktop side, where the
   dashboard's own padding would otherwise inset it. */
.lf-tourbar {
  margin: calc(var(--lf-pad, 16px) * -1) calc(var(--lf-pad, 16px) * -1) 14px;
  padding-left: calc(14px + var(--lf-pad, 16px));
  padding-right: calc(14px + var(--lf-pad, 16px));
}
@media (max-width: 767px) {
  .lf-tourbar {
    top: calc(var(--lf-topbar) + var(--lf-safe-t));
    padding-top: 9px;
    /* NOT a negative top margin. The first version pulled the banner up into the body's
       padding so it sat flush under the fixed top bar — and because sticky then had nowhere
       to travel, the banner stayed put while the page title scrolled underneath it. The
       phone showed the banner covering the word "Today". It starts below the padding now,
       and sticks to the bar's lower edge on the way up. */
    margin-top: 0;
  }
  .lf-tourbar__text { font-size: 12px; }
}

.lf-login__back {
  position: absolute; z-index: 2;
  top: calc(16px + var(--lf-safe-t)); left: 16px;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px; border-radius: 999px;
  background: rgba(255, 255, 255, .7); border: 1px solid var(--lf-line);
  color: var(--lf-earth); font-size: 14px; font-weight: 600; text-decoration: none;
}
.lf-login__back:hover, .lf-login__back:focus-visible { background: #fff; }
.lf-login__foot {
  margin: 26px 0 0;
  font-family: var(--lf-display);
  font-style: italic;
  font-size: 13px;
  color: var(--lf-gold);
  opacity: .85;
}

/* --- the brand mark -------------------------------------------------------
   One PNG serves every place the emblem appears — boot splash, login, top bar, drawer,
   sidebar, Settings. It is 256px square and drawn at 24-46px, so `height: auto` would be
   wrong: the width/height attributes on the tag are what reserve the box before the image
   arrives, and without them the top bar reflows on first paint. Both dimensions are set
   here as well so a cached-but-not-yet-decoded image cannot stretch its container. */
.lf-mark {
  display: block;
  object-fit: contain;
}
.lf-boot__mark .lf-mark { width: 46px; height: 46px; }
.lf-login__mark .lf-mark { width: 42px; height: 42px; }
.lf-topbar__brand .lf-mark,
.lf-drawer__mark .lf-mark,
.lf-avatar--mark .lf-mark { width: 24px; height: 24px; }

/* --- desktop sidebar ------------------------------------------------------ */
.lf-sidebar__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 13px;
  background: linear-gradient(155deg, rgba(197, 160, 89, .20), rgba(197, 160, 89, .07));
  color: var(--lf-gold);
}
.lf-sidebar__brand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 7px;
}
/* 132px of a 640px file. Generous on purpose: this is the only 2x-and-up asset in the
   portal and the sidebar is the one surface a school administrator looks at all day. */
.lf-sidebar__logo {
  display: block;
  width: 132px;
  height: auto;
}
.lf-sidebar__role {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .02em;
  color: var(--lf-slate);
}
.lf-sidebar__name {
  font-family: var(--lf-display);
  font-weight: 600;
  font-size: 15.5px;
  line-height: 1.2;
  color: var(--lf-gold-deep);
}
.lf-navgroup {
  margin: 20px 0 4px;
  padding: 0 14px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(110, 101, 95, .72);
}
/* T-260: a group header is a `<summary>` that folds its links away. */
summary.lf-navgroup {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding-block: 4px;
  border-radius: 8px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
summary.lf-navgroup::-webkit-details-marker { display: none; }
summary.lf-navgroup:hover { color: var(--lf-accent-deep); }
summary.lf-navgroup:focus-visible { outline: 2px solid var(--lf-accent); outline-offset: 1px; }
summary.lf-navgroup .lf-i { transition: transform .15s ease; }
.lf-navsec[open] > summary.lf-navgroup .lf-i { transform: rotate(90deg); }
.lf-navsec > .nav-link + .nav-link { margin-top: 2px; }

/* --- stat tiles ----------------------------------------------------------- */
.lf-stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 26px;
}
.lf-stat {
  position: relative;
  overflow: hidden;
  padding: 18px;
  background: #fff;
  border: 1px solid var(--lf-line-soft);
  border-radius: var(--lf-r-lg);
  box-shadow: var(--lf-shadow-sm);
}
/* A soft bloom of the tile own colour, so four tiles read as a set rather than
   four numbers in four unrelated colours. */
.lf-stat::after {
  content: '';
  position: absolute;
  top: -46px;
  right: -46px;
  width: 132px;
  height: 132px;
  border-radius: 50%;
  /* A gradient rather than a flat disc: a hard-edged quarter circle clipped by
     the tile corner reads as a rendering mistake. */
  background: radial-gradient(circle, var(--tone, var(--lf-gold)) 0%, transparent 68%);
  opacity: .16;
  pointer-events: none;
}
.lf-stat__num {
  margin: 0;
  font-family: var(--lf-display);
  font-optical-sizing: auto;
  font-weight: 600;
  font-size: 36px;
  line-height: 1;
  letter-spacing: -.025em;
  color: var(--tone, var(--lf-gold));
}
.lf-stat__lbl {
  margin: 8px 0 0;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--lf-slate);
}

/* --- the day panel -------------------------------------------------------- */
.lf-today { margin-bottom: 26px; }
.lf-today__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
}
.lf-today__link {
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  padding: 0 13px;
  border-radius: 11px;
  background: rgba(91, 158, 160, .13);
  color: var(--lf-teal-deep);
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: none;
}
.lf-today__link:hover { background: rgba(91, 158, 160, .2); }
.lf-today__body { font-size: 14px; color: var(--lf-slate); }

.lf-act {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 4px 12px;
  align-items: start;
  padding: 12px 0;
  border-bottom: 1px solid var(--lf-line-soft);
}
.lf-act:last-child { border-bottom: 0; padding-bottom: 0; }
.lf-act__main { grid-column: 1; grid-row: 1; min-width: 0; }
.lf-act > .lf-pill { grid-column: 2; grid-row: 1; justify-self: end; }
.lf-act__name {
  margin: 0;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--lf-earth);
}
.lf-act__meta {
  margin: 3px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--lf-slate);
}

.lf-empty { margin: 6px 0; font-size: 13.5px; line-height: 1.6; color: var(--lf-slate); }
.lf-empty a { color: var(--lf-teal); text-decoration: none; border-bottom: 1px solid rgba(91, 158, 160, .35); }
.lf-empty--bad { color: var(--lf-rose-deep); }

/* --- quick links ---------------------------------------------------------- */
.lf-quick__title { margin: 0 0 12px; }
.lf-quick {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}
.lf-quick__card {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px;
  background: #fff;
  border: 1px solid var(--lf-line-soft);
  border-radius: var(--lf-r-lg);
  box-shadow: var(--lf-shadow-sm);
  color: var(--lf-earth);
  text-decoration: none;
  transition: transform .18s var(--lf-ease), box-shadow .22s ease, border-color .22s ease;
}
.lf-quick__card:hover {
  transform: translateY(-2px);
  border-color: var(--lf-line);
  box-shadow: var(--lf-shadow);
}
.lf-quick__card:active { transform: scale(.99); }
.lf-quick__icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 14px;
  background: var(--tone, var(--lf-gold));
  color: #fff;
  box-shadow: 0 6px 14px -8px var(--tone, var(--lf-gold));
}
.lf-quick__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.lf-quick__name { font-size: 14.5px; font-weight: 600; }
.lf-quick__desc { font-size: 12.5px; line-height: 1.45; color: var(--lf-slate); }
.lf-quick__chev { flex: 0 0 auto; color: rgba(110, 101, 95, .38); }

/* --- Beranda: quick actions and the per-role dashboard (T-262, admin/js/home.js) --------
   A tile per page, read from the role's menu, so it carries the menu's icon and the
   menu's colour (the data-tone map above) and cannot fall behind it. */
.lf-home__head { margin-bottom: 22px; }
.lf-home__date {
  margin: 0 0 4px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--lf-gold-deep);
}
.lf-home__skel { height: 14px; margin: 10px 0; }
.lf-home__progress { margin: 4px 0 10px; }
.lf-home__bar {
  height: 8px;
  margin-top: 6px;
  overflow: hidden;
  border-radius: 99px;
  background: rgba(124, 169, 130, .18);
}
.lf-home__bar > span {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--lf-sage);
  transition: width .4s var(--lf-ease);
}
.lf-home__excerpt { color: var(--lf-earth); }
.lf-qa-group { margin: 18px 0 8px; padding: 0; }
.lf-qa-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 10px;
  margin-bottom: 8px;
}
.lf-qa {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  padding: 10px 14px;
  background: #fff;
  border: 1px solid var(--lf-line-soft);
  border-radius: var(--lf-r);
  box-shadow: var(--lf-shadow-sm);
  color: var(--lf-earth);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  transition: transform .18s var(--lf-ease), box-shadow .22s ease, border-color .22s ease;
}
.lf-qa:hover { transform: translateY(-2px); border-color: var(--lf-line); box-shadow: var(--lf-shadow); }
.lf-qa:active { transform: scale(.99); }
.lf-qa__icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 11px;
  background: color-mix(in srgb, var(--lf-accent) 16%, transparent);
  color: var(--lf-accent-deep);
}
.lf-qa__name { min-width: 0; line-height: 1.3; }

/* ============================================================================
   8. PHONE
   ============================================================================ */
@media (max-width: 767px) {

  .lf-shell { display: block; }

  /* Content clears both bars. Tailwind's p-6 on <body> is overridden here
     because the offsets have to be derived from the shell metrics. */
  body.lf-body {
    padding: var(--lf-pad) !important;
    padding-top: calc(var(--lf-topbar) + var(--lf-safe-t) + 12px) !important;
    padding-bottom: calc(var(--lf-tabbar) + var(--lf-safe-b) + 28px) !important;
  }

  /* The dashboard is the one page laid out as an app frame — a fixed-height
     flex row with its own scrolling <main>. On a phone that frame is the
     problem: it hides the sidebar's overflow and fights the URL bar. Unwind it
     to a normal scrolling document. */
  body.lf-body--app { padding: 0 !important; }
  /* `:not(.hidden)` IS THE WHOLE FIX FOR THE SIGN-IN LEAK (T-169).
     This rule unwinds the desktop app frame on a phone, and it was written without
     remembering that `#app` also carries `hidden` until Firebase reports a session. At
     (1,2,0) plus `!important` it beat every attempt to hide that element -- including a
     first-party `#app.hidden` rule at (1,1,0) -- so on a PHONE, and only on a phone, the
     dashboard sat laid out under the sign-in card and scrolling revealed it: Beranda, the
     stat tiles, the lot. Reported three times, and it never reproduced at desktop width
     because this block does not apply there.
     The lesson is the specificity, not the class: a visibility gate loses to any layout
     rule written more specifically, so the layout rule has to say it does not apply while
     the thing is hidden. */
  body.lf-body--app #app:not(.hidden) {
    display: block !important;
    height: auto !important;
    min-height: 100vh;
    overflow: visible !important;
  }
  body.lf-body--app #app > aside { display: none !important; }
  body.lf-body--app #app > main {
    overflow: visible !important;
    padding: calc(var(--lf-topbar) + var(--lf-safe-t) + 12px) var(--lf-pad)
             calc(var(--lf-tabbar) + var(--lf-safe-b) + 28px) !important;
  }

  /* On a phone the drawer and the tab bar are the navigation, so the in-page
     "← Dashboard" link is one more thing to read for no gain.
     EXCEPT ON A PAGE THAT HAS NO SHELL (T-169). `why-no-averages.html` is standalone and
     ungated -- no drawer, no tab bar, nothing -- so this rule was deleting its ONLY way
     back, on the device most of its readers arrive on. It is why the control added in
     T-167 was still reported missing: the markup was right and this hid it.
     `.lf-back--btn` is the marker for "this page carries no navigation of its own". */
  .lf-back:not(.lf-back--btn) { display: none !important; }

  .lf-title { font-size: 26px; }
  .lf-title__note { display: block; margin: 6px 0 0; }
  .lf-sub { font-size: 13.5px; }
  .lf-h2 { font-size: 17px; }

  .lf-page-head { margin-bottom: 18px; }
  /* One thumb, one column: the primary action spans the width rather than
     sitting in a corner. */
  .lf-page-actions > .lf-btn { flex: 1 1 100%; }

  .card, .lf-panel { border-radius: var(--lf-r-lg); }
  .card { padding: 18px; }
  .lf-panel--pad { padding: 16px; }

  /* A toolbar stacks: a search field beside a heading has no room at 375px. */
  .lf-toolbar { flex-direction: column; align-items: stretch; }
  .lf-toolbar__search { width: 100%; }
  /* --tight keeps its row: a section heading with one small button beside it fits
     375px, and stacking it made every panel action a full-width gold bar
     competing with the page's own primary action. */
  .lf-toolbar--tight { flex-direction: row; align-items: center; }

  .lf-day__row { gap: 6px; font-size: 13px; }

  /* Stack every filter row. Two dates side by side is the one exception —
     From/To belong together and fit. */
  .lf-filters { flex-direction: column; align-items: stretch; gap: 12px; }
  /* flex-basis is reset as well as the width: in a column direction the 220px
     basis on .lf-field--grow becomes a *height*, which left a 220px hole
     between the filters and the list below them. */
  .lf-filters > .lf-field,
  .lf-filters > .lf-field--grow { flex: 0 0 auto; min-width: 0; width: 100%; }
  .lf-filters > .lf-btn { width: 100%; }
  /* From/To stay side by side. width:100% is load-bearing, not decorative: a
     date input reports a ~170px min-content width, so without it the pair
     stretches to its own min-content and hangs off the card edge. With it the
     pair takes the container width and each field shrinks to fit. */
  .lf-filters > .lf-filters__pair { flex: 0 0 auto; }
  .lf-filters__pair { gap: 10px; width: 100%; min-width: 0; }
  .lf-filters__pair > .lf-field { flex: 1 1 0; min-width: 0; }
  /* Buys back the ~8px the native date picker button needs at this width. */
  .lf-input[type="date"], .lf-input[type="month"] { padding-left: 11px; padding-right: 8px; }

  /* 16px minimum: see the note on .lf-input. Applies to controls this file does
     not own too, which is why it is broad and !important. */
  .lf-input,
  input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
  select,
  textarea {
    font-size: 16px !important;
    min-height: 46px;
  }
  textarea { min-height: 84px; }

  /* The purge dialog arrives as a bottom sheet like every other modal; .lf-sheet
     already handles the panel, this only unwinds .lf-modal's centring. */
  .lf-modal { align-items: flex-end; padding: 0; }
  .lf-purge__actions { flex-direction: column-reverse; }
  .lf-purge__actions > .lf-btn { flex: 0 0 auto; width: 100%; }

  .lf-set__head { padding: 14px 16px; }
  .lf-set__body { padding: 16px; }
  .lf-avatar { width: 48px; height: 48px; border-radius: 16px; font-size: 20px; }
  .lf-who__name { font-size: 17px; }
  .lf-choice { min-height: 58px; padding: 0 15px; }

  /* Assignments. The revoke button drops to its own line rather than squeezing
     the name column into nothing. */
  .lf-assign__head { padding: 14px; }
  .lf-assign__child { font-size: 17px; }
  .lf-assign__note { padding: 10px 14px; }
  .lf-arow {
    grid-template-columns: 10px minmax(0, 1fr);
    padding: 13px 14px;
  }
  /* Revoke sits on its own line, right-aligned and sized to its label. Full width
     would put a rose bar on every row in the list — heavy for a destructive action
     that is rarely the one wanted, and easier to mis-tap. */
  .lf-arow__act { grid-column: 2; justify-content: flex-end; margin-top: 10px; }
  /* Three across even at 375px. The .lf-stats rule above drops to two columns for the
     dashboard's four tiles; three counts read better as one row than as 2 + 1. */
  /* The assignment board on a phone. One chip per row: two columns of a name that
     wraps to three lines is harder to scan than a list, and this screen is scanned
     far more than it is tapped. The segmented switch and the coverage items go full
     width so both are thumb-sized. */
  .lf-chipgrid { grid-template-columns: minmax(0, 1fr); gap: 8px; }
  .lf-seg { display: flex; width: 100%; }
  .lf-seg__btn { flex: 1 1 0; min-height: 44px; }
  .lf-coverage__item { flex: 1 1 100%; justify-content: flex-start; }
  .lf-board__head { flex-direction: column; }
  .lf-board__head .lf-actions { width: 100%; }
  .lf-board__head .lf-actions > .lf-btn { flex: 1 1 0; }

  /* Two classes, so the dashboard's two-column .lf-stats rule further down cannot win on
     source order — it did, and every three-tile row on a phone read 2 + 1 (found T-262). */
  .lf-stats.lf-stats--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
  .lf-stats--3 .lf-stat { padding: 12px 10px; }
  .lf-stats--3 .lf-stat__num { font-size: 25px; }
  .lf-stats--3 .lf-stat__lbl { margin-top: 5px; font-size: 10.5px; }
  .lf-stats--3 .lf-stat::after { top: -40px; right: -40px; width: 96px; height: 96px; }

  /* Planning grids. The parameter card and the toolbar above the grid both go
     full width, and Go / Save / Add row become thumb-width. */
  .lf-params { display: block; width: 100%; }
  .lf-gridbar { flex-direction: column; align-items: stretch; }
  .lf-gridbar .lf-actions > .lf-btn { flex: 1 1 0; }

  /* Upload widget: nothing shares a line with anything else at 375px. */
  .lf-upload__head { flex-direction: column; align-items: stretch; }
  .lf-upload__head > .lf-btn { width: 100%; }
  .lf-upload__row { flex-direction: column; align-items: stretch; }
  .lf-upload__row > .lf-btn { width: 100%; }
  .lf-file { flex: 0 0 auto; width: 100%; }
  .lf-uploads { grid-template-columns: minmax(0, 1fr); gap: 12px; }

  /* Master data. The activity picker becomes a horizontal card carousel so the
     detail pane it drives stays on screen — as a vertical list, selecting an
     activity scrolled the answer a full screen out of view and the tap read as
     doing nothing. */
  .lf-split { grid-template-columns: minmax(0, 1fr); gap: 14px; }

  .lf-actlist {
    display: flex;
    gap: 10px;
    padding: 12px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
  }
  .lf-actlist__item {
    flex: 0 0 auto;
    width: 196px;
    padding: 12px 14px;
    background: var(--lf-cream);
    border: 1px solid var(--lf-line-soft) !important;
    border-radius: var(--lf-r);
    scroll-snap-align: start;
  }
  .lf-actlist__item.is-active {
    background: rgba(197, 160, 89, .14);
    border-color: rgba(197, 160, 89, .42) !important;
  }

  .lf-metric__bar { padding: 12px 14px; }
  .lf-metric__name { display: block; margin: 3px 0 4px; }
  .lf-tbtn { min-height: 38px; padding: 0 14px; font-size: 13px; }
  .lf-rungs li { padding: 9px 14px; }

  .lf-mini { min-width: 620px; }

  .lf-details { padding: 12px 14px; }
  .lf-fineprint { font-size: 12px; }

  /* Dashboard. Four tiles across becomes a 2x2 block; the quick links become a
     single column of rows, which is the only way three cards carrying a title
     and a line of explanation each stay legible at 375px. */
  .lf-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin-bottom: 22px; }
  .lf-stat { padding: 16px; }
  .lf-stat__num { font-size: 31px; }
  .lf-stat__lbl { font-size: 12px; }

  .lf-today { margin-bottom: 22px; }
  /* The pill leads on a phone: it becomes the row tag rather than a value
     stranded at the right edge of a wrapped line. */
  .lf-act { grid-template-columns: minmax(0, 1fr); gap: 6px; }
  .lf-act > .lf-pill { grid-column: 1; grid-row: 1; justify-self: start; }
  .lf-act__main { grid-row: 2; }

  .lf-quick { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .lf-qa-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .lf-qa { gap: 10px; padding: 10px; font-size: 13px; }
  .lf-qa__icon { width: 32px; height: 32px; }
  .lf-quick__card { padding: 14px; border-radius: var(--lf-r); }
  .lf-quick__icon { width: 40px; height: 40px; border-radius: 12px; }

  .lf-login__mark { width: 70px; height: 70px; border-radius: 23px; margin-bottom: 18px; }
  .lf-login__name { font-size: 30px; }
  .lf-login__card { padding: 18px; border-radius: var(--lf-r-lg); }

  /* -----------------------------------------------------------------------
     Tables become record cards
     -----------------------------------------------------------------------
     A seven-column table cannot be made to fit 375px, and horizontal
     scrolling a list you are meant to scan is worse than not showing it. Each
     row becomes a card: the first cell is the title, the rest are label/value
     lines fed by data-label, and the action cell becomes a row of buttons.
     Pages opt in by putting .lf-cards on the table's wrapper.
     ----------------------------------------------------------------------- */
  .lf-cards {
    background: transparent !important;
    border: 0 !important;
    box-shadow: none !important;
    overflow: visible !important;
  }
  .lf-cards table { display: block !important; width: 100% !important; }
  .lf-cards thead { display: none !important; }
  .lf-cards tbody { display: block !important; }

  .lf-cards tbody tr {
    display: block !important;
    margin-bottom: 12px;
    padding: 14px 16px;
    background: #fff !important;
    border: 1px solid var(--lf-line-soft) !important;
    border-radius: var(--lf-r-lg);
    box-shadow: var(--lf-shadow-sm);
  }
  .lf-cards tbody tr:last-child { margin-bottom: 0; }

  .lf-cards tbody td {
    display: flex !important;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px;
    width: auto !important;
    padding: 5px 0 !important;
    border: 0 !important;
    font-size: 13.5px;
    text-align: right;
  }
  .lf-cards tbody td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 auto;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: rgba(110, 101, 95, .78);
    text-align: left;
    white-space: nowrap;
  }

  /* The row's headline. Full width, left aligned, sits above the label list. */
  .lf-cards tbody td.lf-cell-title {
    display: block !important;
    margin-bottom: 8px;
    padding-bottom: 8px !important;
    border-bottom: 1px solid var(--lf-line-soft) !important;
    font-family: var(--lf-display);
    font-size: 17px;
    font-weight: 600;
    letter-spacing: -.01em;
    line-height: 1.25;
    text-align: left;
    color: var(--lf-earth);
  }
  .lf-cards tbody td.lf-cell-title::before { display: none; }
  /* A log entry is headed by its timestamp, and a timestamp set in 17px display
     serif reads as a headline it is not. */
  .lf-cards tbody td.lf-cell-title--meta {
    font-family: var(--lf-font);
    font-size: 12.5px;
    font-weight: 600;
    letter-spacing: .02em;
    color: var(--lf-slate);
  }

  /* Long values (a UUID, a description) get the full width instead of being
     crushed into the right half. */
  .lf-cards tbody td.lf-cell-block {
    display: block !important;
    text-align: left;
  }
  .lf-cards tbody td.lf-cell-block::before { display: block; margin-bottom: 3px; }

  /* Row actions: real buttons, thumb sized.
     inline-flex rather than flex so that two consecutive action cells — Children
     has Assign and Enrolment as separate columns — land on the same line instead
     of stacking into two full-width bars and doubling the card height. */
  .lf-cards tbody td.lf-cell-actions {
    display: inline-flex !important;
    width: auto !important;
    flex-wrap: wrap;
    gap: 8px;
    margin: 12px 8px 0 0;
    padding: 0 !important;
    border: 0 !important;
    vertical-align: top;
  }
  .lf-cards tbody td.lf-cell-actions::before { display: none; }
  .lf-cards tbody td.lf-cell-actions > button,
  .lf-cards tbody td.lf-cell-actions > a {
    flex: 0 0 auto;
    min-height: 38px;
    padding: 0 16px !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 12px;
    background: rgba(197, 160, 89, .13);
    color: var(--lf-gold-deep) !important;
    font-size: 13px !important;
    font-weight: 600;
    text-decoration: none !important;
  }
  .lf-cards tbody td.lf-cell-actions > button.lf-a-danger {
    background: rgba(219, 140, 131, .15);
    color: var(--lf-rose-deep) !important;
  }
  .lf-cards tbody td.lf-cell-actions > button.lf-a-good {
    background: rgba(124, 169, 130, .17);
    color: var(--lf-sage-deep) !important;
  }
  .lf-cards tbody td.lf-cell-actions > button.lf-a-info {
    background: rgba(91, 158, 160, .15);
    color: var(--lf-teal-deep) !important;
  }
  .lf-cards tbody td.lf-cell-actions > button.lf-a-warn {
    background: rgba(197, 160, 89, .18);
    color: var(--lf-gold-deep) !important;
  }
  .lf-cards tbody td.lf-cell-actions > button.lf-a-wipe {
    background: transparent;
    border: 1px solid rgba(180, 101, 92, .35);
    color: var(--lf-rose-deep) !important;
  }
  .lf-cards tbody td.lf-cell-actions > span { display: none; }

  /* Tighten the label list so a card is a glance rather than a scroll. */
  .lf-cards tbody tr { padding: 14px 16px 16px; }
  .lf-cards tbody td { padding: 4px 0 !important; }

  /* Loading / empty / error rows keep reading as a message, not a record. */
  .lf-cards tbody td[colspan] {
    display: block !important;
    padding: 28px 16px !important;
    text-align: center;
    border: 0 !important;
  }
  .lf-cards tbody td[colspan]::before { display: none; }
  .lf-cards tbody tr:has(td[colspan]) {
    background: #fff !important;
    border-style: dashed !important;
    box-shadow: none;
  }

  /* -----------------------------------------------------------------------
     Modals become bottom sheets
     -----------------------------------------------------------------------
     The pages toggle .hidden on a fixed full-screen overlay; that keeps
     working. What changes is where the panel sits and how it moves: up from
     the bottom edge, thumb-reachable, capped at 92vh with its own scroll so a
     tall form (the metric modal has five value ranges) cannot run off screen.
     ----------------------------------------------------------------------- */
  .lf-sheet {
    /* Above the shell. The pages carry Tailwind z-50 on these overlays, which is
       below the tab bar at z-60 — without this the bar covers a sheet footer, so
       Save and Cancel are unreachable. */
    z-index: 90 !important;
    align-items: flex-end !important;
    justify-content: stretch !important;
    padding: 0 !important;
    background: rgba(46, 38, 37, .42) !important;
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
    overflow: hidden !important;
  }
  .lf-sheet:not(.hidden) { animation: lf-fade .22s ease both; }
  .lf-sheet > div {
    width: 100% !important;
    max-width: none !important;
    margin: 0 !important;
    max-height: 92vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    border-radius: var(--lf-r-xl) var(--lf-r-xl) 0 0 !important;
    padding: 8px 18px calc(20px + var(--lf-safe-b)) !important;
    box-shadow: var(--lf-shadow-lg) !important;
    animation: lf-sheet-up .34s var(--lf-ease) both;
  }
  /* Grab handle. Nothing here is draggable — it is the affordance that tells a
     thumb this panel belongs to the bottom edge. */
  .lf-sheet > div::before {
    content: '';
    display: block;
    width: 38px;
    height: 4px;
    margin: 6px auto 14px;
    border-radius: 999px;
    background: rgba(110, 101, 95, .22);
  }
  .lf-sheet > div > form > div:last-child,
  .lf-sheet > div > div > div:last-child { gap: 10px; }

  /* -----------------------------------------------------------------------
     The planning grids
     -----------------------------------------------------------------------
     mass-main and mass-personalized are a date-per-column spreadsheet. There
     is no honest way to fold that into one column, so it stays a horizontal
     scroller — but a narrower one, snapping to columns, with the pinned first
     column actually opaque against white.
     ----------------------------------------------------------------------- */
  .grid-wrap {
    border-radius: var(--lf-r) !important;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
  }
  .grid-wrap th, .grid-wrap td { scroll-snap-align: start; }
  .grid-wrap thead th:not(.sticky-col) { min-width: 168px !important; }
  .grid-wrap th, .grid-wrap td { padding-left: 8px !important; padding-right: 8px !important; }
  .grid-wrap select { font-size: 16px !important; min-height: 44px; }

  .lf-swipe-hint {
    display: flex !important;
    align-items: center;
    gap: 6px;
    margin: 0 0 8px;
    font-size: 12px;
    color: var(--lf-slate);
  }

  /* Bulk-upload error table: same record-card treatment. */
  .lf-upload__errors table { display: block !important; }
  .lf-upload__errors thead { display: none !important; }
  .lf-upload__errors tbody, .lf-upload__errors tr { display: block !important; }
  .lf-upload__errors td { display: block !important; padding: 4px 12px !important; border: 0 !important; }
  .lf-upload__errors tr { padding: 8px 0; border-top: 1px solid rgba(219, 140, 131, .2) !important; }

  .lf-hide-phone { display: none !important; }
}

/* Very narrow phones. From/To only stops fitting side by side below ~360px, so
   that split is separated from the type scale. */
@media (max-width: 400px) {
  .lf-title { font-size: 24px; }
  .lf-tab { font-size: 9.5px; }
  .lf-cards tbody td { font-size: 13px; }
}
@media (max-width: 359px) {
  .lf-filters__pair { flex-direction: column; }
}

/* ============================================================================
   9. Desktop refinements
   ============================================================================
   The phone work is the point of this file, but the same components render on a
   desktop and had drifted; these keep the two consistent.
   ============================================================================ */
@media (min-width: 768px) {
  .lf-cards { overflow: hidden; }
  .lf-cards table { width: 100%; }
  .lf-cards thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: #FBF9F3;
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--lf-slate);
  }
  .lf-cards tbody tr {
    border-bottom: 1px solid var(--lf-line-soft);
    transition: background-color .14s ease;
  }
  .lf-cards tbody tr:last-child { border-bottom: 0; }
  .lf-cards tbody tr:hover { background: rgba(197, 160, 89, .05); }
  .lf-cards thead tr { border-bottom: 1px solid var(--lf-line); }

  .lf-cards td.lf-cell-actions { white-space: nowrap; }
  /* This rule promotes a plain text-link button into a chip, which is what most row
     actions in the portal are. `:not(.lf-tbtn)` is load-bearing: a .lf-tbtn is *already* a
     chip, and this selector is (0,2,1) against .lf-tbtn--danger's (0,1,0), so without the
     exclusion it silently repainted every tone variant gold — and its `color: !important`
     took the text with it.
     That was not theoretical. On Users, Edit, Deactivate and Delete all rendered as the
     same gold chip on desktop, so the one button that permanently destroys a row looked
     exactly like the one that opens a form. Found by a test asserting --wipe was quieter
     than the button beside it; it was not quieter, it was identical. */
  .lf-cards td.lf-cell-actions > button:not(.lf-tbtn) {
    min-height: 30px;
    padding: 0 11px;
    border: 0;
    border-radius: 9px;
    background: rgba(197, 160, 89, .13);
    color: var(--lf-gold-deep) !important;
    font-family: var(--lf-font);
    font-size: 12px !important;
    font-weight: 600;
    cursor: pointer;
    text-decoration: none !important;
    transition: background-color .16s ease;
  }
  .lf-cards td.lf-cell-actions > button.lf-a-danger:not(.lf-tbtn) {
    background: rgba(219, 140, 131, .14);
    color: var(--lf-rose-deep) !important;
  }
  .lf-cards td.lf-cell-actions > button.lf-a-good:not(.lf-tbtn) {
    background: rgba(124, 169, 130, .16);
    color: var(--lf-sage-deep) !important;
  }
  .lf-cards td.lf-cell-actions > button.lf-a-warn:not(.lf-tbtn) {
    background: rgba(197, 160, 89, .18);
    color: var(--lf-gold-deep) !important;
  }
  .lf-cards td.lf-cell-actions > button.lf-a-info:not(.lf-tbtn) {
    background: rgba(91, 158, 160, .14);
    color: var(--lf-teal-deep) !important;
  }
  .lf-cards td.lf-cell-actions > button.lf-a-wipe:not(.lf-tbtn) {
    margin-left: 8px;
    background: transparent;
    border: 1px solid rgba(180, 101, 92, .35);
    color: var(--lf-rose-deep) !important;
  }
  .lf-swipe-hint { display: none; }
}

/* ============================================================================
   10. Motion
   ============================================================================ */
@keyframes lf-fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes lf-sheet-up {
  from { transform: translate3d(0, 100%, 0) }
  to   { transform: translate3d(0, 0, 0) }
}
@keyframes lf-rise {
  from { opacity: 0; transform: translate3d(0, 10px, 0) }
  to   { opacity: 1; transform: none }
}

/* Staggered entry for the dashboard tiles. Six steps is enough for every grid
   on the page; anything past it simply arrives with the last one. */
.lf-rise > * { animation: lf-rise .5s var(--lf-ease) both; }
.lf-rise > *:nth-child(1) { animation-delay: .02s }
.lf-rise > *:nth-child(2) { animation-delay: .06s }
.lf-rise > *:nth-child(3) { animation-delay: .10s }
.lf-rise > *:nth-child(4) { animation-delay: .14s }
.lf-rise > *:nth-child(5) { animation-delay: .18s }
.lf-rise > *:nth-child(6) { animation-delay: .22s }

/* A skeleton for the moment before the first API response lands. The portal
   showed bare "Loading…" text, or on the dashboard nothing at all. */
.lf-skeleton {
  position: relative;
  overflow: hidden;
  background: rgba(197, 160, 89, .10);
  border-radius: 8px;
  color: transparent !important;
}
.lf-skeleton::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .65), transparent);
  animation: lf-shimmer 1.4s infinite;
}
@keyframes lf-shimmer {
  from { transform: translateX(-100%) }
  to   { transform: translateX(100%) }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  /* The drawer still has to move — it just arrives instantly. */
  .lf-drawer, .lf-scrim { transition: none !important; }
}

/* Printing a report list should not print the shell. */
@media print {
  .lf-shell, .lf-topbar, .lf-tabbar, .lf-drawer, .lf-scrim { display: none !important; }
  body.lf-body { padding: 0 !important; }
}

/* ==========================================================================
   11 · PARENT REPORT
   ==========================================================================
   Used by pages/child-report.html, which renders the same report the Android
   app shows a parent. Kept in one block so the rules that decide *how a child
   may be described* sit together:

     .lf-rungs   five discrete steps, never a percentage. 3 of 5 drawn as 60%
                 says a child is 60% finished with being a person.
     .lf-bloom   petal length from visit counts, and no number printed. "She
                 chose blocks most" is the insight; "34%" is not.
     .lf-domain  counts of metrics per band, not an average of bands, and a
                 stacked bar rather than a radar — a radar draws equal
                 distances between axes that measure different things.
     .lf-chat    the soft warning. Rose, never red, and never an icon.
   ========================================================================== */

/* WHOSE OBSERVATION IS THIS (T-162). A line under every report heading saying which of
   the two records it came from -- the teacher's daily one or the psychologist's monthly
   one. Deliberately a quiet caption and not a loud badge: it must be readable before the
   reader decides what a step means, without competing with the heading itself. The three
   hues are the ones already carrying those meanings elsewhere on the page. */
.lf-srcline {
  margin: -2px 0 12px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--lf-accent-deep, var(--lf-slate));
}
.lf-srcline--teacher { --lf-accent-deep: var(--lf-teal-deep); }
.lf-srcline--psych   { --lf-accent-deep: var(--lf-purple-deep); }
.lf-srcline--both    { --lf-accent-deep: var(--lf-slate); }

.lf-rcard {
  background: #fff;
  border: 1px solid var(--lf-line);
  border-left: 4px solid var(--lf-line);
  border-radius: 18px;
  padding: 16px 18px;
  margin-bottom: 12px;
}
.lf-rcard--good  { border-left-color: var(--lf-sage) }
.lf-rcard--grow  { border-left-color: var(--lf-amber) }
.lf-rcard--watch { border-left-color: var(--lf-rose) }
.lf-rcard--note  { border-left-color: var(--lf-teal) }
.lf-rcard--quiet { border-left-color: var(--lf-line); background: var(--lf-cream) }

/* THREE THINGS, NOT TWO, SO IT WRAPS (T-167). T-162 added the value pill beside the
   domain pill, and the count went from "biasanya - 7 dari 9 kali" to "6/11 kali teramati
   oleh guru". Two pills plus a longer nowrap count overflowed the card on a phone. The row
   wraps now and the count is allowed to sit on its own line rather than be clipped. */
.lf-rcard__head {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 6px 10px; margin-bottom: 8px; min-height: 22px;
}
.lf-rcard__metric { font-size: 15px; font-weight: 700; color: var(--lf-earth) }
/* No longer `nowrap`: the sentence it holds is a sentence now, not three tokens, and on a
   360px screen it has to be allowed to break. `margin-left:auto` keeps it right-aligned
   when it shares the row, and harmless when it wraps below. */
.lf-often { font-size: 12px; color: var(--lf-slate); margin-left: auto; text-align: right }

/* The rung wording. A serif face because it is the only text on the page a
   person wrote about this child specifically, and it should not read as a label. */
.lf-said {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 16px; line-height: 1.45; color: var(--lf-earth);
  padding-left: 12px; border-left: 2px solid var(--lf-gold-light);
  margin-top: 6px;
}

.lf-next {
  margin-top: 11px; padding: 10px 12px; border-radius: 12px;
  background: var(--lf-cream); border: 1px dashed var(--lf-line);
  font-size: 13.5px; line-height: 1.5; color: var(--lf-earth);
}
.lf-next__label,
.lf-chat__label {
  display: block; font-size: 10.5px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--lf-slate); margin-bottom: 3px;
}
.lf-chat {
  margin-top: 11px; padding: 10px 12px; border-radius: 12px;
  background: rgba(219, 140, 131, .10); border: 1px solid var(--lf-rose);
  font-size: 13.5px; line-height: 1.5; color: var(--lf-earth);
}
.lf-chat__label { color: var(--lf-rose-deep) }

.lf-rung-row { padding: 10px 0; border-bottom: 1px solid var(--lf-line-soft) }
.lf-rung-row:last-child { border-bottom: 0 }
.lf-rung-row__name { font-size: 13.5px; font-weight: 600; color: var(--lf-earth) }

/* The child report's five-segment ladder.
   RENAMED 2026-09-13 (defect D-69). These three rules were `.lf-rungs` / `.lf-rung`, the
   same names the two master-data pages use for a stacked list of five sentences and a gold
   numbered badge — two unrelated components sharing two class names, in one stylesheet.
   Being declared later, THIS component won everywhere: the Activities & Metrics detail
   panel laid its five value ranges out side by side in 118px columns, and every numbered
   badge in the portal rendered as a 7x11 grey bar instead of a 25px gold circle. It had
   been doing that since the page shipped, at every width, and twenty-three checkers read
   source rather than layout so none of them could see it. Found by driving a browser
   while building the value tree, which is how all six of the 2026-09-08 defects were
   found too. */
.lf-ladder { display: flex; align-items: center; gap: 5px; margin: 7px 0 6px }
.lf-ladder__step {
  flex: 1; height: 11px; border-radius: 6px;
  background: var(--lf-cream-deep); border: 1px solid var(--lf-line-soft);
}
.lf-ladder__step.is-on {
  background: linear-gradient(180deg, var(--lf-gold-light), var(--lf-gold));
  border-color: var(--lf-gold-deep);
}
.lf-ladder__step.is-best { background: transparent; border: 2px dashed var(--lf-gold-deep) }

.lf-bloom { width: 100%; max-width: 320px; height: auto; display: block; margin: 0 auto }
.lf-legend { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 10px }
.lf-legend__row {
  display: flex; align-items: center; gap: 7px;
  font-size: 12.5px; color: var(--lf-earth);
}
.lf-legend__dot { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto }
.lf-read {
  margin-top: 12px; padding: 11px 13px; border-radius: 12px;
  background: var(--lf-cream); font-size: 13.5px; line-height: 1.55;
  color: var(--lf-earth);
}

.lf-domain { margin-bottom: 18px }
.lf-domain__name {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  font-size: 13.5px; font-weight: 600; color: var(--lf-earth); margin-bottom: 5px;
}
.lf-domain__count { font-size: 12px; font-weight: 500; color: var(--lf-slate) }

/* THE KEY (T-167). A swatch beside its own word, used twice: once as the legend for the
   whole section, and again under each bar with that bar's counts. One component for both
   so the colour can never mean two things on one screen. */
.lf-key {
  display: flex; flex-wrap: wrap; gap: 6px 16px;
  margin: 0 0 14px; padding: 0; list-style: none;
}
.lf-key--tally { margin: 7px 0 0 }
.lf-key__item {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12px; color: var(--lf-slate);
}
.lf-key__item strong { color: var(--lf-earth); font-weight: 700 }
.lf-key__dot {
  width: 11px; height: 11px; flex: none; border-radius: 3px;
  border: 1px solid rgba(74, 62, 61, .10);
}
.lf-domain__bar {
  display: flex; height: 12px; border-radius: 6px; overflow: hidden;
  background: var(--lf-cream-deep);
}
.lf-domain__bar > span { display: block }

.lf-thread { position: relative; padding-left: 20px }
.lf-thread::before {
  content: ''; position: absolute; left: 4px; top: 8px; bottom: 8px; width: 2px;
  background: linear-gradient(180deg, var(--lf-gold), var(--lf-line-soft));
}
.lf-moment { position: relative; padding-bottom: 16px }
.lf-moment:last-child { padding-bottom: 0 }
.lf-moment::before {
  content: ''; position: absolute; left: -20px; top: 4px;
  width: 10px; height: 10px; border-radius: 50%;
  background: #fff; border: 2px solid var(--lf-gold);
}
.lf-moment__date {
  display: block; font-size: 11px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--lf-gold-deep); margin-bottom: 2px;
}
.lf-moment__text { display: block; font-size: 13.5px; line-height: 1.5; color: var(--lf-earth) }

/* SUMMARY FIRST, THEN THREE CHAPTERS (T-275, ADR-0192).
   The report was ~16 sections of equal weight, and a parent had to scroll a long way before
   knowing what it was about. Now: one summary card (.lf-rhero), three chapter chips split by
   WHO recorded it (.lf-seg--chapters, sticky), one "who recorded this" line per chapter
   (.lf-who) instead of a source caption under every heading, and lighter metric cards
   (.lf-acard): one pill, a title, the step sentence, the ladder, one meta line.
   Every colour is an existing token; the secondary text is --lf-slate, 5.7:1 on white and
   5.3:1 on cream, so nothing here sits below WCAG AA. */
.lf-rhero {
  margin-bottom: 14px; padding: 18px;
  border-radius: var(--lf-r-lg);
  background: linear-gradient(135deg, var(--lf-surface) 0%, var(--lf-cream) 50%, rgba(197, 160, 89, .22) 100%);
  border: 1px solid rgba(197, 160, 89, .32);
}
.lf-rhero .lf-agebadge-row { margin: 0 }
.lf-rhero__title {
  margin: 10px 0 4px;
  font-family: var(--lf-display); font-size: 22px; font-weight: 600; line-height: 1.25;
  color: var(--lf-earth);
}
.lf-rhero__period { margin: 0; font-size: 13px; line-height: 1.5; color: var(--lf-slate) }
/* The stat tiles are the dashboard's `.lf-stat`, made compact: three of them must share a
   390 px card, and the bloom behind the number would sit on the gradient as a smudge. */
.lf-rhero__stats { display: flex; gap: 8px; margin-top: 14px }
.lf-rhero__stats .lf-stat {
  flex: 1 1 0; min-width: 0; padding: 10px 8px; text-align: center;
  border-radius: 14px; box-shadow: none;
}
.lf-rhero__stats .lf-stat::after { display: none }
.lf-rhero__stats .lf-stat__num {
  font-size: 22px; color: var(--lf-earth);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lf-rhero__stats .lf-stat__lbl { margin-top: 4px; font-size: 11.5px; line-height: 1.3 }
/* "Untuk di rumah": the psychologist's own recommendation, verbatim. */
.lf-rhero__tip { margin-top: 12px; padding: 12px; border-radius: 14px; background: var(--lf-surface) }
.lf-rhero__tiplbl {
  margin: 0 0 3px; font-size: 10.5px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--lf-gold-text);
}
.lf-rhero__tiptext { margin: 0; font-size: 13.5px; line-height: 1.5; color: var(--lf-earth); white-space: pre-line }

/* The chapter chips: the segmented control, stretched and given the mockup's dark "on".
   Sticky so the reader can change chapter from anywhere in a long one. The top offset is
   the fixed phone top bar; above 767px there is no top bar. */
.lf-chapters {
  position: sticky; top: calc(var(--lf-topbar) + var(--lf-safe-t)); z-index: 20;
  margin: 0 -4px 6px; padding: 8px 4px;
  background: var(--lf-cream);
}
/* The tour banner is sticky under the top bar on a phone, so the chips stick under both. */
body.lf-has-tourbar .lf-chapters { top: calc(var(--lf-topbar) + var(--lf-safe-t) + var(--lf-tourbar-h, 40px)) }
@media (min-width: 768px) {
  .lf-chapters { top: 0 }
  body.lf-has-tourbar .lf-chapters { top: var(--lf-tourbar-h, 40px) }
}
.lf-seg--chapters { display: flex; width: 100%; gap: 6px; padding: 0; background: transparent; border: 0 }
.lf-seg--chapters .lf-seg__btn {
  flex: 1 1 0; min-width: 0; padding: 0 8px;
  border: 1px solid var(--lf-line); border-radius: 999px; background: var(--lf-surface);
  font-size: 12.5px; line-height: 1.2;
}
.lf-seg--chapters .lf-seg__btn.is-on {
  background: var(--lf-earth); border-color: var(--lf-earth); color: #fff; box-shadow: none;
}
.lf-seg--chapters .lf-seg__btn:focus-visible { outline: 2px solid var(--lf-gold-deep); outline-offset: 2px }

/* "Who recorded this", once per chapter. Same hues as `.lf-srcline`. */
.lf-recby {
  display: flex; align-items: center; gap: 10px;
  margin: 6px 0 14px; font-size: 13px; line-height: 1.4; color: var(--lf-slate);
}
.lf-recby__av {
  flex: none; width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center; font-size: 13px; font-weight: 700;
  background: rgba(197, 160, 89, .16); color: var(--lf-gold-text);
}
.lf-recby--teacher .lf-recby__av { background: rgba(91, 158, 160, .16); color: var(--lf-teal-deep) }
.lf-recby--psych .lf-recby__av { background: rgba(163, 139, 184, .20); color: var(--lf-purple-deep) }

/* One card, one message. Rows divided by a hairline inside the section panel, not boxed. */
.lf-acard { padding: 12px 0; border-top: 1px solid var(--lf-line-soft) }
.lf-acard:first-child { border-top: 0; padding-top: 2px }
.lf-acard[hidden] { display: none }
.lf-acard__title { margin-top: 6px; font-size: 14.5px; font-weight: 700; color: var(--lf-earth) }
.lf-acard__desc {
  margin: 4px 0 2px;
  font-family: var(--lf-display); font-size: 15px; line-height: 1.45; color: var(--lf-earth);
}
.lf-acard .lf-ladder { max-width: 360px }   /* a phone's width; edge to edge on a desktop reads as a progress bar */
.lf-acard__meta { font-size: 12.5px; line-height: 1.5; color: var(--lf-slate) }
.lf-acard__meta b { font-weight: 700; color: var(--lf-earth) }
/* "First shown" and "n of m times", one per line, each a flex row: the words wrap inside
   their own item and the (?) help.js appends stays beside them, never alone at the start
   of the next line. The 4px keeps two 30px buttons on consecutive lines from touching. */
.lf-acard__meta--stack > span { display: flex; align-items: center }
.lf-acard__meta--stack > span + span { margin-top: 4px }
.lf-acard__meta--stack .lf-helpbtn { flex: none }
/* The report's own head and controls (T-275 round 2). Summary first on a phone: the intro
   folds behind "About this report", the form packs tighter, so the summary tiles are on the
   first screen at 390x844 as in the approved mockup. Nothing changes from 768px up. */
.lf-reportctl { margin-bottom: 20px }
.lf-about { display: none }
@media (max-width: 767px) {
  .lf-page-head--fold { margin-bottom: 12px }
  .lf-page-head--fold .lf-title { margin-bottom: 2px }
  .lf-about {
    display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0;
    border: 0; background: none; cursor: pointer;
    font-family: var(--lf-font); font-size: 13px; font-weight: 600; color: var(--lf-gold-text);
  }
  .lf-about::after { content: '\25BE'; font-size: 11px; transition: transform .15s ease }
  .lf-about[aria-expanded="true"]::after { transform: rotate(180deg) }
  .lf-about:focus-visible { outline: 2px solid var(--lf-gold-deep); outline-offset: 2px }
  .lf-page-head--fold #reportIntro:not(.is-open) { display: none }
  .lf-reportctl { margin-bottom: 14px; padding: 12px 14px }
  .lf-reportctl--one .lf-filters > .lf-field--grow { display: none }
  .lf-reportctl .lf-filters { gap: 8px }
  .lf-reportctl .lf-toolbar { flex-direction: row; align-items: center; gap: 8px; margin: 10px 0 0 }
  .lf-reportctl .lf-toolbar > .lf-btn,
  .lf-reportctl .lf-toolbar > .lf-helphost { flex: 1 1 0; min-width: 0 }
  .lf-reportctl .lf-toolbar .lf-btn { padding-left: 10px; padding-right: 10px; white-space: normal; line-height: 1.2 }
  .lf-reportctl #out:empty { display: none }
}
.lf-more {
  display: block; width: 100%; margin-top: 4px; padding: 10px 0 2px;
  border: 0; border-top: 1px solid var(--lf-line-soft); background: none; cursor: pointer;
  font-family: var(--lf-font); font-size: 13px; font-weight: 600; color: var(--lf-gold-text);
}

@media (max-width: 767px) {
  .lf-rcard { padding: 14px 15px !important; border-radius: 16px !important }
  .lf-said { font-size: 15px !important }
  .lf-bloom { max-width: 100% !important }
}

/* ===========================================================================
   11b. ROLE SCREENS - teacher, psychologist, parent
   Added 2026-09-06 with the role-aware portal. These are the components the
   admin pages never needed: a Likert rung to choose, a centre to tick, an
   enrolment card for the family.

   NAMED lf-scorerung, NOT lf-rung. `.lf-rung` already exists above as the child
   report's small rung BADGE; reusing the name here would have quietly restyled
   every ladder on that page into a full-width row.
   =========================================================================== */

/* A metric being scored. The activity groups them; the rule this enforces is
   that a teacher never picks a number, they pick the sentence it means.

   NAMED lf-scoremetric, NOT lf-metric — 2026-09-13, defect D-72. The comment
   eleven lines above got `.lf-rung` right and this got `.lf-metric` wrong, in the
   same block, on the same day. `.lf-metric` is the master-data METRIC CARD at line
   704, and being declared later this won on every page: Activities & Metrics and
   Values both lost their card's border, radius and `overflow: hidden` and gained a
   top rule instead, since the day the role screens landed. Nothing could see it,
   because no tool in this repository had ever read this file. */
.lf-scoremetric { padding: 14px 0; border-top: 1px solid var(--lf-line-soft); }
.lf-scoremetric:first-of-type { border-top: 0; }
.lf-scoremetric__name { margin: 0 0 10px; font-weight: 600; color: var(--lf-earth); }

/* One choosable rung.
   The whole row is the target, not the radio: a 16px dot is not a tap target,
   and a teacher is doing this one-handed while watching a room. */
.lf-scorerung {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 12px; margin-bottom: 6px;
  border: 1px solid var(--lf-line-soft); border-radius: 12px;
  background: #fff; cursor: pointer; transition: border-color .12s, background .12s;
}
.lf-scorerung:hover { border-color: var(--lf-gold); }
.lf-scorerung input { margin-top: 3px; accent-color: var(--lf-gold-deep); flex: none; }
.lf-scorerung:has(input:checked) { border-color: var(--lf-gold); background: rgba(197,160,89,.10); }
.lf-scorerung__text { font-size: 14px; line-height: 1.5; color: var(--lf-earth); }

/* Free-play centres. A wrapping set of pills rather than a column of
   checkboxes: six stacked checkboxes read as a form to fill in, not as the
   places a child chose to go. */
.lf-centres { display: flex; flex-wrap: wrap; gap: 10px; }
.lf-centre {
  display: flex; align-items: center; gap: 9px;
  padding: 11px 15px; border: 1px solid var(--lf-line-soft); border-radius: 999px;
  background: #fff; cursor: pointer; font-size: 14px; color: var(--lf-earth);
  transition: border-color .12s, background .12s;
}
.lf-centre input { accent-color: var(--lf-gold-deep); }
.lf-centre:has(input:checked) { border-color: var(--lf-sage); background: rgba(124,169,130,.14); }

/* Emotion chips (T-257). A free-play pill with the emotion's own colour on its edge. The
   colour is data — `colorHex` from the catalogue — so the page sets it inline as `--emo`,
   and ticked means "this colour, filled in", never the sage that means "saved" elsewhere. */
.lf-emochip { border-left: 6px solid var(--emo, #E8E1D2); }
.lf-emochip:has(input:checked) {
  border-color: var(--emo, #E8E1D2);
  background: color-mix(in srgb, var(--emo, #E8E1D2) 16%, #fff);
}
.lf-emochip__emoji { font-size: 20px; line-height: 1; }
/* The Mood Meter strip under the emotion bloom: the domain bar, given room to breathe. */
.lf-emozones { margin-top: 14px; }

/* A From/To pair reads as one control, so it sits on one line until it cannot. */
.lf-row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 520px) { .lf-row2 { grid-template-columns: 1fr; gap: 0; } }

/* The family's enrolment card.
   Sage at rest, amber inside the warning window, never rose. An enrolment
   ending is not a failure, and colouring it like one would be the app being
   dramatic about somebody's child. */
.lf-enrol { border-left: 4px solid var(--lf-sage); }
.lf-enrol--soon { border-left-color: var(--lf-amber); background: rgba(229,178,93,.07); }
.lf-enrol__date { margin: 6px 0 2px; font-size: 15px; color: var(--lf-earth); }
.lf-enrol__days { margin: 0; font-weight: 600; color: var(--lf-sage-deep); }
.lf-enrol__days.is-soon { color: var(--lf-amber-deep); }

/* What a parent reads about one recorded metric.
   NAMED lf-parentscore, not lf-score: the digit here is deliberately never on
   its own, which is the opposite of what a bare score class would suggest. */
.lf-parentscore { padding: 14px 0; border-top: 1px solid var(--lf-line-soft); }
.lf-parentscore:first-of-type { border-top: 0; }
.lf-parentscore__section {
  margin: 18px 0 2px; font-size: 12.5px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--lf-slate);
}
.lf-parentscore__activity { margin: 0; font-weight: 600; color: var(--lf-earth); }
.lf-parentscore__metric { margin: 2px 0 8px; font-size: 13px; color: var(--lf-slate); }
.lf-parentscore__row { display: flex; align-items: flex-start; gap: 10px; }
.lf-parentscore__value {
  flex: none; min-width: 26px; text-align: center;
  padding: 3px 9px; border-radius: 8px;
  background: var(--lf-gold-deep); color: var(--lf-cream);
  font-weight: 700; font-size: 14px;
}
.lf-parentscore__meaning { font-size: 14px; line-height: 1.5; color: var(--lf-earth); }
.lf-parentscore__note {
  margin: 8px 0 0; font-size: 13px; font-style: italic; color: var(--lf-slate);
}

/* ---------------------------------------------------------------------------
   Today's values — the teacher's daily form (sprint AO)
   ---------------------------------------------------------------------------
   The rung rows, the metric block and the panel are all the activity form's own
   classes, deliberately: it is the same control pointed at a different subject,
   and a second visual language for the same gesture would make the change feel
   bigger than it is. Only two things here are new.

   1. THE SOURCE CHIPS. This is the deduplication, rendered. Two activities that
      both carry Estetika produce one Estetika card listing both, and the chips
      are what stop that reading as "an activity went missing".
   2. THE UNLINKED NOTE. A scheduled activity that carries no value is named
      rather than dropped, and it needs to look like information rather than
      like a failure — an admin fixes it in a minute on Activities & Values. */

.lf-srcrow {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin: 10px 0 4px;
}
.lf-srcchip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 11px 5px 6px;
  border: 1px solid var(--lf-line-soft); border-radius: 999px;
  background: var(--lf-cream);
  font-size: 13px; color: var(--lf-earth);
}
/* The session label sits INSIDE the chip rather than beside it, so a wrapped row
   never separates an activity from which session it belonged to. */
.lf-srcchip__sess {
  padding: 2px 8px; border-radius: 999px;
  background: #fff; border: 1px solid var(--lf-line-soft);
  font-size: 11px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--lf-slate);
}

/* --warn, not --bad. Nothing is broken: an activity simply has not been
   classified yet, and the tone should say "worth a minute" rather than "error". */
.lf-note--warn {
  display: block;
  border-color: rgba(229, 178, 93, .55);
  background: rgba(229, 178, 93, .10);
}
.lf-note__title {
  margin: 0 0 4px; font-weight: 700; color: var(--lf-earth);
}
.lf-unlinked {
  margin: 8px 0 0; padding-left: 18px;
  font-size: 14px; color: var(--lf-earth);
}
.lf-unlinked li { margin: 3px 0; }

@media (max-width: 520px) {
  /* A chip per line rather than a squeezed row: the activity description is the
     part being read, and truncating it to keep two chips side by side would hide
     exactly the thing the chip exists to say. */
  .lf-srcrow { flex-direction: column; align-items: flex-start; }
  .lf-srcchip { max-width: 100%; }
}

/* ---------------------------------------------------------------------------
   The folded, optional metric (T-320, ADR-0198) — metric-fold.js
   ---------------------------------------------------------------------------
   Today's values and the Personalised session both open folded: each metric is
   one quiet row with its name and what is recorded, so the whole set reads at a
   glance and nothing looks like a duty. Opening a row shows its rungs.

   The rung rows inside are still .lf-scorerung — the same gesture, the same
   pixels — and the status line of a folded row shows the rung's SENTENCE. The
   pips beside it say where on the ladder the sentence sits; they are never shown
   without it.

   Four states, painted from data-state, each its own mark:
     empty     a hollow ring   nothing recorded, and that is fine
     scored    a gold check    saved
     changed   a gold dot      picked, not yet saved
     clearing  a dashed amber  saved, and will be cleared on save */

.lf-mfbar {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  margin: 0 0 16px; padding: 14px 16px;
  background: linear-gradient(135deg, #fff 0%, #FFFDF8 100%);
  border: 1px solid var(--lf-line); border-radius: var(--lf-r-lg);
  box-shadow: var(--lf-shadow-sm);
}
.lf-mfbar__text { min-width: 0; }
.lf-mfbar__count {
  margin: 0; font-family: var(--lf-display); font-size: 17px; font-weight: 600;
  color: var(--lf-earth); letter-spacing: -.005em;
}
.lf-mfbar__hint { margin: 3px 0 0; font-size: 13px; line-height: 1.45; color: var(--lf-slate); }
.lf-mfbar__all { flex: none; border: 1px solid var(--lf-line); background: #fff; color: var(--lf-gold-text); font-weight: 600; }
.lf-mfbar__all:hover { border-color: var(--lf-gold); background: rgba(197, 160, 89, .08); color: var(--lf-gold-text); }

.lf-mfgroup { margin-bottom: 16px; }
.lf-mfgroup__folds { display: grid; gap: 8px; margin-top: 14px; }

.lf-mfold {
  border: 1px solid var(--lf-line-soft); border-radius: 14px; background: #fff;
  transition: border-color .2s var(--lf-ease), box-shadow .2s var(--lf-ease), background .2s;
}
.lf-mfold:hover { border-color: var(--lf-line); }
.lf-mfold.is-open {
  border-color: rgba(197, 160, 89, .42);
  box-shadow: 0 1px 2px rgba(74, 62, 61, .04), 0 12px 28px -18px rgba(169, 132, 60, .45);
}
.lf-mfold[data-state="scored"]:not(.is-open) { background: linear-gradient(90deg, rgba(197,160,89,.07) 0%, #fff 60%); }

.lf-mfold__head {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 12px;
  width: 100%; min-height: 58px; padding: 12px 14px 12px 16px;
  background: none; border: 0; border-radius: 14px; text-align: left;
  font: inherit; color: inherit; cursor: pointer;
}
.lf-mfold__head:focus-visible { outline: 2px solid var(--lf-gold); outline-offset: 2px; }

.lf-mfold__mark {
  position: relative; width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid #E4D9C3; background: #fff;
  transition: background .2s, border-color .2s, transform .2s var(--lf-ease);
}
.lf-mfold[data-state="scored"] .lf-mfold__mark {
  border-color: var(--lf-gold-deep);
  background: linear-gradient(160deg, #D4B272 0%, var(--lf-gold-deep) 100%);
}
.lf-mfold[data-state="scored"] .lf-mfold__mark::after {
  content: ''; position: absolute; left: 6px; top: 2.5px; width: 5px; height: 10px;
  border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.lf-mfold[data-state="changed"] .lf-mfold__mark { border-color: var(--lf-gold); }
.lf-mfold[data-state="changed"] .lf-mfold__mark::after {
  content: ''; position: absolute; inset: 4px; border-radius: 50%; background: var(--lf-gold);
}
.lf-mfold[data-state="clearing"] .lf-mfold__mark { border-style: dashed; border-color: var(--lf-amber-deep); }

.lf-mfold__main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.lf-mfold__name { font-size: 15px; font-weight: 600; line-height: 1.35; color: var(--lf-earth); }
.lf-mfold__status {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  font-size: 13px; line-height: 1.4; color: var(--lf-slate);
}
.lf-mfold__said { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--lf-earth); }
.lf-mfold.is-open .lf-mfold__said { white-space: normal; }
.lf-mfold__none { color: #9A918A; }
.lf-mfold__tag {
  flex: none; padding: 2px 8px; border-radius: 999px;
  background: rgba(197, 160, 89, .16); color: var(--lf-gold-text);
  font-size: 11px; font-weight: 700; letter-spacing: .03em;
}
.lf-mfold__tag--clear { background: rgba(229, 178, 93, .20); color: var(--lf-amber-deep); }

.lf-mfpips { flex: none; display: inline-flex; gap: 3px; }
.lf-mfpips i { width: 6px; height: 6px; border-radius: 50%; background: #E8DFCC; }
.lf-mfpips i.on { background: var(--lf-gold-deep); box-shadow: 0 0 0 2px rgba(197, 160, 89, .22); }

.lf-mfold__chev {
  width: 30px; height: 30px; border-radius: 50%; position: relative;
  background: var(--lf-cream); transition: background .2s, transform .3s var(--lf-ease);
}
.lf-mfold__chev::before {
  content: ''; position: absolute; left: 11px; top: 9px; width: 7px; height: 7px;
  border: solid var(--lf-gold-text); border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.lf-mfold__head:hover .lf-mfold__chev { background: rgba(197, 160, 89, .16); }
.lf-mfold.is-open .lf-mfold__chev { transform: rotate(180deg); background: rgba(197, 160, 89, .16); }

/* The body grows from nothing on a grid row, which animates to the content's real
   height without measuring it in JavaScript. */
.lf-mfold__body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .32s var(--lf-ease); }
.lf-mfold.is-open .lf-mfold__body { grid-template-rows: 1fr; }
.lf-mfold__inner { min-height: 0; overflow: hidden; }
.lf-mfold__pad { padding: 0 16px 16px 50px; }
.lf-mfold__guide { margin: 0 0 10px; }
.lf-mfold__ask {
  margin: 2px 0 8px; font-size: 11.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--lf-slate);
}
.lf-mfrung { padding: 11px 14px; border-radius: 12px; }
.lf-mfrung:has(input:checked) {
  border-color: var(--lf-gold);
  background: linear-gradient(135deg, rgba(197,160,89,.14) 0%, rgba(197,160,89,.06) 100%);
  box-shadow: 0 0 0 3px rgba(197, 160, 89, .12);
}
.lf-mfrung:has(input:checked) .lf-scorerung__text { font-weight: 500; }

/* The note and Clear belong to a pick: a note on an unobserved metric would be
   dropped on save, so it is not offered until there is something to note. */
.lf-mfold__note, .lf-mfold__foot { display: none; }
.lf-mfold.has-pick .lf-mfold__note { display: block; margin-top: 12px; }
.lf-mfold.has-pick .lf-mfold__foot { display: flex; justify-content: flex-end; margin-top: 4px; }
.lf-mfold__clear {
  display: inline-flex; align-items: center; gap: 6px; min-height: 36px; padding: 0 12px;
  background: none; border: 1px solid transparent; border-radius: 10px;
  font: inherit; font-size: 13px; font-weight: 600; color: var(--lf-slate); cursor: pointer;
}
.lf-mfold__clear::before { content: '\00d7'; font-size: 16px; line-height: 1; }
.lf-mfold__clear:hover { border-color: var(--lf-line); background: var(--lf-cream); color: var(--lf-earth); }
.lf-mfold__clear:focus-visible { outline: 2px solid var(--lf-gold); outline-offset: 2px; }

/* The save bar floats with the reader, so Save is never a long scroll away once
   several rows are open. Above the phone tab bar, never under it. */
.lf-mfsave {
  position: sticky; bottom: 14px; z-index: 5;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-top: 18px; padding: 10px 10px 10px 18px;
  background: rgba(255, 255, 255, .9);
  -webkit-backdrop-filter: saturate(180%) blur(18px); backdrop-filter: saturate(180%) blur(18px);
  border: 1px solid var(--lf-line); border-radius: 18px; box-shadow: var(--lf-shadow);
}
.lf-mfsave__state { margin: 0; font-size: 13px; color: var(--lf-slate); }
.lf-mfsave__state.is-dirty { color: var(--lf-gold-text); font-weight: 600; }
.lf-mfsave .lf-btn { flex: none; }

@media (max-width: 767px) {
  .lf-mfsave { bottom: calc(var(--lf-tabbar) + var(--lf-safe-b) + 10px); }
}
@media (max-width: 520px) {
  .lf-mfbar { flex-direction: column; align-items: stretch; }
  .lf-mfbar__all { width: 100%; }
  .lf-mfold__pad { padding-left: 16px; }
  .lf-mfsave { flex-direction: column; align-items: stretch; padding: 10px; text-align: center; }
  .lf-mfsave .lf-btn { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .lf-mfold, .lf-mfold__body, .lf-mfold__chev, .lf-mfold__mark { transition: none; }
}

/* ============================================================
   THE PUBLIC LANDING PAGE — admin/pages/landing.html (T-150, T-155, rebuilt T-294)
   ============================================================
   The site root, read by strangers before any sign-in exists. One adjacent block, one
   `lf-land__` prefix, and no second declaration of anything above: this stylesheet has
   no compiler (D-69, D-71), so a subject declared twice 2 000 lines apart is a subject
   that silently decides the winner.

   T-294 (ADR-0191) — CONVERSION-FIRST, SAME WORDS. Rebuilt to the owner-approved mockup
   (docs/plans/landing-revamp-mockup.html, the "After" tab); the values below are ported
   from its <style> block, not eyeballed. What it is for, in one line each:
     • WhatsApp and the sample report are always one tap away — the bar, the hero, the
       visit card, the footer, and a sticky bar at the bottom of a phone.
     • Visual rhythm: light, cream and one dark earth block alternate, and each section
       has its own shape (editorial columns, a seamless grid, a timeline, cards, a row).
     • Readable prose: 16px body, 18px lead, about 62 characters a line, and a secondary
       ink darker than --lf-slate (6.1:1 on the cream band, not 4.8).
   Two deliberate departures from the mockup, both contrast: its gold label colour
   #9C7A3C is 3.4:1 on the cream band, so every gold LABEL here is --lf-gold-text
   (4.85:1); and its links were gold too, where the old page's teal links were 2.8:1.
   ============================================================ */
.lf-land {
  max-width: none; padding: 0;
  /* Local tokens, consumed only inside this block. */
  --lf-land-ink2: #5F5650;
  --lf-land-rule: #E8DFCF;
  --lf-land-gsoft: #F3EAD6;
  --lf-land-wa: #2F7D5B;
  --lf-land-ondark: #D9CDBA;
  --lf-land-golddark: #E3C68B;
}
/* Every anchor target clears the sticky bar (about 72px) with room to spare — without it
   every menu link lands with its own heading hidden behind the bar it was clicked from. */
.lf-land [id] { scroll-margin-top: 88px; }

/* Keyboard users reach this before the menu bar; it is off-screen until focused. */
.lf-land__skip {
  position: absolute; left: -9999px; top: 0; z-index: 60;
  padding: 12px 18px; border-radius: 0 0 var(--lf-r-sm) 0;
  background: var(--lf-earth); color: #fff; font-size: 14px; font-weight: 600;
}
.lf-land__skip:focus { left: 0; }

/* ---- the sprite: drawn once, never displayed, referenced by <use> ---- */
.lf-land__sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.lf-land__ico {
  width: 20px; height: 20px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ---- layout primitives ---- */
.lf-land__wrap { max-width: 1160px; margin: 0 auto; padding: 0 40px; }
.lf-land__section { padding: 84px 0; }
.lf-land__section--tint { background: var(--lf-cream-deep); }
.lf-land__section--dark { background: var(--lf-earth); color: #F4ECDD; }

/* ---- type ---- */
.lf-land__eyebrow {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin: 0 0 14px; font-family: var(--lf-font);
  font-size: 12px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
  color: var(--lf-gold-text);
}
.lf-land__eyebrow::before { content: ''; width: 22px; height: 1.5px; background: var(--lf-gold); }
.lf-land__h1 {
  margin: 0 0 22px; font-family: var(--lf-display); font-weight: 500;
  font-size: 64px; line-height: 1.04; letter-spacing: -.015em; color: var(--lf-earth);
}
.lf-land__h2 {
  margin: 0 0 16px; font-family: var(--lf-display); font-weight: 500;
  font-size: 42px; line-height: 1.12; letter-spacing: -.01em; color: var(--lf-earth);
}
.lf-land__h3 {
  margin: 0 0 8px; font-family: var(--lf-display); font-weight: 600;
  font-size: 19px; line-height: 1.3; color: var(--lf-earth);
}
.lf-land__lead { margin: 0; font-size: 18px; line-height: 1.65; color: var(--lf-land-ink2); max-width: 56ch; }
.lf-land__read { margin: 0; font-size: 16px; line-height: 1.75; color: var(--lf-land-ink2); max-width: 62ch; }
.lf-land__read + .lf-land__read { margin-top: 14px; }
.lf-land__read--strong { color: var(--lf-earth); font-weight: 600; }
.lf-land__gap { margin-top: 18px; }
.lf-land__small { margin: 12px 0 0; font-size: 13px; line-height: 1.6; color: var(--lf-land-ink2); }
.lf-land__label {
  margin: 0; font-size: 12px; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--lf-gold-text);
}
/* The first sentence of a section, set as a quote: it is the sentence the section exists
   to say, so it is the one a skimming parent should not miss. */
.lf-land__quote {
  margin: 0 0 28px; padding-left: 22px; border-left: 3px solid var(--lf-gold);
  font-family: var(--lf-display); font-style: italic; font-weight: 400;
  font-size: 30px; line-height: 1.35; color: var(--lf-earth);
}
.lf-land__note {
  margin: 28px auto 0; max-width: 70ch; text-align: center;
  font-family: var(--lf-display); font-style: italic; font-weight: 400;
  font-size: 18px; line-height: 1.6; color: var(--lf-land-ink2);
}
.lf-land__link {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 10px;
  color: var(--lf-gold-text); font-size: 14px; font-weight: 600; text-decoration: none;
}
.lf-land__link:hover { text-decoration: underline; }
.lf-land__link .lf-land__ico { width: 15px; height: 15px; }

/* ---- buttons ---- 44px tall at least, CONTEXT.md § 7. */
.lf-land__btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-height: 44px; padding: 13px 22px; border-radius: 999px;
  border: 1px solid transparent; cursor: pointer; white-space: nowrap;
  font-family: var(--lf-font); font-size: 14.5px; font-weight: 600; line-height: 1.2;
  text-decoration: none;
  transition: background-color .15s var(--lf-ease), border-color .15s var(--lf-ease);
}
.lf-land__btn:focus-visible { outline: 2px solid var(--lf-gold); outline-offset: 2px; }
.lf-land__btn--primary { background: var(--lf-earth); color: #fff; }
.lf-land__btn--primary:hover { background: #3A302F; }
.lf-land__btn--wa { background: var(--lf-land-wa); color: #fff; }
.lf-land__btn--wa:hover { background: #276A4D; }
.lf-land__btn--ghost { background: transparent; border-color: #CDBF9F; color: var(--lf-earth); }
.lf-land__btn--ghost:hover { background: var(--lf-cream-deep); }
.lf-land__btn--sm { padding: 9px 16px; font-size: 13.5px; }
.lf-land__btn--block { width: 100%; }

/* ---- the menu bar ---- */
.lf-land__bar {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: 28px;
  padding: 14px max(40px, calc((100% - 1080px) / 2));
  background: rgba(249, 246, 238, .88);
  backdrop-filter: saturate(180%) blur(10px);
  -webkit-backdrop-filter: saturate(180%) blur(10px);
  border-bottom: 1px solid transparent;
  transition: border-color .2s var(--lf-ease), box-shadow .2s var(--lf-ease);
}
/* The line and the shadow arrive only once the page has moved. */
.lf-land__bar.is-stuck {
  border-bottom-color: var(--lf-land-rule);
  box-shadow: 0 6px 20px -16px rgba(74, 62, 61, .5);
}
.lf-land__brand {
  display: inline-flex; align-items: center; gap: 9px;
  text-decoration: none; color: var(--lf-earth); white-space: nowrap;
  font-family: var(--lf-display); font-weight: 600; font-size: 19px;
}
.lf-land__mark { width: 30px; height: 30px; display: block; }
.lf-land__nav { display: flex; gap: 26px; }
.lf-land__navlink {
  position: relative; padding: 10px 0; white-space: nowrap;
  font-size: 14px; color: var(--lf-land-ink2); text-decoration: none;
}
.lf-land__navlink:hover { color: var(--lf-earth); }
.lf-land__navlink.is-on { color: var(--lf-earth); font-weight: 600; }
.lf-land__navlink.is-on::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 4px;
  height: 2px; border-radius: 2px; background: var(--lf-gold);
}
.lf-land__bar-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.lf-land__signin {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 6px;
  font-size: 14px; color: var(--lf-land-ink2); text-decoration: none; white-space: nowrap;
}
.lf-land__signin:hover { color: var(--lf-earth); text-decoration: underline; }
.lf-land__bar-wa { flex: none; }

/* The language control. In the bar and the footer it is small text, "ID / EN"; in the phone
   menu it is the pill with full names. One button class, two dressings. */
.lf-land__langtext {
  display: inline-flex; align-items: center; gap: 0;
  font-size: 12.5px; letter-spacing: .04em; color: var(--lf-land-ink2);
}
/* A link since T-299 (each language has its own URL), laid out as the button it replaced:
   inline-flex so min-height applies and the label stays centred, no underline. */
.lf-land__langbtn {
  appearance: none; -webkit-appearance: none; border: 0; background: transparent;
  display: inline-flex; align-items: center; justify-content: center; text-decoration: none;
  text-align: center;
  cursor: pointer; min-height: 44px; padding: 7px 14px; border-radius: 999px;
  white-space: nowrap;
  font-family: inherit; font-size: 13px; font-weight: 700; color: var(--lf-land-ink2);
}
.lf-land__langtext .lf-land__langbtn {
  min-width: 30px; padding: 0 4px; border-radius: 6px;
  font-size: inherit; font-weight: 500; letter-spacing: inherit; color: inherit;
}
.lf-land__langtext .lf-land__langbtn.is-on { color: var(--lf-earth); font-weight: 700; }
.lf-land__langbtn:hover { color: var(--lf-earth); }
.lf-land__langbtn:focus-visible { outline: 2px solid var(--lf-gold); outline-offset: 1px; }
.lf-land__lang {
  display: inline-flex; padding: 3px;
  background: var(--lf-cream-deep); border: 1px solid var(--lf-land-rule); border-radius: 999px;
}
.lf-land__lang .lf-land__langbtn.is-on {
  background: var(--lf-surface); color: var(--lf-gold-text); box-shadow: var(--lf-shadow-sm);
}

.lf-land__burger {
  display: none; appearance: none; -webkit-appearance: none; cursor: pointer;
  width: 44px; height: 44px; padding: 0;
  align-items: center; justify-content: center;
  background: var(--lf-surface); border: 1px solid var(--lf-land-rule);
  border-radius: var(--lf-r-sm); color: var(--lf-earth);
}
.lf-land__burger .lf-land__ico { width: 22px; height: 22px; }

/* The phone menu. `hidden` does the hiding, so the closed state is inert to a screen
   reader and to tab order rather than merely invisible. */
.lf-land__menu {
  position: sticky; top: 76px; z-index: 39;
  margin: 0 16px; padding: 10px;
  background: var(--lf-surface); border: 1px solid var(--lf-land-rule);
  border-radius: var(--lf-r); box-shadow: var(--lf-shadow-lg);
}
.lf-land__menu[hidden] { display: none; }
.lf-land__menulink {
  display: flex; align-items: center; gap: 12px;
  min-height: 44px; padding: 10px 14px; border-radius: var(--lf-r-sm);
  text-decoration: none; font-size: 15px; font-weight: 600; color: var(--lf-earth);
}
.lf-land__menulink:hover { background: var(--lf-cream-deep); }
.lf-land__menulink .lf-land__ico { color: var(--lf-gold-text); }
.lf-land__menufoot {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin-top: 8px; padding-top: 12px; border-top: 1px solid var(--lf-land-rule);
}

/* ---- tooltips ---- */
.lf-land__tip { position: relative; display: inline-flex; vertical-align: middle; }
.lf-land__tip-btn {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0; margin-left: 8px;
  background: var(--lf-surface); border: 1px solid var(--lf-gold-light);
  border-radius: 999px; color: var(--lf-gold-text);
  font-family: var(--lf-font); font-size: 12px; font-weight: 800; line-height: 1;
}
.lf-land__tip-btn:hover { background: var(--lf-gold); border-color: var(--lf-gold); color: #fff; }
/* A 22px circle is below the 44px tap target CONTEXT.md § 7 holds every control to, so
   the touch area is widened invisibly instead of the dot being drawn bigger. */
.lf-land__tip-btn::before {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
}
.lf-land__tip-bubble {
  position: absolute; left: 50%; bottom: calc(100% + 10px); transform: translateX(-50%);
  z-index: 50; width: max-content; max-width: 290px;
  padding: 12px 14px; border-radius: var(--lf-r-sm);
  background: var(--lf-earth); color: #F7F3EA;
  font-family: var(--lf-font); font-size: 13px; font-weight: 400; font-style: normal;
  line-height: 1.55; letter-spacing: 0; text-align: left; text-transform: none;
  box-shadow: var(--lf-shadow-lg);
  opacity: 0; visibility: hidden;
  transition: opacity .15s var(--lf-ease), visibility .15s var(--lf-ease);
}
.lf-land__tip-bubble::after {
  content: ''; position: absolute; top: 100%; left: 50%; margin-left: -6px;
  border: 6px solid transparent; border-top-color: var(--lf-earth);
}
/* Three ways in: hover for a mouse, focus-within for a keyboard, `is-open` for touch. */
.lf-land__tip:hover .lf-land__tip-bubble,
.lf-land__tip:focus-within .lf-land__tip-bubble,
.lf-land__tip.is-open .lf-land__tip-bubble { opacity: 1; visibility: visible; }

/* ---- hero ---- */
.lf-land__hero { padding: 56px 0 0; }
.lf-land__hero-grid {
  display: grid; grid-template-columns: 1.1fr .9fr; gap: 56px; align-items: center;
}
.lf-land__ctas { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 32px; }
.lf-land__hero-link { margin-top: 16px; }
.lf-land__hero-fig { margin: 0; }
/* T-315: every photo slot is <picture> (AVIF, then JPG). The wrapper takes no box of its own,
   so the <img> inside lays out exactly as it did when it was the figure's direct child. */
.lf-land__hero-fig picture, .lf-land__photo picture, .lf-land__fc picture { display: contents; }
/* ...which would make its <source> a box too — in the founder card's grid it took the
   portrait's column. Hiding it changes nothing about which file the browser picks. */
.lf-land picture > source { display: none; }
.lf-land__hero-fig img {
  display: block; width: 100%; height: 520px; object-fit: cover;
  border-radius: 28px 28px 120px 28px; background: var(--lf-cream-deep);
}
.lf-land__hero-fig figcaption { margin-top: 10px; font-size: 12.5px; color: var(--lf-land-ink2); text-align: center; }
/* The four facts a parent wants before reading a word. Value above, label below — the
   visual order is reversed from the markup so a screen reader still hears "Ages: 2.5–5". */
.lf-land__keyfacts {
  margin: 64px 0 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  border-top: 1px solid var(--lf-land-rule); border-bottom: 1px solid var(--lf-land-rule);
}
.lf-land__keyfacts > div {
  display: grid; grid-template-columns: 24px minmax(0, 1fr); column-gap: 14px;
  align-items: center; padding: 22px 24px;
}
.lf-land__keyfacts > div + div { border-left: 1px solid var(--lf-land-rule); }
.lf-land__keyfacts .lf-land__ico { grid-row: 1 / 3; width: 24px; height: 24px; color: var(--lf-gold-text); }
.lf-land__keyfacts dd {
  grid-column: 2; grid-row: 1; margin: 0 0 2px;
  font-family: var(--lf-display); font-weight: 600; font-size: 17px; line-height: 1.3; color: var(--lf-earth);
}
.lf-land__keyfacts dt { grid-column: 2; grid-row: 2; font-size: 14px; color: var(--lf-land-ink2); }

/* ---- editorial two columns, heading sticky beside the prose ---- */
.lf-land__ed { display: grid; grid-template-columns: .9fr 1.1fr; gap: 72px; align-items: start; }
.lf-land__ed--intro { gap: 56px; }
.lf-land__ed-aside { padding-top: 34px; }
.lf-land__sticky { position: sticky; top: 96px; }

/* ---- vision and mission ---- */
.lf-land__vm { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; margin-top: 56px; }
.lf-land__vm-card { background: var(--lf-surface); border-radius: 24px; padding: 30px; }
.lf-land__vm-card--vision { background: var(--lf-land-gsoft); }
.lf-land__vm-big {
  margin: 12px 0 0; font-family: var(--lf-display); font-weight: 400;
  font-size: 21px; line-height: 1.5; color: var(--lf-earth);
}
.lf-land__checks { list-style: none; padding: 0; margin: 12px 0 0; display: grid; gap: 12px; }
.lf-land__checks li { display: flex; gap: 10px; font-size: 15px; line-height: 1.6; color: var(--lf-land-ink2); }
.lf-land__checks .lf-land__ico { margin-top: 3px; color: var(--lf-sage); }
.lf-land__checks--lg { margin: 18px 0; }
.lf-land__checks--lg li { font-size: 15.5px; color: var(--lf-earth); }

/* ---- what we nurture: a seamless grid, one rule between cells ---- */
.lf-land__vhead { display: flex; justify-content: space-between; gap: 40px; align-items: end; margin-bottom: 44px; }
.lf-land__vhead-title { max-width: 18ch; margin-bottom: 0; }
.lf-land__vhead-text { max-width: 44ch; }
.lf-land__vgrid {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px;
  background: var(--lf-land-rule); border: 1px solid var(--lf-land-rule);
  border-radius: 24px; overflow: hidden;
}
.lf-land__val { background: var(--lf-cream); padding: 28px 24px 30px; transition: background-color .2s var(--lf-ease); }
.lf-land__val:hover { background: var(--lf-surface); }
.lf-land__val h3 { margin: 0 0 8px; font-family: var(--lf-display); font-weight: 600; font-size: 19px; line-height: 1.3; color: var(--lf-earth); }
.lf-land__val p { margin: 0; font-size: 14px; line-height: 1.65; color: var(--lf-land-ink2); }
/* The default lives in the var() fallback, never as a declaration here (portal-css § 8). */
.lf-land__dot {
  display: grid; place-items: center; width: 44px; height: 44px; margin-bottom: 18px;
  border-radius: 14px;
  background: color-mix(in srgb, var(--lf-accent, var(--lf-gold)) 16%, #fff);
  color: var(--lf-accent-deep, var(--lf-gold-text));
}

/* ---- a day: the timeline ---- */
.lf-land__tl {
  list-style: none; margin: 52px 0 0; padding: 0; position: relative;
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
}
.lf-land__tl::before {
  content: ''; position: absolute; left: 0; right: 0; top: 19px; height: 1.5px;
  background: linear-gradient(90deg, var(--lf-gold), #E9D9B4);
}
.lf-land__tl li { position: relative; padding: 0 16px 0 0; }
.lf-land__tl-n {
  position: relative; z-index: 1;
  display: grid; place-items: center; width: 40px; height: 40px; border-radius: 50%;
  background: var(--lf-cream); border: 1.5px solid var(--lf-gold);
  font-family: var(--lf-display); font-weight: 600; font-size: 15px; color: var(--lf-gold-text);
}
.lf-land__tl-time {
  margin: 18px 0 6px; font-size: 12.5px; font-weight: 700; letter-spacing: .04em;
  color: var(--lf-gold-text);
}
.lf-land__tl h3 { margin: 0 0 6px; font-family: var(--lf-display); font-weight: 600; font-size: 18px; line-height: 1.3; color: var(--lf-earth); }
.lf-land__tl li p:not(.lf-land__tl-time) { margin: 0; font-size: 13.5px; line-height: 1.6; color: var(--lf-land-ink2); }

/* ---- photographs ---- The slots are listed in admin/img/README.md; `object-fit: cover`
   crops whatever is supplied to the frame, so the slot's size is a recommendation for
   sharpness, not a requirement for fit. The day's gallery follows the timeline above it
   (steps 1, 3, 4), and its wide frame is the middle one: the outdoor photo has the depth. */
.lf-land__gallery {
  display: grid; grid-template-columns: 1fr 1.4fr 1fr; gap: 16px; margin-top: 64px;
}
.lf-land__photo { margin: 0; }
.lf-land__photo img {
  display: block; width: 100%; height: 300px; object-fit: cover;
  border-radius: 18px; background: var(--lf-cream-deep);
}
.lf-land__photo figcaption { margin-top: 10px; font-size: 13px; color: var(--lf-land-ink2); }
.lf-land__photo--room img { height: 240px; }
.lf-land__photo--room figcaption {
  font-family: var(--lf-display); font-weight: 600; font-size: 16px; color: var(--lf-earth);
}

/* ---- the dark block: personalised learning + how we work ---- */
.lf-land__section--dark .lf-land__h2,
.lf-land__section--dark h3 { color: #fff; }
.lf-land__section--dark .lf-land__read,
.lf-land__section--dark .lf-land__lead,
.lf-land__section--dark .lf-land__note { color: var(--lf-land-ondark); }
.lf-land__section--dark .lf-land__eyebrow { color: var(--lf-land-golddark); }
.lf-land__section--dark .lf-land__quote { color: #fff; }
.lf-land__journey { margin-top: 88px; }
.lf-land__journey .lf-land__read { margin-top: 14px; }
.lf-land__journey-title { max-width: 22ch; }
.lf-land__cycle {
  list-style: none; margin: 48px 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 14px;
}
.lf-land__cyc {
  background: rgba(255, 255, 255, .06); border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 22px; padding: 24px;
}
.lf-land__cyc-k {
  display: block; margin-bottom: 14px;
  font-family: var(--lf-display); font-weight: 500; font-size: 34px; color: var(--lf-land-golddark);
}
.lf-land__cyc h3 { margin: 0 0 8px; font-family: var(--lf-display); font-weight: 600; font-size: 19px; line-height: 1.3; }
.lf-land__cyc p { margin: 0; font-size: 13.5px; line-height: 1.6; color: var(--lf-land-ondark); }

/* ---- for families: the app in a phone frame ---- */
.lf-land__fam { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 64px; align-items: center; }
.lf-land__phonefig { margin: 0; text-align: center; }
.lf-land__phone {
  width: 300px; height: 560px; margin: 0 auto; overflow: hidden;
  border-radius: 40px; border: 10px solid #2E2A27; background: var(--lf-surface);
  box-shadow: 0 30px 60px rgba(74, 62, 61, .25);
}
.lf-land__phone picture { display: block; height: 100%; }
.lf-land__phone img {
  display: block; width: 100%; height: 100%;
  object-fit: cover; object-position: top; background: var(--lf-cream);
}

/* ---- what we believe: a typographic row ---- */
.lf-land__bel {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  margin-top: 48px; border-top: 1px solid var(--lf-land-rule);
}
.lf-land__bel > div { padding: 28px 22px 8px 0; }
.lf-land__bel > div + div { padding-left: 22px; border-left: 1px solid var(--lf-land-rule); }
.lf-land__bel-k { font-size: 13px; font-weight: 500; letter-spacing: .1em; color: var(--lf-gold-text); }
.lf-land__bel h3 { margin: 10px 0 8px; font-family: var(--lf-display); font-weight: 600; font-size: 18px; line-height: 1.3; color: var(--lf-earth); }
.lf-land__bel p { margin: 0; font-size: 14px; line-height: 1.6; color: var(--lf-land-ink2); }

/* ---- facilities ---- */
.lf-land__rooms { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-top: 40px; }
.lf-land__prem-title { margin-top: 64px; font-size: 26px; font-weight: 500; }
.lf-land__prem { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 14px; margin-top: 24px; }
.lf-land__prem > div { background: var(--lf-surface); border-radius: 20px; padding: 22px; }
.lf-land__prem .lf-land__ico { width: 26px; height: 26px; margin-bottom: 12px; color: var(--lf-gold-text); }
.lf-land__prem h4 { margin: 0 0 6px; font-family: var(--lf-display); font-weight: 600; font-size: 17px; line-height: 1.3; color: var(--lf-earth); }
.lf-land__prem p { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--lf-land-ink2); }

/* ---- the founders ---- */
.lf-land__team-title { margin-top: 40px; }
.lf-land__found { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 28px; margin-top: 8px; }
.lf-land__fc {
  display: grid; grid-template-columns: 150px minmax(0, 1fr); gap: 24px;
  background: var(--lf-surface); border-radius: 26px; padding: 26px;
}
.lf-land__portrait {
  display: block; width: 100%; height: 190px; object-fit: cover;
  border-radius: 20px; background: var(--lf-cream-deep);
}
.lf-land__role {
  margin: 0 0 10px; font-size: 12px; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--lf-gold-text);
}
.lf-land__fc p:not(.lf-land__role) { margin: 0; font-size: 14px; line-height: 1.65; color: var(--lf-land-ink2); }
.lf-land__fc p:not(.lf-land__role) + p { margin-top: 10px; }
.lf-land__aka { font-weight: 400; color: var(--lf-land-ink2); white-space: nowrap; }
/* a teacher's belief, set apart as a quiet pull-quote under the bio */
.lf-land__fc p.lf-land__belief {
  margin-top: 14px; padding-left: 14px; border-left: 3px solid var(--lf-gold);
  font-family: var(--lf-display); font-style: italic; color: var(--lf-earth);
}

/* ---- book a visit (T-325) ---- */
.lf-land__book { display: grid; grid-template-columns: .8fr 1.2fr; gap: 56px; align-items: start; }
.lf-land__book-alt { margin: 22px 0 0; font-size: 15px; color: var(--lf-land-ink2); }
.lf-land__book-walink {
  display: inline-flex; align-items: center; gap: 6px; margin-left: 4px;
  color: var(--lf-land-wa); font-weight: 700; text-decoration: underline; text-underline-offset: 3px;
}
.lf-land__form {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 20px;
  background: var(--lf-surface); border: 1px solid var(--lf-land-rule); border-radius: 28px; padding: 32px;
}
.lf-land__form[hidden], .lf-land__form-done[hidden] { display: none; }
.lf-land__field { display: grid; gap: 6px; min-width: 0; margin: 0; padding: 0; border: 0; }
.lf-land__field--wide { grid-column: 1 / -1; }
.lf-land__field > label, .lf-land__field > legend { font-size: 14px; font-weight: 700; color: var(--lf-earth); padding: 0; }
.lf-land__field input[type="text"], .lf-land__field input[type="tel"], .lf-land__field input[type="date"],
.lf-land__field select, .lf-land__field textarea {
  width: 100%; min-height: 46px; padding: 10px 14px; font: inherit; font-size: 16px; color: var(--lf-earth);
  background: #fff; border: 1px solid #D8CDB8; border-radius: 14px;
}
.lf-land__field textarea { resize: vertical; }
.lf-land__field input:focus-visible, .lf-land__field select:focus-visible, .lf-land__field textarea:focus-visible {
  outline: 3px solid var(--lf-gold); outline-offset: 1px;
}
.lf-land__field [aria-invalid="true"] { border-color: var(--lf-rose-deep); }
.lf-land__radio { display: flex; gap: 10px; align-items: flex-start; font-size: 15px; line-height: 1.5; color: var(--lf-land-ink2); }
.lf-land__radio input { margin-top: 4px; width: 18px; height: 18px; accent-color: var(--lf-gold-deep); }
.lf-land__hint { margin: 0; font-size: 13px; color: var(--lf-slate); }
.lf-land__err { margin: 0; font-size: 13px; font-weight: 700; color: var(--lf-rose-deep); }
.lf-land__form-note { margin: 10px 0 0; font-size: 13px; color: var(--lf-slate); }
/* The honeypot: off-screen rather than display:none, which some bots know to skip. */
.lf-land__hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.lf-land__form-done {
  background: var(--lf-surface); border: 1px solid var(--lf-land-rule); border-radius: 28px; padding: 32px;
  font-size: 15px; line-height: 1.6; color: var(--lf-land-ink2);
}
.lf-land__form-done-title { margin: 0 0 10px; font-family: var(--lf-display); font-size: 22px; color: var(--lf-earth); }
.lf-land__vcard-book { display: inline-flex; align-items: center; gap: 6px; margin-top: 14px; }
@media (max-width: 900px) {
  .lf-land__book { grid-template-columns: minmax(0, 1fr); gap: 28px; }
}
@media (max-width: 560px) {
  .lf-land__form { grid-template-columns: minmax(0, 1fr); padding: 22px 18px; }
}

/* ---- visit + FAQ ---- */
.lf-land__vf { display: grid; grid-template-columns: .95fr 1.05fr; gap: 56px; align-items: start; }
.lf-land__vcard {
  background: var(--lf-earth); color: #F4ECDD; border-radius: 28px; padding: 36px;
}
.lf-land__vcard .lf-land__eyebrow { color: var(--lf-land-golddark); }
.lf-land__vcard-lead {
  margin: 0 0 22px; font-family: var(--lf-display); font-weight: 400;
  font-size: 20px; line-height: 1.45; color: #fff;
}
.lf-land__facts { margin: 22px 0 0; display: grid; gap: 14px; }
.lf-land__fact {
  display: grid; grid-template-columns: 24px 120px minmax(0, 1fr); gap: 10px;
  align-items: start; font-size: 14px; line-height: 1.6;
}
.lf-land__fact .lf-land__ico { color: var(--lf-land-golddark); }
.lf-land__fact dt { color: #BFB29C; }
.lf-land__fact dd { margin: 0; color: #fff; }
.lf-land__vlink {
  display: inline-flex; align-items: center; gap: 6px; min-height: 32px;
  color: var(--lf-land-golddark); text-decoration: none;
}
.lf-land__vlink:hover { text-decoration: underline; }
.lf-land__vlink .lf-land__ico { width: 15px; height: 15px; }
.lf-land__faq details { border-bottom: 1px solid var(--lf-land-rule); padding: 18px 0; }
.lf-land__faq summary {
  list-style: none; cursor: pointer; min-height: 44px;
  display: flex; justify-content: space-between; align-items: center; gap: 20px;
  font-family: var(--lf-display); font-weight: 500; font-size: 19px; line-height: 1.4; color: var(--lf-earth);
}
.lf-land__faq summary::-webkit-details-marker { display: none; }
.lf-land__faq summary::after {
  content: '+'; flex: none; font-family: var(--lf-font); font-weight: 400; font-size: 26px; line-height: 1;
  color: var(--lf-gold-text); transition: transform .2s var(--lf-ease);
}
.lf-land__faq details[open] summary::after { transform: rotate(45deg); }
.lf-land__faq summary:focus-visible { outline: 2px solid var(--lf-gold); outline-offset: 4px; border-radius: 6px; }
.lf-land__faq details p { margin: 12px 0 0; font-size: 15px; line-height: 1.7; color: var(--lf-land-ink2); max-width: 62ch; }

/* ---- footer ---- */
.lf-land__foot { margin: 0; padding: 44px 0; background: #3A302F; color: #CDBFAA; font-size: 14px; }
.lf-land__foot-row { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 24px; }
.lf-land__foot-brand { font-family: var(--lf-display); font-weight: 600; font-size: 18px; color: #fff; }
.lf-land__foot-line { margin: 6px 0 0; max-width: 60ch; }
.lf-land__foot-links { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 18px; }
.lf-land__social { display: flex; flex-wrap: wrap; gap: 4px 18px; }
.lf-land__footlink {
  display: inline-flex; align-items: center; gap: 7px; min-height: 44px;
  color: var(--lf-land-golddark); text-decoration: none;
}
.lf-land__footlink:hover { text-decoration: underline; }
.lf-land__footlink .lf-land__ico { width: 17px; height: 17px; }
.lf-land__foot .lf-land__langtext { color: #CDBFAA; }
.lf-land__foot .lf-land__langtext .lf-land__langbtn.is-on { color: #fff; }

/* ---- the phone's sticky bar: shown below 768px only ---- */
.lf-land__mobcta { display: none; }

/* Ten sections deep, the way back to the top should not be a scroll. */
.lf-land__totop {
  position: fixed; right: 18px; bottom: 18px; z-index: 45;
  width: 46px; height: 46px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  appearance: none; -webkit-appearance: none; cursor: pointer;
  background: var(--lf-surface); color: var(--lf-earth);
  border: 1px solid var(--lf-land-rule); border-radius: 999px;
  box-shadow: var(--lf-shadow);
  opacity: 0; visibility: hidden; transform: translateY(8px);
  transition: opacity .2s var(--lf-ease), visibility .2s var(--lf-ease), transform .2s var(--lf-ease);
}
.lf-land__totop.is-on { opacity: 1; visibility: visible; transform: none; }

/* The visit card is sticky only where it fits: on a short laptop screen a sticky card
   taller than the viewport hides its own last rows until the FAQ column runs out. */
@media (min-width: 901px) and (min-height: 900px) {
  .lf-land__vcard { position: sticky; top: 96px; }
}

@media (prefers-reduced-motion: reduce) {
  .lf-land__bar, .lf-land__btn, .lf-land__val, .lf-land__totop,
  .lf-land__tip-bubble { transition: none; }
  .lf-land__faq summary::after { transition: none; }
}

/* Tablet and small laptop: the five links give way to the menu button before they would
   have to wrap; grids drop a column. */
@media (max-width: 1100px) {
  .lf-land__nav { display: none; }
  .lf-land__burger { display: inline-flex; }
  /* SCOPED TO THE BAR (portal-css § 8): `.lf-land__langtext` is also the footer's. */
  .lf-land__bar > .lf-land__bar-actions > .lf-land__langtext { display: none; }
  .lf-land__h1 { font-size: 50px; }
  .lf-land__tl { grid-template-columns: repeat(3, minmax(0, 1fr)); row-gap: 36px; }
  .lf-land__tl::before { display: none; }
  .lf-land__cycle,
  .lf-land__prem,
  .lf-land__bel { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .lf-land__bel > div:nth-child(3n+1) { padding-left: 0; border-left: 0; }
  .lf-land__vgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1101px) {
  .lf-land__menu { display: none; }
}

/* Below 900px the two-column editorial layouts stack: a 320px column of 50px headline
   cannot hold "perkembangannya". */
@media (max-width: 900px) {
  .lf-land__hero-grid,
  .lf-land__ed,
  .lf-land__fam,
  .lf-land__vf,
  .lf-land__found { grid-template-columns: minmax(0, 1fr); gap: 28px; }
  .lf-land__sticky { position: static; }
  .lf-land__ed-aside { padding-top: 0; }
  .lf-land__ed-aside.lf-land__quote { margin-top: 0; }
  .lf-land__hero-fig img { height: 380px; }
  .lf-land__keyfacts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lf-land__keyfacts > div:nth-child(odd) { border-left: 0; }
  .lf-land__keyfacts > div:nth-child(n+3) { border-top: 1px solid var(--lf-land-rule); }
  .lf-land__vhead { display: block; }
  .lf-land__vhead-text { margin-top: 18px; }
}

@media (max-width: 767px) {
  /* The landing page has no app shell, so the portal's phone rule that pads <body> to
     clear the shell's top bar and tab bar left a 68px empty band above the sticky menu
     bar (kept from the earlier fix: still true of this layout). */
  body.lf-body:has(> .lf-land) { padding: 0 !important; }
  .lf-land [id] { scroll-margin-top: 80px; }
  .lf-land__bar { padding: 12px 20px; gap: 12px; }
  .lf-land__bar > .lf-land__bar-actions > .lf-land__signin,
  .lf-land__bar > .lf-land__bar-actions > .lf-land__bar-wa { display: none; }
  .lf-land__menu { top: 72px; }
  .lf-land__wrap { padding: 0 20px; }
  .lf-land__section { padding: 64px 0; }
  .lf-land__hero { padding-top: 36px; }
  .lf-land__h1 { font-size: 40px; }
  .lf-land__h2 { font-size: 30px; }
  .lf-land__quote { font-size: 24px; }
  .lf-land__hero-fig img { height: 300px; border-radius: 22px 22px 80px 22px; }
  .lf-land__keyfacts { margin-top: 40px; }
  .lf-land__keyfacts > div { padding: 18px 12px; column-gap: 10px; }
  .lf-land__keyfacts dd { font-size: 15.5px; }
  .lf-land__keyfacts dt { font-size: 13px; }
  .lf-land__vm,
  .lf-land__gallery { grid-template-columns: minmax(0, 1fr); }
  .lf-land__vgrid { grid-template-columns: minmax(0, 1fr); }
  /* The day as a vertical timeline: the rule runs down through the numbered circles. */
  .lf-land__tl { grid-template-columns: minmax(0, 1fr); row-gap: 22px; }
  .lf-land__tl::before {
    display: block; left: 19px; right: auto; top: 0; bottom: 0; width: 1.5px; height: auto;
    background: linear-gradient(180deg, var(--lf-gold), #E9D9B4);
  }
  .lf-land__tl li { display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 0 16px; padding: 0; }
  .lf-land__tl-time { margin: 2px 0 4px; }
  .lf-land__photo img { height: 220px; }
  /* Six rooms stacked one per row ran 1,400px of scrolling; two per row keeps them a
     glance, at a 4:3-ish crop of the 3:2 photos. */
  .lf-land__rooms { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin-top: 28px; }
  .lf-land__photo--room img { height: 120px; border-radius: 14px; }
  .lf-land__photo--room figcaption { margin-top: 8px; font-size: 14px; line-height: 1.3; }
  .lf-land__cycle,
  .lf-land__prem { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lf-land__cyc { padding: 20px; }
  .lf-land__bel { grid-template-columns: minmax(0, 1fr); }
  .lf-land__bel > div,
  .lf-land__bel > div + div { padding: 22px 0 14px; border-left: 0; }
  .lf-land__bel > div + div { border-top: 1px solid var(--lf-land-rule); }
  .lf-land__fc { grid-template-columns: minmax(0, 1fr); padding: 22px; }
  .lf-land__portrait { height: 240px; }
  .lf-land__vcard { padding: 26px 22px; }
  .lf-land__vcard-lead { font-size: 18px; }
  .lf-land__fact { grid-template-columns: 24px minmax(0, 1fr); gap: 2px 10px; }
  .lf-land__fact dd { grid-column: 2; }
  .lf-land__faq summary { font-size: 17.5px; }
  .lf-land__foot { padding: 36px 0 24px; }
  /* Both actions, side by side. The WhatsApp label is the short key (lp.waShort) so the
     pair fits at 390px; the tour label may wrap to two lines and that is fine. */
  .lf-land__mobcta {
    display: flex; gap: 8px; position: sticky; bottom: 0; z-index: 30;
    padding: 12px 16px calc(12px + var(--lf-safe-b));
    background: rgba(249, 246, 238, .95);
    backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
    border-top: 1px solid var(--lf-land-rule);
  }
  .lf-land__mobcta .lf-land__btn {
    flex: 1 1 0; min-width: 0; padding: 10px 10px; font-size: 13px;
    white-space: normal; text-align: center;
  }
  .lf-land__totop { bottom: calc(86px + var(--lf-safe-b)); }
  /* Centred and clamped to the viewport: a 290px bubble anchored to a dot near the right
     edge of a 375px screen would otherwise hang off it. */
  .lf-land__tip-bubble {
    position: fixed; left: 16px; right: 16px; bottom: auto; top: 50%;
    width: auto; max-width: none; transform: none;
  }
  .lf-land__tip-bubble::after { display: none; }
}

/* ===========================================================================
   7. Billing (T-199) - admin Billing, Packages and Invoice settings, the parent
   Package and payments page, and the card on the parent's Today page.
   ===========================================================================
   Built from the tokens above and nothing else. Every status is an icon, a word and a
   colour together (.lf-bstat), because a family reading on a phone in sunlight must not
   have to tell amber from rose to know whether a bill is late. Money is tabular so a
   column of rupiah lines up digit under digit.
   --------------------------------------------------------------------------- */
.lf-money { font-variant-numeric: tabular-nums; letter-spacing: -.005em; white-space: nowrap; }
.lf-bicon { flex: none; display: inline-block; vertical-align: -3px; }

/* Status chip: five tones, each a wash with its accessible -deep label. */
.lf-bstat {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px 4px 8px; border-radius: 999px;
  font-size: 12px; font-weight: 650; line-height: 1.2; white-space: nowrap;
}
.lf-bstat--ok   { background: rgba(124, 169, 130, .18); color: var(--lf-sage-deep); }
.lf-bstat--due  { background: rgba(197, 160, 89, .18);  color: var(--lf-gold-text); }
.lf-bstat--wait { background: rgba(137, 147, 196, .18); color: var(--lf-periwinkle-deep); }
.lf-bstat--soon { background: rgba(229, 178, 93, .2);   color: var(--lf-amber-deep); }
.lf-bstat--late { background: rgba(219, 140, 131, .2);  color: var(--lf-rose-deep); }
.lf-bstat--mute { background: rgba(110, 101, 95, .1);   color: var(--lf-slate); }

/* The one-sentence summary that opens the admin page. */
.lf-bhero {
  margin-bottom: 16px; padding: 20px 22px;
  background: linear-gradient(135deg, #fff 0%, var(--lf-cream-deep) 100%);
  border: 1px solid var(--lf-line); border-radius: var(--lf-r-lg); box-shadow: var(--lf-shadow-sm);
}
.lf-bhero__eyebrow { margin: 0 0 6px; font-size: 11.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--lf-gold-text); }
.lf-bhero__line { margin: 0; font-family: var(--lf-display); font-weight: 600; font-size: 21px; line-height: 1.35; letter-spacing: -.01em; color: var(--lf-earth); }

.lf-btiles { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; margin-bottom: 22px; }
.lf-btile {
  position: relative; padding: 14px 16px; background: #fff;
  border: 1px solid var(--lf-line-soft); border-radius: var(--lf-r-lg); box-shadow: var(--lf-shadow-sm);
}
.lf-btile__icon { display: inline-flex; width: 30px; height: 30px; align-items: center; justify-content: center; border-radius: 10px; margin-bottom: 8px; }
.lf-btile__num { margin: 0; font-family: var(--lf-display); font-weight: 600; font-size: 24px; line-height: 1.1; color: var(--lf-earth); font-variant-numeric: tabular-nums; }
.lf-btile__lbl { margin: 4px 0 0; font-size: 12.5px; line-height: 1.35; color: var(--lf-slate); }
.lf-btile--ok .lf-btile__icon   { background: rgba(124, 169, 130, .16); color: var(--lf-sage-deep); }
.lf-btile--due .lf-btile__icon  { background: rgba(197, 160, 89, .16);  color: var(--lf-gold-text); }
.lf-btile--wait .lf-btile__icon { background: rgba(137, 147, 196, .16); color: var(--lf-periwinkle-deep); }
.lf-btile--soon .lf-btile__icon { background: rgba(229, 178, 93, .18);  color: var(--lf-amber-deep); }
.lf-btile--late .lf-btile__icon { background: rgba(219, 140, 131, .18); color: var(--lf-rose-deep); }
.lf-btile--late { border-color: rgba(219, 140, 131, .35); }

.lf-btoolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin: 6px 0 14px; }
.lf-btoolbar__right { display: flex; flex-wrap: wrap; gap: 10px; flex: 1; justify-content: flex-end; }
.lf-btoolbar__search { max-width: 280px; }
.lf-btoolbar__filter { max-width: 230px; }

/* One family. The left edge carries the attention tone, so a scroll down the list is a scan. */
.lf-bfams { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.lf-bfam {
  display: flex; flex-direction: column; gap: 10px; padding: 16px 18px;
  background: #fff; border: 1px solid var(--lf-line-soft); border-left: 4px solid var(--lf-sage);
  border-radius: var(--lf-r-lg); box-shadow: var(--lf-shadow-sm);
}
.lf-bfam--late, .lf-bfam--no_package { border-left-color: var(--lf-rose); }
.lf-bfam--needs_verification { border-left-color: var(--lf-periwinkle); }
.lf-bfam--unpaid { border-left-color: var(--lf-gold); }
.lf-bfam--ending_soon { border-left-color: var(--lf-amber); }
.lf-bfam__head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 8px 10px; }
.lf-bfam__name { margin: 0; font-family: var(--lf-display); font-weight: 600; font-size: 18px; letter-spacing: -.01em; color: var(--lf-earth); }
.lf-bfam__who { margin: 3px 0 0; font-size: 12.5px; color: var(--lf-slate); }
.lf-bfam__special { display: inline-flex; align-items: center; gap: 3px; color: var(--lf-purple-deep); font-weight: 600; }
.lf-bfam__pkg, .lf-bfam__inv {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
  padding: 10px 12px; background: var(--lf-cream); border-radius: var(--lf-r-sm);
}
.lf-bfam__inv { background: rgba(197, 160, 89, .08); border: 1px dashed rgba(197, 160, 89, .35); }
.lf-bfam__invmain { flex: 1; min-width: 150px; }
.lf-bfam__invact { display: flex; gap: 6px; width: 100%; }
.lf-bfam__k { margin: 0; font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--lf-gold-text); }
.lf-bfam__v { margin: 2px 0 0; font-size: 14px; font-weight: 600; color: var(--lf-earth); }
.lf-bfam__s { margin: 2px 0 0; font-size: 12.5px; color: var(--lf-slate); }
.lf-bfam__none { margin: 0; padding: 10px 12px; font-size: 13px; color: var(--lf-rose-deep); background: rgba(219, 140, 131, .08); border-radius: var(--lf-r-sm); }
.lf-bfam__foot { display: flex; flex-wrap: wrap; gap: 6px; padding-top: 10px; border-top: 1px solid var(--lf-line-soft); }
.lf-binvact { white-space: nowrap; }
.lf-binvact .lf-btn { margin-left: 4px; }

.lf-bempty {
  display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 36px 20px; text-align: center;
  color: var(--lf-slate); background: #fff; border: 1px dashed var(--lf-line); border-radius: var(--lf-r-lg);
}
.lf-bempty p { margin: 0; max-width: 420px; font-size: 14px; line-height: 1.55; }

/* Alerts: what happened, why it matters, what to do, with an icon and never colour alone. */
.lf-alert { display: flex; gap: 12px; margin: 10px 0; padding: 12px 14px; border: 1px solid transparent; border-radius: var(--lf-r-sm); }
.lf-alert__icon { flex: none; display: inline-flex; padding-top: 1px; }
.lf-alert__body { min-width: 0; }
.lf-alert__title { margin: 0 0 2px; font-size: 13px; font-weight: 700; }
.lf-alert__text { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--lf-earth); }
.lf-alert__text a { color: var(--lf-teal-deep); font-weight: 600; }
.lf-alert--info  { background: rgba(124, 169, 130, .12); border-color: rgba(124, 169, 130, .3); color: var(--lf-sage-deep); }
.lf-alert--warn  { background: rgba(229, 178, 93, .14); border-color: rgba(229, 178, 93, .4); color: var(--lf-amber-deep); }
.lf-alert--block { background: rgba(219, 140, 131, .13); border-color: rgba(219, 140, 131, .4); color: var(--lf-rose-deep); }

/* The impact preview above the alerts. */
.lf-impact { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin: 6px 0 4px; }
.lf-impact__fig { padding: 10px 12px; background: var(--lf-cream); border-radius: var(--lf-r-sm); }
.lf-impact__k { display: block; font-size: 11px; font-weight: 600; color: var(--lf-slate); }
.lf-impact__v { display: block; margin-top: 2px; font-size: 15px; font-weight: 700; color: var(--lf-earth); font-variant-numeric: tabular-nums; }
.lf-impact__v--save { color: var(--lf-sage-deep); }

.lf-ack { margin: 8px 0 4px; padding: 12px 14px; background: rgba(229, 178, 93, .08); border-radius: var(--lf-r-sm); }
.lf-ack__tick { display: flex; align-items: flex-start; gap: 10px; margin: 0 0 10px; font-size: 13.5px; line-height: 1.45; color: var(--lf-earth); cursor: pointer; }
.lf-ack__tick input { width: 20px; height: 20px; margin-top: 1px; accent-color: var(--lf-gold); flex: none; }
.lf-ack__owner { margin-top: 8px; }
.lf-bfull { margin-top: 14px; }

/* Sheets: a centred dialog on a wide screen, a bottom sheet on a phone. */
.lf-bsheet { align-items: center; }
.lf-bsheet__panel { max-width: 520px; max-height: calc(100vh - 32px); overflow-y: auto; }
.lf-bsheet__panel--wide { max-width: 680px; }
.lf-bsheet__head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
.lf-bsheet__title { margin: 0; }
.lf-bsheet__body .lf-field { margin-top: 12px; }
.lf-bsheet__sub { margin: 18px 0 4px; font-size: 13px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--lf-gold-text); }
.lf-bsheet__actions { position: sticky; bottom: -24px; margin: 18px -24px -24px; padding: 14px 24px 20px; background: linear-gradient(to top, #fff 75%, rgba(255, 255, 255, 0)); }

.lf-bsum { margin: 0 0 14px; padding: 14px 16px; background: var(--lf-cream); border-radius: var(--lf-r-lg); }
.lf-bsum--row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.lf-bsum__k { margin: 0; font-size: 12.5px; color: var(--lf-slate); }
.lf-bsum__big { margin: 4px 0 0; font-family: var(--lf-display); font-weight: 600; font-size: 28px; color: var(--lf-earth); }
.lf-bsum__v { margin: 2px 0 0; font-weight: 600; color: var(--lf-earth); }
.lf-bsum__s { margin: 2px 0 0; font-size: 12.5px; color: var(--lf-slate); }
.lf-bsum__flag { display: flex; align-items: center; gap: 6px; margin: 8px 0 0; font-size: 12.5px; font-weight: 600; color: var(--lf-periwinkle-deep); }

/* Package options: a big radio card each, the saving in rupiah beside the total. */
.lf-pkgopts { display: grid; gap: 10px; margin: 10px 0; }
.lf-pkgopt {
  position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 12px;
  padding: 14px 16px 14px 46px; background: #fff; border: 1.5px solid var(--lf-line); border-radius: var(--lf-r-lg);
  cursor: pointer; transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
}
.lf-pkgopt input { position: absolute; left: 16px; top: 17px; width: 20px; height: 20px; accent-color: var(--lf-gold); }
.lf-pkgopt.is-on { border-color: var(--lf-gold); background: rgba(197, 160, 89, .06); box-shadow: 0 0 0 3px rgba(197, 160, 89, .14); }
.lf-pkgopt__name { font-size: 15px; font-weight: 700; color: var(--lf-earth); }
.lf-pkgopt__total { grid-row: span 2; align-self: center; font-family: var(--lf-display); font-weight: 600; font-size: 19px; color: var(--lf-earth); }
.lf-pkgopt__per { font-size: 12.5px; color: var(--lf-slate); }
.lf-pkgopt__save { grid-column: 1 / -1; justify-self: start; display: inline-flex; align-items: center; gap: 4px; margin-top: 6px; padding: 3px 9px; border-radius: 999px; background: rgba(124, 169, 130, .16); color: var(--lf-sage-deep); font-size: 12px; font-weight: 700; }
.lf-pkgopt__desc { grid-column: 1 / -1; margin-top: 4px; font-size: 12.5px; color: var(--lf-slate); }

/* Packages page. */
.lf-bfee { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 18px; margin-bottom: 22px; }
.lf-bfee__now { min-width: 0; }
.lf-bfee__amount { margin: 0; font-family: var(--lf-display); font-weight: 600; font-size: 34px; letter-spacing: -.02em; color: var(--lf-earth); }
.lf-bfee__side { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.lf-bfee__list { margin: 0; padding: 0; list-style: none; font-size: 12.5px; color: var(--lf-slate); text-align: right; }
.lf-bpkgs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
.lf-bpkg { display: flex; flex-direction: column; padding: 18px; background: #fff; border: 1px solid var(--lf-line-soft); border-radius: var(--lf-r-lg); box-shadow: var(--lf-shadow-sm); }
.lf-bpkg.is-off { opacity: .66; }
.lf-bpkg__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.lf-bpkg__name { margin: 0; font-family: var(--lf-display); font-weight: 600; font-size: 20px; color: var(--lf-earth); }
.lf-bpkg__months { margin: 2px 0 0; font-size: 12.5px; color: var(--lf-slate); }
.lf-bpkg__total { margin: 14px 0 0; font-family: var(--lf-display); font-weight: 600; font-size: 26px; color: var(--lf-earth); }
.lf-bpkg__per { margin: 2px 0 0; font-size: 12.5px; color: var(--lf-slate); }
.lf-bpkg__facts { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 12px; margin: 14px 0 0; padding: 12px 0 0; border-top: 1px solid var(--lf-line-soft); }
.lf-bpkg__facts dt { font-size: 11px; font-weight: 600; color: var(--lf-slate); }
.lf-bpkg__facts dd { margin: 1px 0 0; font-size: 14px; font-weight: 700; color: var(--lf-earth); }
.lf-bpkg__desc { margin: 12px 0 0; font-size: 13px; line-height: 1.5; color: var(--lf-slate); }
.lf-bpkg__foot { display: flex; gap: 6px; margin-top: auto; padding-top: 14px; }

.lf-bform .lf-field { margin-top: 12px; }
.lf-bform__gap { margin-top: 26px; }

/* Parent page and the Today card. */
.lf-bnext {
  display: flex; align-items: center; gap: 14px; margin-bottom: 16px; padding: 16px 18px;
  border-radius: var(--lf-r-lg); border: 1px solid transparent; text-decoration: none;
}
.lf-bnext__icon { flex: none; display: inline-flex; width: 42px; height: 42px; align-items: center; justify-content: center; border-radius: 14px; background: rgba(255, 255, 255, .7); }
.lf-bnext__text { flex: 1; margin: 0; font-size: 15px; line-height: 1.45; color: var(--lf-earth); }
.lf-bnext__text b { display: block; font-weight: 650; }
.lf-bnext__sub { display: block; margin-top: 2px; font-size: 12.5px; color: var(--lf-slate); }
.lf-bnext__go { flex: none; font-size: 13px; font-weight: 700; color: var(--lf-gold-text); }
.lf-bnext--link:active { transform: scale(.99); }
.lf-bnext--ok   { background: rgba(124, 169, 130, .12); border-color: rgba(124, 169, 130, .3); }
.lf-bnext--ok .lf-bnext__icon { color: var(--lf-sage-deep); }
.lf-bnext--due  { background: rgba(197, 160, 89, .12); border-color: rgba(197, 160, 89, .35); }
.lf-bnext--due .lf-bnext__icon { color: var(--lf-gold-text); }
.lf-bnext--wait { background: rgba(137, 147, 196, .12); border-color: rgba(137, 147, 196, .3); }
.lf-bnext--wait .lf-bnext__icon { color: var(--lf-periwinkle-deep); }
.lf-bnext--soon { background: rgba(229, 178, 93, .14); border-color: rgba(229, 178, 93, .4); }
.lf-bnext--soon .lf-bnext__icon { color: var(--lf-amber-deep); }
.lf-bnext--late { background: rgba(219, 140, 131, .13); border-color: rgba(219, 140, 131, .4); }
.lf-bnext--late .lf-bnext__icon { color: var(--lf-rose-deep); }

/* T-252: the tour's guide on Package & payments. Dashed, so it reads as a note about the
   page rather than as part of the family's billing. */
.lf-tourguide {
  margin-bottom: 16px; padding: 14px 16px; border-radius: var(--lf-r-lg);
  border: 1.5px dashed rgba(197, 160, 89, .55); background: rgba(255, 255, 255, .6);
}
.lf-tourguide__k { margin: 0; font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--lf-gold-text); }
.lf-tourguide__text { margin: 4px 0 0; font-size: 14px; line-height: 1.5; color: var(--lf-earth); }
.lf-tourguide__actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }

.lf-bpar { margin-bottom: 26px; }
.lf-bpar__child { margin: 0 0 10px; font-family: var(--lf-display); font-weight: 600; font-size: 20px; color: var(--lf-earth); }
.lf-bpar__card { display: grid; gap: 12px; margin-bottom: 14px; }
.lf-bpar__h { margin: 22px 0 8px; font-size: 13px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--lf-gold-text); }
.lf-bpar__list { display: grid; gap: 8px; }
.lf-bpkgline { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 10px; }
.lf-bpkgline__k { margin: 0; font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--lf-gold-text); }
.lf-bpkgline__v { margin: 2px 0 0; font-size: 16px; font-weight: 700; color: var(--lf-earth); }
.lf-bpkgline__s { margin: 2px 0 0; font-size: 13px; color: var(--lf-slate); }

.lf-binv { margin-bottom: 14px; }
.lf-binv__head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 8px 10px; }
.lf-binv__k { margin: 0; font-size: 12.5px; color: var(--lf-slate); }
.lf-binv__amount { margin: 4px 0 0; font-family: var(--lf-display); font-weight: 600; font-size: 30px; letter-spacing: -.02em; color: var(--lf-earth); }
.lf-binv__s { margin: 2px 0 0; font-size: 13px; color: var(--lf-slate); }
.lf-binv__lines { margin: 14px 0 0; padding: 10px 0 0; border-top: 1px solid var(--lf-line-soft); }
.lf-binv__lines div { display: flex; justify-content: space-between; gap: 12px; padding: 5px 0; font-size: 13.5px; color: var(--lf-earth); }
.lf-binv__lines dt, .lf-binv__lines dd { margin: 0; }
.lf-binv__lines .lf-binv__save { color: var(--lf-sage-deep); font-weight: 600; }
.lf-binv__lines .lf-binv__total { margin-top: 4px; padding-top: 9px; border-top: 1px dashed var(--lf-line); font-weight: 700; }
.lf-binv__due { display: flex; align-items: center; gap: 6px; margin: 12px 0 0; font-size: 13px; font-weight: 600; color: var(--lf-gold-text); }
.lf-binv__wait { margin: 12px 0 0; padding: 10px 12px; font-size: 13.5px; line-height: 1.5; color: var(--lf-periwinkle-deep); background: rgba(137, 147, 196, .12); border-radius: var(--lf-r-sm); }
.lf-binv__actions { display: flex; flex-direction: column; align-items: stretch; gap: 8px; margin-top: 14px; }
.lf-binv__actions .lf-btn--sm { align-self: center; }

.lf-bbank { margin-top: 12px; padding: 14px 16px; background: var(--lf-cream); border: 1px solid var(--lf-line); border-radius: var(--lf-r-lg); }
.lf-bbank__k { margin: 0; font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--lf-gold-text); }
.lf-bbank__name { margin: 4px 0 0; font-weight: 700; color: var(--lf-earth); }
.lf-bbank__acct { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: 2px 0 0; }
.lf-bbank__acct .lf-mono { font-size: 19px; font-weight: 700; letter-spacing: .04em; color: var(--lf-earth); }
.lf-bbank__holder { margin: 2px 0 0; font-size: 13px; color: var(--lf-slate); }

.lf-bpast { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; padding: 12px 14px; background: #fff; border: 1px solid var(--lf-line-soft); border-radius: var(--lf-r-lg); }
.lf-bpast__main { flex: 1; min-width: 180px; }
.lf-bpast__v { margin: 0; font-size: 14px; font-weight: 600; color: var(--lf-earth); }
.lf-bpast__s { margin: 2px 0 0; font-size: 12.5px; color: var(--lf-slate); }
.lf-bpast__act { display: flex; gap: 4px; width: 100%; justify-content: flex-end; }
.lf-bhist { display: grid; gap: 6px; padding: 12px 0; border-bottom: 1px solid var(--lf-line-soft); }
.lf-bhist:last-child { border-bottom: 0; }
.lf-bhist__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 10px; }

.lf-btoast {
  position: fixed; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom)); z-index: 120;
  display: flex; align-items: center; gap: 8px; max-width: min(92vw, 460px); padding: 12px 16px;
  border-radius: 14px; font-size: 14px; font-weight: 600; box-shadow: var(--lf-shadow-lg);
  transform: translateX(-50%); transition: opacity .4s ease;
}
.lf-btoast--good { background: var(--lf-earth); color: #fff; }
.lf-btoast--bad  { background: var(--lf-rose-deep); color: #fff; }
.lf-btoast.is-out { opacity: 0; }

@media (max-width: 1023px) {
  .lf-btiles { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .lf-bpkgs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 767px) {
  .lf-bhero__line { font-size: 18px; }
  .lf-btiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lf-bfams, .lf-bpkgs { grid-template-columns: minmax(0, 1fr); }
  .lf-impact { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lf-btoolbar__right { justify-content: stretch; }
  .lf-btoolbar__search, .lf-btoolbar__filter { max-width: none; }
  .lf-bsheet { align-items: flex-end; padding: 0; }
  .lf-bsheet__panel { max-width: none; border-radius: var(--lf-r-xl) var(--lf-r-xl) 0 0; max-height: 92vh; padding-bottom: calc(24px + env(safe-area-inset-bottom)); }
  .lf-bfee__side { align-items: stretch; width: 100%; }
  .lf-bfee__list { text-align: left; }
  .lf-bnext { flex-wrap: wrap; }
  .lf-bnext .lf-btn { width: 100%; }
}

/* ---- planning page (T-224) ------------------------------------------------
   The business plan: a form of small number fields, a strip of month cards, and the
   results the server sends back. Mobile-first: everything stacks at 375px; the fields
   flow into as many columns as fit. Tables reuse .lf-cards / .lf-tablewrap. */
.lf-plan__hero { margin-bottom: 14px; }
.lf-plan__bar { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 10px 14px; margin-top: 14px; }
.lf-plan__pick { flex: 1 1 240px; min-width: 0; margin: 0; }
.lf-plan__acts { display: flex; flex-wrap: wrap; gap: 6px; }
.lf-plan__fields { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 4px 14px; }
.lf-plan__months { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 10px; margin-top: 14px; }
.lf-plan__month { padding: 10px 12px; background: var(--lf-cream); border-radius: var(--lf-r-sm); }
.lf-plan__month .lf-input { margin-bottom: 6px; }
.lf-plan__mlabel { margin: 0 0 6px; font-size: 13px; font-weight: 700; color: var(--lf-earth); }
.lf-plan__num { max-width: 110px; }
.lf-plan__amt { max-width: 180px; }
.lf-plan__problems { margin: 4px 0 0; padding-left: 18px; font-size: 13.5px; line-height: 1.5; color: var(--lf-earth); list-style: disc; }
.lf-plan__steps { margin: 6px 0 0; padding-left: 18px; font-size: 14px; line-height: 1.6; color: var(--lf-earth); list-style: disc; }
@media (max-width: 767px) {
  .lf-plan__acts { width: 100%; }
  .lf-plan__acts .lf-btn { flex: 1 1 auto; }
  .lf-plan__num, .lf-plan__amt { max-width: none; }
}

/* ---- plan simulator (T-227) -----------------------------------------------
   The simulator reuses the planning page's fields, tiles and tables; only the
   plan picker for the side-by-side compare and the "use for every month" button
   need a rule of their own. */
.lf-sim__picks { display: flex; flex-wrap: wrap; gap: 4px 18px; margin: 6px 0 12px; }
.lf-sim__picks .lf-check { margin: 0; }
.lf-sim__applyf { display: flex; align-items: flex-end; padding-bottom: 6px; }
/* One flex item per compare cell, so a phone's card puts the delta under the figure. */
.lf-sim__cell { display: block; min-width: 0; }
@media (max-width: 767px) {
  .lf-sim__picks { flex-direction: column; }
  .lf-sim__applyf .lf-btn { width: 100%; }
  /* A scenario's name is the card label here, and a name can be long. */
  .lf-sim__cmp .lf-cards tbody td[data-label]::before { white-space: normal; }
}

/* Today on Beranda (T-341): every tile is a link to the page that fixes what it counts. */
.lf-today-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.lf-today-tile { text-decoration: none; color: inherit; display: block; }
.lf-today-tile:hover, .lf-today-tile:focus-visible { outline: 2px solid var(--tone, #C5A059); outline-offset: 2px; }

/* Teacher attendance (T-342): the parent's own report, under the child's row. */
.att-parent { margin: 4px 0 0 44px; color: #7F6228; }
/* ---- Drafts and the unsaved-changes question (draft.js, ADR-0213) ---------------------- */
.lf-draftask{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:16px;background:rgba(74,62,61,.45);}
.lf-draftask__box{width:min(420px,100%);padding:20px;border-radius:18px;background:#fff;box-shadow:0 12px 40px rgba(0,0,0,.18);}
.lf-draftask__actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px;}
.lf-draftask__actions .lf-btn{flex:1 1 110px;min-height:48px;}
.lf-draftbar{display:flex;align-items:center;gap:10px;margin:10px 0;}
.lf-draftbar .lf-alert__body{flex:1 1 auto;}
.lf-dayopen{display:inline-block;margin-top:6px;font-weight:600;color:var(--lf-teal-deep);text-decoration:underline;text-underline-offset:3px;}
.lf-home__daychips{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 14px;}
.lf-home__daychips a{text-decoration:none;min-height:36px;display:inline-flex;align-items:center;gap:4px;}
.lf-home__daychips a[data-state="absent"]{opacity:.55;text-decoration:line-through;}
.lf-home__daygo{display:flex;width:100%;justify-content:center;min-height:56px;font-size:16px;}
