/* Laya Framework Core Styles
   One file, all engines — see laya.js for the matching JS. Tokens are
   --l- prefixed so a page can load both chillar.css and laya.css without
   collision (spec.md §3). Values are placeholders, not a settled visual
   identity (spec.md §9.4). */

/* Accessibility: html[font-size="small|medium|large"] scales the rem base. */
html { font-size: 16px; }
html[font-size="small"]  { font-size: 14px; }
html[font-size="medium"] { font-size: 16px; }
html[font-size="large"]  { font-size: 18px; }

:root {
  --l-bg:   #ffffff;
  --l-fg:   #000000;
  --l-dim:  rgba(0,0,0,.32);
  --l-mute: rgba(0,0,0,.09);
  --l-surface: #f5f5f5;
  --l-border: rgba(0,0,0,.1);
  /* Two accent roles, not one — a HUD/label accent (caption text, active
     chips, etc.) and a separate "like" accent (hearts), since real apps
     usually want those to read as different colors (cinema: yellow
     tactical labels, pink hearts). Both are plain overridable custom
     properties — set them on :root or html in your own stylesheet. */
  --l-accent: #4f6df5;
  --l-accent-like: #ef4444;
  --l-ease: cubic-bezier(.23,1,.32,1);
  --l-spring: cubic-bezier(.34,1.56,.64,1);

  /* Fonts — three roles, not three fixed families: display (headlines,
     usually heavy-weight), label (HUD-style uppercase meta/numerals —
     Swiss design leans on a geometric grotesk here), body (paragraphs).
     Defaults are a solid system stack so nothing looks broken before an
     app calls laya.fonts({...}) to swap in real (Google) fonts — see
     laya.js. A cinema-style look sets display/body to the same humanist
     sans and label to a distinct grotesk; nothing requires 3 distinct
     families, the roles just don't have to share one. */
  --l-font-display: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
  --l-font-label:   -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
  --l-font-body:    -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
  /* A 4th role, and the one exception that defaults to an actual
     monospace stack rather than a proportional system font — lfuzzy's
     data-row look (chillar's own app: dot + name + right-aligned
     amount, all monospace) wants this out of the box, with zero
     laya.fonts() call required. Override with a real monospace web
     font (e.g. 'JetBrains Mono', 'Roboto Mono') via laya.fonts({mono}). */
  --l-font-mono: ui-monospace, 'SF Mono', 'Roboto Mono', Menlo, Consolas, monospace;

  /* Vertical rhythm: every --l-lh-* line-height below is a whole multiple
     of this baseline unit, and so is every --l-sp-* / --l-gap-* spacing
     value — text and spacing both snap to the same 0.5rem (8px) grid.
     --l-fs-xxl is the one deliberate exception: display numerals/hero
     type set line-height 1 instead, a standard Swiss-typography move —
     tight display type, rhythm resumes at the next label/body line
     below it. */
  --l-baseline: .5rem;

  --l-fs-xxs: .6875rem;  --l-lh-xxs: 1rem;
  --l-fs-xs:  .8125rem;  --l-lh-xs:  1rem;
  --l-fs-sm:  1rem;      --l-lh-sm:  1.5rem;
  --l-fs-lg:  1.25rem;   --l-lh-lg:  1.5rem;
  --l-fs-xl:  1.75rem;   --l-lh-xl:  2rem;
  --l-fs-xxl: 3rem;      --l-lh-xxl: 1;

  --l-fw-reg:   400;
  --l-fw-med:   500;
  --l-fw-bold:  700;
  --l-fw-heavy: 800;

  --l-sp-xs: calc(var(--l-baseline) * 1);   /* 8px  */
  --l-sp-sm: calc(var(--l-baseline) * 2);   /* 16px */
  --l-sp-md: calc(var(--l-baseline) * 3);   /* 24px */
  --l-sp-lg: calc(var(--l-baseline) * 4);   /* 32px */
  --l-sp-xl: calc(var(--l-baseline) * 8);   /* 64px */

  --l-gap-xs: calc(var(--l-baseline) * 1);  /* 8px  */
  --l-gap-sm: calc(var(--l-baseline) * 2);  /* 16px */
  --l-gap-md: calc(var(--l-baseline) * 3);  /* 24px */

  --l-radius-pill: 99px;
}

/* Dark mode: manual override via html[theme], falls back to OS preference
   when no explicit theme is set — same mechanism as chillar.css. */
html[theme="dark"] {
  --l-bg:   #000000;
  --l-fg:   #ffffff;
  --l-dim:  rgba(255,255,255,.32);
  --l-mute: rgba(255,255,255,.09);
  --l-surface: #1a1a1a;
  --l-border: rgba(255,255,255,.1);
}
@media (prefers-color-scheme: dark) {
  html:not([theme="light"]):not([theme="dark"]) {
    --l-bg:   #000000;
    --l-fg:   #ffffff;
    --l-dim:  rgba(255,255,255,.32);
    --l-mute: rgba(255,255,255,.09);
    --l-surface: #1a1a1a;
    --l-border: rgba(255,255,255,.1);
  }
}

/* Every laya engine toggles visibility by setting/removing the native
   `hidden` attribute — never a class, never inline style — so this one
   rule is what actually makes that work, for every current and future
   `l*` tag, permanently. Without it: author-origin `display` rules (the
   ones below giving lflick/lfuzzy/ljot/lcamera/etc. their layout) beat
   the browser's own UA-origin `[hidden]{display:none}` regardless of
   selector specificity or source order — CSS origin/importance is
   resolved before specificity, so a same-file, zero-`!important` author
   rule always wins over any UA rule. That's not a hypothetical: it's
   shipped three times this way (lflick, then ljot, then lblocks) as
   separate per-tag `tag[hidden]{display:none}` overrides that someone
   (me) had to remember to add for every single new mountable tag, and
   forgot at least once. `!important` here is what actually closes the
   hole for good — it's the one legitimate use of it: restoring a
   browser default an author rule accidentally clobbered, not fighting
   another author's styles. Do not add per-tag `[hidden]` overrides
   anywhere else in this file; if one shows up, delete it, this rule
   already covers it. */
[hidden] { display: none !important; }

*, *::before, *::after {
  box-sizing: border-box; margin: 0; padding: 0;
  -webkit-tap-highlight-color: transparent;
  -webkit-font-smoothing: antialiased;
}

body {
  background: var(--l-bg);
  color: var(--l-fg);
  font-family: var(--l-font-body);
  line-height: var(--l-lh-sm);
  min-height: 100dvh;
}

/* ── lflick — full-bleed Y-snap feed (spec.md §6) ──────────────────────── */
lflick {
  display: block;
  height: 100dvh;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
  outline: none;
}
lflick::-webkit-scrollbar { display: none; }
lslide {
  display: block;
  position: relative;
  overflow: hidden;
  height: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  background: var(--l-bg);
  color: var(--l-fg);
}

/* ── flick's optional `map` card recipe (laya.js's buildCards()) — a
   full-bleed photo with a bottom gradient scrim, the default look when
   an app hands flick `items`+`map` instead of building <lslide> markup
   itself. `> img` (direct child), not a bare descendant selector —
   buildCards() always puts its own <img> directly under <lslide>
   (wireImage() confirms this), but an app building its OWN <lslide>
   content is free to nest an <img> arbitrarily deep inside it (a photo
   thumbnail grid, say) without that being silently caught by this
   full-bleed-card-only opacity/position treatment — real bug this fixed,
   a nested thumbnail grid stuck permanently invisible (opacity:0, never
   getting the data-loaded attribute only wireImage() ever sets). ────── */
lslide > img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0; transition: opacity .5s var(--l-ease);
  background: var(--l-surface);
}
lslide > img[data-loaded="true"] { opacity: 1; }

/* Top HUD — a badge (left) and/or a hero numeral (right), cinema's
   platform-pill + date-hero. Both independently optional; `lhero`'s own
   margin (not `justify-content: space-between`, which would center a
   lone child instead of pushing it right) is what keeps the hero
   right-aligned whether or not a badge is present. */
ltop {
  display: flex;
  align-items: flex-start;
  position: absolute; top: 0; left: 0; right: 0; z-index: 1;
  padding: max(var(--l-sp-xl), calc(env(safe-area-inset-top) + var(--l-sp-lg))) var(--l-sp-lg) 0;
  gap: var(--l-sp-sm);
}
lbadge {
  display: inline-flex;
  align-items: center;
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxs);
  font-weight: var(--l-fw-bold);
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: var(--l-baseline) var(--l-sp-sm);
  border-radius: 12px;
  background: var(--l-accent);
  color: #fff;
  box-shadow: 0 10px 25px rgba(0,0,0,.4);
  line-height: 1;
}
lhero {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  margin-left: auto;
  text-align: right;
}
lhero b {
  font-family: var(--l-font-label);
  font-size: 3.4rem;
  font-weight: var(--l-fw-bold);
  letter-spacing: -.03em;
  line-height: .8;
  text-shadow: 0 6px 30px rgba(0,0,0,.6);
}
lhero span {
  display: block;
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxs);
  font-weight: var(--l-fw-bold);
  letter-spacing: .2em;
  text-transform: uppercase;
  margin-top: var(--l-baseline);
  text-shadow: 0 2px 12px rgba(0,0,0,.6);
}

linfo {
  display: block;
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
  padding: 30% var(--l-sp-lg) max(var(--l-sp-xl), env(safe-area-inset-bottom));
  background: linear-gradient(to top, rgba(0,0,0,.95) 0%, rgba(0,0,0,.8) 35%, rgba(0,0,0,.25) 70%, transparent 100%);
  color: #fff;
  max-width: 560px;
  margin: 0 auto;
}
/* linfo and lrail are independent absolutely-positioned siblings (not
   nested — the rail has to sit on top of the gradient, not inside the
   text column), so nothing in normal flow keeps the rail from covering
   the text on a narrow screen where linfo spans edge-to-edge. Reserve
   room for it explicitly: rail button width + its own right offset +
   a small gap. */
linfo[rail] { padding-right: calc(var(--l-sp-lg) + 44px + var(--l-gap-xs)); }
/* Numerals lean on the label font (a geometric grotesk usually has
   cleaner tabular figures than a humanist display face) — matches
   cinema's own date/tactical-readout typography. */
lyear { display: block; }
linfo lyear {
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxl);
  line-height: var(--l-lh-xxl);
  font-weight: var(--l-fw-bold);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  opacity: .85;
  text-shadow: 0 2px 20px rgba(0,0,0,.5);
}
linfo h1 {
  font-family: var(--l-font-display);
  font-size: clamp(1.5rem, 6vw, 2.25rem);
  font-weight: var(--l-fw-heavy);
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin-top: var(--l-baseline);
}
/* HUD-style caption: small caps, wide tracking, the label font, an
   accent color — a "readout," not muted secondary text. */
lcat { display: block; }
linfo lcat {
  font-family: var(--l-font-label);
  margin-top: var(--l-baseline);
  font-size: var(--l-fs-xxs);
  line-height: var(--l-lh-xxs);
  font-weight: var(--l-fw-bold);
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--l-accent);
}
linfo p {
  font-family: var(--l-font-body);
  margin-top: var(--l-sp-xs);
  font-size: var(--l-fs-sm);
  line-height: var(--l-lh-sm);
  opacity: .95;
}
linfo a {
  display: inline-block;
  font-family: var(--l-font-label);
  margin-top: var(--l-sp-xs);
  font-size: var(--l-fs-xxs);
  line-height: var(--l-lh-xxs);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: rgba(255,255,255,.55);
  text-decoration: none;
}
linfo a:active { color: #fff; }

/* ── icon rail — bottom-right, one circular glassy button per action
   (like + whatever `map.actions` supplies). Cinema's "action-rail": a
   vertical stack of icons instead of a single floating button. ─────── */
lrail {
  display: flex;
  flex-direction: column;
  gap: var(--l-gap-xs);
  position: absolute;
  right: var(--l-sp-lg);
  bottom: max(var(--l-sp-xl), calc(env(safe-area-inset-bottom) + var(--l-sp-lg)));
  z-index: 2;
}
lrail button {
  width: 44px; height: 44px; border-radius: 50%;
  border: none; background: rgba(0,0,0,.38); backdrop-filter: blur(10px);
  color: #fff; display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: transform .15s var(--l-spring), background .2s;
}
lrail button svg { width: 19px; height: 19px; stroke: currentColor; fill: none; stroke-width: 1.9; }
lrail button:active { transform: scale(.88); background: rgba(255,255,255,.22); }
lrail button[data-like][liked] {
  color: var(--l-accent-like);
  background: rgba(239,68,68,.2);
  background: color-mix(in srgb, var(--l-accent-like) 20%, transparent);
  animation: l-heart-pop .3s var(--l-spring);
}
lrail button[data-like][liked] svg { fill: currentColor; stroke: none; }
@keyframes l-heart-pop {
  0% { transform: scale(1); }
  45% { transform: scale(1.35); }
  100% { transform: scale(1); }
}

/* ── lfuzzy — full-page browse-and-filter search, restyled to match
   sites/chillar's own minimal list (spec.md §5's original "floating
   modal overlay" gave way to this once shown a real reference: no dark
   scrim, no bordered/pill input, no boxed result card — a plain page,
   monospace, a thin rule under the input, dot-bulleted rows). Opening
   it shows everything (laya.js's `browseAll`, on by default) and
   typing narrows it — not "type first, see nothing." Tap a result or
   Enter fires 'select'; Esc clears the query, then closes on an
   already-empty one. Visibility (open/closed) is app/orchestrator-
   controlled via the native `hidden` attribute, same as any other
   laya.app() flow step. ──────────────────────────────────────────── */
lfuzzy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  position: fixed;
  inset: 0;
  z-index: 200;
  padding: var(--l-sp-xl) var(--l-sp-lg) 0;
  background: var(--l-bg);
  overflow-y: auto;
}

lfuzzy button[data-close] {
  position: absolute;
  top: max(var(--l-sp-lg), env(safe-area-inset-top));
  right: var(--l-sp-lg);
  z-index: 1;
  width: 32px; height: 32px;
  border: none;
  background: transparent;
  color: var(--l-dim);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
}
lfuzzy button[data-close] svg { width: 16px; height: 16px; stroke: currentColor; stroke-width: 2; fill: none; }
lfuzzy button[data-close]:active { color: var(--l-fg); }

lfuzzy input {
  display: block;
  width: 100%;
  max-width: 560px;
  background: transparent;
  color: var(--l-fg);
  border: none;
  padding: 0;
  font-family: var(--l-font-mono);
  font-size: var(--l-fs-xl);
  line-height: var(--l-lh-xl);
  font-weight: var(--l-fw-reg);
  outline: none;
}
lfuzzy input::placeholder { color: var(--l-dim); }

lresults {
  display: block;
  width: 100%;
  max-width: 560px;
  margin-top: var(--l-sp-lg);
  padding-top: var(--l-sp-lg);
  padding-bottom: var(--l-sp-xl);
  border-top: 1px solid var(--l-border);
}
lresults li {
  display: flex;
  align-items: center;
  gap: var(--l-sp-sm);
  list-style: none;
  padding: var(--l-sp-md) 0;
  color: var(--l-fg);
  cursor: pointer;
}
lresults li::before {
  content: '';
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--l-accent);
  flex-shrink: 0;
}
/* A thumbnail already anchors the row visually — skip the dot rather
   than stack two bullets in front of it. */
lresults li:has(img)::before { display: none; }
lresults li[active] { background: var(--l-mute); }

/* fuzzy's optional `map` result recipe (laya.js's default render when
   `map` is given) — a thumbnail + year + title + caption row; without
   an image (your own `render`, or map without `image`) it reads as a
   plain chillar-style dot + name row instead. Applies whenever a
   result happens to contain these, not only when `map` was used — same
   "just descendant selectors" rule as everywhere else in this file. */
lresults img {
  width: 40px; height: 40px; flex-shrink: 0; border-radius: 6px;
  object-fit: cover; background: var(--l-mute);
}
lresults lyear {
  font-family: var(--l-font-mono); font-variant-numeric: tabular-nums;
  color: var(--l-dim); font-weight: var(--l-fw-bold); font-size: var(--l-fs-xs);
  line-height: var(--l-lh-xs);
  width: 3em; flex-shrink: 0;
}
lresults li div { min-width: 0; flex: 1; }
lresults strong {
  display: block; font-family: var(--l-font-mono); font-weight: var(--l-fw-reg);
  font-size: var(--l-fs-lg); line-height: var(--l-lh-lg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
lresults small {
  display: block; font-family: var(--l-font-mono);
  color: var(--l-dim); font-size: var(--l-fs-xxs); line-height: var(--l-lh-xxs);
  text-transform: uppercase; letter-spacing: .06em; margin-top: 2px;
}

/* ── ljot — day/group-grouped editable block stream (laya.js's `jot()`,
   extracted from sites/journal). <ljot> is just a wrapper — <lstream>
   is the part render() actually clears/rebuilds, kept separate so the
   toolbar/camera/talk elements jot builds once at init survive every
   re-render. Shape: lstream > lgroup > lhead + lblocks >
   (ltext | lcheck | lmedia)*, plus ltools/ltalk/lcamera as siblings of
   lstream, all built only if `opts.camera`/`opts.talk` are enabled
   (the default). ─────────────────────────────────────────────────── */
ljot { display: block; }
lstream {
  display: flex;
  flex-direction: column;
  gap: var(--l-sp-xl);
  max-width: 42rem;
  margin: 0 auto;
  padding: var(--l-sp-xl) var(--l-sp-md) calc(var(--l-sp-xl) * 2);   /* extra bottom room so content doesn't sit under ltools */
}
lgroup { display: flex; flex-direction: column; gap: var(--l-sp-sm); }
/* A confident statement heading, not a tiny tracked label — laya/samples'
   own references (pins 009/025) both use a bold, clean headline for this
   role. Was uppercase/--l-fs-xxs/--l-font-label; apps that specifically
   want the old HUD-tag look can still opt back into it locally. Bumped
   to --l-fs-xxl/--l-fw-heavy (from --l-fs-lg/--l-fw-bold) — still read
   as a caption next to a real hero headline (see laya/samples' own
   "Explore"-style references); --l-fs-xxl is reserved for exactly this
   "display numerals/hero" role already. The extra breathing room below
   it lives on lhead itself (margin-bottom), not lgroup's own gap — gap
   applies uniformly to EVERY child, so bumping it here once also pushed
   photos (lmediagroups) far from the text they belong to, not just the
   heading from the text below it. */
/* The row, not lhead itself, carries the space-below-heading margin —
   lhead always renders inside one now, and lheadactions (opts.
   editableLabels only) sits alongside it, so the row is the one thing
   consistently sized to put a gap under regardless of which is taller. */
lheadrow { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--l-sp-sm); margin-bottom: var(--l-sp-lg); }
lheadrow lhead { flex: 1; min-width: 0; }
lhead {
  display: block;
  font-family: var(--l-font-display);
  font-size: var(--l-fs-xxl);
  line-height: var(--l-lh-xxl);
  font-weight: var(--l-fw-heavy);
  color: var(--l-fg);
  cursor: pointer;
}
lhead:hover { color: var(--l-accent); }
/* opts.editableLabels (jot) only — same plain-text-editing language as
   lmedialabeltext: no pencil icon, just a cursor on click. Not a
   pointer/hover-accent here since a click edits in place rather than
   navigating anywhere. */
lhead[contenteditable] {
  cursor: text;
  outline: none;   /* no browser focus ring — the color change below is the only "you're editing" cue */
}
lhead[contenteditable]:hover { color: var(--l-fg); }
lhead[contenteditable]:focus { color: var(--l-accent); }

/* opts.editableLabels only — collapse/delete for a user-created section,
   as explicit controls rather than overloading lhead's own click (which
   already means "edit the title" once labels are editable). */
lheadactions {
  display: flex;
  align-items: center;
  gap: var(--l-gap-xs);
  flex: 0 0 auto;
  padding-top: .3em;   /* roughly centers on lhead's cap-height rather than its full line-box */
}
lheadactions button {
  border: none;
  background: none;
  color: var(--l-dim);
  font-family: var(--l-font-label);
  font-size: var(--l-fs-sm);
  line-height: 1;
  padding: var(--l-baseline);
  cursor: pointer;
}
lheadactions button:hover { color: var(--l-fg); }
/* The "are you sure" state (a second tap within ~2.5s actually
   deletes) — no native confirm(), matching journal's own convention
   throughout of a real in-place UI over a browser dialog. */
lheadactions button[data-act="delete-group"][data-confirm] {
  color: #c0392b;
  font-size: var(--l-fs-xxs);
  text-transform: uppercase;
  letter-spacing: .04em;
}

/* No left padding — that was reserved for inline floated media, which
   media-grouping (buildMediaGroups) moved out of the text stream
   entirely; left as a stray indent under every paragraph until now. */
lblocks { display: block; }

ltext, lcheck {
  display: block;
  font-family: var(--l-font-body);
  font-size: var(--l-fs-lg);
  line-height: var(--l-lh-lg);
  white-space: pre-wrap;
  outline: none;
  margin: var(--l-sp-xs) 0;
  min-height: var(--l-lh-lg);
}
ltext:empty::before, lcap:empty::before {
  content: attr(data-ph);
  color: var(--l-dim);
  pointer-events: none;
}
ltext a, lcap a { color: inherit; text-underline-offset: 3px; opacity: .75; }
ltext a:hover, lcap a:hover { opacity: 1; }

/* Checklist: a ltext with a tappable box (☐/☑) in the left gutter — the
   box is a CSS pseudo-element, not a real <input>, so it stays part of
   the same contenteditable region as the text. */
lcheck { position: relative; padding-left: 30px; }
lcheck::before {
  content: '\2610';
  position: absolute; left: 0; top: 0; width: 28px;
  color: var(--l-dim);
  cursor: pointer;
}
lcheck[done]::before { content: '\2611'; color: var(--l-fg); }
lcheck[done] { color: var(--l-dim); text-decoration: line-through; }
lcheck:empty::before { content: '\2610'; }
lcheck:empty::after { content: attr(data-ph); color: var(--l-dim); pointer-events: none; }

/* opts.listShortcuts (jot) only — "- "/"x "/"q "/"n "/"h " typed at the
   very start of an empty block convert it to one of these. Same gutter-
   marker pattern as lcheck's ☐/☑ above: a ::before pseudo-element, not
   real markup, so the marker never becomes part of the editable text. */
ltext[data-list="bullet"], ltext[data-list="numbered"] { position: relative; padding-left: 28px; }
ltext[data-list="bullet"]::before {
  content: '\2022';
  position: absolute; left: 6px; top: 0;
  color: var(--l-dim);
}
/* One counter per lblocks — numbers run continuously down the whole
   section rather than restarting after a non-numbered block in between,
   simplest correct behavior without tracking "runs" of consecutive
   numbered blocks by hand. */
lblocks { counter-reset: l-numbered; }
ltext[data-list="numbered"] { counter-increment: l-numbered; }
ltext[data-list="numbered"]::before {
  content: counter(l-numbered) '.';
  position: absolute; left: 0; top: 0;
  color: var(--l-dim);
}
ltext[data-list="quote"] {
  padding-left: var(--l-sp-sm);
  border-left: 3px solid var(--l-border);
  font-style: italic;
  color: var(--l-dim);
}
ltext[data-list="heading"] {
  font-family: var(--l-font-display);
  font-size: var(--l-fs-xl);
  font-weight: var(--l-fw-bold);
  font-style: normal;
  color: var(--l-fg);
}

/* ── media groups — photos/videos pulled out of the inline block
   stream (extracted from journal, the source app; see jot()'s own
   comment). "All" is always first and always everything in the group;
   named stacks are additional curated views, always the fanned-pile
   visual regardless of count (only All uses the >3 threshold). ────── */
/* No margin-top of its own — lgroup's own gap (var(--l-sp-sm)) already
   separates it from the text block above; an extra margin here on top
   of that made photos read as floating apart from the text they belong
   to instead of sitting right under it. */
lmediagroups { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--l-gap-md); }
lmediagroup { display: flex; flex-direction: column; gap: var(--l-baseline); }
lmedialabel { display: flex; align-items: center; gap: var(--l-baseline); }
lmedialabeltext {
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxs);
  text-transform: uppercase; letter-spacing: .06em;
  color: var(--l-dim);
  outline: none;
}
lmedialabeltext[contenteditable] { cursor: text; }
lmedialabeltext[contenteditable]:focus { color: var(--l-fg); }
lmedialabel button {
  background: none; border: none; padding: 0;
  font-size: var(--l-fs-xs);
  color: var(--l-dim);
  cursor: pointer;
}
lmedialabel button:hover { color: var(--l-accent-like); }

lmediarow, lmediastack { display: block; cursor: pointer; }
lmediarow { display: flex; gap: var(--l-gap-xs); }
/* Sized to always fit 3-across inside lstream's own known side padding
   (var(--l-sp-md) each side) — a row can hold up to 3 (see buildMediaGroup
   in laya.js), so the tile size has to guarantee 3 fit rather than just
   assume desktop width. Was a fixed 140px — on a narrow phone, a 3-photo
   row ran past the edge of the screen and dragged the whole page into
   horizontal scroll, not just clipped that photo. min() keeps the same
   140px on anything wide enough for it. */
lmediarow img, lmediarow video, lmediastack {
  --l-media-tile: min(140px, calc((100vw - 2 * var(--l-sp-md) - 2 * var(--l-gap-xs)) / 3));
}
lmediarow img, lmediarow video {
  width: var(--l-media-tile); height: var(--l-media-tile);
  object-fit: cover;
  border-radius: 10px;
  display: block;
}
lmediastack { position: relative; height: var(--l-media-tile); width: var(--l-media-tile); }
lmediastack img, lmediastack video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  border-radius: 10px;
  border: 2px solid var(--l-bg);
  box-shadow: 0 2px 8px rgba(0,0,0,.15);
}
/* Fanned back-to-front, counted from the END of the sibling list
   (nth-last-child) — the front (unrotated) card is always the last
   child regardless of how many there are, so a 1-photo stack still
   lands on the flat "front" position instead of the rotated one a
   count-from-the-start rule would give it. */
lmediastack img:nth-last-child(3), lmediastack video:nth-last-child(3) { transform: rotate(-6deg) translate(-6px, 2px); z-index: 1; }
lmediastack img:nth-last-child(2), lmediastack video:nth-last-child(2) { transform: rotate(5deg) translate(5px, -2px); z-index: 2; }
lmediastack img:nth-last-child(1), lmediastack video:nth-last-child(1) { transform: none; z-index: 3; }
lmediacount {
  position: absolute; bottom: -8px; right: -8px; z-index: 4;
  display: flex; align-items: center; justify-content: center;
  min-width: 28px; height: 28px; padding: 0 var(--l-baseline);
  border-radius: var(--l-radius-pill);
  background: var(--l-fg); color: var(--l-bg);
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxs); font-weight: var(--l-fw-bold);
}

lmediaview {
  position: fixed; inset: 0; z-index: 200;
  background: var(--l-bg);
  overflow-y: auto;
  padding: calc(var(--l-sp-xl) + var(--l-sp-sm)) var(--l-sp-md) var(--l-sp-xl);
}
lmediaviewtitle { display: block; max-width: 42rem; margin: 0 auto var(--l-sp-sm); font-family: var(--l-font-display); font-size: var(--l-fs-lg); font-weight: var(--l-fw-bold); color: var(--l-fg); }
lmediatools, lmedianamerow {
  position: fixed; top: var(--l-sp-lg); left: var(--l-sp-lg); right: var(--l-sp-lg); z-index: 201;
  display: flex; align-items: center; gap: var(--l-gap-xs);
}
lmediatools button, lmedianamerow button {
  padding: var(--l-baseline) var(--l-sp-sm);
  border-radius: var(--l-radius-pill);
  background: var(--l-surface); border: 1px solid var(--l-border);
  color: var(--l-fg);
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxs);
  text-transform: uppercase; letter-spacing: .08em;
  cursor: pointer;
}
lmediatools button[data-on] { background: var(--l-fg); color: var(--l-bg); border-color: var(--l-fg); }
lmediatools button[data-act="close"] {
  margin-left: auto; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; padding: 0;
  border-radius: 50%;
  font-size: 1.2rem; line-height: 1;
}
lmedianamerow input {
  flex: 1; min-width: 0;
  padding: var(--l-baseline) var(--l-sp-sm);
  border-radius: var(--l-radius-pill);
  background: var(--l-surface); border: 1px solid var(--l-border);
  color: var(--l-fg);
  font-family: var(--l-font-body);
  font-size: var(--l-fs-xs);
}
lmediagrid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--l-gap-md);
  max-width: 42rem;
  margin: 0 auto;
}
lmediatile { display: block; position: relative; }
lmediatile button[data-act="delete"] {
  position: absolute; top: var(--l-baseline); right: var(--l-baseline); z-index: 2;
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0;
  border-radius: 50%; border: none;
  background: rgba(0,0,0,.55); color: #fff;
  font-size: 1rem; line-height: 1;
  cursor: pointer;
}
lmediatile img, lmediatile video {
  display: block; width: 100%; height: auto;
  border-radius: 10px;
  cursor: pointer;
  outline: 3px solid transparent; outline-offset: -3px;
  transition: outline-color .12s var(--l-ease);
}
lmediatile[data-selected] img, lmediatile[data-selected] video { outline-color: var(--l-fg); }
lmediatile lcap {
  display: block;
  margin-top: var(--l-baseline);
  font-family: var(--l-font-body);
  font-size: var(--l-fs-xs);
  color: var(--l-fg);
  white-space: pre-wrap; outline: none;
  min-height: 1.4em;
}

/* ── lcamera — full-screen live camera capture (laya.js's `camera()`,
   extracted from sites/journal). Self-managed visibility (`open()`/
   `close()` toggle its own `hidden`), unlike lflick/lfuzzy which laya.app()
   toggles — a camera is a transient overlay a button opens, not a flow
   step you navigate between. ─────────────────────────────────────────── */
lcamera {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  position: fixed;
  inset: 0;
  z-index: 210;
  background: #000;
}

lcamtop {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--l-sp-sm) var(--l-sp-sm) calc(var(--l-sp-sm) + env(safe-area-inset-top));
  position: relative;
  z-index: 2;
  /* scrim so icons stay legible over a bright frame, not floating bare on the video */
  background: linear-gradient(to bottom, rgba(0,0,0,.5), transparent);
}
lcamleft { display: flex; align-items: center; gap: var(--l-gap-xs); }
lcamtop button {
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px;
  border: none; border-radius: 50%; cursor: pointer;
  background: rgba(255,255,255,.14);
  color: #fff;
  transition: background .15s var(--l-ease), transform .12s var(--l-spring);
}
lcamtop button:active { transform: scale(.9); background: rgba(255,255,255,.26); }
lcamtop button svg { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 2; }
lcamtop button[data-act="done"] { background: var(--l-accent); }
lcamtop button[data-act="done"]:active { background: var(--l-accent); opacity: .85; }

lcamcount {
  display: block;
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxs);
  font-weight: var(--l-fw-med);
  letter-spacing: .04em;
  color: #fff;
  background: rgba(255,255,255,.14);
  border-radius: var(--l-radius-pill);
  padding: 4px 10px;
}

lcamstage { display: flex; position: relative; flex: 1; align-items: center; justify-content: center; overflow: hidden; touch-action: none; }   /* none: lets JS own pinch-zoom instead of the browser's page-pinch gesture */
lcamstage video { width: 100%; height: 100%; object-fit: cover; background: #111; transition: filter .2s var(--l-ease); }
lcamstage video[data-facing="user"] { transform: scaleX(-1); }   /* natural selfie preview */
lcamstage[data-error] video { filter: brightness(.35) saturate(.7); }

lcamflash {
  display: block;
  position: absolute; inset: 0;
  background: #fff;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s ease-out;
}
lcamflash[data-on] { opacity: .85; transition: opacity 25ms linear; }

lcamzoom {
  display: block;
  position: absolute; top: var(--l-sp-md); left: 50%; transform: translateX(-50%);
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xs);
  font-weight: var(--l-fw-med);
  color: #fff;
  background: rgba(0,0,0,.55);
  border-radius: var(--l-radius-pill);
  padding: 4px 12px;
  pointer-events: none;
}

lcammsg {
  display: flex; flex-direction: column; align-items: center; gap: var(--l-gap-xs);
  position: absolute; left: var(--l-sp-md); right: var(--l-sp-md); bottom: var(--l-sp-md);
  text-align: center;
}
lcammsg [data-msg] {
  display: block;
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxs);
  text-transform: uppercase; letter-spacing: .16em;
  color: rgba(255,255,255,.75);
  text-shadow: 0 1px 4px rgba(0,0,0,.7);
  pointer-events: none;
}
lcamstage[data-error] lcammsg [data-msg] { color: #fff; text-transform: none; letter-spacing: normal; font-family: var(--l-font-body); font-size: var(--l-fs-xs); }
lcammsg button[data-act="retry"] {
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxs);
  font-weight: var(--l-fw-med);
  text-transform: uppercase; letter-spacing: .08em;
  color: #fff;
  background: rgba(255,255,255,.18);
  border: none; border-radius: var(--l-radius-pill);
  padding: 8px 20px;
  cursor: pointer;
}
lcammsg button[data-act="retry"]:active { background: rgba(255,255,255,.3); }

lcamlenses { display: flex; align-items: center; justify-content: center; gap: var(--l-gap-xs); padding: var(--l-sp-xs) var(--l-sp-md); overflow-x: auto; background: #000; }
lcamlenses button {
  flex: 0 0 auto;
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxs);
  font-weight: var(--l-fw-med);
  color: rgba(255,255,255,.75);
  background: rgba(255,255,255,.12);
  border: none; border-radius: var(--l-radius-pill);
  padding: 6px 14px;
  cursor: pointer;
  transition: background .15s var(--l-ease), color .15s var(--l-ease);
}
lcamlenses button[data-active] { background: var(--l-accent); color: #fff; }

lcamstrip { display: flex; gap: var(--l-gap-xs); padding: var(--l-sp-sm) var(--l-sp-md); overflow-x: auto; background: #000; }
lcamstrip:empty { display: none; }
lcamstrip img {
  flex: 0 0 auto; width: 54px; height: 54px; object-fit: cover;
  border-radius: 8px;
  box-shadow: 0 0 0 1px rgba(255,255,255,.18);
  animation: camShotIn .25s var(--l-spring);
}
@keyframes camShotIn { from { transform: scale(.5); opacity: 0; } to { transform: scale(1); opacity: 1; } }

/* grid (not flex) so the shutter stays dead-center regardless of the native-fallback
   button occupying only the left column — a flex row would need a dummy spacer on the
   right to balance it. */
lcamshoot { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; padding: var(--l-sp-md) 0 calc(var(--l-sp-lg) + env(safe-area-inset-bottom)); background: #000; }
lcamshoot button[data-act="snap"] {
  grid-column: 2;
  width: 66px; height: 66px;
  border: 4px solid rgba(255,255,255,.5);
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #fff, #e2e2e2);
  box-shadow: 0 2px 12px rgba(0,0,0,.45);
  cursor: pointer;
  transition: transform .12s var(--l-spring);
}
lcamshoot button[data-act="snap"]:active { transform: scale(.9); }
lcamshoot button[data-act="native"] {
  grid-column: 1;
  justify-self: start;
  margin-left: var(--l-sp-lg);
  display: flex; align-items: center; justify-content: center;
  width: 46px; height: 46px;
  border: none; border-radius: 50%;
  background: rgba(255,255,255,.14);
  color: #fff;
  cursor: pointer;
  transition: background .15s var(--l-ease), transform .12s var(--l-spring);
}
lcamshoot button[data-act="native"]:active { transform: scale(.9); background: rgba(255,255,255,.26); }
lcamshoot button[data-act="native"] svg { width: 21px; height: 21px; stroke: currentColor; fill: none; stroke-width: 2; }

/* ltalk (laya.js's `talk()`) — a floating live-caption pill by default
   (journal's own `clive`), whether it's jot's auto-built one or an app's
   own standalone `laya.talk(el, opts)` call. Ordinary CSS, not
   `!important` like the [hidden] rule up top — override it in your own
   stylesheet same as any other laya default. */
ltalk {
  display: block;
  position: fixed;
  left: 50%;
  bottom: calc(max(var(--l-sp-lg), env(safe-area-inset-bottom)) + 56px);   /* stacks above ltools */
  transform: translateX(-50%);
  z-index: 41;
  max-width: min(88vw, 40rem);
  padding: var(--l-sp-xs) var(--l-sp-md);
  background: var(--l-fg);
  color: var(--l-bg);
  font-family: var(--l-font-body);
  font-size: var(--l-fs-xs);
  line-height: var(--l-lh-xs);
  text-align: center;
  border-radius: 18px;
}
ltalk:empty { display: none; }

/* jot's default toolbar (laya.js's jot() builds this only when
   `opts.camera`/`opts.talk` are enabled) — journal's own `cbar`. */
ltools {
  display: flex;
  gap: var(--l-gap-sm);
  position: fixed;
  left: 50%;
  bottom: max(var(--l-sp-lg), env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 40;
}
ltools button {
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxs);
  text-transform: uppercase;
  letter-spacing: .18em;
  padding: var(--l-sp-sm) var(--l-sp-md);
  border-radius: var(--l-radius-pill);
  border: 1px solid var(--l-border);
  background: var(--l-bg);
  color: var(--l-fg);
  cursor: pointer;
  transition: transform .15s var(--l-spring);
}
ltools button:active { transform: scale(.96); }
ltools button[data-on] { background: var(--l-accent-like); border-color: var(--l-accent-like); color: #fff; }

/* ── lbento — Pinterest-style masonry/bento grid (laya.js's `bento()`).
   grid-auto-rows is a fine-grained unit, not a real row of content — each
   <ltile>'s grid-row-end: span N (set by JS, one N per tile) is what makes
   it occupy the right number of those units to match its actual rendered
   height, which is how a CSS-grid masonry avoids gaps without JS having to
   also compute *positions*, only *spans*. ─────────────────────────────── */
lbento {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--l-bento-col, 160px), 1fr));
  grid-auto-rows: var(--l-bento-row, 4px);
  gap: var(--l-gap-sm);
}
ltile {
  display: block;
  position: relative;
  overflow: hidden;
  border-radius: 16px;
  background: var(--l-surface);
  cursor: pointer;
  transition: transform .2s var(--l-spring);
}
ltile:active { transform: scale(.97); }
ltile img {
  display: block;
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;   /* fills whatever height the tile ends up at — its own natural ratio, or a curated aspect-ratio override — cropping instead of squashing */
  opacity: 0;
  transition: opacity .35s var(--l-ease);
}
ltile img[data-loaded] { opacity: 1; }

/* Bottom gradient-scrim overlay, same visual family as flick's linfo (laya.css
   above) but scaled down for a grid tile instead of a full-bleed slide — a
   fresh, more specific rule rather than reusing linfo's oversized values
   directly, per spec.md's "style via descendant selectors off the tag"
   convention (same tag name, re-scoped per parent context is the idiom, not
   a collision). */
ltile linfo {
  display: flex;
  flex-direction: column;   /* NOT row — h3/p are separate direct children, not a pre-grouped text stack, so a row here (as if pairing "text stack" against a lone icon) split them into two half-width flex items and wrapped both, badly */
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
  /* percentage padding-top resolves against the tile's WIDTH (a CSS quirk,
     not a bug) — same trick flick's own linfo uses — so the scrim's height
     scales with column width instead of needing a fixed px guess per tile
     height, and stays a gradual fade instead of reading as a hard-edged
     solid bar the way a short fixed padding did. */
  padding: 24% var(--l-sp-sm) var(--l-sp-sm);
  background: linear-gradient(to top, rgba(0,0,0,.9) 0%, rgba(0,0,0,.55) 40%, transparent 100%);
  color: #fff;
  pointer-events: none;   /* the title/gradient never blocks a tap on the tile — only the icon button (below) re-enables it */
}
ltile linfo[data-icon] h3, ltile linfo[data-icon] p { padding-right: calc(32px + var(--l-gap-xs)); }   /* same reservation trick as flick's linfo[rail] */
ltile linfo h3 {
  margin: 0;
  font-family: var(--l-font-display);
  font-size: var(--l-fs-sm);
  font-weight: var(--l-fw-bold);
  line-height: var(--l-lh-sm);
  text-shadow: 0 2px 12px rgba(0,0,0,.5);
}
ltile linfo p {
  margin: var(--l-baseline) 0 0;
  font-family: var(--l-font-body);
  font-size: var(--l-fs-xxs);
  line-height: var(--l-lh-xxs);
  color: rgba(255,255,255,.75);
}
ltile linfo button[data-act="icon"] {
  position: absolute; right: var(--l-sp-sm); bottom: var(--l-sp-sm);   /* a column-flow linfo can't also flex an icon to the opposite corner, so this is independently placed, same as lbadge */
  pointer-events: auto;
  width: 32px; height: 32px; border-radius: 50%;
  border: none; background: rgba(255,255,255,.16); backdrop-filter: blur(6px);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: transform .15s var(--l-spring), background .2s;
}
ltile linfo button[data-act="icon"] svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; }
ltile linfo button[data-act="icon"]:active { transform: scale(.88); background: rgba(255,255,255,.28); }
ltile linfo button[data-act="icon"][liked] { color: var(--l-accent-like); background: color-mix(in srgb, var(--l-accent-like) 22%, transparent); }
ltile linfo button[data-act="icon"][liked] svg { fill: currentColor; stroke: none; }

ltile lbadge { position: absolute; top: var(--l-sp-xs); left: var(--l-sp-xs); z-index: 1; }

/* ── ldots — a grid of filled/unfilled dots (laya.js's `dots()`):
   progress count or a tappable day grid, laya/samples' most-repeated
   idea. `--l-dots-col` is set inline per instance (opts.columns). ──── */
ldots {
  display: grid;
  grid-template-columns: repeat(var(--l-dots-col, 7), 1fr);
  gap: var(--l-gap-xs);
}
ldots button {
  aspect-ratio: 1;
  border-radius: 50%;
  border: none;
  padding: 0;
  background: var(--l-mute);
  color: var(--l-dim);
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxs);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background .2s, color .2s, transform .15s var(--l-spring);
}
ldots button[filled] { background: var(--l-accent); color: #fff; }
ldots button[active] { box-shadow: 0 0 0 2px var(--l-accent); }
ldots button:disabled { opacity: .35; cursor: default; }
ldots button:active:not(:disabled) { transform: scale(.88); }

/* ── lnav — a generic bottom tab bar. Static/presentational, not an
   Engine (spec.md §9's "second macro category" — no init()/Handle, no
   persistent gesture listeners) — plain <button>s inside it (same
   pattern as lcamtop/lrail/ltools: real semantic HTML for anything
   interactive, never a custom tag standing in for <button>). One
   button can carry `raised` to get the floating circular accent-color
   treatment that overlaps the bar, for whichever action deserves the
   visual weight (a "compose," a search trigger, whatever the app
   decides — laya has no opinion on which). ─────────────────────────── */
lnav {
  display: flex;
  align-items: center;
  justify-content: space-around;
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
  padding: var(--l-sp-sm) var(--l-sp-md) calc(var(--l-sp-sm) + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--l-bg) 88%, transparent);
  backdrop-filter: blur(20px);
  border-top: 1px solid var(--l-border);
}
lnav button {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  background: none; border: none; color: var(--l-dim); cursor: pointer;
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxs);
  text-transform: uppercase; letter-spacing: .06em;
  transition: color .2s, transform .15s var(--l-spring);
}
lnav button svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 1.8; }
lnav button:active { transform: scale(.92); }
lnav button[active] { color: var(--l-fg); }
lnav button[raised] {
  position: relative;
  margin-top: -34px;
  width: 56px; height: 56px; border-radius: 50%;
  justify-content: center;
  background: var(--l-accent); color: #fff;
  box-shadow: 0 10px 25px color-mix(in srgb, var(--l-accent) 45%, transparent);
}
lnav button[raised] svg { width: 24px; height: 24px; }
lnav button[raised] span { display: none; }   /* an icon-only floating button, no label crowding the circle */

/* ── lsubnav — a horizontal pill row for switching context within a
   surface (spec.md §9's macro table, same tier/shape as lnav above:
   static, plain <button>s, no init()/Handle). One button carries
   `active` at a time — app-level job to toggle it and re-render
   whatever the pill controls, same division of labor lsubnav's own
   entry describes: it only owns the row's own look/scroll, not what
   switching a pill does. ─────────────────────────────────────────── */
lsubnav {
  display: flex;
  gap: var(--l-gap-xs);
  overflow-x: auto; scrollbar-width: none;
  position: sticky; top: 0; z-index: 40;
  padding: var(--l-sp-xs) var(--l-sp-sm);
  background: color-mix(in srgb, var(--l-bg) 88%, transparent);
  backdrop-filter: blur(20px);
  border-bottom: 1px solid var(--l-border);
}
lsubnav::-webkit-scrollbar { display: none; }
lsubnav button {
  flex: 0 0 auto;
  padding: 6px var(--l-sp-sm);
  border-radius: var(--l-radius-pill);
  border: 1px solid var(--l-border);
  background: var(--l-surface); color: var(--l-dim);
  font-family: var(--l-font-label);
  font-size: var(--l-fs-xxs);
  text-transform: uppercase; letter-spacing: .06em;
  cursor: pointer;
  transition: color .2s, background .2s, border-color .2s;
}
lsubnav button[active] {
  color: #fff;
  background: var(--l-accent);
  border-color: var(--l-accent);
}

/* Utilities */
.msg { padding: 40px 0; color: var(--l-dim); text-transform: uppercase; font-size: .7rem; text-align: center; }
