/* ══════════════════════════════════════════════════════════════════════════
   floom-ui.css — the shadcn/ui design system, in plain CSS

   shadcn/ui ships as React components on Radix + Tailwind. This site is
   static HTML with no build step, so what is portable is the part that
   actually *is* the design system: the semantic token layer, the radius
   scale derived from one base, the focus-ring convention, and the component
   recipes (variant × size). Those are reproduced here verbatim in behaviour.

   Tokens carry a `ui-` prefix rather than shadcn's bare names because three
   of those names already mean something else on this site — `--accent` is
   Floom orange (shadcn uses it for a muted hover fill), and `--border` /
   `--radius-lg` are load-bearing across twelve pages. Prefixing keeps
   shadcn's vocabulary without hijacking tokens the existing markup depends
   on.
   ══════════════════════════════════════════════════════════════════════════ */

:root{
  /* ── token layer — shadcn's semantic roles, on Floom's warm palette ────── */
  --ui-background:            #faf6f1;
  --ui-foreground:            #181818;
  --ui-card:                  #FFFFFF;
  --ui-card-foreground:       #181818;
  --ui-popover:               #FFFFFF;
  --ui-popover-foreground:    #181818;
  --ui-primary:               #181818;
  --ui-primary-foreground:    #faf6f1;
  --ui-secondary:             #ededed;
  --ui-secondary-foreground:  #181818;
  --ui-muted:                 #ededed;
  --ui-muted-foreground:      #767270;   /* Medium Gray — 4.42:1 on the Off
                                            White ground, 4.76:1 on White */
  --ui-hover:                 #ededed;   /* shadcn's `accent`: the hover fill */
  --ui-hover-foreground:      #181818;
  --ui-destructive:           #ff3c00;   /* Orange — 3.56:1 on White. Error
                                            is carried by an icon and words;
                                            the colour only reinforces it. */
  --ui-destructive-foreground:#FFFFFF;
  --ui-border:                rgba(24,24,24,.10);
  --ui-input:                 rgba(24,24,24,.14);
  --ui-ring:                  #ff3c00;   /* Orange — the focus colour */

  /* ── radius scale — shadcn derives all four from one base ─────────────── */
  --ui-radius:     10px;
  --ui-radius-sm:  calc(var(--ui-radius) - 4px);
  --ui-radius-md:  calc(var(--ui-radius) - 2px);
  --ui-radius-lg:  var(--ui-radius);
  --ui-radius-xl:  calc(var(--ui-radius) + 4px);

  --ui-shadow-xs: 0 1px 2px rgba(24,24,24,.05);
  --ui-shadow-sm: 0 1px 3px rgba(24,24,24,.07), 0 1px 2px rgba(24,24,24,.04);
}

/* ══ Button ════════════════════════════════════════════════════════════════
   .ui-btn [data-variant] [data-size] — shadcn's exact API surface.        */
.ui-btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--ui-radius-md);
  font-family: var(--font, inherit);
  font-size: 14px;
  font-weight: 500;
  line-height: 1;
  height: 36px;
  padding: 0 16px;
  cursor: pointer;
  transition: background-color .16s ease, color .16s ease,
              border-color .16s ease, box-shadow .16s ease;
}
/* shadcn sizes every icon inside a button to 16px and stops it shrinking. */
.ui-btn svg{ width: 16px; height: 16px; flex-shrink: 0; }
.ui-btn:disabled,
.ui-btn[aria-disabled="true"]{ pointer-events: none; opacity: .5; }

/* Scoped with :not([data-variant]) rather than on the bare class — a bare
   `.ui-btn` rule paints every variant and then relies on each one to undo it,
   which only holds for variants that happen to set the same properties. */
.ui-btn:not([data-variant]),
.ui-btn[data-variant="default"]{
  background: var(--ui-primary);
  color: var(--ui-primary-foreground);
  box-shadow: var(--ui-shadow-xs);
}
.ui-btn:not([data-variant]):hover,
.ui-btn[data-variant="default"]:hover{
  background: color-mix(in srgb, var(--ui-primary) 90%, #FFFFFF);
}
.ui-btn[data-variant="secondary"]{
  background: var(--ui-secondary); color: var(--ui-secondary-foreground);
  box-shadow: var(--ui-shadow-xs);
}
.ui-btn[data-variant="secondary"]:hover{
  background: color-mix(in srgb, var(--ui-secondary) 80%, var(--ui-foreground));
}
.ui-btn[data-variant="destructive"]{
  background: var(--ui-destructive); color: var(--ui-destructive-foreground);
  box-shadow: var(--ui-shadow-xs);
}
.ui-btn[data-variant="outline"]{
  background: var(--ui-background); color: var(--ui-foreground);
  border-color: var(--ui-input); box-shadow: var(--ui-shadow-xs);
}
.ui-btn[data-variant="outline"]:hover{
  background: var(--ui-hover); color: var(--ui-hover-foreground);
}
.ui-btn[data-variant="ghost"]{ background: transparent; color: var(--ui-foreground); }
.ui-btn[data-variant="ghost"]:hover{
  background: var(--ui-hover); color: var(--ui-hover-foreground);
}
.ui-btn[data-variant="link"]{
  background: transparent; color: var(--ui-primary);
  height: auto; padding: 0; text-underline-offset: 4px;
}
.ui-btn[data-variant="link"]:hover{ text-decoration: underline; }

.ui-btn[data-size="sm"]{ height: 32px; padding: 0 12px; gap: 8px; border-radius: var(--ui-radius-md); }
.ui-btn[data-size="lg"]{ height: 40px; padding: 0 24px; }
.ui-btn[data-size="icon"]{ width: 36px; height: 36px; padding: 0; }

/* ══ Card ═════════════════════════════════════════════════════════════════ */
.ui-card{
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding: 24px 0;
  background: var(--ui-card);
  color: var(--ui-card-foreground);
  border: 1px solid var(--ui-border);
  border-radius: var(--ui-radius-xl);
  box-shadow: var(--ui-shadow-sm);
}
.ui-card-header,
.ui-card-content,
.ui-card-footer{ padding-inline: 24px; }
.ui-card-header{ display: flex; flex-direction: column; gap: 8px; }
.ui-card-title{ font-family: var(--font-display, inherit); font-weight: 600; font-size: 16px; line-height: 1; }
.ui-card-description{ color: var(--ui-muted-foreground); font-size: 14px; line-height: 1.3; }
.ui-card-footer{ display: flex; align-items: center; }

/* ══ Input / Label ════════════════════════════════════════════════════════ */
.ui-input{
  display: block;
  width: 100%;
  height: 36px;
  padding: 4px 12px;
  background: transparent;
  color: var(--ui-foreground);
  border: 1px solid var(--ui-input);
  border-radius: var(--ui-radius-md);
  box-shadow: var(--ui-shadow-xs);
  font-family: var(--font, inherit);
  font-size: 14px;
  transition: border-color .16s ease, box-shadow .16s ease;
}
.ui-input::placeholder{ color: var(--ui-muted-foreground); }
.ui-input:disabled{ cursor: not-allowed; opacity: .5; }
textarea.ui-input{ height: auto; min-height: 64px; padding: 8px 12px; }

.ui-label{
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 14px; font-weight: 500; line-height: 1;
}

/* ══ Badge ════════════════════════════════════════════════════════════════ */
.ui-badge{
  display: inline-flex; align-items: center; gap: 4px;
  width: fit-content; white-space: nowrap;
  padding: 4px 8px;
  border: 1px solid transparent;
  border-radius: var(--ui-radius-md);
  font-size: 12px; font-weight: 500; line-height: 1.3;
}
.ui-badge svg{ width: 12px; height: 12px; flex-shrink: 0; }
.ui-badge:not([data-variant]),
.ui-badge[data-variant="default"]{ background: var(--ui-primary); color: var(--ui-primary-foreground); }
.ui-badge[data-variant="secondary"]{ background: var(--ui-secondary); color: var(--ui-secondary-foreground); }
.ui-badge[data-variant="destructive"]{ background: var(--ui-destructive); color: var(--ui-destructive-foreground); }
.ui-badge[data-variant="outline"]{ background: transparent; color: var(--ui-foreground); border-color: var(--ui-border); }

/* ══ Separator ════════════════════════════════════════════════════════════ */
.ui-separator{ flex-shrink: 0; background: var(--ui-border); border: 0; }
.ui-separator[data-orientation="vertical"]{ width: 1px; height: 100%; }
.ui-separator:not([data-orientation="vertical"]){ height: 1px; width: 100%; }

@media (prefers-reduced-motion: reduce){
  .ui-btn, .ui-input{ transition: none; }
}

/* ── focus ring ────────────────────────────────────────────────────────────
   Last in the file on purpose. `.ui-btn:focus-visible` and
   `.ui-btn[data-variant="outline"]` are both specificity (0,2,0), so whichever
   is written later wins — with the ring declared above the variants, every
   variant that sets its own box-shadow silently ate it.

   shadcn's current convention: a 3px ring at 50% alpha plus a solid border
   shift, on :focus-visible only, so pointer users never see it. Applied to the
   site's own controls too, so focus reads the same everywhere. */
.ui-btn:focus-visible,
.ui-input:focus-visible,
.ui-badge:focus-visible,
.btn:focus-visible,
.fl-btn:focus-visible,
.nav-toggle:focus-visible,
.nv-trigger:focus-visible{
  outline: none;
  border-color: var(--ui-ring);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-ring) 50%, transparent);
}
/* No color-mix (older Safari/Firefox): fall back to a solid 2px outline —
   the ring is an affordance, so it degrades to visible, never to absent. */
@supports not (color: color-mix(in srgb, red 50%, transparent)){
  .ui-btn:focus-visible,
  .ui-input:focus-visible,
  .ui-badge:focus-visible,
  .btn:focus-visible,
  .fl-btn:focus-visible,
  .nav-toggle:focus-visible,
  .nv-trigger:focus-visible{
    outline: 2px solid var(--ui-ring);
    outline-offset: 2px;
    box-shadow: none;
  }
}

/* ── Grid backdrop ─────────────────────────────────────────────────────────
   Aceternity's grid background, with one change of construction. Theirs lays
   the grid down and then puts a SECOND sheet of the page colour on top of it,
   masked by a radial gradient, to fade the grid out towards the edges — which
   only works for as long as that sheet's colour is exactly the page's, in every
   theme, on every section it is used in. Masking the grid itself is the same
   picture with one element and no colour to keep in sync.

   40px and a 1px line are theirs. The line colour is ours: #e2e2e2 is a cool
   grey and everything on this page is warm, so it reads as the one borrowed
   element it is. --border is the same weight (10%) in the palette's own hue. */
.fl-grid-bg{
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* BEHIND the section's content and above its background. z-index:0 is not
     enough: CSS paints positioned boxes after in-flow text, so at 0 the ruling
     lands on top of the copy. A negative index paints it in the step before
     in-flow content — but only inside a stacking context, or it slides behind
     the page itself and disappears. .fl-grid-host below is that context. */
  z-index: -1;
  --fl-grid: 40px;
  /* Measured in the render rather than picked off a swatch. At .13 a rule read
     22.5 levels darker than its paper, which is a drawn grid; .085 put it at
     15, and this — the weight every section below the hero carries — lands
     near 10. The hero keeps .085 (floom-hero.css): it is one screen of mostly
     empty paper where the ruling is the texture, while everything below it is
     already carrying cards, panels and copy and only wants the suggestion.
     Below roughly 8 levels a rule stops surviving a dimmed display, so this is
     about as far as the light surfaces go. */
  --fl-grid-line: rgba(24,24,24,.055);
  background-size: var(--fl-grid) var(--fl-grid);
  /* Anchored to the VIEWPORT, not to the page: the ruling holds still and the
     content travels across it. It also makes every section's lines fall on the
     same 40px reckoning, which an element-anchored grid cannot do — each
     section begins at whatever offset the page happens to give it, so the
     horizontal rules jogged at every boundary. */
  background-attachment: fixed;
  background-image:
    linear-gradient(to right,  var(--fl-grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--fl-grid-line) 1px, transparent 1px);
  --fl-grid-fade: radial-gradient(ellipse at center, #000 0%, #000 38%, rgba(0,0,0,.45) 64%, transparent 88%);
  -webkit-mask-image: var(--fl-grid-fade);
          mask-image: var(--fl-grid-fade);
}

/* Tall sections get a band, not a bloom. An ellipse drawn over three thousand
   pixels of section is not a fade — it is a vignette, and it puts a bright
   patch in the middle of every section that has one. Fade the two edges the
   section actually has and keep the ruling even everywhere between them. */
.fl-grid-bg.is-band{
  --fl-grid-fade:
    linear-gradient(to bottom, transparent 0, #000 9%, #000 91%, transparent 100%),
    linear-gradient(to right,  transparent 0, #000 6%, #000 94%, transparent 100%);
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
}

/* On ink. The same 13% of the text colour would be invisible, so the line
   becomes the page colour at the weight that reads the same against black. */
.fl-grid-bg.on-dark{ --fl-grid-line: rgba(250,246,241,.026); }

/* Whatever holds a grid. `isolation` is the whole point — it makes a stacking
   context and nothing else, where `position: relative; z-index: 0` would also
   quietly change how the element stacks against its own siblings. */
.fl-grid-host{
  position: relative;
  isolation: isolate;
}

/* Touch devices get the scrolling default back. iOS ignores fixed attachment
   outright, and where it is honoured on a phone it costs a full-layer repaint
   on a frame budget that has no room for one. */
@media (hover: none){
  .fl-grid-bg{ background-attachment: scroll; }
}
@media (max-width: 760px){
  .fl-grid-bg{ --fl-grid: 28px; }
}

/* ── Embedded ───────────────────────────────────────────────────────────────
   A guide shown inside the blog's reader (blog/index.html) arrives with ?embed=1
   and an .is-embed class on <html>, set in <head> before the body parses. It
   then drops everything that is the site rather than the article: the nav and
   its menu, the curtain, the footer, the breadcrumb back to a page you are
   already on — and sits on a transparent ground, so it reads as part of the
   reader holding it rather than as a page inside a page. The reader keeps its
   own bar across the top; the hero leaves room for it. */
html.is-embed .fl-pre,
html.is-embed .nav,
html.is-embed .mobile-menu,
html.is-embed .mega,
html.is-embed .mega-scrim,
html.is-embed .ft-reveal,
html.is-embed footer,
html.is-embed .crumbs{ display: none !important; }
html.is-embed,
html.is-embed body{ background: transparent; overflow: hidden; }
html.is-embed .article-hero{ padding-top: 88px; }

/* ── In the sheet ───────────────────────────────────────────────────────────
   ?embed=1&sheet=1: the page is inside the home page's bottom sheet
   (floom-sheet.js), not the blog's reader. The reader sizes its frame to the
   whole page and scrolls the frame; the sheet lets the page scroll itself,
   so its reveals and sticky parts keep working. No bar across the top here,
   so the hero sits close under the sheet's edge. */
html.is-sheet{ overflow: hidden auto; overscroll-behavior: contain; }
html.is-sheet body{ overflow: visible; }
html.is-sheet .article-hero{ padding-top: round(clamp(44px, 5vw, 60px), 8px); }
