/* Instrument Serif from the site itself, so the headline is never drawn in
   a stand-in face: 'block' keeps it unseen for the moment the file takes
   rather than showing Georgia first. */
@font-face { font-family: 'Instrument Serif'; src: url('/assets/site/fonts/InstrumentSerif-Regular.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: block; }
/* Spica — CMS
   Uses the same palette, type and shapes as the client-facing work order. */

:root{
  /* The system UI action color is blue. (These vars keep their old
     yellow-era names so existing rules don't change; only the CMS uses
     them — the work-order slides have their own palette in order.css and
     are unaffected.) */
  --green: #17317A;
  --green-pill: #D5E6FC;
  --green-border: #93C0F5;
  --gray-pill: #ededed;
  --card-border: #949494;
  --line: #e4e4e4;
  --muted: #8a8a8a;
  --dark: #313131;
  --red: #ff2116;
  --shadow-pop: #ededed;
  /* Card/frame stroke + shadow, from Figma dev mode ("Rec Sample"):
     border-radius:6px; border:.3px solid #D6D6D6; background:#FFF;
     box-shadow:0 0 3px 0 rgba(0,0,0,.07). Radius stays per-element (already
     tuned per the earlier rounding passes) — only the border colour/weight
     and the ambient shadow are shared here. */
  --frame-border: #D6D6D6;
  /* The hairline on a chip that sits on an #EFEFEC bar (see .be-sq). It is
     --frame-border — the colour of the bar's own separators — taken 20%
     darker, because against #EFEFEC rather than white the separator's own
     value is too faint to give the circle an edge. */
  --chip-stroke-on-bar: #ABABAB;
  --frame-shadow: 0 0 3px 0 rgba(0,0,0,.07);
}

*{ box-sizing: border-box; }

/* Form controls do not inherit the page's face on their own — the browser
   gives a <button>, <input>, <select> or <textarea> its own system font unless
   it is told otherwise. Most controls here already said `font-family:inherit`
   one at a time, which meant the two that did not (the helper card's close
   button and the password reveal) were quietly in Arial. Saying it once covers
   every control, including the ones not written yet.

   Specimens are unaffected: they set the design's face with a class or an
   inline style, and both beat a bare element selector. */
button, input, select, textarea{ font-family:inherit; }

html,body{
  margin:0; padding:0;
  background:#fff;
  font-family:'Parkinsans', 'Google Sans', Arial, sans-serif;
  color:#111;
  -webkit-font-smoothing:antialiased;
}
/* Builds and the build editor sit on their own panel background — see
   body.theme-panel below. */

a{ color:inherit; }

/* ---------------------------------------------------------------------------
   The home screen — Figma "Home Screen" (node 523-2306).

   Every value here was read from the file's inspect panel. The frame is
   1920×1080 and the rail is not in it, so the screen is laid out fluid and
   centred in whatever the rail leaves rather than scaled: a 1235px row of four
   293px cards, which wraps to two and then one when the window cannot hold it.

   Parkinsans was on trial here first; it is the system face everywhere now
   (see `layout`), so this screen no longer loads or declares it.
   --------------------------------------------------------------------------- */
body.home-panel{ background:#fff; }
/* The wash is .page-band-wash now, shared with the Gallery and the three list
   screens; the home screen only says how far down it sits. */
body.home-panel{ --band-y:150px; }
body.home-panel .admin-main{ max-width:none; padding:0; min-height:100vh; }
.home{
  display:flex; flex-direction:column; align-items:center;
  padding:118px 24px 80px;
}
.home-head{ text-align:center; }
.home-title{
  margin:0; font-weight:500; font-size:41px; line-height:153%; color:#000;
}
.home-sub{
  margin:0; font-weight:300; font-size:17px; line-height:153%; color:#000;
}

/* ---- the four ways in ----
   1235 = four 293s and three 21s. */
.home-cards{
  display:flex; flex-wrap:wrap; justify-content:center; align-items:stretch;
  gap:21px; max-width:1235px; margin-top:46px;
}
.home-card-form{ display:contents; }
.home-card{
  display:flex; flex-direction:column; align-items:flex-start; gap:15px;
  box-sizing:border-box; width:293px; padding:24px 25px;
  border:0; border-radius:12px; background:#FFF;
  box-shadow:0 4px 14px 0 rgba(0, 0, 0, 0.08);
  text-align:start; text-decoration:none; color:inherit; font:inherit;
  cursor:pointer;
  transition:transform .26s cubic-bezier(.22,.61,.36,1), box-shadow .26s ease;
}
/* The same 1% and the same easing the build cards answer with, so the two
   screens agree about what a card does under the cursor. */
.home-card:hover{ transform:scale(1.01); box-shadow:0 8px 22px 0 rgba(0, 0, 0, 0.12); }
/* Drawn, with nothing behind it yet — it looks like the others and stays put,
   the way the rail's Gallery does. */
.home-card.is-soon{ cursor:default; }
.home-card.is-soon:hover{ transform:none; box-shadow:0 4px 14px 0 rgba(0, 0, 0, 0.08); }

.hc-art{
  display:flex; align-items:center; justify-content:center;
  align-self:stretch; height:162px;
  border-radius:18.985px; background:#FFE9E1;
}
/* All four glyphs are the accent, including the two with nothing behind them
   yet: they are drawn, not disabled, and greying them said the wrong thing. */
.hc-glyph{ width:54px; height:54px; color:#FF3C02; stroke-width:1.4; }

/* ---- the two blocks that hold the cards level ----
   Name and subtitle are one text node in the file, two lines in one box, and
   the description is another of a fixed height. Both are pinned here, because
   a card whose copy runs one line longer than its neighbour's pushes its own
   description and tag pill down and the row stops lining up — which is exactly
   what happened the first time a subtitle did not fit.

   So the two lines never wrap: each is its own line and too long a one is cut
   with an ellipsis rather than allowed to move anything. 52.4px is two lines
   at 131% of 20px; the file says 48, which is less than its own two lines
   need. 44px is the file's, and it is two lines of the description exactly. */
.hc-name{ display:block; align-self:stretch; height:52.4px; overflow:hidden; }
.hc-name b, .hc-name i{
  display:block; font-size:20px; line-height:131%; letter-spacing:.2px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.hc-name b{ font-weight:500; color:#000; }
.hc-name i{ font-style:normal; font-weight:300; color:#8F8F8F; }
.hc-desc{
  display:block; align-self:stretch; height:44px; overflow:hidden;
  font-weight:400; font-size:14px; line-height:155%; letter-spacing:.84px;
  color:#9E9E9E;
}

/* The tag pill. Card one is the warm one; the rest are grey on the same
   geometry, which is how the file draws them. */
.hc-tag{
  display:inline-flex; align-items:center; justify-content:center; gap:3px;
  padding:5px 8px; border-radius:5px;
  background:#F2F2F2; color:#767676;
  font-weight:400; font-size:11px; line-height:1; letter-spacing:.5px;
  text-transform:uppercase; white-space:nowrap;
}
.hc-tag.warm{ background:#FFE0D7; color:#FF3C02; }
.hc-tag-ico{ width:11.99px; height:11.99px; flex:none; }

/* ---- what you were last doing ---- */
/* The line under the cards (Figma 523:2306). IN THE FLOW, not in the backdrop
   behind it: absolutely positioned at a percentage of the screen it sat where
   the 1920x1080 frame put it and nowhere else -- on a shorter window the
   content moved and the line did not, and it landed on "Your recent
   activity". Sitting between the two in the normal flow, it cannot collide at
   any window size, and it is real text rather than decoration.

   Geist Mono: the brand already draws on Geist for the sign-in slogan, so
   this keeps one mono in the product rather than introducing a second. */
.home-line{
  margin:50px 0 0; text-align:center;
  font-family:'Geist Mono', ui-monospace, monospace;
  font-size:22px; font-weight:400; line-height:1.4; letter-spacing:.02em;
  color:#A5B0D8;
}

.home-recent{ margin-top:74px; display:flex; flex-direction:column; align-items:center; }
.home-recent-cap{
  margin:0 0 18px; font-weight:500; font-size:19px; line-height:153%;
  letter-spacing:.38px; color:#000;
}
.home-row{
  display:flex; align-items:center; gap:20.93px;
  box-sizing:border-box; width:632px; max-width:100%; padding:24px 25px;
  border-radius:12px; background:#FFF;
  box-shadow:0 4px 14px 0 rgba(0, 0, 0, 0.08);
  text-decoration:none; color:inherit;
  transition:transform .26s cubic-bezier(.22,.61,.36,1), box-shadow .26s ease;
}
.home-row:hover{ transform:scale(1.01); box-shadow:0 8px 22px 0 rgba(0, 0, 0, 0.12); }
.hr-text{ flex:1 0 0; min-width:0; display:flex; flex-direction:column; }
.hr-text b{ font-weight:500; font-size:15px; line-height:150%; color:#000; }
.hr-text i{ font-style:normal; font-weight:300; font-size:14px; line-height:150%; color:#767676; }
.hr-go{
  display:inline-flex; align-items:center; justify-content:center; gap:4.565px;
  flex:none; width:121px; height:37.759px; padding:7.054px 11.534px;
  border-radius:17.789px; background:#CCE2FF; color:#0A2250;
  font-weight:400; font-size:14px; line-height:1;
}
.hr-arrow{ width:15.45px; height:15.45px; flex:none; }

@media (max-width: 1000px){
  .home{ padding-top:64px; }
  .home-title{ font-size:32px; }
}


/* ---------------------------------------------------------------------------
   The gallery — Figma "Gallery_Design Themes" (node 529-2582).

   The frame is 1920 × 1690 on white. Laid out fluid and centred in what the
   rail leaves, the way the home screen is, rather than scaled.

   Measured from the file: the link row is 1001 wide at gap 17.174, each card
   322.217 with 9.814/10.632 padding, a 16.356 radius and the same soft shadow
   the home cards wear; the header block is 696.16 × 78, a column at gap 6 over
   a 1920 × 423 radial band that only the top of the screen wears.
   --------------------------------------------------------------------------- */
body.gallery-panel{ background:#fff; }
body.gallery-panel .admin-main{ max-width:none; padding:0; min-height:100vh; }
.gallery{
  position:relative; z-index:0;
  display:flex; flex-direction:column; align-items:center;
  padding:104px 24px 120px;
}

/* ---- the vertical rhythm ----
   104 in, then 60 / 104 / 68 between the four blocks: the title, the three
   ways in, the header, the catalogue. Bigger than the first pass, which read
   as one block. It also runs with the frame rather than against it — Figma
   puts the header at y=721, well below the 423 the band covers, so the more
   air these get the closer the header lands to where it is drawn. */

/* ---- the three ways in (Frame 1321315060, 1001 x 89.8 at gap 17.174) ----
   Every figure below is the file's. A card is the 73.194 x 70.144 tile, then a
   column at gap 7.36 carrying the two lines of type with the tag underneath —
   not one row of glyph, name and tag, which is what these were when they were
   the home screen's cards wearing the home screen's copy, and why the wording
   had nowhere to go. 1001 = three 322.217s and two 17.174s. */
.gallery-links{
  display:flex; flex-wrap:wrap; justify-content:center; align-items:center;
  gap:17.174px; max-width:1001px; margin-top:60px;
}
.gallery-link{
  display:flex; justify-content:center; align-items:center; gap:10.632px;
  box-sizing:border-box; width:322.217px; padding:9.814px 10.632px;
  border:0; border-radius:16.356px; background:#FFF;
  box-shadow:0 3.271px 11.449px 0 rgba(0, 0, 0, 0.08);
  text-align:start; text-decoration:none; color:inherit; font:inherit;
  cursor:pointer;
  transition:transform .26s cubic-bezier(.22,.61,.36,1), box-shadow .26s ease;
}
.gallery-link:hover{ transform:scale(1.01); box-shadow:0 8px 22px 0 rgba(0, 0, 0, 0.12); }
.gallery-link.is-soon{ cursor:default; }
.gallery-link.is-soon:hover{ transform:none; box-shadow:0 3.271px 11.449px 0 rgba(0, 0, 0, 0.08); }

/* Empty in the frame, so empty here. The peach square is the whole of it. */
.gl-art{
  flex:none; width:73.194px; height:70.144px;
  border-radius:8.379px; background:#FFE9E1;
}
.gl-col{
  display:flex; flex-direction:column; justify-content:center;
  align-items:flex-start; gap:7.36px; flex:1 0 0; align-self:stretch;
}
.gl-name{ display:block; }
.gl-name b, .gl-name i{
  display:block; font-size:16.356px; line-height:131%; letter-spacing:.164px;
}
.gl-name b{ font-weight:500; color:#000; }
.gl-name i{ font-style:normal; font-weight:300; color:#8F8F8F; }

/* The tag is its own thing here, not the home screen's .hc-tag: 8.625/100% at
   .604 tracking in a 4.089 box, two greys where the home screen has one. */
.gl-tag{
  display:flex; justify-content:center; align-items:center; gap:2.453px;
  padding:4.089px 6.542px; border-radius:4.089px;
  background:#EFEFEF; color:#686868;
  font-size:8.625px; font-weight:600; line-height:100%;
  letter-spacing:.604px; text-transform:uppercase; white-space:nowrap;
}
.gl-tag-ico{ flex:none; width:9.8px; height:9.8px; color:#5C5C5C; }
.gl-tag.warm{ background:#FFE0D7; color:#FF3C02; }
.gl-tag.warm .gl-tag-ico{ color:#FF3C02; }

/* ---- the gallery's own header (Frame 1321315063, 696.16 x 78 at gap 6) ----
   A row at gap 12, vertically centred: the rail's Gallery tile, the word
   Gallery at 34/100%, the grey "Explore:" at 15.063/153%, then the two pills
   in their own 429-wide row at gap 10.245. Under it, 6px down, the grey line
   of copy stretched to the header's width. All figures are the file's. */
.gallery-head{
  display:flex; flex-direction:column; align-items:flex-start; gap:6px;
  /* 696.16 is the frame's width, measured around the title when it read
     `Gallery`. It now reads `Service Gallery`, which is wider than the box,
     so the frame's number becomes the floor rather than the whole rule: the
     row is flex with every child flex:none, and a fixed width would simply
     let it spill. Everything inside keeps its own Figma numbers. */
  width:fit-content; min-width:696.16px; max-width:100%; margin-top:104px;
}
.gh-row{ display:flex; align-items:center; gap:12px; align-self:stretch; }
.gh-ico{ flex:none; display:flex; width:49px; height:49px; }
.gh-ico img{ width:100%; height:100%; display:block; }
.gh-title{
  flex:none; font-size:34px; font-weight:400; line-height:100%;
  color:#000; text-align:center;
}
.gh-explore{
  flex:none; font-size:15.063px; font-weight:400; line-height:153%;
  color:#8A8A8A;
}
.gh-pills{ display:flex; align-items:center; gap:10.245px; }
.gh-pill{
  display:flex; justify-content:center; align-items:center; gap:2.561px;
  padding:13.66px 19.637px; box-sizing:border-box;
  border-radius:7.684px; border:0.342px solid #CECECE;
  background:rgba(228, 228, 228, 0.27);
  font-size:19.97px; font-weight:300; line-height:100%;
  color:#979797; text-align:center; text-decoration:none;
  white-space:nowrap;
  transition:border-color .15s, background-color .15s, color .15s;
}
.gh-pill:hover{ color:#5A5A5A; border-color:#B9B9B9; }
.gh-pill.on{
  border-color:#AEBEFF; background:rgba(223, 229, 255, 0.43);
  color:#17317A; text-decoration:underline; text-underline-offset:2px;
}
.gh-pill.on:hover{ color:#17317A; border-color:#AEBEFF; }
.gh-line{
  align-self:stretch; margin:0;
  font-size:15.063px; font-weight:400; line-height:153%; color:#8A8A8A;
}

.gallery-body{
  /* The catalogue is the one full-bleed block on this screen: in the frame it
     runs 1877 of the 1920, where the link row above it is a centred 1001. So
     it takes what the rail leaves, less the 45 gutter the New Design Theme
     button sits on. */
  width:100%; padding-inline:45px; box-sizing:border-box; margin-top:68px;
}

/* ---- the bar over the catalogue ----
   The button, then the field taking the rest. Both 45.55 tall, which is the
   button's own height in the frame. */
.gt-bar{ display:flex; align-items:center; gap:18px; margin-bottom:36px; }

/* Figma's Frame 1321314892, to the decimal. */
.gt-new{
  display:inline-flex; justify-content:center; align-items:center; gap:4.565px;
  padding:15.05px 25.53px; border:0; border-radius:57.79px;
  background:#CCE2FF; color:#0A2250;
  font-family:inherit; font-size:12.014px; font-weight:400; line-height:100%;
  text-align:center; white-space:nowrap; cursor:pointer;
  transition:background-color .15s, transform .2s;
}
.gt-new:hover{ background:#B9D8FF; }
.gt-new:active{ transform:scale(.985); }
.gt-new-ico{ width:15.45px; height:15.45px; stroke-width:2; }

.gt-search{
  flex:1; min-width:0; display:flex; align-items:center; gap:10px;
  height:45.55px; padding:0 18px; box-sizing:border-box;
  border:1px solid #D9D9D9; border-radius:10px; background:#FFF;
  transition:border-color .15s, box-shadow .15s;
}
.gt-search:focus-within{ border-color:#AEBEFF; box-shadow:0 0 0 3px rgba(223, 229, 255, .6); }
.gt-search-ico{ flex:none; width:18px; height:18px; color:#8A8A8A; }
.gt-search input{
  flex:1; min-width:0; border:0; outline:0; background:transparent;
  font:inherit; font-size:15px; color:#2A2A2A;
}
.gt-search input::placeholder{ color:#8A8A8A; }
.gt-search input::-webkit-search-cancel-button{ cursor:pointer; }

/* ---- the catalogue ----
   Six columns at the frame's width, fewer as the window narrows: 285 wide at
   gap 27 is what the frame draws, and minmax keeps that count without pinning
   the screen to 1920. */
.gt-grid{
  display:grid; grid-template-columns:repeat(auto-fill, minmax(258px, 1fr));
  gap:27px;
}
.gt-none{
  margin:0; padding:40px 0; text-align:center;
  font-size:15px; color:#8A8A8A;
}

.gt-card{
  display:flex; flex-direction:column; margin:0; padding:0;
  border-radius:12px; background:#FFF; overflow:hidden;
  box-shadow:0 0 0 1px #ECECEC, 0 1px 3px rgba(0, 0, 0, .05);
  transition:transform .26s cubic-bezier(.22,.61,.36,1), box-shadow .26s ease;
}
/* A form is display:flex here, which outranks the browser's own rule for
   [hidden] — so a card the search filtered out would stay on screen. */
.gt-card[hidden]{ display:none; }
.gt-card:hover{
  transform:scale(1.01);
  box-shadow:0 0 0 1px #E0E0E0, 0 10px 26px rgba(0, 0, 0, .13);
}

/* The stage is the theme's neutral, or its cover picture where it has one.
   The sheet on it is the theme's ground, and everything written on the sheet
   is the theme's ink and its two faces — so the card is the theme rather than
   an illustration of it. */
.gt-stage{
  position:relative; display:block; width:100%; aspect-ratio:285 / 158;
  padding:15px; box-sizing:border-box; border:0;
  background:var(--dc-neutral, #EDEDED); cursor:pointer; overflow:hidden;
}
/* A stage with a picture gives it more room to be seen: on a flat theme the
   mat is only there to separate the sheet from the card, but here it is the
   cover the proposal will actually wear. */
.gt-stage.has-pic{ padding:24px 26px; }
.gt-pic{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; display:block;
}
.gt-sheet{
  position:relative; display:flex; flex-direction:column; justify-content:center;
  gap:5px; width:100%; height:100%; padding:14px 16px; box-sizing:border-box;
  border-radius:9px; background:var(--dc-ground, #FFF); text-align:start;
  box-shadow:0 1px 4px rgba(0, 0, 0, .07);
}
.gt-title{
  display:block; font-family:var(--dc-font, inherit);
  font-size:25px; font-weight:600; line-height:1.08;
  color:var(--dc-ink, #000);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.gt-body{
  display:block; font-family:var(--dc-font2, inherit);
  font-size:12.5px; font-weight:400; line-height:1.3;
  color:var(--dc-ink, #000);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
/* The muting sits on the words rather than on the line, so the link keeps the
   full strength of the colour it was picked for. */
.gt-body-t{ opacity:.72; }
.gt-link{
  color:var(--dc-link, var(--dc-ink, #000)); font-weight:600;
  text-decoration:underline; text-underline-offset:2px;
}

.gt-foot{
  display:flex; align-items:center; justify-content:space-between; gap:8px;
  padding:11px 12px 11px 14px; background:#FFF;
}
.gt-name{
  min-width:0; display:flex; align-items:center;
  font-size:14px; font-weight:400; color:#1A1A1A;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.gt-mine{
  flex:none; margin-inline-start:8px; padding:2px 7px; border-radius:20px;
  background:#DFE5FF; color:#17317A;
  font-size:9.5px; font-weight:500; letter-spacing:.4px; text-transform:uppercase;
}
.gt-more{
  flex:none; display:flex; align-items:center; justify-content:center;
  width:26px; height:26px; padding:0; border:0; border-radius:6px;
  background:transparent; color:#9A9A9A; cursor:pointer;
  transition:background-color .15s, color .15s;
}
.gt-more:hover, .gt-more[aria-expanded="true"]{ background:#F1F1F1; color:#2A2A2A; }
.gt-more-ico{ width:16px; height:16px; }

/* One menu for the whole grid, moved to whichever card asked for it — a menu
   per card would be twenty-five identical popups in the page. */
.gt-menu{
  position:absolute; z-index:40; min-width:214px; padding:6px;
  border:1px solid #E4E4E4; border-radius:10px; background:#FFF;
  box-shadow:0 12px 30px rgba(0, 0, 0, .15);
}
.gt-menu[hidden]{ display:none; }
.gt-mi{
  display:block; width:100%; padding:9px 10px; border:0; border-radius:7px;
  background:transparent; font:inherit; font-size:13.5px; color:#2A2A2A;
  text-align:start; cursor:pointer;
}
.gt-mi:hover{ background:#F1F4FF; }
.gt-mi.danger{ color:#B3372B; }
.gt-mi.danger:hover{ background:#FCEEEB; }
.gt-mi[hidden]{ display:none; }

/* Read by a screen reader, drawn for nobody. */
.gt-sr{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}

@media (max-width: 900px){
  .gallery-body{ padding-inline:24px; }
  .gt-bar{ flex-wrap:wrap; }
  .gt-search{ flex-basis:100%; }
}

.gallery-wait{
  margin:0; padding:48px 32px; text-align:center;
  border:1px dashed #DCDCD6; border-radius:12px;
  font-size:15px; line-height:1.6; color:#8A8A8A;
}
.gallery-wait code{
  font-family:ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:13px; color:#5A5A5A;
}


/* ---------------------------------------------------------------------------
   The two browsers behind "Show all".

   Not a screen of their own: the shelf's tab at full width, reached from one
   build and returning to it. So the cards are the shelf's cards and the
   gallery's cards exactly as they are — only the column they sat in is gone,
   which is the whole reason to come here.
   --------------------------------------------------------------------------- */
body.cb-panel{ background:#fff; }
body.cb-panel .admin-main{ max-width:none; padding:0; min-height:100vh; }
.cb{ padding:64px 45px 96px; }

.cb-head{ margin-bottom:34px; }
.cb-back{
  display:inline-flex; align-items:center; gap:6px;
  font-size:13px; color:#5A5A5A; text-decoration:none;
  transition:color .15s;
}
.cb-back:hover{ color:#17317A; }
/* The arrow is the one lucide file already loaded, turned round. */
.cb-back-ico{ width:14px; height:14px; transform:scaleX(-1); }
.cb-title{
  margin:16px 0 0; font-size:34px; font-weight:400; line-height:100%; color:#000;
}
.cb-sub{
  margin:9px 0 0; max-width:780px;
  font-size:15.063px; font-weight:400; line-height:153%; color:#8A8A8A;
}
.cb-bar{ display:flex; align-items:center; gap:18px; max-width:900px; margin-bottom:30px; }
.cb-grid{
  display:grid; grid-template-columns:repeat(auto-fill, minmax(258px, 1fr));
  gap:27px; align-items:start;
}
/* The Components browser is the one page with room to spare: a card half
   again as wide as the shelf's, carrying the detailed drawing. 336 = 258 + 30%. */
.cb-grid.cb-wide{ grid-template-columns:repeat(auto-fill, minmax(336px, 1fr)); gap:32px; }
.cb-wide .be-slidecard{ padding:11px; border-radius:10px; }
.cb-wide .be-cname{ font-size:21px; }
.cb-wide .be-cpurpose{ font-size:15px; }
.cb-wide .be-cart{ margin-top:8px; }
.cb-item{ margin:0; }
.cb-item[hidden]{ display:none; }
.cb-item .be-slidecard{ width:100%; }
/* The diagram fills the card here rather than taking the shelf's fixed box. */
.cb-item .be-cart{ width:100%; height:auto; aspect-ratio:240 / 135; }
.cb-item .be-shelfcard{
  gap:3px; cursor:pointer;
  transition:transform .26s cubic-bezier(.22,.61,.36,1),
             box-shadow .26s ease, border-color .15s ease;
}
.cb-item:not(.is-full) .be-shelfcard:hover{
  transform:scale(1.03);
  box-shadow:0 8px 22px 0 rgba(0, 0, 0, .12);
}
.cb-item.is-full .be-shelfcard{ opacity:.55; cursor:default; }
.cb-note{ display:block; margin-top:8px; font-size:11px; color:#8A8A8A; }
.cb-none{
  margin:0; padding:40px 0; text-align:center; font-size:15px; color:#8A8A8A;
}
.cb-none[hidden]{ display:none; }
/* Same lift as the components beside it, which is three times the gallery's —
   there a card is the width of a column, here it is one of four. */
.cb-theme:not(.on):hover{
  transform:scale(1.03);
  box-shadow:0 0 0 1px #E0E0E0, 0 10px 26px rgba(0, 0, 0, .13);
}
/* The theme this build is already in: shown, named, and not pickable. */
.cb-theme.on{ box-shadow:0 0 0 2px #AEBEFF, 0 1px 3px rgba(0, 0, 0, .05); }
.cb-theme.on .gt-stage{ cursor:default; }
.cb-theme.on:hover{ transform:none; box-shadow:0 0 0 2px #AEBEFF, 0 1px 3px rgba(0, 0, 0, .05); }

/* ---------- left rail (grey menu) ----------
   Measurements from the Figma "Change of side bar" section: 59px rail,
   47px items, 38px icons (pill background baked into the SVGs), 11px
   labels, no separators. The star-man logo lives at the top of the rail
   now, so there's no separate header pushing the first item down.

   The frame drew it 59 wide with 11px labels, which could not hold the
   longest word: at 11px in 59 they touched both edges. It went to 79 with
   12px labels. Two of the names are two words now (Service Gallery, My
   Services) and those break onto two lines, so the widest thing the rail has
   to hold is one word again and it has come back to 74.

   Measured in Parkinsans rather than estimated: at 12px "Proposals" is 59px
   and "Pre-sales" 57, so at 11px they are 54.1 and 52.2. An item is
   74 - 12 = 62 wide, which leaves the widest of them 4px inside its own item
   and 10px clear of the rail's edge — the same room the 79 held, for a word
   one size smaller. Everything else on the page follows `--rail-w`, so this
   is the one value. */
:root{ --rail-w:74px; }

.side-rail{
  position:fixed; top:0; left:0; bottom:0; width:var(--rail-w); z-index:60;
  background:#ECECEC; color:#111; border-right:1px solid #AAAAAA;
  display:flex; flex-direction:column; justify-content:space-between;
  padding:20px 6px 28px;
}
.rail-top{ display:flex; flex-direction:column; gap:40px; }
/* 12px of breathing room under the logo, plus the 50px the nav items were
   asked to drop by — the logo itself stays where it was. */
.rail-logo{ margin-bottom:62px; }
.rail-item{
  /* 3, not the 7 it was: the label belongs to the mark above it, and at 7 the
     two read as two things stacked. */
  display:flex; flex-direction:column; align-items:center; gap:3px;
  /* The rail's full inner width, less its 6px of padding either side, so a
     label has every pixel the rail has to give. Derived rather than typed:
     the next person to change the rail's width should not have to find this. */
  width:calc(var(--rail-w) - 12px); text-decoration:none; color:#111;
  background:none; border:0; padding:0; cursor:pointer; font-family:inherit;
}

/* ---- the nav-bar helpers ----------------------------------------------
   The card that explains a rail item on hover, and shows the user their own
   screen behind it. Every number below is from the Figma frame "Nav bar
   menue info helpers" (224-1142); the behaviour is in admin.js and the copy
   in lib/nav-help.mjs.

   The frame draws the card 253 wide overall: a 198px body with the pointer
   reaching 55px further left, its tip landing on the rail. That split is
   what --nh-tail-w and --nh-card-w are.

   Typeface: the frame specifies Google Sans Flex, which this project does
   not load — the admin is Google Sans throughout (HANDOVER §6), so these
   inherit it rather than pulling in a second family for one card. */
.rail-slot{ position:relative; display:block; }

.nav-help{
  --nh-card-w:198px;
  --nh-tail-w:55px;
  /* How far the card's middle sits from the rail item's middle. admin.js
     writes this when the card would otherwise run off the top or bottom of
     the window; the tail slides the other way to stay on the item. */
  --nh-shift:0px;

  position:absolute; z-index:70;
  inset-inline-start:calc(100% + 9px);
  top:50%;
  width:calc(var(--nh-tail-w) + var(--nh-card-w));
  transform:translateY(calc(-50% + var(--nh-shift)));
  /* One shadow over the card and its pointer together, as in the frame —
     a shadow on each would show the seam between them. */
  filter:drop-shadow(6px 5px 0 rgba(0,0,0,.08));
  opacity:0; visibility:hidden;
  transition:opacity .14s ease, visibility 0s linear .14s;
}
.nav-help.on{ opacity:1; visibility:visible; transition-delay:0s; }

/* The pointer. Its tip is the bottom-left of its own box, so it is placed
   tip-last: the tip lands on the middle of the card, which is the middle of
   the rail item, and --nh-shift moves it back by however far the card was
   pushed to stay on screen. Measured from 50% rather than from the card's
   height so that a card grown by longer copy still points in the right
   place. */
.nh-tail{
  position:absolute; inset-inline-start:0;
  top:calc(50% - 127.782px - var(--nh-shift));
  width:56.5px; height:127.782px;
  color:#0C1947; line-height:0; pointer-events:none;
}
.nh-tail svg{ display:block; width:56.5px; height:127.782px; }

/* The card lays its parts out in flow rather than at the fixed offsets the
   frame happens to use, because the copy is not fixed: the frame's Builds
   text is three lines of title and two of body, and pinning the picture at
   the y it lands on there put longer copy straight through it. The gaps
   below are that frame's gaps, measured between the visible edges.

   The height is left to the content rather than fixed at the frame's 294px.
   Held at 294 the two shortest helpers ended with dead navy below the link —
   35px under Home, 154px under Gallery, which has no picture at all — and
   that reads as a mistake rather than as breathing room. Hugging the content
   means every card ends the same distance below its last line, whatever it
   holds. Builds, the one the frame draws, still comes out at exactly 294. */
.nh-card{
  position:relative;
  width:var(--nh-card-w);
  margin-inline-start:var(--nh-tail-w);
  background:#0C1947; border-radius:4px;
  padding:22px 20px 9px;
  box-sizing:border-box;
  display:flex; flex-direction:column; align-items:flex-start;
}

.nh-x{
  position:absolute; top:8px; inset-inline-end:10px;
  width:12px; height:12px; padding:0;
  background:none; border:0; cursor:pointer; color:#FFF;
  display:flex; align-items:center; justify-content:center;
  opacity:.75; transition:opacity .15s;
}
.nh-x:hover{ opacity:1; }
.nh-x svg{ width:12px; height:12px; display:block; stroke-width:2.6; }

/* The title clears the x rather than running under it. */
.nh-title{
  margin:0; padding-inline-end:14px;
  color:#FFF; font-size:21px; font-weight:400;
  line-height:126%; letter-spacing:0.42px;
}
.nh-body{
  margin:7px 0 0;
  color:#DFEEFD; font-size:11px; font-weight:400;
  line-height:153%; letter-spacing:0.55px;
}

/* The picture: a real admin page, scaled. 1600 x 1005 of page shown at
   152.2 x 95.61 is a scale of 0.095125. It takes no pointer events, so the
   rail inside the preview can never be hovered into showing its own
   helpers. */
.nh-shot{
  margin:16px 0 0; align-self:center; flex:none;
  width:160px; height:103.415px;
  border:0.7px solid #205283; border-radius:7.805px;
  box-sizing:border-box;
  display:flex; align-items:center; justify-content:center;
}
.nh-frame{
  width:152.2px; height:95.61px;
  border-radius:3.9px; overflow:hidden; background:#FFF;
  position:relative;
}
.nh-frame iframe{
  position:absolute; top:0; inset-inline-start:0;
  width:1600px; height:1005px; border:0;
  transform:scale(0.095125); transform-origin:top left;
  pointer-events:none;
}

/* Always on the card's last line, however tall the card has grown. */
.nh-off{
  margin:10px auto 0; align-self:center; flex:none;
  padding:0; height:14px;
  background:none; border:0; cursor:pointer; font-family:inherit;
  color:#71767C; font-size:9px; font-weight:400;
  line-height:153%; letter-spacing:0.63px; white-space:nowrap;
  transition:color .15s;
}
.nh-off:hover{ color:#DFEEFD; }

@media (prefers-reduced-motion: reduce){
  .nav-help{ transition-duration:0s; }
}
/* Three looks, stacked and crossfaded: no pill at rest, a grey pill under the
   pointer, a blue pill on the page you are actually on. Each look is a whole
   pre-baked SVG, so this only changes which one is showing. The page you are on
   keeps its blue pill even while hovered — hovering it tells you nothing you
   don't already know. */
.rail-ico{ display:block; width:38px; height:38px; position:relative; }
.rail-ico img{ width:38px; height:38px; display:block; transition:opacity .15s; }
.rail-ico .ico-hover,
.rail-ico .ico-on{ position:absolute; inset:0; opacity:0; }

.rail-item:hover .ico-hover{ opacity:1; }
.rail-item:hover .ico-rest{ opacity:0; }

.rail-item.on .ico-on{ opacity:1; }
.rail-item.on .ico-rest,
.rail-item.on .ico-hover,
.rail-item.on:hover .ico-hover{ opacity:0; }

.rail-item span{
  /* 11px, not the 12 it was: the two-word names could not hold 12 in this
     rail. `pre-line` so the newline the two `stack` items carry (see
     render-admin.mjs) breaks them onto two lines, and so anything else that
     outgrows its item still wraps rather than spilling. line-height is 1.15
     rather than 1 so the two lines do not touch. */
  font-size:11px; line-height:1.15; letter-spacing:0; font-weight:400;
  color:#333333; text-align:center;
  white-space:pre-line; overflow-wrap:break-word;
}
/* Designed, in the rail, but with no screen behind it yet: it looks like the
   others and answers to hover, it just does not go anywhere. */
.rail-item.is-soon{ cursor:default; }
.rail-logout{ margin:0; display:flex; justify-content:center; }

/* ---------- header ----------
   Empty — the brand/logo moved into the rail (see .rail-logo above). It is
   still in the DOM, at zero height, because the build editor anchors its own
   logo to it (see body.theme-panel:has(.be-stage) .admin-header). Nothing
   else needs it. */
.admin-header{
  display:flex; align-items:flex-start;
  background:none; border:0; margin:0;
  padding:0; height:0; overflow:hidden;
}

/* The rail is fixed, so the page is inset by its width and the content
   stays centred in the space that's left, as it was before. */
body{ padding-left:var(--rail-w); }
body.login-body{ padding-left:0; }

.admin-main{ max-width:1180px; margin:0 auto; padding:100px 32px 90px; }

/* ---------- page head ---------- */
.page-head{
  display:flex; align-items:flex-start; justify-content:space-between;
  gap:24px; margin-bottom:28px;
}
/* Page title — Google Sans (Flex) Regular 33.85px, black (from Figma). */
.page-head h1{
  font-family:'Parkinsans', 'Google Sans', Arial, sans-serif;
  font-size:33.85px; font-weight:400; color:#000;
  margin:0 0 6px; letter-spacing:0;
}
.page-head .sub{ margin:0; color:var(--muted); font-size:15px; }
/* Orders/Proposals title gets its matching sidebar icon beside it. */
.page-title-row{ display:flex; align-items:center; gap:12px; margin-bottom:6px; }
.page-title-row h1{ margin:0; }
.page-title-row img{ width:50px; height:50px; display:block; }
.back-link{ display:inline-block; font-size:14px; color:var(--muted); text-decoration:none; margin-bottom:10px; }
.back-link:hover{ color:#111; }
.head-actions{ display:flex; gap:10px; flex:none; }

/* ---------- buttons ---------- */
.btn{
  display:inline-flex; align-items:center; justify-content:center;
  vertical-align:middle; box-sizing:border-box; flex:none;
  font-family:inherit; font-size:15px; line-height:1;
  padding:13px 24px; border-radius:100px;
  border:1px solid transparent; cursor:pointer; text-decoration:none;
  transition:background .15s, border-color .15s;
  white-space:nowrap;
}
.btn.primary{ background:var(--green); color:#fff; }
/* Hover on system buttons inverts to black with white text. */
.btn.primary:hover{ background:#111; color:#fff; border-color:#111; }
.btn.ghost{ background:#fff; border-color:#cfcfcf; color:#111; }
/* Ghost buttons (Filter, Duplicate, Edit, Copy link, and the mail/trash/eye
   icon chips) light up in OP Button colours on hover instead of inverting
   to black. */
.btn.ghost:hover{ background:#CCE2FF; color:var(--green); border-color:#B6D6FF; }
.btn.danger{ background:#fff; border-color:#f0b4b0; color:var(--red); }
.btn.danger:hover{ background:#fff2f1; border-color:var(--red); }
.btn.small{ font-size:13px; padding:0 16px; height:33px; }
.btn.icon-only{ padding:0 13px; color:#111; }
.btn.icon-only svg{ display:block; width:15px; height:15px; }
.btn.full-w{ width:100%; margin-bottom:10px; }

/* "OP Button" — light-blue pill style, from a Figma dev-mode sample:
   height 44px, padding 8.22px 13.44px, gap 5.32px, radius 20.729px,
   border 1px solid #B6D6FF, background #CCE2FF. Applied one button at a
   time as Tomer points them out — not wired to any button yet. */
.btn.op-btn{
  height:44px; min-width:141px; padding:8.22px 24px; gap:5.32px;
  border-radius:20.729px;
  border:1px solid #B6D6FF; background:#CCE2FF; color:var(--green);
}
.btn.op-btn:hover{ background:#b9d7fb; border-color:#93c0f5; }

/* `.btn` sets display:inline-flex, which would otherwise beat the browser's
   own [hidden] rule and leave a "hidden" button on screen. */
.btn[hidden]{ display:none; }

/* "Saved" — the settled state after a save went through: same pill, flat grey,
   not pressable. */
.btn.save-done{
  height:44px; min-width:141px; padding:8.22px 24px; gap:5.32px;
  border-radius:20.729px; border:1px solid transparent;
  background:#E8E8E8; color:#8A8A8A; cursor:default;
}
.btn.save-done:hover{ background:#E8E8E8; border-color:transparent; color:#8A8A8A; }
.btn.save-done svg{ width:16px; height:16px; display:block; }

/* ---------- stats ---------- */
.stat-row{ display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin-bottom:22px; }
.stat{
  background:#fff; border:.3px solid var(--frame-border); border-radius:10.71px;
  box-shadow:var(--frame-shadow);
  padding:18px 22px; display:flex; flex-direction:column; gap:4px;
}
.stat-n{ font-size:30px; font-weight:500; }
.stat-l{ font-size:13px; color:var(--muted); }

/* ---------- filters ---------- */
.filter-bar{ display:flex; gap:10px; margin-bottom:18px; flex-wrap:wrap; }
.filter-bar input, .filter-bar select{
  font-family:inherit; font-size:15px;
  padding:12px 18px; border-radius:100px;
  border:1px solid #d8d8d8; background:#fff; outline:none;
}
.filter-bar input{ min-width:240px; }
.filter-bar input.date-find{ min-width:0; width:130px; text-align:center; }
.filter-bar input:focus, .filter-bar select:focus{ border-color:#111; }
.is-hidden{ display:none !important; }
/* Active filters glow green so you can see what's narrowing the list. */
.filter-bar input.active{ background:var(--green-pill); border-color:var(--green-border); }
.sel-btn.active{ background:var(--green-pill); border-color:var(--green-border); }
/* Clear only appears when a filter is on — invert it so it stands out. */
.filter-bar .clear-btn{ background:#111; color:#fff; border-color:#111; }
.filter-bar .clear-btn:hover{ background:#000; border-color:#000; }

/* ---------- saved / frozen order state ---------- */
.saved-bar{ display:flex; align-items:center; gap:12px; margin-bottom:18px; }
.saved-pill{
  display:inline-flex; align-items:center; gap:8px;
  background:#e6e6e3; color:#8a8a8a;
  padding:9px 18px; border-radius:100px; font-size:15px;
}
.saved-go{
  display:inline-flex; align-items:center; gap:8px;
  background:#111; color:#fff; border-color:#111;
}
.saved-go:hover{ background:#000; border-color:#000; }

.form-card.is-frozen{ position:relative; }
.form-card.is-frozen h2,
.form-card.is-frozen .hint,
.form-card.is-frozen .field > span{ color:#b4b4b4; }
.form-card.is-frozen .field input,
.form-card.is-frozen .field textarea,
.form-card.is-frozen .price-row input,
.form-card.is-frozen .sel-btn{
  color:#a6a6a6; background:#f4f4f2; border-color:#e6e6e4;
}
.form-card.is-frozen .btn.primary{ background:#e0e0dd; color:#9a9a9a; }
.freeze-veil{
  position:absolute; inset:0; z-index:75;
  cursor:pointer; border-radius:inherit; background:transparent;
}
/* Custom dropdown (replaces the native OS list on filter selects) */
.sel{ position:relative; display:inline-block; }
.sel .sel-native{ position:absolute; opacity:0; width:100%; height:100%; pointer-events:none; }
.sel-btn{
  font-family:inherit; font-size:15px; color:#111; cursor:pointer;
  padding:12px 46px 12px 18px; border-radius:100px;
  border:1px solid #d8d8d8; background:#fff; outline:none;
  display:inline-flex; align-items:center; gap:10px; white-space:nowrap;
}
.sel.open .sel-btn, .sel-btn:hover{ border-color:#111; }
.sel-chev{
  position:absolute; right:20px; top:50%; width:12px; height:8px; transform:translateY(-50%);
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23111' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center;
  transition:transform .15s;
}
.sel.open .sel-chev{ transform:translateY(-50%) rotate(180deg); }
.sel-list{
  position:absolute; top:calc(100% + 8px); left:0; z-index:60;
  background:#fff; border:1px solid var(--line); border-radius:9.52px;
  box-shadow:0 12px 30px rgba(0,0,0,.12);
  padding:8px; min-width:100%; white-space:nowrap;
}
.sel-list[hidden]{ display:none; }
.sel-opt{
  font-family:inherit; font-size:15px; color:#111; cursor:pointer;
  padding:11px 16px; border-radius:5.95px;
}
.sel-opt:hover{ background:#f4f4f2; }
.sel-opt.on{ background:var(--green-pill); }

/* Form selects keep their rounded-rectangle shape and full width. */
.field .sel{ display:block; width:100%; }
.field .sel-btn{
  width:100%; border-radius:8.33px; font-size:16px; padding:13px 46px 13px 18px;
  border:.3px solid var(--frame-border); box-shadow:var(--frame-shadow);
}
.price-row .sel{ flex:none; width:auto; }
.price-row .sel-btn{ width:auto; min-width:112px; }

/* ---------- cards ---------- */
.card{
  background:#fff; border:.3px solid var(--frame-border);
  border-radius:13.09px; padding:26px 28px;
  box-shadow:var(--frame-shadow);
}
.table-card{ padding:0; overflow:hidden; }
.note-card{ margin-top:20px; }
.note-card h2{ font-size:17px; font-weight:500; margin:0 0 8px; }
.note-card p{ margin:0; color:var(--muted); font-size:15px; line-height:1.6; }

/* ---------- order cards ---------- */
.order-list{ display:flex; flex-direction:column; gap:14px; }
.order-card{
  display:flex; align-items:center; gap:20px;
  background:#fff; border:.3px solid var(--frame-border); border-radius:11.9px;
  padding:16px 20px; cursor:pointer;
  box-shadow:var(--frame-shadow);
  transition:box-shadow .15s, border-color .15s;
}
.order-card:hover{ box-shadow:0 6px 20px rgba(0,0,0,.07); border-color:#dcdcd6; }

.order-thumb{ display:flex; align-items:center; gap:8px; flex:none; }
/* A thumbnail grows 3% under the cursor and settles back. The picture is the
   thing being offered, so it is the thing that answers — slow enough out of
   the ease to read as a movement rather than a jump, and on `transform`, which
   costs the page nothing. It was 7%, then 5%: on a 298px card both were a
   lurch, and the point is to acknowledge the cursor, not to perform. */
.thumb-frame{
  position:relative; width:160px; height:100px; flex:none;
  border:.3px solid var(--frame-border); border-radius:5.95px; overflow:hidden; background:#fff;
  box-shadow:var(--frame-shadow);
  transition:transform .26s cubic-bezier(.22,.61,.36,1), box-shadow .26s ease;
}
.thumb-frame:hover{ transform:scale(1.03); }
/* Someone who has asked their system not to animate gets the size change
   without the travel. */
@media (prefers-reduced-motion: reduce){
  .thumb-frame{ transition:none; }
}
.thumb-frame iframe{
  position:absolute; top:5px; left:0; width:1920px; height:1080px; border:0;
  transform:scale(0.08333); transform-origin:top left; pointer-events:none;
}
.thumb-page{
  position:absolute; bottom:4px; right:5px; z-index:2;
  background:rgba(0,0,0,.55); color:#fff; font-size:10px;
  padding:2px 6px; border-radius:100px;
}
.thumb-arrow{
  flex:none; width:28px; height:28px; border-radius:50%;
  border:1px solid #d8d8d8; background:#fff; color:#111; cursor:pointer;
  display:flex; align-items:center; justify-content:center; padding:0;
}
.thumb-arrow:hover{ border-color:#111; }
.thumb-arrow svg{ width:16px; height:16px; }

/* Larger, centered order preview inside the edit page side column. The card
   sits on a slightly deeper grey so the white thumbnail pops, with a crisp
   (unblurred) drop shadow under the frame. */
.preview-card{ background:#e7e7e3; border:1px solid #d4d4cf; }
.preview-card h2{ margin-bottom:16px; }
.preview-thumb{ justify-content:center; }
.preview-thumb .thumb-frame{
  width:340px; height:191px; max-width:100%;
  box-shadow:3px 3px 0 rgba(0,0,0,.10);
}
.preview-thumb .thumb-frame iframe{ top:0; transform:scale(0.177); }

.order-body{ flex:1; min-width:0; display:flex; flex-direction:column; gap:20px; }
.order-main{ display:flex; align-items:center; gap:0; flex-wrap:wrap; }
.order-main .client-name{ font-size:20px; color:#111; }
/* Notice pill — New / Duplicated / Build Change. Straight from the Figma
   badge frame: #DBEEFF fill, 4px radius, lucide/bell at 14.56px, and #00359A
   text at 14px with 0.42px letter-spacing. The asymmetric padding is the
   design's own — the bell sits a touch closer to the left edge. */
.dup-badge{
  display:inline-flex; align-items:center; gap:8.32px;
  padding:8.22px 13.44px 8.22px 11.44px;
  border-radius:4px; background:#DBEEFF;
  font-family:'Parkinsans', 'Google Sans', Arial, sans-serif;
  font-size:14px; font-weight:400; line-height:1; letter-spacing:.42px;
  color:#00359A; white-space:nowrap;
  vertical-align:middle; margin-right:10px;
}
.dup-badge svg{ display:block; flex:none; }
.order-main .client-sep{ font-size:20px; color:var(--muted); margin:0 12px; }

/* Type/Date/Price/Status on the left, the actions on the right, both on one
   row — which is what lets the name above run the full width of the card. */
.order-foot{ display:flex; align-items:center; gap:20px; }
.order-meta{ display:flex; gap:31px; flex-wrap:wrap; align-items:center; flex:1 1 auto; min-width:0; }
.meta-item{ display:flex; flex-direction:column; gap:2px; font-size:15px; }
.meta-label{ font-size:11px; letter-spacing:.4px; text-transform:uppercase; color:var(--muted); }

/* A meta item is a label stacked over its value, so centring the buttons on
   the whole item leaves them sitting between the two lines. They belong on the
   value line — level with "₪25,000" and "Draft" — which is 7px lower:
   the 12px label plus the stack's 2px gap puts the value's top at 14px and its
   centre at 22.5px, against the item's own centre of 15.5px. Nudged with
   position rather than margin so the card's height and the meta row itself
   stay exactly where they are. */
.order-actions{
  display:flex; align-items:center; gap:10px; flex:none; margin-inline-start:auto;
  position:relative; top:7px;
}
.order-actions form{ margin:0; }
.order-actions > *{ margin:0; }

/* Resting look of the action chips, straight from Figma dev mode:
     border-radius: 73.684px;  border: 0.3px solid #E4E4E4;
     background: #FFF;         box-shadow: 0 0 7.368px 0 rgba(0,0,0,0.10);
   The radius is far larger than any of these buttons, so it simply rounds the
   pills fully and the square ones into circles. Hover still wins over the
   border and fill (see .btn.ghost:hover) — only the resting state is set here. */
.order-actions .btn{
  border-radius:73.684px;
  border:0.3px solid #E4E4E4;
  background:#FFF;
  /* The spec's shadow, softened to .068 — 32% lighter than the 0.10 it
     specifies, over the same 7.368px spread. */
  box-shadow:0 0 7.368px 0 rgba(0,0,0,.068);
}

/* Mail, delete and preview are round, not oval: a square box at the pill
   buttons' own height, so the whole row still sits on one line. */
.order-actions .btn.small.icon-only{
  width:33px; height:33px; padding:0; border-radius:50%;
}

/* ---- what each action does, on hover ----
   Not from the Figma file — there is no tooltip in it — so it is kept as plain
   as possible: the same near-black the confirm buttons use, small text, and a
   short delay so it only appears when someone actually pauses.

   The box itself is built by admin.js and parented to the body, not drawn as a
   ::after on the button. It has to be: on the Builds cards `.build-scroll` is
   `overflow:hidden auto` and each card carries a `drop-shadow` filter whose
   region stops at its top edge, so a pseudo-element above the button is sliced
   in half there. Being in the body puts it outside every clipper, which is why
   it can sit above the button on every screen. Rows that opt in are listed in
   `ROWS` in admin.js. */
.ui-tip{
  position:fixed; z-index:80; pointer-events:none;
  padding:6px 10px; border-radius:6px;
  background:#111; color:#fff;
  font-family:inherit; font-size:12px; font-weight:400; line-height:1;
  letter-spacing:.2px; white-space:nowrap;
  opacity:0; transition:opacity .12s ease;
}
.ui-tip.on{ opacity:1; }
/* The caret is placed by script along the box's own edge, so it stays on the
   button even when the box has been pulled back inside the window. */
.ui-tip-caret{
  position:absolute; top:100%; margin-inline-start:-5px;
  border:5px solid transparent; border-bottom-width:0; border-top-color:#111;
}
.ui-tip.below .ui-tip-caret{
  top:auto; bottom:100%;
  border-top-width:0; border-bottom-width:5px;
  border-top-color:transparent; border-bottom-color:#111;
}

.empty-card{
  background:#fff; border:.3px solid var(--frame-border); border-radius:11.9px;
  padding:44px 20px; text-align:center; color:var(--muted);
  box-shadow:var(--frame-shadow);
}

@media (max-width: 900px){
  .order-card{ flex-wrap:wrap; }
  /* Too narrow for one row: the actions drop below the meta and keep right —
     and on their own line there is no value text to line up with, so the 7px
     nudge comes back off. */
  .order-foot{ flex-wrap:wrap; }
  .order-actions{ width:100%; justify-content:flex-end; top:0; }
}

/* ---------- table ---------- */
.orders-table{ width:100%; border-collapse:collapse; }
.orders-table th{
  text-align:left; font-size:12px; font-weight:500; letter-spacing:.4px;
  text-transform:uppercase; color:var(--muted);
  padding:16px 20px; border-bottom:1px solid var(--line);
}
.orders-table td{ padding:16px 20px; border-bottom:1px solid #f0f0f0; font-size:15px; vertical-align:middle; }
.orders-table tbody tr[data-href]{ cursor:pointer; }
.orders-table tbody tr[data-href]:hover{ background:#fafaf8; }
.orders-table tr:last-child td{ border-bottom:none; }
.client-name{ display:block; font-size:16px; }
.client-sub{ display:block; font-size:13px; color:var(--muted); margin-top:2px; }
.cell-date, .cell-price{ white-space:nowrap; }
/* Flex row rather than inline boxes: guarantees the icon button aligns with
   the text buttons regardless of their differing baselines. */
.cell-actions{ white-space:nowrap; }
.cell-actions{ display:flex; justify-content:flex-end; align-items:center; gap:6px; }
.cell-actions form{ margin:0; }
.empty-row td{ padding:44px 20px; text-align:center; color:var(--muted); }

.type-pill{
  display:inline-block; background:var(--gray-pill);
  padding:6px 14px; border-radius:100px; font-size:13px; white-space:nowrap;
}

.status{
  display:inline-block; padding:6px 14px; border-radius:100px;
  font-size:13px; white-space:nowrap;
}
.status.draft{ background:var(--gray-pill); color:#5a5a5a; }
.status.sent{ background:#e6f1fb; color:#185fa5; }
.status.viewed{ background:#faeeda; color:#854f0b; }
.status.signed{ background:var(--green-pill); color:var(--green); }
/* Pre-sale statuses. `confirmed` is the pre-sale's finish line, so it wears the
   same pill as a signed proposal; `answered` is the quoted-but-not-yet-agreed
   state in between, one step lighter. */
.status.answered{ background:#E9F1FB; color:#4A6FA8; }
.status.confirmed{ background:var(--green-pill); color:var(--green); }

/* ---------- forms ---------- */
.form-card h2{ font-size:18px; font-weight:500; margin:0 0 4px; }
.form-card h2 + .hint{ margin-top:0; }
.form-card h2:not(:first-child){ margin-top:32px; }
.hint{ color:var(--muted); font-size:14px; margin:0 0 18px; line-height:1.5; }

.field-grid{ display:grid; grid-template-columns:repeat(2,1fr); gap:16px; }
.field-wide{ grid-column:1 / -1; }
.presale-row{ margin-top:12px; padding:16px; }
/* The pre-sale build editor and the pre-sale page's Answers card.
   ⚠ Placeholder styling: no Figma frame exists for either. Colours are the
   admin's own blues (§6) so they sit with the rest until they are drawn. */
.ps-rough{ margin-top:-8px; }
.pse-facts{ display:grid; grid-template-columns:auto 1fr; gap:6px 12px; margin:0; font-size:13.5px; }
.pse-facts dt{ color:var(--muted); }
.pse-facts dd{ margin:0; }

/* The editor: screens | the client's screen | that screen's tools. The rail
   borrows the proposal editor's card language (white, hairline, the blue of
   the chosen one) rather than inventing one. */
body.pse2-page .admin-main{ max-width:none; }
.pse2-head{ display:flex; align-items:baseline; gap:16px; flex-wrap:wrap; }
.pse2-head h1{ margin:0; }
.pse2-state{ font-size:13px; color:var(--muted); min-width:80px; }
.pse2-open{ margin-left:auto; font-size:14px; }
.pse2{ display:grid; grid-template-columns:190px minmax(0,1fr) 340px; gap:20px; align-items:start; margin-top:8px; }
.pse2-rail{ position:sticky; top:16px; display:flex; flex-direction:column; gap:6px;
            max-height:calc(100vh - 32px); overflow:auto; padding:2px; }
.pse2-card{ position:relative; display:flex; gap:8px; align-items:baseline; width:100%; padding:10px 12px;
            border:1px solid #E4E4E4; border-radius:10px; background:#fff; font-size:13px; text-align:start;
            cursor:pointer; color:inherit; }
.pse2-card:hover{ border-color:#B6D6FF; }
.pse2-card[aria-current="step"]{ background:#D5E6FC; border-color:#B6D6FF; color:#17317A; }
.pse2-no{ flex:none; font-size:11.5px; color:var(--muted); min-width:16px; }
.pse2-title{ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.pse2-dot{ position:absolute; top:8px; right:8px; width:6px; height:6px; border-radius:50%; background:#2A5FC4; }
.pse2-stage{ position:sticky; top:16px; border:1px solid #E4E4E4; border-radius:14px; overflow:hidden; background:#fff; }
.pse2-stage iframe{ display:block; width:100%; height:calc(100vh - 120px); min-height:560px; border:0; }
.pse2-tools{ display:flex; flex-direction:column; gap:14px; }
.pse2-box{ padding:14px 16px; border:1px solid #E4E4E4; border-radius:12px; background:#fff; }
.pse2-box h3{ margin:0 0 6px; font-size:15px; }
.pse2-hint{ margin:0 0 10px; font-size:12.5px; line-height:1.45; color:var(--muted); }
.pse2-field{ display:flex; flex-direction:column; gap:4px; margin-bottom:10px; font-size:12.5px; color:var(--muted); }
.pse2-field input, .pse2-own input[type="text"], .pse2-own input[type="number"], .pse2-own select, .pse2-own textarea{
  width:100%; padding:8px 10px; border:1px solid #E4E4E4; border-radius:8px; font-size:13.5px; color:#000; background:#fff; }
.pse2-field output{ float:right; color:#17317A; font-weight:500; }
.pse2-pair{ display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.pse2-list{ display:flex; flex-direction:column; max-height:340px; overflow:auto; margin:0 -4px; padding:0 4px; }
.pse2-toggle{ display:flex; align-items:center; gap:10px; padding:7px 0; border-bottom:1px solid #F0F0F0; cursor:pointer; }
.pse2-toggle span{ flex:1; display:flex; flex-direction:column; font-size:13.5px; }
.pse2-toggle b{ font-weight:400; }
.pse2-toggle small{ font-size:11.5px; color:var(--muted); }
.pse2-toggle input{ position:absolute; opacity:0; pointer-events:none; }
.pse2-toggle i{ flex:none; width:32px; height:18px; border-radius:999px; background:#DADADA; position:relative; transition:background .15s; }
.pse2-toggle i::after{ content:""; position:absolute; top:2px; left:2px; width:14px; height:14px; border-radius:50%; background:#fff; transition:left .15s; }
.pse2-toggle input:checked + i{ background:#2A5FC4; }
.pse2-toggle input:checked + i::after{ left:16px; }
.pse2-toggle input:focus-visible + i{ outline:2px solid #17317A; outline-offset:2px; }
.pse2-own{ display:flex; flex-direction:column; gap:8px; padding:10px; margin-bottom:10px; border:1px solid #EDEDED; border-radius:10px; }
/* `hidden` must win: the spare rows wait behind "+ Add", and display:flex above would show them all. */
.pse2-own[hidden]{ display:none; }
.pse2-own-head{ display:flex; gap:8px; align-items:center; }
.pse2-own textarea{ resize:vertical; margin-top:6px; }
.pse2-own details summary{ cursor:pointer; font-size:12.5px; color:var(--muted); }
.pse2-own .pse2-hint{ margin:0; }
.pse2-mini{ display:flex; align-items:center; gap:4px; flex:none; font-size:12.5px; color:var(--muted); }
.pse2-chips{ display:flex; flex-wrap:wrap; gap:5px; }
.pse2-chips label{ cursor:pointer; }
.pse2-chips input{ position:absolute; opacity:0; pointer-events:none; }
.pse2-chips span{ display:inline-block; padding:3px 9px; border:1px solid #E4E4E4; border-radius:999px; font-size:12px; }
.pse2-chips input:checked + span{ background:#D5E6FC; border-color:#B6D6FF; color:#17317A; }
.pse2-add{ width:100%; padding:9px; border:1px dashed #B6D6FF; border-radius:10px; background:none; color:#17317A; font-size:13px; cursor:pointer; }
.pse2-add:hover{ background:#F4F7FC; }
/* With the script running: one screen's tools at a time, saving by itself.
   Without it: every screen's tools, stacked, and a Save button. */
.pse2.pse2-js .pse2-panel:not(.on){ display:none; }
.pse2.pse2-js .pse2-save{ display:none; }
.pse2-panel{ display:flex; flex-direction:column; gap:14px; }
@media (max-width:1280px){ .pse2{ grid-template-columns:160px minmax(0,1fr) 300px; } }
@media (max-width:1000px){ .pse2{ grid-template-columns:1fr; } .pse2-rail, .pse2-stage{ position:static; }
  .pse2-rail{ flex-direction:row; overflow:auto; max-height:none; } .pse2-card{ width:auto; min-width:140px; } }
/* The Pre-sales Templates tab. ⚠ Placeholder: no Figma frame. */
.pst-h{ margin:28px 0 10px; font-size:18px; }
.pst-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:16px; }
.pst-card{ display:flex; flex-direction:column; gap:10px; padding:18px; border:1px solid #E4E4E4; border-radius:14px;
           background:#fff; box-shadow:0 4px 14px rgba(0,0,0,.08); }
.pst-name{ font-size:17px; font-weight:500; }
.pst-he{ margin-inline-start:6px; font-size:14px; font-weight:400; color:var(--muted); }
.pst-line{ font-size:14px; color:var(--muted); }
.pst-scope{ list-style:none; margin:0; padding:0; font-size:13px; }
.pst-scope li{ padding:5px 0; border-top:1px solid #F0F0F0; }
.pst-models{ display:flex; flex-wrap:wrap; gap:6px; }
.pst-models span{ padding:3px 10px; border-radius:999px; background:#EFEFEF; font-size:12px; }
.pst-card .btn{ align-self:flex-start; margin-top:auto; }
.pst-actions{ display:flex; flex-wrap:wrap; align-items:center; gap:10px; margin-top:auto; }
.pst-actions .btn{ margin-top:0; }
.pst-link{ font-size:13px; }
.ps-answers{ display:grid; grid-template-columns:minmax(140px,220px) 1fr; gap:8px 16px; margin:0; font-size:14px; }
.ps-answers dt{ color:var(--muted); }
.ps-answers dd{ margin:0; overflow-wrap:anywhere; }
.presale-thumb-note{ display:flex; align-items:center; justify-content:center;
  text-align:center; color:#888; font-size:13px; padding:24px 16px; }
.field{ display:flex; flex-direction:column; gap:7px; }
.field.full{ margin-top:16px; }

/* ---- the service a proposal is made from --------------------------------
   Sits between the client's details and the private notes, because it is the
   one thing on this page that is not the client: it is where the slides come
   from. The row is a link, and the whole of it is the target — the picture,
   the name and the pencil are one thing to click, the way a build card is on
   the Builds list.

   The thumbnail is .order-thumb/.thumb-frame unchanged, so the preview here
   and the preview on the Builds list are the same 160x100 picture drawn by
   the same iframe. Nothing about it is re-specified here. */
.svc-row{
  display:flex; align-items:center; gap:16px;
  padding:14px 16px; box-sizing:border-box;
  border:.3px solid var(--frame-border); border-radius:8.33px;
  background:#fff; box-shadow:var(--frame-shadow);
  text-decoration:none; color:inherit;
  transition:transform .26s cubic-bezier(.22,.61,.36,1), box-shadow .26s ease;
}
/* The same answer the build card gives: 1% and a deeper shadow. The picture
   inside it keeps its own 3%, so the two read as one movement. */
.svc-row:hover{ transform:scale(1.01); box-shadow:0 8px 22px 0 rgba(0,0,0,.12); }
@media (prefers-reduced-motion: reduce){ .svc-row{ transition:none; } }
.svc-text{ display:flex; flex-direction:column; gap:5px; min-width:0; flex:1; }
.svc-name{ font-size:18px; color:#17317A; }
.svc-hint{ font-size:13.5px; line-height:1.5; color:#8A8A8A; }
.svc-go{
  flex:none; display:flex; align-items:center; justify-content:center;
  width:36px; height:36px; border-radius:50%;
  border:1px solid #d8d8d8; background:#fff; color:#111;
}
.svc-row:hover .svc-go{ border-color:#AEBEFF; color:#17317A; }
@media (max-width: 720px){
  .svc-row{ align-items:flex-start; }
  .svc-go{ display:none; }
}
.field > span{ font-size:14px; color:#444; }
.field input, .field select, .field textarea{
  font-family:inherit; font-size:16px; color:#111;
  padding:13px 18px; border:.3px solid var(--frame-border); border-radius:8.33px;
  background:#fff; outline:none; width:100%;
  box-shadow:var(--frame-shadow);
}
.field textarea{ resize:vertical; line-height:1.5; }
.field input:focus, .field select:focus, .field textarea:focus{ border-color:#111; }

/* Currency picker sits inline with the amount */
.price-row{ display:flex; gap:8px; }
.price-row .currency-select{ width:auto; flex:none; min-width:112px; }
.price-row input{ flex:1; min-width:0; }

.field .field-error-note{ display:block; color:var(--red); font-size:13px; margin-top:6px; }
.type-error{ display:block; color:var(--red); font-size:13px; margin-top:10px; }
.field input.input-error, .field .input-error,
.field input.input-error:focus, .field .input-error:focus{ border-color:var(--red); }

.form-actions{ margin-top:26px; display:flex; gap:10px; }

/* ---------- locked password field ----------
   Reads as a filled-in field that can't be typed into: grey text, grey fill,
   and a pencil as the only way to change it. The dots are a placeholder, not
   the password — passwords are stored as one-way hashes and cannot be read
   back (see lib/auth.mjs). */
.pw-locked{
  display:flex; align-items:center; gap:10px;
  padding:13px 14px 13px 18px;
  border:.3px solid var(--frame-border); border-radius:8.33px;
  background:#f6f6f6; box-shadow:var(--frame-shadow);
}
.pw-value{
  flex:1; min-width:0; font-size:16px; color:#9a9a9a;
  letter-spacing:2px; line-height:1.2; user-select:none;
}
.pw-edit{
  flex:none; display:inline-flex; align-items:center; justify-content:center;
  width:34px; height:34px; padding:0; cursor:pointer;
  background:#fff; border:1px solid #d8d8d8; border-radius:50%; color:#111;
  transition:background .15s, border-color .15s, color .15s;
}
.pw-edit svg{ width:16px; height:16px; display:block; }
.pw-edit:hover{ background:#CCE2FF; border-color:#B6D6FF; color:var(--green); }

/* Password field with a reveal toggle sitting inside it. */
.pw-input{ position:relative; display:block; }
.pw-input input{ padding-right:50px; }
.pw-reveal{
  position:absolute; right:7px; top:50%; transform:translateY(-50%);
  display:inline-flex; align-items:center; justify-content:center;
  width:34px; height:34px; padding:0; cursor:pointer;
  background:none; border:0; border-radius:50%; color:var(--muted);
  transition:background .15s, color .15s;
}
.pw-reveal svg{ display:block; }
.pw-reveal:hover{ background:var(--green-pill); color:var(--green); }
/* Eye at rest ("show it"), eye-with-a-slash once the text is visible. */
.pw-reveal .ico-hide{ display:none; }
.pw-reveal.on .ico-show{ display:none; }
.pw-reveal.on .ico-hide{ display:block; }
.pw-reveal.on{ color:var(--green); }

/* The rules, spelled out in the dialog before the fields. */
.pw-rules{
  margin:14px 0 4px; font-size:14px; line-height:1.5; color:#5f5f5f;
  background:var(--green-pill); border:1px solid var(--green-border);
  border-radius:8.33px; padding:12px 16px;
}
.pw-rules strong{ font-weight:500; color:var(--green); }

/* ---------- type chooser ---------- */
.type-grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:14px; margin-bottom:8px; }
.type-choice input{ position:absolute; opacity:0; pointer-events:none; }
.type-card{
  display:flex; flex-direction:column; gap:5px;
  border:.3px solid var(--frame-border); border-radius:10.71px; padding:20px;
  cursor:pointer; transition:border-color .15s, background .15s;
  height:100%;
  box-shadow:var(--frame-shadow);
}
.type-choice:hover .type-card{ border-color:#111; }
.type-choice input:checked + .type-card{
  border-color:var(--green-border); background:var(--green-pill);
}
.type-name{ font-size:17px; }
.type-name-he{ font-size:14px; color:#5a5a5a; }
.type-draft{ font-size:12px; color:#854f0b; margin-top:6px; }

/* ---------- order detail ---------- */
.split{ display:grid; grid-template-columns:1.55fr 1fr; gap:20px; align-items:start; }
.side-col{ display:flex; flex-direction:column; gap:16px; }
/* Settings page: each h2 group is its own card now, stacked with the same
   gap as the side column, instead of one long scrolling form. */
.form-sections{ display:flex; flex-direction:column; gap:16px; }

.link-card h2, .danger-card h2, .signed-box h3{ font-size:17px; font-weight:500; margin:0 0 6px; }
.link-row{ display:flex; gap:8px; }
.link-row input{
  flex:1; min-width:0; font-family:inherit; font-size:13px; color:#444;
  padding:12px 16px; border:1px solid #d8d8d8; border-radius:100px;
  background:#fafafa; outline:none;
}

.signed-box{
  background:var(--green-pill); border:1px solid var(--green-border);
  border-radius:13.09px; padding:22px 26px;
}
.signed-box p{ margin:4px 0 0; font-size:15px; }
.signed-box .muted{ color:var(--green); font-size:13px; }

.danger-card{ border-color:#efdcdb; }

.flash{
  background:var(--green-pill); border:1px solid var(--green-border);
  border-radius:100px; padding:12px 22px; font-size:14px;
  display:inline-block; margin-bottom:18px;
}

/* ---------- delete ---------- */
/* Same shape as the eye button. It used to turn red on hover; it now uses the
   same OP Button hover as its neighbours so the row reads as one set. The
   red warning still appears in the delete confirmation itself. */
.trash-btn{ color:#111; }
.cell-status{ white-space:nowrap; }
.cell-status .trash-btn{ vertical-align:middle; }

.btn.danger-solid{ background:#D04131; color:#fff; border-color:#D04131; }
.btn.danger-solid:hover{ background:#b8392b; border-color:#b8392b; }
.btn.with-icon{ display:inline-flex; align-items:center; gap:8px; }
.btn.with-icon svg{ width:17px; height:17px; }

/* Pushed to the far right so it lines up with the edge of the orders table */
.deleted-link{ margin-left:auto; }

.del-note{
  background:#fcebeb; border:1px solid #f3d4d2; border-radius:8.33px;
  padding:14px 18px; font-size:14px; line-height:1.6; color:#7a2320; margin:16px 0 0;
}
.modal.modal-sm{ max-width:440px; }

/* Duplicate dialog — centered, neutral (no red). */
.dup-modal .modal-head{ justify-content:center; position:relative; }
.dup-modal .modal-head h2{ text-align:center; font-weight:400; font-size:26px; }
.dup-modal .modal-close{ position:absolute; top:0; right:0; }
.dup-modal .modal-actions{ justify-content:center; }
/* The running text under a dialog's headline. Smaller and tighter than it
   was: it is a caption on a question, not a paragraph, and the question is
   what the eye should land on. */
.dup-note{
  text-align:center; color:#5f5f5f;
  font-size:13.5px; line-height:1.55; margin:12px 0 0;
}
.dup-modal .send-result{ text-align:center; }
.btn.dark-solid{ background:#111; color:#fff; border-color:#111; }
.btn.dark-solid:hover{ background:#000; border-color:#000; }

/* The proposals named in the build-change dialog. */
.bc-list{
  margin:14px 0 0; padding:0; list-style:none;
  max-height:190px; overflow-y:auto;
  text-align:left; font-size:14px; line-height:1.5;
}
.bc-list li{
  padding:8px 12px; border-radius:8.33px; background:#f6f6f6; color:#333;
}
.bc-list li + li{ margin-top:6px; }
/* Already signed: the ones worth hesitating over. */
.bc-list li.is-signed{ background:#fcebeb; color:#7a2320; }
.bc-list li.is-more{ background:none; color:var(--muted); font-size:13px; }

.days-left{
  display:inline-block; padding:6px 14px; border-radius:100px;
  font-size:13px; white-space:nowrap;
  background:var(--gray-pill); color:#5f5e5a;
}
.days-left.urgent{ background:#fcebeb; color:#a32d2d; }

/* ---------- send dialog ---------- */
.modal-backdrop{
  position:fixed; inset:0; z-index:100;
  background:rgba(0,0,0,.4);
  display:flex; align-items:center; justify-content:center;
  padding:24px;
}
.modal-backdrop[hidden]{ display:none; }
.modal{
  /* 30 on every side, so nothing inside ever sits on an edge. */
  background:#fff; border:.3px solid var(--frame-border); border-radius:14.28px; padding:30px;
  width:100%; max-width:520px;
  max-height:88vh; overflow-y:auto;
  box-shadow:var(--frame-shadow);
}
.modal-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:16px; }
.modal-head h2{ font-size:22px; font-weight:500; margin:0; }
.modal-close{
  border:none; background:none; font-size:30px; line-height:1;
  color:var(--muted); cursor:pointer; padding:0 4px;
}
.modal-close:hover{ color:#111; }
.modal .field{ margin-top:16px; }
/* Wraps rather than pushing the buttons into the padding, for the one case a
   pair of labels is still wider than the dialog. */
.modal-actions{ display:flex; flex-wrap:wrap; justify-content:flex-end; gap:10px; margin-top:22px; }

.send-warning{
  background:#faeeda; border:1px solid #f0d9ae; border-radius:8.33px;
  padding:12px 16px; font-size:14px; color:#854f0b; line-height:1.5; margin:16px 0 0;
}
.send-warning code{ font-size:13px; }

.send-result{ margin:16px 0 0; font-size:14px; padding:12px 16px; border-radius:8.33px; }
.send-result.ok{ background:var(--green-pill); color:var(--green); }
.send-result.err{ background:#fcebeb; color:#a32d2d; }

.sent-box h2{ font-size:17px; font-weight:500; margin:0 0 6px; }
.sent-box p{ margin:2px 0 0; font-size:15px; }
.sent-box .muted{ color:var(--muted); font-size:13px; }

/* ---------- login ---------- */
.login-body{
  min-height:100vh; display:flex; align-items:center; justify-content:center;
  background:#f6f6f4;
}
.login-card{
  background:#fff; border:.3px solid var(--frame-border); border-radius:15.47px;
  padding:44px 40px; width:380px; max-width:92vw;
  display:flex; flex-direction:column; gap:14px; text-align:center;
  box-shadow:var(--frame-shadow);
}
.login-logo{ height:32px; margin:0 auto 6px; }
.login-card h1{ font-size:22px; font-weight:500; margin:0 0 6px; }
.login-card input{
  font-family:inherit; font-size:16px; text-align:center;
  padding:14px 18px; border:1px solid #d8d8d8; border-radius:100px; outline:none;
}
.login-card input:focus{ border-color:#111; }
.login-error{ color:var(--red); font-size:14px; margin:0; }

/* ---------- 404 / page not found ----------
   From the Figma "Page not found" artboard: white-to-blue wash, the Spica
   mark echoed twice as a watermark (one flipped copy clipped off the top
   edge, one upright and larger lower down), a quip instead of a plain
   error, and the OP Button "Go back" pill. The mark itself is masked from
   the real logo file so it's the actual asset, just tinted and faded. */
.notfound-body{
  min-height:100vh; margin:0; overflow:hidden; position:relative;
  display:flex; align-items:center; justify-content:center;
  background:linear-gradient(180deg, #FFFFFF 0%, #CBE2FF 100%);
  padding-left:0 !important;
}
/* One flat asset, straight out of Figma (1208x481). It already contains the
   mirrored top copy clipped at its own top edge, the upright copy, the
   shadow ellipse and all the blue gradients — so it just gets placed, never
   recoloured or duplicated here. Figma has it at x=338 of a 1920 frame
   (17.6%) at 1208 wide (62.9%), flush with the top. */
.notfound-mark{
  position:absolute; top:0; left:17.6%; width:62.9%; height:auto;
  display:block; pointer-events:none; user-select:none;
}
.notfound-content{ position:relative; z-index:1; text-align:center; padding:0 24px; }
.notfound-content h1{
  margin:0 0 4px; font-family:'Parkinsans', 'Google Sans', Arial, sans-serif;
  font-size:33px; font-weight:600; letter-spacing:-.33px; line-height:1.53; color:#0A2250;
}
/* One line, as in the artboard — only allowed to wrap on a narrow screen. */
.notfound-content p{
  margin:0 0 28px; font-family:'Parkinsans', 'Google Sans', Arial, sans-serif;
  font-size:17.86px; font-weight:400; letter-spacing:.54px; line-height:1.53; color:#919191;
  white-space:nowrap;
}
@media (max-width: 560px){ .notfound-content p{ white-space:normal; } }

/* ---------- Sign in ----------------------------------------------------
   Rebuilt Sep 2026 from the "Sign In" frame in the *Spica Project* Figma
   file (node 371:586) -- a different file from Spica Brand + Product, which
   is where the first version came from and where this screen no longer
   lives.

   The BOX did not change. 466 wide, 439 tall, 42 padding-top, 279 x 51
   fields centred with 15 between them, the same 279 x 51 button: all of it
   is the original frame's, and all of it is still here. What changed is the
   skin -- the card went from frosted white glass to solid black -- and the
   separate "Show Password" row came back, which is why the card is 439
   again rather than the 410 it was while that control lived inside the
   password field.

   Read as measured, not from Dev Mode: that file is only open to link
   viewers, so the numbers below come off the rendered frame, calibrated
   against two things Figma states outright -- the tagline layer at 204 x 22
   and the fields at 279 x 51. Every other value then landed within a unit of
   a round number, which is the check that the calibration was right. The
   corner radius is the one value with no independent confirmation; 30 is
   what the corner measures, against the 40 it used to be.

   Vertical stack inside the 439px card:
     42     padding-top
     52     logo             (217 x 52 -- was 245 x 66.99, and now white)
     28.16  gap
     14     tagline
     36.5   gap
     51     Email            (279 x 51)
     15     gap
     51     Password
     15     gap
     14     Show Password row
     15     gap
     51     Sign in button
     14     gap
     12     New here? row    (182 x 12)
     28.34  padding-bottom
   ------------------------------------------------------------------ 439.
   The logo lost 15 units against the old frame and the card did not, so 15
   went back into the stack: the render puts ~6 of it above the Email field
   and the rest under the last row, which is how the gap after the tagline
   and the bottom padding come to differ from the old frame's 30.84 and 19.
   Everything else is that frame's own number, untouched. */
.signin-body{
  min-height:100vh; margin:0; padding:0;
  display:flex; align-items:center; justify-content:center;
  background:#000;
}

/* ---- the artwork, and its three lines of type as real text --------------
   Sep 2026, from Tomer: the headline and the slogan are no longer baked into
   the photograph. The picture is now the clean plate (Backgrounds/Signin BG
   no texts.webp) and the words are set live in the two faces the Figma frame
   uses -- Instrument Serif for the headline, Geist Pixel for the slogan, both
   from Google Fonts, both added to the link in authShell().

   Live type means it can be edited, translated and read by a screen reader
   -- and it means the words now have to follow the picture exactly as the
   picture scales, which a background image on <body> cannot do for them.

   So the plate and the type share one stage, sized by hand to precisely what
   background-size:cover would have computed for a 2211 x 1082 frame:

     --sw  the stage's width -- whichever of the two fits, the viewport's own
           width or the width the viewport's height demands at this aspect.

   Every number below is then a position inside that 2211-unit frame, scaled
   by the same --sw, so the type is welded to the artwork at any window size.
   Fixed rather than absolute so it neither scrolls nor takes part in the
   body's centring of the card.

   The positions came off the OLD background -- the one with the words baked
   in -- read at its native 4422 x 2164 and halved, which is the frame's own
   grid. The two font sizes were then solved rather than guessed: measuring
   each string's ink in the real face at 100px and dividing the baked ink into
   it agreed to within half a unit on width and on height independently, which
   is what says 170 and 30 are the sizes the frame was set in.

   The tops are baselines turned into box tops through each face's own
   ascent at line-height:1; they were then checked against the baked artwork
   in the browser, which is the only way to be sure of it. */
.signin-art{
  /* exactly background-size:cover for a 2211 x 1082 picture */
  --sw:max(100vw, 100vh * 2211 / 1082);
  position:fixed; top:50%; left:50%; transform:translate(-50%,-50%);
  width:var(--sw); height:calc(var(--sw) * 1082 / 2211);
  z-index:0; pointer-events:none;
  /* The plate starts its entrance 4% oversized, so the stage has to crop it. Clipping here rather than letting it spill is also
     what keeps an oversized fixed layer from reaching past the viewport. The
     type is inside the frame at every position, so none of it is at risk. */
  overflow:hidden;
}
/* The plate itself, on its own layer so it can fade in without taking the
   type with it: Tomer wants the words and the card present from the first
   frame and the photograph arriving under them, up from the flat black the
   body is painted anyway.

   A CSS animation rather than a class a script adds, so there is no state in
   which the page has no picture -- if the animation never runs, `both` leaves
   the plate at its `to` value and the screen is simply the finished design.
   The cost of doing it this way is that the fade starts when the element is
   first styled, which can be before a cold image has arrived; the preload
   link loginPage() puts in the head is what buys that back, starting the
   fetch while the head is still parsing so the picture is nearly always ready
   by the time there is anything to fade. */
.signin-art::before{
  content:''; position:absolute; inset:0;
  background:url(/assets/auth/signin-bg.webp) center/100% 100% no-repeat;
  transform-origin:center;
  animation:signin-plate-in 1.5s ease-out both;
}
/* Up from black over a second and a half, settling out of a 4% push-in as it
   comes. Small enough that the picture reads as still arriving rather than as
   a zoom, and ease-out spends most of that time at the end, so the last of
   the move is slow and the plate comes to rest rather than stopping. */
@keyframes signin-plate-in{
  from{ opacity:0; transform:scale(1.04); }
  to  { opacity:1; transform:scale(1); }
}
/* Someone who has asked for less movement gets the picture, not the entrance
   -- neither the plate's nor the headline's. */
@media (prefers-reduced-motion: reduce){
  .signin-art::before,
  .signin-words span,
  .signin-words{ animation:none; }
}
/* ---- the headline: "For Designers" | card | "By Designers" -------------
   Oct 2026, Figma "Sign In" (899:6863): Instrument Serif 169 at -1%
   tracking, black, one word either side of the card, the slogan above the
   card gone.

   The words are children of the card, placed from its edges rather than
   from the picture, so they can never slide in behind it. Their size comes
   from the room beside the card: at the frame's 2211 that room is 873 and
   the type is 169, and it keeps that ratio as the window narrows -- the
   card stays 466, so the words give way first and still keep a margin to
   the window edge (about 11% of the room) and the frame's gap to the card.

   Every offset is in em so it scales with the type:
     left word   ink ends 40 before the card  (box right = ink + 2.36)
     right word  ink starts 33 after the card (box left = ink - 3)
     baseline    7 below the card's middle, i.e. a line-height:1 box whose
                 top is .7988em above it (the face puts the baseline .8402em
                 down a 1em line). */
.signin-words{
  --sw-size:calc((50vw - 233px) * 169 / 873);
  position:absolute; inset:0; margin:0; padding:0;
  pointer-events:none;
  font-family:'Instrument Serif', Georgia, 'Times New Roman', serif;
  font-weight:400; font-size:var(--sw-size); line-height:1;
  letter-spacing:-.01em; color:#000; white-space:nowrap;
}
.signin-words span{
  position:absolute; top:50%; margin-top:-.7988em;
  animation:signin-word-in 1.5s ease-out both;
}
.signin-words .sw-a{ right:calc(100% + .2227em); --word-from:-30; }
.signin-words .sw-b{ left:calc(100% + .1775em);  --word-from: 30; }
.signin-words .sw-sep{ display:none; }
/* Each word slides the last 30 frame units into place as the plate comes
   up, closing on the card from either side, on the plate's clock. */
@keyframes signin-word-in{
  from{ transform:translateX(calc(var(--word-from) / 169 * 1em)); }
  to  { transform:translateX(0); }
}
@media (prefers-reduced-motion: reduce){
  .signin-words, .signin-words span{ animation:none; }
}
/* Solid black, no edge. The 0.7px white border and the blurred translucent
   gradient both go: against this photograph the card is a cut-out, and a
   white hairline on it reads as a seam rather than as glass. */
.signin-card{
  box-sizing:border-box;
  position:relative; z-index:1;
  width:466px; max-width:calc(100vw - 32px); min-height:439px;
  padding:43.11px 0 28.5px;
  display:flex; flex-direction:column; align-items:center;
  border-radius:40px;
  background:#000;
  text-align:center;
  font-family:'Google Sans Flex', 'Google Sans', Arial, sans-serif;
}
.signin-logo{ display:block; width:217px; height:51.84px; }
/* the logo leads back to the home page */
.signin-home{ display:block; border-radius:8px; transition:opacity .2s ease; }
.signin-home:hover{ opacity:.8; }
.signin-home:focus-visible{ outline:2px solid #FFF; outline-offset:6px; }

/* One white line at one weight. The navy-semibold "Sign in," against grey is
   gone with the white card -- there is no second colour anywhere on this
   card now, which is rather the point of it. */
.signin-tagline{
  margin:29.21px 0 26.84px;
  font-family:inherit;
  font-size:17.687px; font-weight:400; line-height:22px;
  letter-spacing:.177px; color:#FFF;
}

.signin-form{ display:flex; flex-direction:column; align-items:center; margin:0; }

/* 279 x 51, fully rounded, text inset 20px. No border: on black the pill is
   read by its own white against the card, and the hairline the white version
   needed would draw a grey ring around it. */
.signin-field{ display:block; width:279px; }
.signin-field input{
  box-sizing:border-box;
  width:279px; height:51px; padding:0 20px;
  border-radius:60px; border:.8px solid #B4B4B4; background:#FFF;
  font-family:inherit;
  font-size:15px; font-weight:400; letter-spacing:.3px; color:#111;
  outline:none;
}
.signin-field input::placeholder{ color:#737373; }
/* Focus has to stay visible without a border to recolour: a ring outside the
   pill, which the black card shows cleanly. */
.signin-field input:focus{ box-shadow:0 0 0 2px #FFF, 0 0 0 4px #000; }
.signin-field + .signin-field{ margin-top:15px; }

/* The reveal control is a row of its own again, under the password field, as
   the frame has it -- not the eye tucked inside the field. It keeps the
   .pw-reveal class and its data-target, so the toggle in public/js/admin.js
   drives it unchanged; everything here only undoes that class's absolute
   positioning and gives it a label. The label swaps with the icon, because a
   toggle that always reads "Show" is wrong half the time. */
.signin-reveal{
  position:static; transform:none;
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  width:auto; height:15px; margin:13px 0 0; padding:0;
  background:none; border:0; border-radius:0;
  font-family:inherit;
  font-size:11px; font-weight:400; line-height:11px; letter-spacing:.33px;
  color:#FFF; cursor:pointer;
}
.signin-reveal svg{ width:15px; height:15px; }
.signin-reveal:hover{ background:none; color:#FFF; text-decoration:underline; }
.signin-reveal.on{ color:#FFF; }
.signin-reveal .rv-hide{ display:none; }
.signin-reveal.on .rv-show{ display:none; }
.signin-reveal.on .rv-hide{ display:inline; }

/* Black on black: the button is drawn by its hairline white edge, the same
   0.7px the card itself used to carry. Hover inverts rather than going green
   -- the old green hover was white type on #48fd7e, thin already and worse
   now that white is the only colour on the card. */
.signin-submit{
  box-sizing:border-box;
  display:flex; align-items:center; justify-content:center;
  width:279px; height:51px; margin-top:16px;
  border-radius:60px; border:1px solid #B4B4B4; background:#000;
  font-family:inherit;
  font-size:17px; font-weight:400; letter-spacing:.17px; color:#FFF;
  cursor:pointer; transition:background .15s, color .15s;
}
.signin-submit:hover{ background:#FFF; color:#000; }

/* Not in the Figma frame -- only rendered when Google sign-in is configured,
   styled to sit with the rest rather than to a design of its own. */
.signin-google{
  display:flex; align-items:center; justify-content:center; gap:10px;
  box-sizing:border-box; width:279px; height:51px; margin-top:14px;
  border-radius:60px; border:.7px solid #FFF; background:#FFF;
  font-family:inherit;
  font-size:15px; letter-spacing:.3px; color:#111; text-decoration:none;
}
.signin-google:hover{ background:#EDEDED; }

.signin-alt{
  display:flex; align-items:center; justify-content:center; gap:7px;
  height:12px; margin:13.5px 0 0;
  font-family:inherit;
  font-size:12px; font-weight:400; line-height:12px; letter-spacing:.12px;
}
.signin-alt span{ color:#A6A6A6; font-weight:600; }
.signin-alt a{ color:#FFF; text-decoration:underline; }

/* #a32d2d was picked against the white card and is barely legible on black;
   this is that red carried up to where it reads. */
.signin-error{
  margin:0 0 14px; width:279px;
  font-family:inherit;
  font-size:13px; line-height:1.45; color:#FF9B93;
}
/* a calm line on the same card: link sent, what to do next */
.signin-notice{
  margin:0 0 14px; width:279px;
  font-family:inherit;
  font-size:13px; line-height:1.5; color:#D8D8D8;
}
.signin-forgot{ margin-top:10px; }
.signin-turnstile{ margin:0; }
.signin-turnstile:has(iframe[style*="display: block"]), .signin-turnstile:has(iframe:not([style*="display: none"])){ margin:4px 0 10px; }

/* ---- phone: Figma "Sign In - Mobile" (903:254), drawn 838 wide, so --m is
   one frame pixel. Its own plate (the sky and sun, the white box with its
   rays and stars at the lower right under the card, the dunes) exported from
   that frame without the card and the words. Oct 2026: the slogan is gone;
   the headline is one black line, "For Designers / By Designers",
   Instrument Serif 77.125 at +1%, its baseline 117 above the card; the card
   starts at 375. The page scrolls with the plate, so a long card (Google, an
   error) simply continues on the black. */
@media (max-width: 760px){
  /* --m is held so the words and the whole card fit between the browser's
     bars (1259 frame pixels from the top to under the card; sign-up has one
     more field): on a short screen the design shrinks, it does not scroll. */
  .signin-body{ --m:min(100vw / 838, 100dvh / 1259); display:flow-root; min-height:100dvh; padding:0 0 calc(30 * var(--m)); box-sizing:border-box; position:relative; overflow-x:clip; }
  .signin-body:has(input[name="business_name"]){ --m:min(100vw / 838, 100dvh / 1399); }
  .signin-art{ --sw:100vw; position:absolute; top:0; left:0; transform:none; width:100vw; height:100%; }
  .signin-art::before{ background:#000 url(/assets/auth/signin-bg-mobile.webp) center top / 100% auto no-repeat; }
  /* One centred line above the card. Sized in --m like the card, and --m is
     never more than the width allows, so the line (720 of 838) always fits
     the screen and cannot wrap or reach the card. */
  .signin-words{ inset:auto; left:50%; bottom:calc(100% + 104.7 * var(--m)); transform:translateX(-50%);
    font-size:calc(77.125 * var(--m)); letter-spacing:.01em;
    animation:signin-word-up 1.5s ease-out both; }
  .signin-words span{ position:static; margin:0; animation:none; }
  .signin-words .sw-sep{ display:inline; white-space:pre; }
  .signin-card{ width:calc(701 * var(--m)); max-width:none; min-height:calc(796 * var(--m)); margin:calc(375 * var(--m)) auto 0;
    padding:calc(44.27 * var(--m)) 0 calc(48.9 * var(--m)); border-radius:calc(65.64 * var(--m)); }
  .signin-logo{ width:calc(356 * var(--m)); height:auto; }
  .signin-tagline{ margin:calc(48 * var(--m)) 0 calc(44.1 * var(--m)); font-size:max(13px, calc(29.03 * var(--m))); line-height:max(16px, calc(36 * var(--m))); letter-spacing:.01em; }
  .signin-field, .signin-field input, .signin-submit, .signin-google, .signin-error, .signin-notice{ width:calc(575 * var(--m)); }
  .signin-field input, .signin-submit, .signin-google{ height:max(44px, calc(105 * var(--m))); border-radius:calc(124 * var(--m)); }
  .signin-field input{ padding:0 calc(41.2 * var(--m)); border-width:max(.8px, calc(1.65 * var(--m))); font-size:16px; /* 16px: iPhone does not zoom into the field */ }
  .signin-field + .signin-field{ margin-top:calc(30.9 * var(--m)); }
  .signin-reveal{ margin-top:calc(26.8 * var(--m)); height:auto; font-size:max(11px, calc(22.68 * var(--m))); letter-spacing:.03em; gap:calc(16.5 * var(--m)); }
  .signin-reveal svg{ width:max(14px, calc(30.9 * var(--m))); height:max(14px, calc(30.9 * var(--m))); }
  .signin-submit{ margin-top:calc(33 * var(--m)); border-width:max(1px, calc(2.06 * var(--m))); font-size:max(15px, calc(35.04 * var(--m))); }
  .signin-google{ margin-top:calc(31 * var(--m)); }
  .signin-alt{ margin-top:calc(27.8 * var(--m)); height:auto; gap:calc(14.4 * var(--m)); font-size:max(11px, calc(24.74 * var(--m))); }
}
/* a very short phone screen (a small phone with both browser bars showing):
   the floors under the fields give a little so the card still fits */
@media (max-width: 760px) and (max-height: 600px){
  .signin-field input, .signin-submit, .signin-google{ height:40px; }
  .signin-tagline{ font-size:12px; }
  .signin-forgot{ margin-top:6px; }
}
@keyframes signin-word-up{
  from{ opacity:0; transform:translate(-50%, 12px); }
  to  { opacity:1; transform:translate(-50%, 0); }
}
@media (prefers-reduced-motion: reduce){ .signin-words{ animation:none; } }

/* On a short window the card scrolls rather than being clipped. */
@media (max-height: 520px) and (min-width: 761px){
  .signin-body{ align-items:flex-start; padding:16px 0; overflow-y:auto; }
}

/* ---------- auth (sign in / sign up) ---------- */
.auth-card{
  background:#fff; border:.3px solid var(--frame-border); border-radius:15.47px;
  padding:40px 40px; width:400px; max-width:92vw;
  display:flex; flex-direction:column; gap:14px; text-align:center;
  box-shadow:var(--frame-shadow);
}
.auth-card h1{ font-size:24px; font-weight:500; margin:2px 0 8px; }
.auth-form{ display:flex; flex-direction:column; gap:12px; margin:0; }
.auth-card input{
  font-family:inherit; font-size:16px;
  padding:14px 18px; border:1px solid #d8d8d8; border-radius:100px; outline:none;
}
.auth-card input:focus{ border-color:#111; }
.auth-card .btn.primary{ width:100%; padding:14px 18px; }
.auth-alt{ font-size:14px; color:var(--muted); margin:4px 0 0; }
.auth-alt a{ color:#111; }
.google-btn{
  display:flex; align-items:center; justify-content:center; gap:10px;
  width:100%; padding:13px 18px; background:#fff; border:1px solid #d8d8d8;
}
.google-btn:hover{ border-color:#111; }
.auth-or{
  display:flex; align-items:center; gap:12px; color:var(--muted); font-size:13px;
  margin:2px 0;
}
.auth-or::before, .auth-or::after{ content:""; flex:1; height:1px; background:var(--line); }

/* ---------- header account area ---------- */
.biz-name{
  font-size:15px; color:#111; text-decoration:none; max-width:220px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.biz-name:hover{ text-decoration:underline; }
.flash-error{ background:#fcebeb; border-color:#f3d4d2; color:#7a2320; }

/* ---------- lead colour picker (settings) ---------- */
.mt-32{ margin-top:32px; }
.lead-row{ display:flex; align-items:flex-start; gap:12px; flex-wrap:wrap; margin-top:4px; }
/* The picker is a circle matching the preview swatches, wrapped in a ring of
   white space and a hairline border so it reads as a pressable control. */
.lead-pick{ cursor:pointer; margin:0 12px 0 6px; }
.lead-row input[type="color"]{
  width:46px; height:46px; padding:0; cursor:pointer;
  border:1px solid rgba(0,0,0,.12); border-radius:50%; background:none;
  -webkit-appearance:none; appearance:none; overflow:hidden;
  box-shadow:0 0 0 5px #fff, 0 0 0 6px #d8d8d8;
  transition:box-shadow .15s;
}
.lead-pick:hover input[type="color"]{ box-shadow:0 0 0 5px #fff, 0 0 0 6px #111; }
.lead-row input[type="color"]::-webkit-color-swatch-wrapper{ padding:0; }
.lead-row input[type="color"]::-webkit-color-swatch{ border:none; border-radius:50%; }
.lead-row input[type="color"]::-moz-color-swatch{ border:none; border-radius:50%; }
.lead-row input[type="text"]{
  width:120px; font-family:inherit; font-size:16px; text-transform:lowercase;
  padding:13px 16px; border:1px solid #d8d8d8; border-radius:8.33px; outline:none;
}
.lead-row input[type="text"]:focus{ border-color:#111; }
.lead-swatches{ display:flex; gap:16px; margin-left:8px; }
.lead-item{ display:flex; flex-direction:column; align-items:center; gap:7px; }
.lead-sw{
  width:46px; height:46px; border-radius:50%;
  border:1px solid rgba(0,0,0,.12); display:block;
}
.lead-item em{ font-style:normal; font-size:12px; color:#6a6a6a; letter-spacing:.2px; }
/* Sits to the right of the swatches, vertically centred against the circles. */
#lead-reset{ margin:7px 0 0 20px; }


/* ---------- small info icon with hover tooltip ---------- */
.info-tip{
  display:inline-flex; align-items:center; justify-content:center;
  margin-left:7px; color:var(--muted); cursor:help;
  position:relative; vertical-align:-2px; outline:none;
}
.info-tip:hover, .info-tip:focus-visible{ color:#111; }
.info-tip::after{
  content:attr(data-tip);
  position:absolute; bottom:calc(100% + 9px); left:50%; transform:translateX(-50%);
  width:250px; background:#111; color:#fff; text-align:left;
  font-size:13px; line-height:1.5; padding:10px 13px; border-radius:7.14px;
  opacity:0; pointer-events:none; transition:opacity .15s; z-index:80;
}
.info-tip::before{
  content:""; position:absolute; bottom:calc(100% + 3px); left:50%;
  transform:translateX(-50%);
  border:6px solid transparent; border-top-color:#111;
  opacity:0; pointer-events:none; transition:opacity .15s; z-index:80;
}
.info-tip:hover::after, .info-tip:hover::before,
.info-tip:focus-visible::after, .info-tip:focus-visible::before{ opacity:1; }

@media (max-width: 900px){
  .split{ grid-template-columns:1fr; }
  .field-grid, .type-grid, .stat-row{ grid-template-columns:1fr; }
}

/* ============================================================
   Builds page — the catalogue of proposal / pre-sale templates.
   Every value below is taken from the Figma "New Builds" frame.
   ============================================================ */
/* These pages sit on a textured panel rather than plain white. It used to be
   painted by a fixed .panel-sweep element that slid in and out; the Builds
   screens open like any other page now, so the paint lives directly on the
   body and the whole slide-in machinery is gone. */
body.theme-panel{
  background-color:#f6f6f4;
  background-image:radial-gradient(var(--dot-color) var(--dot-size), transparent var(--dot-size));
  background-size:var(--dot-gap) var(--dot-gap);
  /* Anchored to the viewport, not the document, so the texture sits still
     while the page scrolls over it — which is what the fixed element it
     replaced did. Offset by the rail's width because that element started at
     the rail's edge, so the dot grid keeps exactly the same origin. */
  background-attachment:fixed;
  background-position:var(--rail-w) 0;
  /* `html` above carries background:#fff, and a background on the root element
     stops the body's own from propagating to the canvas — so this paints only
     the body's own box rather than the whole window. On Builds the cards
     scroll inside .build-scroll, which shrinks to its content, so with two
     builds the body ended well short of the bottom and html's white showed
     through. Holding the body to at least the window's height puts the panel
     behind the whole screen; pages taller than the window already covered it
     and are unaffected. */
  min-height:100vh;
}

/* ---- the wash ----
   Home, Gallery, Proposals, Pre-sales and Builds all open with the same
   gradient. The only difference between them is how far down it sits, which
   is `--band-y`: where the ellipse's centre lands relative to the top of the
   page. That is the whole of it — one element, one gradient, one number.

   It was three separate things before (a gradient on the home screen's
   <main>, a .gallery-band inside the gallery, a background layer on the list
   screens' <body>). One element instead of three is worth having on its own:
   the five screens cannot drift apart, and there is one number to change.

   It briefly also carried its own `view-transition-name`, so the browser
   would slide it from one screen's position to the next. That is gone, and
   must not come back: an element with a view-transition-name is lifted out of
   the page into the transition overlay for the length of the animation, and
   that overlay paints above the whole document — so the wash, whose only job
   is to sit behind the page, came over the top of the text on every
   navigation. There is no ordering that fixes it. Below the root snapshot it
   is hidden by the page's own background instead, which is the same bug the
   other way up.

   The screens still fade between each other. That is done at the root
   instead — see the block after this one. Nothing here is named.

   The box is the ellipse at full size — 948 tall, its centre in the middle —
   pulled up so the centre lands on --band-y. Whatever falls outside the
   ellipse is already the page's own colour, so the overhang paints nothing. */
:root{ --band-end:#FFFFFF; --band-y:-51px; }
body.page-band{
  position:relative;
  /* A stacking context, so the wash's z-index:-1 lands above the body's own
     background rather than behind it. This does not change where a
     position:fixed descendant sits — only the order things paint in. */
  z-index:0;
}
.page-band-wash{
  position:absolute; z-index:-1; inset-inline:0; pointer-events:none;
  top:calc(var(--band-y) - 474px);
  height:948px;
  background:radial-gradient(112.03% 50% at 49.48% 50%,
    var(--band-end) 5%, #FFEAE1 41.69%, #DFE5FF 73.1%, var(--band-end) 95.23%);
}
/* Home's wash is the Home frame's own, straight from its dev-mode value:

     radial-gradient(89.75% 89.72% at 49.32% 10.28%,
       #FFF 5%, #FFEAE1 41.69%, #DFE5FF 73.1%, #FFF 95.23%)

   Note what that is and is not. It is a near-circle whose centre sits a tenth
   of the way down, sized against the whole 1920x1080 frame -- not the 948px
   band the other screens use, which is a different construction with a
   different centre. So it is scoped to Home rather than replacing the shared
   one: the value came off the Home frame and nothing says the Gallery or the
   lists were drawn with it.

   Fixed and filling the viewport, because the frame IS the screen here. The
   ends keep var(--band-end) so the Builds panel's cream still works. */
body.home-panel .page-band-wash{
  position:fixed; inset:0; height:auto;
  background:radial-gradient(89.75% 89.72% at 49.32% 10.28%,
    var(--band-end) 5%, #FFEAE1 41.69%, #DFE5FF 73.1%, var(--band-end) 95.23%);
}

/* Its ends are the page's own colour, or it would stop dead against the
   Builds panel instead of fading into it. */
body.theme-panel{ --band-end:#F6F6F4; }
/* And on the panel the dots run over the wash, so the texture carries the
   height of the page instead of vanishing behind the top of it. `fixed`
   anchors this grid to the viewport exactly as the body's own is anchored,
   so the two stay in register. */
body.theme-panel .page-band-wash{
  background-image:
    radial-gradient(var(--dot-color) var(--dot-size), transparent var(--dot-size)),
    radial-gradient(112.03% 50% at 49.48% 50%,
      var(--band-end) 5%, #FFEAE1 41.69%, #DFE5FF 73.1%, var(--band-end) 95.23%);
  background-repeat:repeat, no-repeat;
  background-size:var(--dot-gap) var(--dot-gap), 100% 100%;
  background-position:var(--rail-w) 0, 0 0;
  background-attachment:fixed, scroll;
}

/* ---- the rally on Home ---------------------------------------------------
   A background, behind everything: two paddles at the edges of the screen and
   a ball going back and forth between them. Sits with the wash above, which
   is why the stage is z-index:-1 too -- later in the source, so it paints on
   top of the wash and under every piece of content.

   Fixed rather than absolute. The rally belongs to the screen, not to the
   document: on a short window Home scrolls, and a rally that scrolled away
   with it would be a thing on the page rather than the room the page is in.

   The five poses are read straight off the storyboard (PingPong_1..5, Figma
   718:1383 in Spica Project), which comes out symmetric about the centre:

     contact  x 11.0%  y 40.2%      the ball leaves the orange paddle
     bounce   x 27.0%  y 67.9%      on the near half of the table
     apex     x 50.2%  y 40.6%      over the net, level with the paddles
     bounce   x 74.7%  y 67.9%      on the far half
     contact  x 89.8%  y 40.2%      the blue paddle returns it

   That is a real rally -- one bounce each side of the net -- which is why the
   outer arcs are short and the middle one is long. The keyframe times below
   are those same x values read as time, because the horizontal speed is
   constant: the shape of the path is the storyboard's, not invented.

   Every number is a percentage of the screen, so it holds at any window size
   with nothing to re-measure.

   TRANSFORMS, never left/top. This runs for as long as Home is open, and
   animating a position property would put the page through layout on every
   frame of it, indefinitely. So each moving thing is a full-stage layer that
   translates: a percentage on such a layer is a percentage of the STAGE,
   which is exactly the coordinate system the poses are written in, and the
   mark inside it sits at that layer's origin. */
.pp-stage{
  --rally:2s;            /* one crossing; a full loop is two of them */
  /* 55px of the 1080-tall frame the assets are drawn at, in vh because the
     stage IS the viewport. Height, not width: the paddles are sized by the
     screen's height too, and a ball measured against the width would swell
     and shrink against them every time the window changed shape -- on a wide
     short window, two paddles and a marble.

     Heights scale, widths stretch, and that is the right way round: a wider
     window is a longer table, not a bigger game. */
  --ball:5.09vh;
  --table:70.5%;         /* bounce centre + the ball's radius = the surface */
  /* Inset by the rail, not the whole viewport. The rail is fixed and opaque,
     so a stage starting at 0 puts the left paddle behind it -- half a paddle,
     which is worse than none. The table is the room the content is in. */
  position:fixed; top:0; right:0; bottom:0; left:var(--rail-w);
  z-index:-1; overflow:hidden; pointer-events:none;
}

/* The paddles hold their position and punch on contact -- a wrist flick, not
   a swing. transform-origin sits down at the handle rather than in the middle
   of the image, so the blade travels and the grip stays, which is what makes
   it read as a hit rather than a slide.

   translateX is a percentage of the PADDLE, not the screen: 8% of a 217-wide
   paddle is about 0.9% of the frame, which is the point of keeping it small.
   A full loop is two crossings, so the left paddle strikes at 0 and 100% and
   the right one halfway between. */
.pp-paddle{ position:absolute; display:block; height:37.4%; width:auto; }
/* Negative insets, and deliberately so. The served SVGs carry the full handle
   now, which the Figma export's viewBox was cutting off, so each image is
   wider than it was and the blade sits further in from its own edge. These
   two numbers put the BLADES back on the storyboard's 1.34% and 98.32%, and
   the handle runs off the side of the screen the way it does in the frames --
   cropped by the edge rather than by a box in mid-air. */
.pp-left {
  left:-1.79%; top:28.5%; transform-origin:22% 92%;
  animation:pp-punch-l calc(var(--rally) * 2) infinite;
}
.pp-right{
  right:-1.16%; top:28.5%; transform-origin:78% 92%;
  animation:pp-punch-r calc(var(--rally) * 2) infinite;
}
/* Eased in and out at both ends, and a fifth less travel than it had: 6.4% of
   the paddle and 3.2 degrees. The snap has gone -- it reads as a stroke
   rather than a flinch. */
@keyframes pp-punch-l{
  0%   { transform:translateX(6.4%) rotate(3.2deg); animation-timing-function:cubic-bezier(.45,0,.55,1); }
  13%  { transform:none; }
  87%  { transform:none; animation-timing-function:cubic-bezier(.45,0,.55,1); }
  100% { transform:translateX(6.4%) rotate(3.2deg); }
}
@keyframes pp-punch-r{
  0%   { transform:none; }
  37%  { transform:none; animation-timing-function:cubic-bezier(.45,0,.55,1); }
  50%  { transform:translateX(-6.4%) rotate(-3.2deg); animation-timing-function:cubic-bezier(.45,0,.55,1); }
  63%  { transform:none; }
  100% { transform:none; }
}

/* x and y are separate layers on purpose. One animation cannot carry a
   constant horizontal speed and a bouncing vertical one without every
   keyframe restating both, which turns retiming the bounce into a rewrite. */
.pp-layer{ position:absolute; inset:0; will-change:transform; }
.pp-ball-x{ animation:pp-x var(--rally) linear infinite alternate; }
.pp-ball-y{ animation:pp-y var(--rally) infinite; }

/* Sized by WIDTH with aspect-ratio doing the height, and centred with a
   transform rather than negative margins. Both matter: a percentage height
   resolves against the stage's HEIGHT and a percentage margin against its
   WIDTH, so the obvious `width:2.86%; height:2.86%; margin:-1.43%` gives an
   ellipse hung off its own corner. A transform percentage is the one kind
   that resolves against the element itself, which is what centring wants. */
.pp-ball{
  position:absolute; top:0; left:0; display:block;
  width:var(--ball); height:auto; aspect-ratio:1;
  transform:translate(-50%, -50%);
}

/* The two ends are measured from the PADDLE, not from the frame.

   Everything about the paddles is sized from the screen's height, while a
   plain 11%/89.8% is a fraction of its width -- so on any window that is not
   16:9 the ball drifts away from the bat it is supposed to be hitting. The
   paddle image is 239 x 404 units drawn at 37.4vh, so it is 22.12vh wide;
   -1.79% is where its outer edge sits, and the blade runs right out to the
   image's edge. 24.12vh out from there is the blade's face plus the ball's
   own radius, which is contact. The bounces in between stay percentages of
   the width, because a wider window really is a longer table. */
@keyframes pp-x{
  from{ transform:translateX(calc(-1.79% + 24.12vh)); }
  to  { transform:translateX(calc(101.16% - 24.12vh)); }
}
/* Falling is ease-in, rising is ease-out -- which is what gravity looks like.
   The four segments alternate between the two. */
@keyframes pp-y{
  0%    { transform:translateY(40.2%); animation-timing-function:cubic-bezier(.4,0,1,1); }
  20.3% { transform:translateY(67.9%); animation-timing-function:cubic-bezier(0,0,.6,1); }
  49.7% { transform:translateY(40.6%); animation-timing-function:cubic-bezier(.4,0,1,1); }
  80.8% { transform:translateY(67.9%); animation-timing-function:cubic-bezier(0,0,.6,1); }
  100%  { transform:translateY(40.2%); }
}

/* The shadow on the table: the ball's x, pinned to the surface, tightening
   and darkening as the ball comes down to meet it. */
.pp-shadow-x{ animation:pp-x var(--rally) linear infinite alternate; }
.pp-shadow{
  position:absolute; left:0; top:var(--table);
  width:var(--ball); height:auto; aspect-ratio:1 / .34;
  border-radius:50%;
  /* Flat, as the storyboard draws it: one solid tone with a hard edge, not a
     soft blur. Measured off the frames it is about a 6% darkening of the wash
     at its faintest, which is what the base tone and the opacity below come
     to between them. */
  background:rgba(60,72,110,.17);
  animation:pp-shadow var(--rally) infinite;
}
/* The centring travels inside the keyframes, because the scale would
   otherwise replace it -- one transform property, not two. */
@keyframes pp-shadow{
  0%    { transform:translate(-50%,-50%) scale(2.1); opacity:.30; animation-timing-function:cubic-bezier(.4,0,1,1); }
  20.3% { transform:translate(-50%,-50%) scale(1);   opacity:.85; animation-timing-function:cubic-bezier(0,0,.6,1); }
  49.7% { transform:translate(-50%,-50%) scale(2.1); opacity:.28; animation-timing-function:cubic-bezier(.4,0,1,1); }
  80.8% { transform:translate(-50%,-50%) scale(1);   opacity:.85; animation-timing-function:cubic-bezier(0,0,.6,1); }
  100%  { transform:translate(-50%,-50%) scale(2.1); opacity:.30; }
}

/* The shadow the ball casts on the paddle it is meeting.

   Read off PingPong_1 and _5 rather than invented: it is a FLAT circle the
   size of the ball, hard-edged, sitting just INSIDE the blade at the ball's
   own height -- tucked behind the ball, toward the middle of the blade. Not a
   soft grey cloud, and not outside the paddle.

   Plain black at low alpha rather than a colour, because that darkens
   whatever is under it by a fixed fraction: the storyboard's orange goes
   (235,125,74) -> (212,104,57) and its blue (131,170,233) -> (120,148,193),
   which is the same ~15% knock-down on both. One value, two paddles, and it
   stays right if either blade is ever recoloured.

   Centred on its own position, so the numbers below are the middle of the
   circle -- the earlier version was anchored by its corner, which is what put
   it off the edge of the blade.

   x 8.6% is where it measures on the orange blade (which spans 1.34-9.73%),
   a bit over a percent inside the edge; the blue one is that same inset
   mirrored into its blade (89.93-98.32%). */
.pp-hit{
  position:absolute; width:calc(var(--ball) * .49); height:auto; aspect-ratio:1 / 1.7;
  border-radius:50%; opacity:0;
  background:rgba(0,0,0,.037);
  /* Not a circle: measured off the storyboard it is an ellipse about 1.7
     times taller than wide, leaning ~6 degrees with the blade -- a round ball
     casting onto a tilted oval face. The blade's own major axis measures 6
     degrees off vertical the same way, which is why the two agree. */
  transform:translate(-50%, -50%) rotate(-6deg);
}
/* 3vh in from the blade's outer edge, so a ball-sized circle clears it with
   a little to spare and never spills onto the background. Same reasoning as
   the contact points above: measured from the paddle, not from the frame. */
.pp-hit-left { left:calc(-1.79% + 19.12vh);  top:40.2%; animation:pp-hit-l calc(var(--rally) * 2) infinite; }
.pp-hit-right{ left:calc(101.16% - 19.12vh); top:40.2%; animation:pp-hit-r calc(var(--rally) * 2) infinite; }
/* A shadow with a whole life, not a flash. It shows a second out, wide and
   invisible, and draws in and darkens as the ball closes on the blade --
   tight and at full strength exactly on the contact. Then the reverse, over
   the second after: it spreads back out and fades to nothing as the ball
   leaves.

   The loop is two 2s crossings, so a second is 25% of it. The two paddles
   therefore tile it exactly between them: the left one lives 75-100% and
   0-25%, the right one 25-75%, and they hand over at the quarter marks. One
   shadow on the screen at a time, which is also what is physically true --
   the ball can only be near one bat.

   Eased so the movement is quick at the blade and slow far from it: ease-in
   on the way in, ease-out on the way back. A shadow changes fastest when its
   object is closest to the surface.

   The transform is restated in full in every frame because a keyframe
   replaces the property outright -- leave the translate out of one and the
   ellipse jumps off its own centre for that frame. */
@keyframes pp-hit-l{
  0%    { opacity:1; transform:translate(-50%,-50%) rotate(-6deg) scale(1);
          animation-timing-function:cubic-bezier(.25,.5,.55,1); }
  25%   { opacity:0; transform:translate(-50%,-50%) rotate(-6deg) scale(2); }
  75%   { opacity:0; transform:translate(-50%,-50%) rotate(-6deg) scale(2);
          animation-timing-function:cubic-bezier(.45,0,.75,.5); }
  100%  { opacity:1; transform:translate(-50%,-50%) rotate(-6deg) scale(1); }
}
@keyframes pp-hit-r{
  0%    { opacity:0; transform:translate(-50%,-50%) rotate(-6deg) scale(2); }
  25%   { opacity:0; transform:translate(-50%,-50%) rotate(-6deg) scale(2);
          animation-timing-function:cubic-bezier(.45,0,.75,.5); }
  50%   { opacity:1; transform:translate(-50%,-50%) rotate(-6deg) scale(1);
          animation-timing-function:cubic-bezier(.25,.5,.55,1); }
  75%   { opacity:0; transform:translate(-50%,-50%) rotate(-6deg) scale(2); }
  100%  { opacity:0; transform:translate(-50%,-50%) rotate(-6deg) scale(2); }
}

/* ---- the rest of the backdrop ----------------------------------------
   Two more things behind the content, from the Home frame (Figma 523:2306):
   the lines-and-stars plate up the right side and the word "Design" set huge
   and nearly invisible in the bottom corner. Both are decoration -- the whole
   stage is aria-hidden and nothing in it can be clicked.

   (The line of mono type under the cards used to live here too. It is in the
   page flow now, as .home-line, because a backdrop position is a fraction of
   the screen while the content around it is not: on any window that is not
   1920x1080 the two drifted apart and the line landed on "Your recent
   activity".)

   Sized in vh like the rally, for the same reason: they are pinned to the
   right-hand edge and the bottom, and a width-relative size would swell and
   shrink against the paddles every time the window changed shape. */

/* Tomer's export, 591 x 1080 -- drawn to the full height of the frame, so it
   is simply given that height and hung off the right edge. */
.pp-stars{
  position:absolute; top:0; right:0; height:100%; width:auto;
  opacity:.9;
}

/* Runs off the right edge on purpose: the frame shows it cut. Instrument
   Serif, the same face the sign-in headline is set in. */
.pp-design{
  position:absolute; right:-2vh; bottom:-4.5vh;
  margin:0; font-family:'Instrument Serif', Georgia, serif; font-weight:400;
  font-size:26vh; line-height:1; letter-spacing:-.01em;
  color:#E9EDFB; white-space:nowrap;
}

/* Asked for less movement: the rally holds at its apex, so the screen still
   reads as a rally rather than losing the ball altogether. */
@media (prefers-reduced-motion: reduce){
  .pp-ball-x, .pp-ball-y, .pp-shadow-x, .pp-shadow, .pp-hit,
  .pp-left, .pp-right{ animation:none; }
  .pp-ball-x{ transform:translateX(50.2%); }
  .pp-ball-y{ transform:translateY(40.6%); }
  .pp-shadow-x{ transform:translateX(50.2%); }
  .pp-shadow{ transform:translate(-50%,-50%) scale(2.1); opacity:.28; }
}

/* ---- moving between screens ----
   The screen being left fades out and the one arriving fades in, over the
   wash going with them. It is a cross-document View Transition, and the one
   rule that matters is that NOTHING on these pages carries a
   `view-transition-name`. Only `root` is animated, so the browser takes one
   snapshot of the whole page — background, wash, rail, text, in their real
   paint order — and fades that. The moment any single element is named it is
   pulled out of that snapshot into the overlay above the document, which is
   what put the wash over the text the first time this was tried.

   The leaving screen goes first and a little faster, so the arriving one
   reads as coming in behind it rather than the two dissolving into each
   other. Both start together: a gap between them shows bare canvas and
   flashes. */
@view-transition{ navigation:auto; }
@keyframes page-out{ from{ opacity:1 } to{ opacity:0 } }
@keyframes page-in { from{ opacity:0 } to{ opacity:1 } }
::view-transition-old(root){
  animation:page-out 170ms cubic-bezier(.4,0,1,1) both;
}
::view-transition-new(root){
  animation:page-in 260ms cubic-bezier(0,0,.2,1) both;
}
/* A browser without view transitions changes screens the way it always did,
   and so does anyone who asked their system not to animate. */
@media (prefers-reduced-motion: reduce){
  ::view-transition-old(root),
  ::view-transition-new(root){ animation-duration:1ms; }
}

/* Fine dot texture over the panel, matching the Figma texture fill.
   Tune with these three: colour/opacity, dot size, spacing. */
:root{
  --dot-color: rgba(0,0,0,.07);
  --dot-size: .8px;    /* radius — ~1.6px dots */
  --dot-gap: 25px;
}

.theme-panel .admin-header{ background:none; }
.theme-panel .wordmark{ color:#111; }
.theme-panel .page-head h1{ color:#111; }
.theme-panel .page-head .sub{ color:var(--muted); }

.builds-head{ display:block; margin-bottom:0; }
/* Rule under the page intro (Figma: 1421px wide, sits 26px below the sub). */
.builds-rule{ height:1px; background:var(--frame-border); margin:26px 0 0; }

/* --- Proposals / Pre-sale switch (top right of the list) --- */
.build-tabs{ display:flex; justify-content:flex-end; gap:16.89px; margin:29px 0 28px; }
.build-tab,
.build-badge{
  display:inline-flex; align-items:center; justify-content:center; gap:7.15px;
  background:#fff; border:1px solid var(--frame-border); border-radius:8.5px;
  padding:7.777px 15.554px;
  color:var(--muted); font-size:15.554px; line-height:1; text-decoration:none;
  filter:drop-shadow(5px 6px 0 rgba(0,0,0,.05));
  white-space:nowrap;
}
.build-tab img, .build-badge img{ width:30.493px; height:30.493px; display:block; }
/* The badge shares the tab's box but not its colours: it is a label, not a
   control, so it wears the notice-pill blue the proposals list uses for
   New / Duplicated / Build Change. */
/* --green-pill, not a blue of its own: the icon inside is
   /assets/menu/on/orders.svg, which is the rail's artwork and carries that
   pill baked into it at #D5E6FC. Any other blue here and the icon's own
   background shows as a square sitting on the badge. The active Builds tab
   holds the same art and is right for the same reason. */
.build-badge{ background:var(--green-pill); border-color:var(--green-pill); color:var(--green); }
/* Grey icon at rest, blue once the tab is active or hovered. */
.tab-ico{ display:block; width:30.493px; height:30.493px; position:relative; }
.tab-ico .ico-on{ position:absolute; inset:0; opacity:0; transition:opacity .15s; }
.tab-ico .ico-rest{ transition:opacity .15s; }
.build-tab.on .tab-ico .ico-on, .build-tab:hover .tab-ico .ico-on{ opacity:1; }
.build-tab.on .tab-ico .ico-rest, .build-tab:hover .tab-ico .ico-rest{ opacity:0; }
.build-tab.on{ background:var(--green-pill); border-color:var(--green-border); color:var(--green); }
.build-tab:hover{ border-color:#B6D6FF; color:var(--green); }

/* --- The card grid: two 693px cards per row, 35px apart --- */
.build-grid{
  display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:35px;
  /* Cards in a row take the height of the tallest, so a long name doesn't
     leave its neighbour short. */
  align-items:stretch;
}
/* Only the builds scroll; the page head and tabs stay put.
   The slider is drawn as real elements and driven by script, so it can match
   the design exactly: a browser scrollbar computes the thumb's width and its
   corner radius on the same box, which makes a narrow thumb taper to a point. */
.build-scroll-wrap{ position:relative; }
.build-scroll{
  overflow-y:auto; overflow-x:hidden;
  max-height:calc(100vh - 300px);
  /* Room on all four sides for a card that grows and for the shadow under it,
     taken straight back with negative margins so the grid sits exactly where
     it did. This box clips — `overflow-x:hidden`, and `overflow-y:auto` clips
     too — and it was slicing the growing card's left edge and, at the foot of
     the list, the bottom of its shadow. The 24 on the sides was measured for
     the hard offset shadow these cards used to draw; the soft one now blurs
     22, so it is still the right number and there is a little slack.
     Measured clear on all four sides at 1500, 1600, 1800 and 2200. */
  padding:8px 24px 26px; margin-inline:-24px; margin-block:-8px -26px;
  scrollbar-width:none;                 /* the native bar is hidden */
}
.build-scroll::-webkit-scrollbar{ display:none; }

/* Figma: 12px track, 4px thumb, both fully rounded, thumb inset 4px each
   side. Track and thumb lightened with the rest of the screen. */
.bslider{
  position:absolute; top:20px; right:-24px; width:12px;
  height:calc(100% - 20px);            /* 20px inset at the top */
  background:#EDEDEA; border-radius:51px;
}
.bslider-thumb{
  position:absolute; left:4px; width:4px; top:0; height:0;
  background:rgba(120,120,120,.55); border-radius:51px;
  cursor:grab;
}
.bslider-thumb:active{ cursor:grabbing; }
.build-empty{
  color:var(--muted); font-size:15px; background:#fff; border:1px solid var(--frame-border);
  border-radius:11.611px; padding:38px 30px; width:100%;
}

.build-card{
  display:flex; align-items:flex-start;
  width:100%; max-width:693px;
  background:#fff; border:.546px solid var(--frame-border); border-radius:11.611px;
  padding:25px 30px;
  /* The home screen's card shadow, not a hard offset of its own. These are the
     same kind of object — a container you click into — and two screens using
     two shadows for it reads as two products. */
  box-shadow:0 4px 14px 0 rgba(0, 0, 0, 0.08);
  transition:transform .26s cubic-bezier(.22,.61,.36,1), box-shadow .26s ease;
}
/* And the same answer under the cursor: 1% and the deeper shadow, which is a
   third of what the picture inside it does — a card lifting is a statement
   about the card and should not compete with the thing being pointed at. */
.build-card:hover{
  transform:scale(1.01);
  box-shadow:0 8px 22px 0 rgba(0, 0, 0, 0.12);
}
@media (prefers-reduced-motion: reduce){
  .build-card{ transition:none; }
}
.build-main{
  flex:1 0 0; min-width:0; align-self:stretch;
  display:flex; flex-direction:column; gap:21px; align-items:flex-start;
}
/* The whole column opens the build. It is not a button and gets no frame of
   its own — the cursor and the name warming to the interface's navy are the
   whole of the affordance, which is as much as a card that is already one
   piece needs. */
.build-main[data-href]{ cursor:pointer; }
.build-main[data-href] .b-name{ transition:color .15s; }
.build-main[data-href]:hover .b-name{ color:#17317A; }

/* Name / value blocks */
.build-name{ display:flex; flex-direction:column; width:100%; line-height:1.53; }
.b-label{ color:var(--muted); font-size:13.66px; letter-spacing:.2732px; }
.b-name{ color:#111; font-size:31px; }
.build-field{ display:flex; flex-direction:column; gap:3px; width:100%; }
.b-cap{ color:var(--muted); font-size:12px; letter-spacing:.72px; line-height:1; }
.b-val{ color:#111; font-size:17.58px; letter-spacing:1.0548px; line-height:1.53; white-space:nowrap; }

/* ---- "Last Used for": the proposals built on this build ----
   The newest one is a link; any others sit behind a capsule that opens a
   list of links. See usedByBlock() in render-admin.mjs. */
.b-used{ display:inline-flex; align-items:baseline; gap:9px; }
.b-used-name{
  color:#111; text-decoration:none;
  border-bottom:1px solid transparent; transition:color .15s, border-color .15s;
}
.b-used-name:hover{ color:var(--green); border-bottom-color:var(--green); }

/* Small, grey and quiet: it is a footnote on the line, not a second value. */
.b-more{
  flex:none; align-self:center; font-family:inherit; cursor:pointer;
  padding:2px 9px; border-radius:100px;
  background:#EFEFEC; border:0.3px solid var(--chip-stroke-on-bar);
  color:var(--muted); font-size:11px; letter-spacing:.3px; line-height:1.5;
  white-space:nowrap;
  transition:background .15s, border-color .15s, color .15s;
}
.b-more:hover,
.b-more[aria-expanded="true"]{ background:#CCE2FF; border-color:#B6D6FF; color:var(--green); }

/* The popover admin.js fills from .b-more-list. Unlike .ui-tip this one takes
   pointer events — its links are the whole point — so it also has to survive
   the pointer crossing the gap between the capsule and itself. */
.b-pop{
  position:fixed; z-index:80;
  min-width:150px; max-width:280px; padding:6px;
  background:#FFF; border:0.3px solid var(--frame-border); border-radius:8px;
  box-shadow:0 10px 28px rgba(0,0,0,.16);
  opacity:0; visibility:hidden;
  transition:opacity .12s ease, visibility 0s linear .12s;
}
.b-pop.on{ opacity:1; visibility:visible; transition-delay:0s; }
.b-pop a{
  display:block; padding:6px 9px; border-radius:5px;
  color:#111; font-family:inherit; font-size:13px; line-height:1.35;
  letter-spacing:0; text-decoration:none;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.b-pop a:hover{ background:#CCE2FF; color:var(--green); }

/* Component pills */
.build-comp{ display:flex; flex-direction:column; gap:10px; width:100%; margin-top:23px; }
.comp-pills{ display:flex; flex-wrap:wrap; gap:7.89px; }
.comp-pill{
  background:var(--gray-pill); border:1px solid transparent; border-radius:17.62px;
  padding:8.22px 13.44px;
  color:#4a4a4a; font-family:inherit; font-size:9px; letter-spacing:.54px;
  line-height:1; white-space:nowrap; cursor:pointer;
  transition:border-color .15s, color .15s, background .15s;
}
.comp-pill:hover{ background:#CCE2FF; border-color:#B6D6FF; color:var(--green); }
.comp-pill.on{ background:var(--green-pill); border-color:var(--green-border); color:var(--green); }

/* --- Right column: actions above the slide preview --- */
.build-side{
  align-self:stretch; flex:none;
  width:298.008px;                  /* same width as the slide preview */
  display:flex; flex-direction:column; align-items:stretch;
}
.build-actions{
  /* Five fixed buttons on one line — never let one drop to a second row.
     10px apart, as the proposals row. */
  display:flex; flex-wrap:nowrap; gap:10px;
  align-items:center; justify-content:flex-end; width:100%;
  margin-bottom:23px;
}
/* Icon-only chips, 61.6 x 46.05, matching the badge and tab styling. */
/* The proposals action chip, to the pixel: the same 33px circle, the same
   #111 glyph at 15px, the same border, fill and shadow. See
   .order-actions .btn and .btn.small.icon-only — this is that button on
   another screen, not a cousin of it. */
.build-btn{
  display:inline-flex; align-items:center; justify-content:center;
  width:33px; height:33px; flex:none;
  background:#FFF; border:0.3px solid #E4E4E4; border-radius:50%;
  padding:0; cursor:pointer; text-decoration:none; color:#111;
  box-shadow:0 0 7.368px 0 rgba(0,0,0,.068);
  transition:background .15s, border-color .15s;
}
/* One size for all of them, matching the proposals chips rather than the
   per-icon widths the old dark artwork carried.

   Stroke weight has to be converted, not copied. The proposals glyphs are
   20-unit viewBoxes stroked 1.4 and drawn at 15px, so they land at
   1.4 x 15/20 = 1.05px on screen. These are Lucide, on a 24-unit viewBox, so
   the same 1.05px needs 1.05 x 24/15 = 1.68. Setting it here rather than in
   the constants keeps the weight tied to the size it is drawn at — the same
   glyphs appear at 30px in the header chips and 16.293px in the bar, where
   Lucide's own 2 is right. */
.build-btn svg{ display:block; width:15px; height:15px; stroke-width:1.68; }
.build-btn:hover{ background:#CCE2FF; border-color:#B6D6FF; color:var(--green); }
.build-btn:disabled{ cursor:default; }
.build-btn:disabled:hover{ background:#FFF; border-color:#E4E4E4; color:#111; }
/* Locked: the lock alone wears the blue pill and sits a touch wider; the
   other buttons keep their box and simply wear their grey icons. */
/* Keeps the same circle as its neighbours and only changes colour, so the row
   still fits exactly and the locked build reads by fill, not by shape. */
.lock-btn.on{
  border:0.3px solid var(--green-border); background:var(--green-pill);
  border-radius:50%;
  box-shadow:0 0 7.368px 0 rgba(0,0,0,.068);
}
.lock-btn.on:hover{ background:var(--green-pill); border-color:var(--green-border); }
/* The locked lock is the same size as its neighbours — it reads by fill. */
.build-btn.is-off{ opacity:.5; }

/* A fresh copy says so, until it is opened. */
.build-dup{
  font-style:normal; font-weight:700; color:var(--green); margin-right:10px;
}

/* Slide preview — 298.008 x 167.629 is exactly 1920x1080 at 0.15521 */
.build-thumb{ flex-direction:column; gap:17px; align-items:center; }
.build-thumb .thumb-frame{
  width:298.008px; height:167.629px;
  border:0; border-radius:10.2px;
  /* White, matching the slide itself, so a preview that is still loading
     doesn't show the panel through it and then flash to white. The
     background is clipped by the radius, and the iframe overfills slightly
     (see below), so no white edge shows at the corners. */
  background:#fff;
  /* A soft lift rather than the hard offset: on a light panel a zero-blur
     shadow reads as a flat grey slab. */
  box-shadow:0 8px 22px rgba(0,0,0,.10);
}
/* A hair over the exact 0.15521 so no sliver of the frame's own background
   shows along the right and bottom edges; the overflow is cropped. */
.build-thumb .thumb-frame iframe{ top:0; transform:scale(0.1556); }
/* The preview opens the build, so it says so under the cursor: 3% and a little
   more lift.

   The number here is not 3%. Pointing at the picture also hovers the card
   around it, and nested transforms multiply — a plain 1.03 inside the card's
   own growth overshoots. So the picture adds only 1.03/1.01, and what the eye
   gets is the 3% asked for. Elsewhere (a proposal row) nothing is scaling
   around it and the plain 1.03 above applies. */
.build-thumb .thumb-frame[data-href]{ cursor:pointer; }
.build-thumb .thumb-frame:hover{ transform:scale(1.0198); }
.build-thumb .thumb-frame[data-href]:hover{ box-shadow:0 14px 30px rgba(0,0,0,.14); }
.build-thumb .thumb-page{ display:none; }
.build-arrows{ display:flex; align-items:center; gap:45.41px; }
.build-thumb .thumb-arrow{
  width:29.3px; height:29.3px; border:0; background:none;
  color:var(--muted); padding:0;
}
.build-thumb .thumb-arrow:hover{ color:var(--green); }
.build-thumb .thumb-arrow svg{ width:29.3px; height:29.3px; }

/* The builds area is wider than the rest of the app: 693 + 35 + 693. */
.theme-panel .admin-main{ max-width:1421px; }
@media (max-width: 1560px){
  .build-grid{ grid-template-columns:minmax(0, 1fr); }
}

/* ---------------------------------------------------------------------------
   Build slide editor — Figma frame 117:4351.
   The slide is the editing surface, centred, with a settings strip and a tool
   strip above it. Everything spans the same 1318px column and is scaled down
   as one piece on a narrower screen.
--------------------------------------------------------------------------- */

/* The editor is one fixed-size block: it sits in the middle of the window,
   both across and down, whatever room is left beside the rail. */
body.theme-panel .admin-main:has(.be-stage){
  max-width:none; width:100%; padding:0 32px;
  min-height:100vh; display:flex; flex-direction:column; justify-content:center;
}
.be-scaler{ width:100%; }
.be-stage{ margin-inline:auto; }
/* On this page the logo sits beside the top row rather than above it, so the
   header floats and the content starts at the very top — as in the frame,
   where the row begins 37px down. */
body.theme-panel:has(.be-stage) .admin-header{
  position:absolute; top:0; left:var(--rail-w); right:0; z-index:2;
  /* It floats over the top row, so only the logo itself takes clicks —
     otherwise it would swallow the Back and Saved buttons underneath. */
  pointer-events:none;
}
body.theme-panel:has(.be-stage) .admin-header .brand{ pointer-events:auto; }
.be-scaler{ min-width:0; }
.be-stage{ width:1318px; transform-origin:top left; }

/* ---- top row ---- */
/* ---- the strip above the rule ----
   Redrawn to the frame, Sep 2026. Every number is measured against the
   toolbar's own round buttons, which the frame still draws at 37: the chips
   come down from 60 to 43, they sit 9 apart rather than 31, and the three
   columns are one group set 61 clear of the last chip so a longer lock label
   cannot drag them sideways. */
.be-head{ display:flex; align-items:center; gap:9px; margin:0; height:45px; }
/* Content-sized, 112 apart — the frame's, and the reason they are not equal
   thirds: "02/07/26" is a third the width of a build name and an equal column
   would leave a hole beside it. */
.be-headcols{ display:flex; align-items:center; gap:112px; margin-inline-start:61px; }
/* The proposals pill, at this row's own height. The radius is the same
   73.684px the Figma chip specifies, which on a 60px box simply rounds the
   ends; the border, fill and shadow are the proposals values. Height, padding,
   type size and the min-widths are the frame's and are left alone. */
.be-chip{
  display:inline-flex; align-items:center; justify-content:center; gap:9.25px;
  height:45px; padding:0 15.554px;
  /* Read off Tomer's 1914px export of the four chips, Sep 2026, where a chip
     measures 367x157 device pixels — 3.4889x — so everything below is a
     measurement rather than a preference.

     No fill: the interiors sample as the page's own #F6F6F4 exactly.
     0.5 hairline at #C9C9C9, not the 0.3 of #E4E4E4 this used to carry.
     A 5px corner, which the export gives twice over — the top edge's straight
     run begins 14 device pixels in, and the left border reaches the edge 20
     down.

     The shadow is the thing that was wrong: it has NO BLUR. It is a flat block
     of #E9E9E8 offset down and to the right, 15 device pixels deep under the
     bottom border and 19 beside the right one, dead uniform in between and one
     antialiased pixel at its outer edge. A blurred shadow cannot produce that.
     The offsets below are those bands at this scale; see the note in the
     handover about getting Figma's own numbers for them. */
  background:transparent; border:0.5px solid #C9C9C9; border-radius:5px;
  box-shadow:5px 4px 0 0 #E9E9E8;
  transition:background .15s, border-color .15s;
  /* #3C3C3C on the icons and the labels — the export's ink, and a good deal
     darker than the --muted these carried. "Back" is the exception: its label
     alone is #676767 in the frame while its arrow stays dark, which reads as a
     way out rather than a thing to do, so it is set on the span below. */
  color:#3C3C3C;
  font-size:12.5px; line-height:1; text-decoration:none; white-space:nowrap;
}
/* 22px beside 15.554px type, at the same 1.05px on-screen stroke the whole
   system uses — 1.05 x 24/22 = 1.145 on Lucide's 24-unit viewBox. The old
   30px came from the image assets these replaced, not from the frame. */
/* 18, not 22: the frame's glyphs measure 15-16 of ink, and a lucide path fills
   about 0.88 of its box. Stroke held at the system's on-screen 1.05, which on
   an 18px box off a 24-unit viewBox is 1.4. */
.be-chip svg{ width:18px; height:18px; display:block; stroke-width:1.4; }
.be-back{ min-width:105px; }
.be-back span{ color:#676767; }
.be-back:hover{ background:#CCE2FF; border-color:#B6D6FF; color:var(--green); }
.be-saved{ min-width:108.3px; }
.be-saved.busy{ color:var(--green); }
.be-saved.dirty{ color:#111; }
.be-saved.bad{ color:var(--red); border-color:#f0b4b0; }
/* Same chip, same reason as .build-badge: it holds the rail's own artwork,
   so it has to wear the pill that artwork was drawn on. */
/* The one that says what this build is, so it is the one that is filled. The
   navy is the export's own ink for it, and it keeps a border — a blue a shade
   deeper than the fill — which this used to hide by matching the two. */
.be-kind{
  min-width:126.8px; color:#22326F;
  background:var(--green-pill); border-color:#A5BEED;
}
.be-kind span{ color:#22326F; }
/* Same lock as the card wears, in chip form: state on the face, popup on click. */
.be-lock{
  min-width:105px; cursor:pointer;
  font-family:inherit;
}
.be-lock svg{ width:18px; height:18px; display:block; }
.be-lock:hover{ background:#CCE2FF; border-color:#B6D6FF; color:var(--green); }
.be-lock.on{ background:var(--green-pill); border-color:var(--green-border); color:var(--green); }
.be-lock.on:hover{ border-color:var(--green-border); color:var(--green); }
.be-kind img{ width:18px; height:18px; display:block; }

.be-meta{ display:flex; flex-direction:column; gap:5px; }
.be-meta .b-cap{ font-size:12px; color:var(--muted); letter-spacing:.6px; font-weight:500; line-height:1.5; }
/* 20, fitted on width rather than on height: the three values in the frame —
   a build name, a date, a proposal name — all come back within 1.5% of this
   face at 20, which is a far tighter test than measuring a cap height through
   antialiasing at 1.8x. It was 25. */
.be-meta .b-val{ font-size:20px; color:#111; line-height:1; }

.be-stage .builds-rule{ margin:23px 0 0; }

/* ---- the build bar ----
   One bar now, not two. 1330 is the slide's 1318 plus six either side, which
   is how the frame draws it: the bar overhangs the slide rather than lining up
   with it. Its three groups sit at the ends and the middle, so the bar is
   space-between with a 25px gutter and no gap of its own. */
.be-bars{
  position:relative; z-index:5;
  display:flex; align-items:center; justify-content:space-between;
  width:1330px; height:51px; margin:21px 0 0 -6px;
  padding:0 25px; background:#F6F6F4; border-radius:15px;
}
.be-fonts{ display:flex; align-items:center; gap:12px; height:100%; }
.be-colours{ display:flex; align-items:center; gap:9px; height:100%; }
.be-tools{ display:flex; align-items:center; gap:21px; }

/* A named face. The caption is small and grey, the name is set in the face it
   names at 18 — the one place in the editor where the type is the content.

   Both boxes are a fixed width, not a hugging one. Stepping through pairings
   otherwise made the whole bar breathe in and out — "Zain" and "Special Gothic
   Condensed One" are not the same width, and a toolbar that resizes under the
   cursor is unusable. The two widths are the frame's own, measured at Fraunces
   and Google Flex. */
.be-font{
  display:flex; align-items:center; gap:12px; height:100%; flex:none;
  padding:11px 30px 11px 25px;
  background:#F6F6F4; border:.4px solid #C3C3C3; border-radius:7px;
  cursor:pointer; text-align:left; font-family:inherit;
  transition:background .15s, border-color .15s;
}
/* The same blue as every other control in this bar and in the strip beside it,
   rather than a grey of its own: one hover across the whole panel. */
.be-font:hover{ background:#CCE2FF; border-color:#B6D6FF; }
.be-font[disabled]{ cursor:default; opacity:.55; }
.be-font[disabled]:hover{ background:#F6F6F4; border-color:#C3C3C3; }
.be-fonts .be-font:first-child{ width:179.8px; }
.be-fonts .be-font:nth-child(3){ width:226px; }
.be-fcap{ font-size:10px; letter-spacing:.7px; color:#474747; white-space:nowrap; }
.be-fname{
  font-size:18px; letter-spacing:.36px; color:#1E1E1E; line-height:1.6;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0;
}

/* ---- Save this dressing as a design ----
   Was a fixed-width button carrying three stacked lines of text; it is now the
   heart, which is one of the round buttons and takes all of its geometry from
   them. Everything that made the worded version work — its own width so the
   space-between bar did not breathe, its three measured line widths — went
   with the words.

   What stayed is the [hidden] rule, and it is the only reason this selector
   still exists: the button is shown only while the build is dressed, and the
   editor brings it back the moment a swatch or the slider moves, so it lives
   in the markup permanently and `hidden` decides. */
/* Hidden, but still taking up its room. display:none would hand its 37px
   and its 21px gap back to a space-between bar, and every control to its left
   would slide over — including the slider, which is the one control you can
   be holding at the time. Moving the slider is what makes a build dressed, so
   that is not a rare collision: it is the first thing that happens every time
   the slider is used on a build that was in a design.

   The same reasoning the font boxes' fixed widths are already written down
   for: a toolbar that resizes under the cursor is unusable. */
.be-save-design[hidden]{ visibility:hidden; pointer-events:none; }

/* ---- the roundness slider ----
   Every number here is off the frame. The one that mattered: the control is
   25px tall and 77 wide, NOT one of the 37px round buttons — it is a smaller
   object sitting on a pale plate, which is why matching it to its neighbours
   read as wrong however right the rest was.

   Frame 1321315013  77 x 25, padding 1px 7.26px, radius 15, border 1px #000
   Group 1597879788  the rail, 58.88 x 9
   Rectangle 22925   the track, 52 x 3, radius 60, 90deg gradient
   the thumb         9 x 9, radius 9, 1px #717171 on #FFF, shadow 0 2px 0 0

   The plate's own height is the one value not in the frame's panel. It takes
   the round buttons' 37px, because that is measured and because the plate is
   what gives the slider the same two edges as everything else on the bar; its
   width is the frame's 77 plus the frame's own 7.26 either side. If the plate
   has its own numbers in Figma, these two are what to replace.

   The gradient's stops and the shadow's alpha were cut off in the panel, so
   they are the nearest read off the picture rather than the file — the only
   two values here that are not exact. */
.be-slider{
  position:relative; flex:none;
  width:91.52px; height:37px;
  background:#F6F6F4; border:.4px solid #C3C3C3;
  cursor:ew-resize; touch-action:none;
}
.be-slider[data-locked]{ cursor:default; opacity:.5; }
.be-slider:hover{ background:#CCE2FF; border-color:#B6D6FF; }
.be-slider:focus-visible{ outline:2px solid #17317A; outline-offset:2px; }

/* The stroked capsule. Its radius is the value the slider is setting, so it
   is written by the script; 15px is where the frame draws it, and 25px tall
   makes 12.5 a full capsule — so the frame's own 15 is already past round,
   which is what puts the top of the slider unambiguously at a capsule. */
.be-slider-body{
  position:absolute; left:7.26px; top:6px;
  width:77px; height:25px; box-sizing:border-box;
  border:1px solid #000;
  transition:border-radius .12s;
}
/* The rail is the frame's own icon: 58.88 x 9, centred. The thumb travels its
   width less its own — 49.88 — which is the number the frame marks. */
.be-slider-rail{
  position:absolute; left:50%; top:50%;
  width:58.88px; height:9px; margin:-4.5px 0 0 -29.44px;
}
.be-slider-track{
  position:absolute; left:6.88px; top:3px; width:52px; height:3px;
  border-radius:60px;
  background:linear-gradient(90deg,#B4B4B4,#3A3A3A);
}
.be-slider-thumb{
  position:absolute; top:0; left:0; width:9px; height:9px; box-sizing:border-box;
  border-radius:9px; background:#FFF; border:1px solid #717171;
  box-shadow:0 2px 0 0 rgba(0,0,0,.25);
}
/* A build that has never touched the slider is not sitting on a value, so the
   thumb is drawn hollow: it shows where the design's own roundness falls
   without claiming the build chose it. */
.be-slider[data-round=""] .be-slider-thumb{ background:#EDEDED; border-color:#C3C3C3; box-shadow:none; }

/* Every round control in the bar is the same 37px circle: the pairing list,
   the colours list, and the four tools. */
.be-round{
  display:flex; align-items:center; justify-content:center; flex:none;
  width:37px; height:37px; padding:0;
  background:#F6F6F4; border:.4px solid #C3C3C3; border-radius:50%;
  color:#1E1E1E; cursor:pointer; text-decoration:none;
  transition:background .15s, border-color .15s, color .15s;
}
.be-round svg{ width:18px; height:18px; display:block; }
.be-round:hover{ background:#CCE2FF; border-color:#B6D6FF; color:var(--green); }
.be-round[disabled]{ opacity:.35; cursor:default; }
.be-round[disabled]:hover{ background:#F6F6F4; border-color:#C3C3C3; color:#1E1E1E; }

/* The build's four tones, as four blocks inside one capsule. The capsule
   clips them, which is what gives the strip its rounded ends without any of
   the blocks knowing about it. */
.be-tones{
  display:flex; align-items:stretch; height:100%;
  /* Sep 2026: the toolbar's own Figma board redrew this narrower — each
     block now hugs its label (like .be-tonechip already did) instead of the
     four splitting 360.8px evenly, which is what forced "BG"/"Txt" wider than
     their own content just to match "Cap A"/"Cap B". That 360.8px number had
     its own history (see the block below it before this comment replaced it:
     a past shrink to 248.28 was reverted as wrong), so this isn't solving the
     same layout problem again — it's a new frame superseding the old one.
     The per-block padding below is read off the new screenshot, not a
     measured frame; confirm the real numbers against Figma. */
  background:#fff; border:.4px solid #C3C3C3; border-radius:100px; overflow:hidden;
}
/* Each block is a label wrapping its own colour input, so the whole block is
   the hit target and the browser opens its picker without a line of script.
   position:relative is for the input, which sits inside rather than being
   display:none — a hidden input opens the picker in the corner of the screen
   on some platforms, and one anchored to its own block opens over the block. */
.be-tone{
  position:relative; flex:none; display:flex; align-items:center;
  padding:0 16px 0 14px; min-width:0; cursor:pointer;
}
.be-tone.is-busy{ cursor:default; opacity:.6; }
.be-tone input[type="color"]{
  position:absolute; left:50%; bottom:0; width:1px; height:1px;
  padding:0; margin:0; border:0; background:none;
  opacity:0; pointer-events:none;
}
/* The chip hugs its label rather than being a fixed circle: "BG" and "Txt" come
   out round, "Cap A" and "Cap B" come out as capsules, which is what the frame
   draws. A fixed 23 wrapped the two-word ones onto a second line. */
.be-tonechip{
  display:flex; align-items:center; justify-content:center; flex:none;
  min-width:23px; height:23px; padding:0 7px; border-radius:60px;
  background:#fff; border:.5px solid #CACACA; white-space:nowrap;
  font-size:8px; font-weight:500; letter-spacing:.72px; color:#000; line-height:1;
  position:relative; pointer-events:none;
  transition:background .15s, border-color .15s;
}
/* Hover lands on the chip, not on the block behind it. A block is a rectangle
   inside a capsule that clips it, so anything drawn on its own edges — a ring,
   a border, an inset shadow — comes out as two straight lines where the
   rounding cuts through, which reads as a glitch rather than a highlight. The
   chip is already round and already the label, and taking the panel's blue
   makes this behave like every other control in the bar. */
.be-tone:hover .be-tonechip{ background:#CCE2FF; border-color:#B6D6FF; }

/* ---- capsule designs ----
   Sep 2026, from Tomer's "Capsuls Framing" board. Wired like the palette
   button beside it — a round button opens a small list under it — because
   it is the same kind of choice: pick one of a few named looks by eye.

   The panel is centred on the slide below it, not on its own button —
   Tomer's own correction, matching the Figma frame, where the popup sits
   in the middle of the canvas regardless of where the button that opened
   it sits in the bar. .be-bars is 1330px, the slide's own 1318px plus its
   6px overhang either side (see the comment above .be-bars), so the two
   share a centre line; anchoring here rather than on .cj-wrap is what
   makes left:50%/translateX(-50%) land the panel over the slide instead
   of over the button. .be-bars also gets the z-index below so the panel
   (position:absolute, z-index:20) draws over .be-work/the slide iframe
   where the two overlap, rather than under it.

   The six previews are Tomer's own exported images (copied into
   public/assets/menu/capsule-join/, see the markup comment in
   render-admin.mjs), so this panel only lays them out in a row — it does
   not draw them. Padding, gap and shadow below are read off Tomer's Figma
   screenshot of this exact popup, not a measured frame or an Inspect
   panel (this session's Figma access is view-only, without Inspect), so
   treat the numbers as close rather than exact until confirmed. */
.cj-wrap{ flex:none; height:100%; display:flex; align-items:center; }
.cj-panel{
  /* Sep 2026, measured off Tomer's Figma frame of this popup rather than
     eyeballed: the five dividers in it are evenly spaced, 104px apart at this
     scale, which only works if every preview sits in a cell of the same width
     — the assets are not all the same width (gap.svg is 75, the rest 67). So
     the cell is the widest asset, 75, and the gap is the remainder of the
     pitch, 29. Six cells and five gaps come to 595, and with 27px of side
     padding the panel is 649 wide, which is the frame's own width. */
  --cj-cell:75px;
  --cj-gap:29px;
  position:absolute; top:calc(100% + 10px); left:50%; transform:translateX(-50%); z-index:20;
  display:flex; align-items:center; gap:var(--cj-gap); padding:23px 27px;
  background:#fff; border:.4px solid #C3C3C3; border-radius:16px;
  box-shadow:0 12px 30px rgba(0,0,0,.15);
}
.cj-panel[hidden]{ display:none; }
.cj-opt{
  position:relative;
  display:flex; align-items:center; justify-content:center; flex:none;
  /* One cell wide whatever it holds, so the dividers come out evenly spaced.
     The 4px padding is the hover bleed and the -4px margin takes it back out
     of the layout, so the *cell* is --cj-cell even though the box is 8 wider. */
  width:calc(var(--cj-cell) + 8px);
  padding:4px; margin:-4px; background:none; border:none; border-radius:8px;
  cursor:pointer; line-height:0; transition:box-shadow .12s, background .12s;
}
/* The thin rule between each preview, matching the popup's own Figma frame:
   dead centre of the gap, which with equal cells puts all five on an even
   pitch. Offset derived from --cj-gap rather than stated, so changing the gap
   cannot leave the rules behind: left:100% is the button's own box edge,
   which sits 4px outside the cell (the hover bleed), and half the 1px rule
   comes off again to centre it. Drawn as a pseudo-element rather than a
   border so it stays off the hit and hover area of either button.
   Colour and height are a plain hairline in the same family as the panel's
   own border, not a measurement; easily nudged. */
.cj-opt:not(:last-child)::after{
  content:''; position:absolute; top:50%; left:100%;
  margin-left:calc(var(--cj-gap) / 2 - 4px - .5px);
  width:1px; height:28px; transform:translateY(-50%);
  background:#E4E4E1;
}
.cj-opt img{ display:block; }
.cj-opt:hover{ background:#F6F6F4; }
/* The ring the rest of the app already uses to mark "this one, of a short
   list" — .cb-theme.on uses the same colour for the same reason. The
   previews carry their own fill/background, so a solid highlight the way
   the old text rows had would fight the artwork; a ring sits outside it. */
.cj-opt.on{ box-shadow:0 0 0 2px #AEBEFF; }
.cj-opt.on:hover{ background:none; }

/* ---- the palettes list ----
   A palette has no name — forty colour schemes could not be told apart by one
   if they had — so a row is its four colours and a specimen of text set on the
   ground in the ink. That specimen is the row's whole point: it is the one
   thing that shows, before you choose, whether the text will be readable. The
   catalogue only offers palettes that clear the contrast floor, so this is a
   confirmation rather than a warning, but it is still shown. */
/* Sized to its widest row and no wider: four swatches, a specimen, a number
   and a name. It was built at the pairings list's width, where two long font
   names have to fit — nothing here is that wide, and the empty half read as a
   dialog that had lost something. */
.pal-modal{ max-width:452px; }
.pal-list{ list-style:none; margin:30px 0 0; padding:0; max-height:58vh; overflow-y:auto; }
.pal-list li + li{ border-top:1px solid #ECECEA; }
.pal-row{
  display:flex; align-items:center; gap:16px;
  width:100%; padding:12px 16px; margin:0;
  background:none; border:0; cursor:pointer; text-align:left;
  transition:background .12s;
}
.pal-row:hover{ background:#F5F5F3; }
.pal-row.on{ background:#EDEDEA; }
.pal-row.is-busy{ opacity:.5; cursor:default; }
/* The four colours in the order the build wears them: ground, ink, cap A,
   cap B. Same capsule-clips-its-blocks trick as the bar's own strip, at half
   the size. */
.pal-strip{
  display:flex; flex:none; width:152px; height:28px;
  border-radius:100px; overflow:hidden; border:.5px solid #CACACA;
}
.pal-strip i{ flex:1 1 0; display:block; }
/* The specimen. Fixed width so the rows line up, and a border because a light
   ground on the white dialog would otherwise have no edge. */
.pal-name{
  display:flex; align-items:center; justify-content:center; flex:none;
  min-width:44px; height:28px; padding:0 10px; border-radius:6px;
  border:.5px solid #CACACA; font-size:16px; line-height:1; white-space:nowrap;
}
/* The design's own row has no specimen: its four are already on screen in the
   bar, and an "Aa" beside them would say nothing the strip has not. */
.pal-row[data-palette=""] .pal-name{ display:none; }

/* Number and name, sitting at the right edge. Grey and small on purpose —
   the row is chosen by its colours, and read back by its name. The number is
   its place in the list, in tabular figures and a fixed column so the names
   line up under each other however many digits it has. */
.pal-meta{
  display:flex; align-items:baseline; gap:9px; margin-left:auto;
  flex:none; width:112px;
  font-size:13px; color:#9A9A9A; white-space:nowrap;
}
/* Fixed width and left-aligned rather than simply pushed right: names of
   different lengths would otherwise each start at their own x and the column
   would read as ragged noise down the side of the list. */
.pal-meta i{
  font-style:normal; flex:none; width:20px;
  font-variant-numeric:tabular-nums; letter-spacing:.4px; color:#B4B4B4;
}
.pal-meta b{ font-weight:400; color:#8A8A8A; }
/* The design's own row has no number, so its name starts where the others'
   names do rather than where their numbers do. */
.pal-row[data-palette=""] .pal-meta b{ margin-inline-start:29px; }

/* ---- the pairings list ----
   The list never scrolls sideways: a row that has to be dragged into view is a
   row nobody reads. It used to hold that by being wide enough for the longest
   pairing and dropping the one name that would not fit at any sensible width —
   which is the wrong way round, because it let the widest face in the
   catalogue decide the size of the dialog and then threw the face away. A pair
   that does not fit on one line now takes a second, and a single name wider
   than the row breaks inside itself. Short rows are unchanged; nothing can
   overflow. */
.fp-modal{ max-width:720px; }
.fp-list{ list-style:none; margin:30px 0 0; padding:0; max-height:58vh; overflow-y:auto; }
.fp-list li + li{ border-top:1px solid #ECECEA; }
.fp-row{
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  width:100%; padding:14px 16px; margin:0;
  background:none; border:0; cursor:pointer; text-align:left;
  transition:background .12s;
}
.fp-row:hover{ background:#F5F5F3; }
.fp-row.on{ background:#EDEDEA; }
.fp-names{ display:flex; align-items:baseline; gap:2px 10px; min-width:0; flex-wrap:wrap; }
.fp-major{ font-size:20px; color:#1E1E1E; overflow-wrap:anywhere; }
.fp-amp{ font-size:17px; color:#9A9A9A; flex:none; }
.fp-minor{ font-size:20px; color:#4A4A4A; overflow-wrap:anywhere; }
.fp-mark{ flex:none; width:20px; height:20px; color:var(--green); opacity:0; }
.fp-row.on .fp-mark{ opacity:1; }
.fp-mark svg{ width:20px; height:20px; display:block; }
.fp-row.is-busy{ opacity:.5; cursor:default; }

/* One face at a time. Narrower than the pairings list because a single name
   is half the width of two, and a dialog sized for content it does not have
   reads as a mistake. */
.fo-modal{ max-width:420px; }
.fo-list{ list-style:none; margin:30px 0 0; padding:0; max-height:58vh; overflow-y:auto; }
.fo-list li + li{ border-top:1px solid #ECECEA; }
.fo-row{
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  width:100%; padding:12px 16px; margin:0;
  background:none; border:0; cursor:pointer; text-align:left;
  transition:background .12s;
}
.fo-row:hover{ background:#F5F5F3; }
.fo-row.on{ background:#EDEDEA; }
.fo-row.on .fp-mark{ opacity:1; }
/* Same rule as the pairings: a name wider than the dialog wraps rather than
   running out of it. Goudy Bookletter 1911 and Special Gothic Expanded One
   both do, at 20px in a 420px list. */
.fo-name{ font-size:20px; color:#1E1E1E; overflow-wrap:anywhere; min-width:0; }

/* Kept for the settings strip, which still wears it. */
.be-bar{
  flex:0 0 947px; display:flex; align-items:center; gap:20px;
  height:46px; padding:7px 20px; background:#EFEFEC; border-radius:5.752px;
}
.be-div{ width:1px; align-self:stretch; background:var(--frame-border); flex:none; }
.be-cell{ display:flex; align-items:center; gap:10px; flex:none; }
.be-cap{ font-size:12px; color:var(--muted); letter-spacing:.84px; line-height:1; white-space:nowrap; }
.be-slide-name{ font-size:20.978px; color:#111; line-height:1.53; white-space:nowrap; }

/* Same circle as the tool strip beside it, so the bar reads as one family
   rather than two. 33px in a 32px content box overflows half a pixel into the
   bar's padding, which is why the bar's own 46px height is unchanged.

   At rest the base takes the bar's own #EFEFEC, so the chip does not read as
   an object sitting on top of the bar. What describes the circle is a hairline
   rather than a shadow — --chip-stroke-on-bar, the separators' own colour
   taken 20% darker. On white the proposals #E4E4E4 is enough; on #EFEFEC it
   very nearly vanishes, which is why this one value differs. Hover paints the
   proposals blue back in. Everything on white keeps the white chip. */
.be-sq{
  display:flex; align-items:center; justify-content:center; flex:none;
  width:33px; height:33px; padding:0;
  background:#EFEFEC; border:0.3px solid var(--chip-stroke-on-bar); border-radius:50%;
  color:#111; cursor:pointer;
  transition:background .15s, border-color .15s;
}
.be-sq svg{ display:block; width:15px; height:15px; stroke-width:1.68; }
.be-sq:hover{ background:#CCE2FF; border-color:#B6D6FF; color:var(--green); }
/* Everything in the strips goes quiet while the build is locked. */
.be-sq:disabled{ opacity:.35; cursor:default; }
.be-sq:disabled:hover{ background:#EFEFEC; border-color:var(--chip-stroke-on-bar); color:#111; }
.be-bars[data-locked="1"] .be-swatch{ opacity:.45; cursor:default; }
.be-bars[data-locked="1"] .be-swatch input[type="color"]{ pointer-events:none; }

/* The swatches are circles too — the colour input inside was already round,
   so this only rounds the frame around it. Unlike the chips beside them these
   keep their hairline and their white fill: a swatch *is* its colour, and a
   white one with no edge would read as a hole in the bar rather than as white. */
.be-swatch{
  display:flex; align-items:center; justify-content:center; flex:none;
  width:33px; height:33px;
  background:#FFF; border:0.3px solid var(--chip-stroke-on-bar); border-radius:50%; cursor:pointer;
  transition:border-color .15s;
}
.be-swatch:hover{ border-color:#B6D6FF; }
.be-swatch input[type="color"]{
  width:18px; height:18px; padding:0; border:0; border-radius:50%;
  background:none; cursor:pointer; -webkit-appearance:none; appearance:none; overflow:hidden;
}
.be-swatch input[type="color"]::-webkit-color-swatch-wrapper{ padding:0; }
.be-swatch input[type="color"]::-webkit-color-swatch{ border:none; border-radius:50%; }
.be-swatch input[type="color"]::-moz-color-swatch{ border:none; border-radius:50%; }

.be-thumb{
  position:relative; flex:none; width:58px; height:31px;
  border-radius:1.36px; overflow:hidden;
  background-color:#EDEDEA;
  background-image:
    linear-gradient(45deg, #DCDCD8 25%, transparent 25%, transparent 75%, #DCDCD8 75%),
    linear-gradient(45deg, #DCDCD8 25%, transparent 25%, transparent 75%, #DCDCD8 75%);
  background-size:8px 8px; background-position:0 0, 4px 4px;
}
.be-thumb.has-img{ background:#fff; }
.be-thumb img{ width:100%; height:100%; object-fit:contain; display:block; }

/* ---- tool strip ----
   The strip's own bar is gone: the tools are a group inside the build bar now,
   styled at the top of this section with the other two. What is left here is
   the round chip, still worn by the picture strip. */
.be-tool{
  display:flex; align-items:center; justify-content:center; flex:none;
  width:33px; height:33px; padding:0;
  background:#EFEFEC; border:0.3px solid var(--chip-stroke-on-bar); border-radius:50%;
  color:#111; cursor:pointer; text-decoration:none;
  transition:background .15s, border-color .15s;
}
.be-tool svg{ display:block; width:15px; height:15px; stroke-width:1.68; }
.be-tool:hover{ background:#CCE2FF; border-color:#B6D6FF; color:var(--green); }
.be-tool[disabled]{ opacity:.35; cursor:default; }
.be-tool[disabled]:hover{ background:#EFEFEC; border-color:var(--chip-stroke-on-bar); color:#111; }

/* ---- the slide, at full size, with its five components in the margin ----
   The rail is not part of the 1318 column: it sits to the left of the slide,
   in the empty space beside it, exactly as the frame has it. */
.be-work{ position:relative; margin-top:28px; }

/* The rail is as tall as the slide beside it and scrolls inside that, however
   many slides the build has. Bounding it here is what keeps the page itself
   from scrolling — before this, a long rail pushed the document taller and
   scrolling to reach the last slide carried the toolbars and the slide away
   with it. `scrollbar-gutter:stable` reserves the track whether or not it is
   needed, so adding a slide never shifts the column sideways. */
/* ---- the column the slide list lives in ----
   The absolute placement moved off .be-rail and onto this, so the header can
   sit over the list inside the same box. -136 puts the header's rule on the
   same line as the shelf's on the other side of the slide: that strip is at
   -138 and is 38 tall, so its rule is at -100, and this one is 36 tall.
   Figma "Retrolet - look in editor" draws both rules at the same y, which is
   what the two lists being one height means. */
.be-railcol{
  /* The gutter and the gap beside it are both added to the width and taken off
     the offset, so the cards stay at exactly the x the design puts them at
     while the scrollbar sits out in the empty space to their right. */
  --rail-gutter:10px;   /* the scrollbar track itself */
  --rail-shift:20px;    /* clear air between the cards and the scrollbar */
  --rail-card:196px;    /* the card column; 168 until the list was enlarged */
  position:absolute; top:-136px;
  inset-inline-end:calc(1317.618px + 103px - var(--rail-gutter) - var(--rail-shift));
  width:calc(var(--rail-card) + var(--rail-gutter) + var(--rail-shift));
  display:flex; flex-direction:column;
}
.be-rail-head{ position:relative; flex:none; height:46px; }
.brh-strip{ display:block; }
/* The words and the mark sit over the strip's flat top, centred in the 36
   above the rule. 58 is where that flat run begins. */
.brh-label{
  position:absolute; top:9px; inset-inline-start:58px;
  display:inline-flex; align-items:center; gap:5.623px;
  font-size:13.121px; font-weight:500; line-height:100%; letter-spacing:.262px;
  color:#17317A; white-space:nowrap;
}
.brh-ico{ width:17px; height:17px; flex:none; stroke-width:1.6; }

.be-rail{
  /* 6.6 = the 16.6 the shelf leaves between its rule and its first card, less
     the 10 of tail this header hangs below its own rule. */
  margin-top:6.6px;
  padding-inline-end:var(--rail-shift);
  display:flex; flex-direction:column; gap:26px;
  /* Still ending where it always did, now that it begins 83.4 higher. */
  max-height:824.56px;
  overflow-y:auto; overflow-x:hidden;
  overscroll-behavior:contain;  /* reaching the end does not scroll the page */
  scrollbar-gutter:stable; scrollbar-width:thin;
  scrollbar-color:#c9c9c9 transparent;

  /* Soft top and bottom edges, so slides fade out of the list rather than
     being sliced off by it. admin.js eases each end between 0 and 50px as the
     rail scrolls, so the first and last slide are never dimmed for nothing;
     with no script at all this stays a plain 50px feather at both ends. */
  --fade-top:50px;
  --fade-bottom:50px;
  -webkit-mask-image:linear-gradient(to bottom,
    transparent 0, #000 var(--fade-top),
    #000 calc(100% - var(--fade-bottom)), transparent 100%);
  mask-image:linear-gradient(to bottom,
    transparent 0, #000 var(--fade-top),
    #000 calc(100% - var(--fade-bottom)), transparent 100%);
}
.be-rail::-webkit-scrollbar{ width:8px; }
.be-rail::-webkit-scrollbar-track{ background:transparent; }
.be-rail::-webkit-scrollbar-thumb{
  background:#cdcdcd; border-radius:4px; border:2px solid transparent;
  background-clip:content-box;
}
.be-rail::-webkit-scrollbar-thumb:hover{ background:#b0b0b0; background-clip:content-box; }
/* Each rail entry is the card plus its two hover controls. The controls sit
   outside the card so that pressing one never also selects the slide. */
.be-slideitem{ position:relative; }
.be-slidecard{
  display:flex; flex-direction:column; gap:6px; align-items:stretch; width:100%;
  padding:7px; background:#fff; border:1px solid var(--frame-border); border-radius:8.5px;
  cursor:pointer; text-align:left; font-family:inherit;
  transition:background-color .14s ease, border-color .14s ease, box-shadow .18s ease;
}
.be-slidecard:hover{ border-color:#B6D6FF; }
.be-slidecard.on{ background:var(--green-pill); border-color:var(--green-border); }
.be-slidename{ font-size:11.5px; color:var(--muted); letter-spacing:.4px; line-height:1.2; }
.be-slidecard.on .be-slidename{ color:var(--green); }
.be-slidethumb{
  position:relative; display:block; width:182px; height:102.375px;
  background:#fff; border-radius:3.4px; overflow:hidden;
}
.be-slidethumb iframe{
  position:absolute; top:0; left:0; width:1920px; height:1080px; border:0;
  /* 182 over the 1920 the slide is laid out at. */
  transform:scale(.09479); transform-origin:top left; pointer-events:none;
}

/* ---- duplicate / delete, on the thumbnail's top right ----
   Out of the way until the thumbnail is hovered, so the rail stays a list of
   slides rather than a list of controls. The offset clears the name line and
   the card's padding, landing them just inside the thumbnail's corner. */
.be-slideacts{
  position:absolute; top:31px; inset-inline-end:11px;
  display:flex; gap:4px; z-index:2;
  opacity:0; pointer-events:none; transition:opacity .12s ease;
}
.be-slideitem:hover .be-slideacts,
.be-slideitem:focus-within .be-slideacts{ opacity:1; pointer-events:auto; }
.be-slideact{
  display:flex; align-items:center; justify-content:center; flex:none;
  width:19px; height:19px; padding:0;
  background:#fff; border:0.3px solid #E4E4E4;
  box-shadow:0 0 7.368px 0 rgba(0,0,0,.068);
  border-radius:4.5px; color:#4a4a4a; cursor:pointer; line-height:0;
}
.be-slideact svg{ display:block; }
.be-slideact:hover{ background:#CCE2FF; border-color:#B6D6FF; color:var(--green); }
.be-slideact.be-del:hover{ background:#FBE3E1; border-color:#f0b4b0; color:var(--red); }

/* ---- picking a slide up and putting it down ----
   Three moments, in order: the press (smaller, lighter, just off the surface),
   the lift (carried above the list on a long soft shadow), and the settle
   (everything comes off together and the slide travels the last gap into its
   new place). The carrying itself is an inline transform from the script, so
   transform is deliberately absent from the dragging transition — it must
   track the pointer with no lag — and present in the other two. */
.be-rail[data-reorder] .be-slideitem{ cursor:grab; touch-action:none; }
.be-rail[data-reorder] .be-slidename{ user-select:none; }
.be-slideitem{ transition:transform .13s ease; }

/* the press */
.be-slideitem.is-pressed{ transform:scale(.95); z-index:30; }
.be-slideitem.is-pressed .be-slidecard{
  background:#F7F7F4; border-color:#B6D6FF;
  box-shadow:0 2px 4px rgba(0,0,0,.10);
}
.be-slideitem.is-pressed .be-slidename{ color:#5a5a5a; }

/* the lift — off the page, over its neighbours */
.be-slideitem.is-dragging{
  cursor:grabbing; z-index:30; transition:none;
}
.be-slideitem.is-dragging .be-slidecard{
  background:#fff; border-color:var(--green-border);
  box-shadow:0 18px 30px rgba(0,0,0,.18), 0 6px 10px rgba(0,0,0,.12);
}
.be-slideitem.is-dragging .be-slidename{ color:var(--green); }

/* the settle */
.be-slideitem.is-settling{
  z-index:30; transition:transform .22s cubic-bezier(.2,.8,.3,1);
}
.be-slideitem.is-settling .be-slidecard{
  transition:background-color .22s ease, border-color .22s ease, box-shadow .26s ease;
}

/* Nothing to press while a slide is in the air. */
.be-slideitem.is-pressed .be-slideacts,
.be-slideitem.is-dragging .be-slideacts,
.be-slideitem.is-settling .be-slideacts{ opacity:0; pointer-events:none; }
.be-rail.is-reordering .be-slidethumb{ pointer-events:none; }
.be-rail.is-busy{ opacity:.55; pointer-events:none; }

/* Someone who has asked for less motion gets the states without the travel. */
@media (prefers-reduced-motion: reduce){
  .be-slideitem, .be-slideitem.is-settling,
  .be-slideitem.is-settling .be-slidecard, .be-slidecard{ transition:none; }
}

/* ---- the shelf ----
   Mirror of the rail, on the other side of the slide: the space to its right,
   the same 168px cards, the same 741px height with its own scroll and the same
   feathered edges. Nothing here is a new card design — Components and Designs
   wear the rail's, because a component card and a slide card are the same
   object at different moments in its life. */
.be-shelf{
  --rail-gutter:10px;
  --rail-shift:20px;
  /* The column is as wide as its widest card. Components keeps the rail's
     168px — a component card *is* a slide card — while a design card is 255,
     the size Tomer drew it at, so the shelf takes the larger of the two and
     the Components list sits left-aligned inside it. */
  --shelf-card:255px;
  /* Where the tabs sit, vertically.
     The strip is placed so its rule lands ON the page's own rule — the one
     that closes the Back row and runs across to the left of here. They are one
     line, not two at similar heights, and Tomer drew an arrow from each to the
     other to say so. The tabs used to sit 21px above it, which read as a rule
     that had slipped.

     So this is measured from that rule rather than from the head row: the
     rule's centre is 100.5 above .be-work's top, the strip is the tab's height
     tall and its own rule is the last pixel of it, hence -138. Changing
     --tab-h moves the top and leaves the two rules together.

     Measured off Tomer's marked-up frame, where the page rule and the strip's
     rule are both at y 290 and the round buttons — 37px, the anchor for the
     whole frame — come back 138, putting it at 3.66x. */
  position:absolute; top:-138px;
  inset-inline-start:calc(1317.618px + 103px - var(--rail-gutter) - var(--rail-shift));
  width:calc(var(--shelf-card) + var(--rail-gutter) + var(--rail-shift));
  /* Still reaching the bottom edge it always has, now that it starts 21 lower. */
  max-height:879px;
  /* The rule to the first card's ring is 18.6 in that frame: 16.6 here plus the
     2px the list keeps for a selected card's ring. It was 42, measured from an
     earlier export against a strip that was not sitting on the rule — with the
     strip moved down and this brought in, the cards stay where they were and
     only the tabs move. */
  display:flex; flex-direction:column; gap:16.6px;
}
/* The gap before the scrollbar has to live *inside* the scroller to sit between
   the cards and the track — put on the shelf it would land on the far side of
   the track instead, and the slider would hug the cards. The rail gets it the
   same way. Tabs and heading take the gap plus the track, so their content
   lines up with the cards to the pixel. */
/* The heading stops 11px short of the cards' right edge, which is where the
   scrollbar track begins — measured off the frame, where the pills run
   1767..2008 against cards at 1764..2019.

   The tab row does not. That inset was left over from when these were two
   pills, and on a tab it cuts the rule 11px early: the line ran out before the
   column did, with the scrollbar track carrying on past its end. A rule that
   stops short of the edge it is meant to close reads as unfinished, so the
   strip now runs the full width and the line goes to the edge. */
.be-shelf-head{ padding-inline-end:11px; }

/* ---- the shelf's two tabs ----
   Built from Tomer's construction drawing: a line, the panel behind it, and a
   light ramp.

   The line is the point. It is ONE continuous rule — along the bottom of the
   tab not chosen, up and over the one that is, and away to the far edge — so
   the chosen tab is a bay opened in the panel's top edge rather than a button
   with its underline rubbed out. The climb between the two levels is a cubic
   bezier with both handles at half its width, which is what the drawing
   measures to: sampled every 20px the fit lands within a pixel the whole way,
   where two quarter-circles would stand vertical at the halfway point and the
   drawing's steepest slope is only 2:1. Its box is 1.153 wide for every 1 it
   climbs, and it climbs exactly the tab's height.

   Sizes, Sep 2026, second pass. Tomer's first build looked short and cramped
   beside the frame, and it was: measured against two things present in both
   his screenshot and the frame — the round eye button and the page's own dot
   grid, which agree on the scale within 2% — the tab was about 11% short while
   the icons and the label were within 2% of the frame. So the tab carried the
   whole error: the type and the icons were right, and the box around them was
   not, which is what reads as cramped. The tab is 38, the frame's own measure
   rounded up.

   The label and the icons are then a further 20% on Tomer's word — 16.2 and
   21.6. The 7.5 between them is measured rather than scaled: in the frame the
   drawn palette clears the "D" by 10.1 and the drawn pyramid clears the "C" by
   7.9, and a palette sits about 2.6 inside its own box where a pyramid nearly
   fills one — so a single 7.5 between the boxes puts Designs exactly on the
   frame and Components a shade over it, which is closer than one number
   usually gets. At that size the content fills the chosen
   tab and its 14px of side padding collapses to about 6: the labels stay whole
   and well clear of the climb, but the padding is now the give. The column is
   a fixed width so nothing moves on its own; if it is ever narrowed, this is
   what gives first, and the padding should come down before the type does.

   The straight runs are backgrounds rather than borders, each stopping a
   curve's width short of the side the curve is on, so the rule never doubles
   back under it. The curve itself is one small SVG hung outside the chosen
   tab, at its exact size, so nothing about it is stretched. */
.be-shelf-tabs{ display:flex; gap:0; flex:none; --tab-h:38px; --tab-s:43.8px; }
.be-shelf-tab{
  position:relative; isolation:isolate;
  display:flex; align-items:center; justify-content:center; gap:7.5px;
  /* Sized from their own content, not halves: the drawing splits the strip
     63/37 and that is exactly what the two labels plus the climb's clearance
     come to. */
  flex:1 1 auto; min-width:0; height:var(--tab-h); padding:0 14px;
  background:none; border:0; border-radius:0;
  color:#ABABAB;
  font-family:inherit; font-size:16.2px; line-height:1; letter-spacing:0;
  cursor:pointer; white-space:nowrap;
  transition:color .15s;
}
/* The rule under the tab that is not chosen, stopping where the climb starts.
   Which side that is depends on which tab is chosen, and a tab only ever has
   one neighbour, so the side is simply the one away from the strip's own end. */
.be-shelf-tab:not(.on){
  background:linear-gradient(var(--frame-border), var(--frame-border)) no-repeat;
  /* A pixel of overlap with the climb, so sub-pixel rounding cannot
     leave a nick where the two meet. */
  background-size:calc(100% - var(--tab-s) + 1px) 1px;
  background-position:0 100%;
  /* The climb hangs over this tab, so its label keeps clear of it and sits
     centred in what is left — which is where the drawing has it, with
     "Components" ending exactly where the rule starts to rise. */
  padding-inline-end:calc(14px + var(--tab-s));
}
.be-shelf-tab:not(.on):last-child{
  background-position:100% 100%;
  padding-inline:calc(14px + var(--tab-s)) 14px;
}
/* The icons are the frame's own lucide files, and take the tab's colour —
   #ABABAB at rest and #17317A when chosen are the two the files were
   exported in. */
.be-shelf-tab svg{ width:21.6px; height:21.6px; display:block; flex:none; }
.be-shelf-tab:hover{ color:#17317A; }
.be-shelf-tab.on{
  color:#17317A; cursor:default; z-index:1;
  background:linear-gradient(var(--frame-border), var(--frame-border)) no-repeat;
  background-size:100% 1px;
  background-position:0 0;
  /* The chosen tab's icon and label sit 10px toward the climb rather than in
     the middle of the tab, which is where the frame has them: there the
     Designs pair starts at the tab's own leading edge and leaves the space at
     the far end. Taken off one side and added to the other, so the padding
     still totals 28 and the strip does not re-split between the two tabs.
     "Toward the climb" rather than "left", so it mirrors when Components is
     the chosen one and the climb is on the other side. */
  padding-inline:4px 24px;
}
.be-shelf-tab.on:first-child{ padding-inline:24px 4px; }
/* The ramp, and the silhouette it is cut to. One element spans the climb and
   the chosen tab together, so the blue is a single ellipse with no seam where
   the two meet: fitted over the drawing's whole blue field it centres 6px
   inside the tab's leading edge and 7.4px below the rule, 87% of this box
   across by 25.4 tall — which is why the blue is strongest beside the climb
   and has died a few pixels before the rule below. Peak #DCE6F8, read off the
   drawing; the far top corner rounded 18.6, the near one cut by the climb.

   The mask is two layers: the climb's own silhouette at its exact size, and
   plain black over the rest. It is drawn a pixel taller than this box and
   pulled up by one, so its edge sits on the line rather than a hair below it. */
.be-shelf-tab.on::after{
  content:''; position:absolute; z-index:-1;
  top:1px; bottom:0;
  inset-inline-start:calc(-1 * var(--tab-s)); inset-inline-end:0;
  border-start-end-radius:18.6px;
  background:radial-gradient(87% 25.4px at calc(var(--tab-s) - 6px) 6.4px,
             #DCE6F8 0%, rgba(220,230,248,0) 100%);
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 43.8 38'%3E%3Cpath d='M0 38C21.9 38 21.9 0 43.8 0L43.8 38Z' fill='%23000'/%3E%3C/svg%3E"),
                     linear-gradient(#000, #000);
  -webkit-mask-size:var(--tab-s) var(--tab-h), calc(100% - var(--tab-s)) 100%;
  -webkit-mask-position:0 -1px, 100% 0;
  -webkit-mask-repeat:no-repeat, no-repeat;
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 43.8 38'%3E%3Cpath d='M0 38C21.9 38 21.9 0 43.8 0L43.8 38Z' fill='%23000'/%3E%3C/svg%3E"),
             linear-gradient(#000, #000);
  mask-size:var(--tab-s) var(--tab-h), calc(100% - var(--tab-s)) 100%;
  mask-position:0 -1px, 100% 0;
  mask-repeat:no-repeat, no-repeat;
}
/* The climb's line. */
.be-shelf-tab.on::before{
  content:''; position:absolute; z-index:-1; top:0; bottom:0;
  inset-inline-start:calc(-1 * var(--tab-s)); width:var(--tab-s);
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 43.8 38'%3E%3Cpath d='M0 37.5C21.9 37.5 21.9 .5 43.8 .5' fill='none' stroke='%23D6D6D6'/%3E%3C/svg%3E") no-repeat 0 0/100% 100%;
}
/* Chosen first, and the whole climb — line, silhouette and the ellipse inside
   it — is the same drawing the other way round. */
.be-shelf-tab.on:first-child::before{
  inset-inline-start:auto; inset-inline-end:calc(-1 * var(--tab-s));
  transform:scaleX(-1);
}
.be-shelf-tab.on:first-child::after{
  inset-inline-start:0; inset-inline-end:calc(-1 * var(--tab-s));
  transform:scaleX(-1);
}

.be-shelf-body{ min-height:0; flex:1 1 auto; display:flex; flex-direction:column; }
/* display:flex would otherwise beat the browser's own [hidden] rule, and both
   tabs' panels would show at once. */
.be-shelf-body[hidden]{ display:none; }
.be-shelf-head{
  margin:0; flex:none;
  font-size:11.5px; line-height:1.35; letter-spacing:.3px; color:var(--muted);
}
.be-shelf-head strong{ color:var(--green); font-weight:400; }

/* The list scrolls on its own, like the rail, and fades at both ends. */
.be-shelf-list{
  min-height:0; flex:1 1 auto; display:flex; flex-direction:column; gap:26px;
  /* 2px of air on every side the scroller can clip, so a selected card's
     outside ring survives at the very top of the list and at both edges.
     The shelf's own gap is 40 rather than 42 to pay for the top 2. */
  /* 14 on the leading side, not 2, since the cards lift 3% on hover: a 255
     card grows 3.8 each side and needs room for its shadow besides, and
     overflow-x is hidden here — a scroller cannot be `visible` on one axis
     only. The negative margin gives the 12 back, so the cards sit exactly
     where they did and only the clipping edge moves. The trailing side is
     untouched: --rail-shift is the air between the cards and the scrollbar
     track, and that gap is measured. */
  padding-block:2px;
  padding-inline:14px var(--rail-shift);
  margin-inline:-12px 0;
  overflow-y:auto; overflow-x:hidden; overscroll-behavior:contain;
  scrollbar-gutter:stable; scrollbar-width:thin;
  scrollbar-color:#c9c9c9 transparent;
  --fade-top:0px; --fade-bottom:50px;
  -webkit-mask-image:linear-gradient(to bottom,
    transparent 0, #000 var(--fade-top),
    #000 calc(100% - var(--fade-bottom)), transparent 100%);
  mask-image:linear-gradient(to bottom,
    transparent 0, #000 var(--fade-top),
    #000 calc(100% - var(--fade-bottom)), transparent 100%);
}
.be-shelf-list::-webkit-scrollbar{ width:8px; }
.be-shelf-list::-webkit-scrollbar-track{ background:transparent; }
.be-shelf-list::-webkit-scrollbar-thumb{
  background:#cdcdcd; border-radius:4px; border:2px solid transparent;
  background-clip:content-box;
}
.be-shelf-list::-webkit-scrollbar-thumb:hover{ background:#b0b0b0; background-clip:content-box; }

.be-shelfitem{ position:relative; flex:none; }
.be-shelfcard{ cursor:grab; touch-action:none; }

/* ---- the lift ----
   Both tabs' cards rise on hover, the way a design theme's card does on the
   gallery: a small scale and a shadow under it, on the gallery's own easing.
   3% rather than the gallery's 1% because these are half the size — the same
   apparent movement needs more of it.

   A card that cannot be taken — one whose rule is already met, or the design
   this build is already in — does not move. Offering a lift on something that
   will not answer is the worst of both. */
.be-shelfitem .be-shelfcard,
.dslot .dcard{
  transition:transform .26s cubic-bezier(.22,.61,.36,1),
             box-shadow .26s ease, border-color .15s ease;
}
.be-shelfitem:not(.is-full):not(.on) .be-shelfcard:hover,
.dslot .dcard:not(.on):hover{
  transform:scale(1.03);
  /* Tighter than the gallery's 0 8px 22px: this one is drawn inside a 255px
     scroller that clips, and a shadow wider than the room it has reads as cut
     rather than soft. The browsers behind Show all use the full one. */
  box-shadow:0 6px 16px 0 rgba(0, 0, 0, .11);
}
/* Mid-drag the card is a ghost under the pointer; a hover lift on top of that
   reads as two cards moving. */
.be-shelfcard.is-lifting:hover{ transform:none; box-shadow:none; }
/* Filtered out by the search. Stated rather than left to the browser, because
   both of these carry their own display in one state or another. */
.be-shelfitem[hidden], .dslot[hidden]{ display:none; }

/* ---- find one, or go and look at all of them ----
   One bar under both tabs. It filters whichever tab is showing; Show all
   opens that tab's own page. The pill is the gallery's New Design Theme
   button at this column's scale: same #CCE2FF on #0A2250, same 57.79 radius,
   smaller because the shelf is 255 wide and that one is 186. */
.be-shelf-find{
  flex:none; display:flex; align-items:center; gap:8px;
  margin-top:-4px; padding-inline:2px var(--rail-shift);
}
.be-find{
  flex:1; min-width:0; display:flex; align-items:center; gap:7px;
  height:30px; padding:0 10px; box-sizing:border-box;
  border:1px solid var(--frame-border); border-radius:7px; background:#FFF;
  transition:border-color .15s, box-shadow .15s;
}
.be-find:focus-within{ border-color:#AEBEFF; box-shadow:0 0 0 2px rgba(223, 229, 255, .7); }
.be-find-ico{ flex:none; width:13px; height:13px; color:#9A9A9A; }
.be-find input{
  flex:1; min-width:0; border:0; outline:0; background:transparent;
  font:inherit; font-size:11.5px; color:#2A2A2A;
}
.be-find input::placeholder{ color:#9A9A9A; }
.be-showall{
  flex:none; display:inline-flex; align-items:center; justify-content:center;
  padding:8px 13px; border-radius:57.79px;
  background:#CCE2FF; color:#0A2250;
  font-size:10.5px; font-weight:400; line-height:100%; letter-spacing:.2px;
  text-decoration:none; white-space:nowrap;
  transition:background-color .15s;
}
.be-showall:hover{ background:#B9D8FF; }
.be-shelf-none{
  margin:0; padding:18px 2px; font-size:11.5px; color:var(--muted);
}
.be-shelf-none[hidden]{ display:none; }

/* ---- a component card ----
   The name is the card's heading now, not a caption under it, and the line
   beneath says what the slide is for. The diagram then sits where the live
   render used to — see lib/component-art.mjs for why it is drawn rather than
   rendered, and why it is blue in every design. */
.be-shelf-body[data-panel="components"] .be-shelfcard{ gap:3px; }
/* The name is the navy the chosen tab is set in, two inches above it — the
   card and the tab are the same family of thing and now say so.

   Both lines carry 5px of their own beyond the card's padding, so the words
   sit further inside the card than the diagram does. The card's own padding
   is not raised instead: that would move the diagram too, and the diagram is
   meant to reach the card's edge. */
.be-cname{
  padding:5px 5px 0;
  font-size:18px; font-weight:500; line-height:1.2; letter-spacing:.2px;
  color:#17317A;
}
.be-cpurpose{
  padding:0 5px;
  font-size:13.5px; font-weight:300; line-height:1.35; color:#8A8A8A;
}
.be-cart{ margin-top:5px; background:transparent; }
.ca-svg{ display:block; width:100%; height:100%; }

/* The Components card at the shelf's full width, so both tabs fill the column
   and a component is previewed at the same size as a design. It is still the
   rail's card — the same 7px padding, name and frame — only bigger, because a
   component card and a slide card remain the same object. The rail itself is
   untouched: there the card has to stay the width the slides are drawn at.

   241 = 255 − the card's 7px padding on each side; the scale is that over the
   1920 the preview is laid out at, so the iframe lands exactly in the frame. */
.be-shelf-body[data-panel="components"] .be-shelfitem{ width:255px; }
.be-shelf-body[data-panel="components"] .be-slidethumb{
  width:241px; height:135.5625px;
}
.be-shelf-body[data-panel="components"] .be-slidethumb iframe{
  transform:scale(.12552);
}
.be-shelfcard.on{ background:var(--green-pill); border-color:var(--green-border); }
.be-shelfitem .be-slidename{ user-select:none; }

/* A component whose rule is already met, or the design already in force:
   still shown, so the shelf is a full catalogue rather than a shrinking list,
   but plainly not something to drag. */
.be-shelfitem.is-full .be-shelfcard,
.be-shelfitem.on .be-shelfcard{ cursor:default; }
.be-shelfitem.is-full .be-slidethumb{ opacity:.4; }
.be-shelf-note{
  display:block; margin-top:5px;
  font-size:10.5px; line-height:1; letter-spacing:.3px; color:var(--muted);
}
.be-shelfitem.on .be-shelf-note{ color:var(--green); }

/* ---- the design card ----
   Figma, the Designs panel frame. A portrait of one design: its paper, its
   display face, its capsules and its palette, at 255×191 with a radius of 7.

   Every colour and the type below come from custom properties the server
   writes onto the element from the design's own record (designCard() in
   lib/designs.mjs). There is deliberately no per-design CSS here and no place
   to put any — a new design in designs.mjs gets a correct card for free, which
   is the point of the whole token layer. */
.be-designs{ gap:16px; }
.dcard{
  position:relative; flex:none;
  width:255px; height:191px; box-sizing:border-box;
  border-radius:7px; overflow:hidden;
  background:var(--dc-ground, #fff);
  border:1px solid #C0C0C0;
  padding:14px 0 0 16px;
  font-family:inherit; text-align:start; cursor:pointer;
  /* A <button> centres its content both ways by UA rule, which would drop the
     name into the middle of the card on top of the specimen. Laying it out
     explicitly is the only way to put the name where it was drawn. */
  display:flex; flex-direction:column; align-items:flex-start; justify-content:flex-start;
  transition:border-color .15s, box-shadow .15s;
}
.dcard:hover{ border-color:#B6D6FF; }
/* In use. Figma draws a 2px stroke *Outside* and no grey border at all, so
   that is what this is: an outline, which is painted outside the border edge
   and takes up no layout, and the card's own 1px border turned transparent so
   nothing grey shows through under it. Drawing it as a 1px border plus a 1px
   shadow — which is what this was — gave two half-tones instead of one line,
   and read as a smudge rather than a selection. */
.dcard.on{
  cursor:default;
  border-color:transparent;
  outline:2px solid #2A5FC4;
  outline-offset:0;
  box-shadow:0 2px 10px rgba(0,0,0,.10);
}
/* The tick is always in the markup and shown only by the ring, so a build that
   has been given its own faces or colours loses both marks together and the
   editor can move the state without rebuilding a card. */
/* ---- a gallery theme's cover picture, on its card ----
   The nine that wear one show it here as they do in the gallery. A veil of the
   theme's own ground sits over it: the card is read for its four colours, its
   two faces and its capsules — Inna's words — and ink on a bare photograph is
   none of those. At .72 the picture is plainly there and the specimen is
   plainly legible, which is the whole of what this card is for. */
.dcard-pic{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; display:block;
}
.dcard-veil{ position:absolute; inset:0; background:var(--dc-ground, #FFF); opacity:.72; }
/* The only child of the card that is not positioned, so the only one a
   picture would otherwise paint over. */
.dcard-name{ position:relative; }

.dcard-check{
  position:absolute; top:12px; inset-inline-end:14px;
  display:none; color:#2A5FC4;
  filter:drop-shadow(0 1px 3px rgba(0,0,0,.25));
}
.dcard.on .dcard-check{ display:block; }

/* ---- a saved design's card ----
   The card itself is the same card; the slot around it exists only to hang
   rename and delete off, the way the rail hangs duplicate and delete off a
   slide. They keep out of the way until the card is under the cursor, because
   a design is picked far more often than it is renamed. */
.dslot{ position:relative; flex:none; width:255px; }
/* Top right, where nothing else is — not top left, which is the name: a
   control over the top of a name reads as the name being broken. On a card
   that is in use the tick is already in that corner, so they step inboard of
   it, and only then can a long name and the controls want the same pixels.
   That case is settled below, on the name. */
.dcard-acts{
  position:absolute; top:10px; inset-inline-end:14px;
  display:flex; gap:6px; opacity:0; pointer-events:none;
  transition:opacity .15s;
}
.dcard.on ~ .dcard-acts{ inset-inline-end:48px; }
.dslot:hover .dcard-acts,
.dcard-acts:focus-within{ opacity:1; pointer-events:auto; }
.dcard-act{
  display:flex; align-items:center; justify-content:center; flex:none;
  width:26px; height:26px; padding:0;
  background:#fff; border:.5px solid #D6D6D6; border-radius:50%;
  color:#1E1E1E; cursor:pointer;
  box-shadow:0 1px 4px rgba(0,0,0,.14);
  transition:background .15s, border-color .15s, color .15s;
}
.dcard-act svg{ width:13px; height:13px; display:block; }
.dcard-act:hover{ background:#CCE2FF; border-color:#B6D6FF; }
.dcard-act[data-delete-design]:hover{ background:#FFE2E2; border-color:#FFC9C9; color:#B3261E; }

/* ---- naming a saved design ----
   One field and a rule under it. Narrow, because the field is eight letters
   wide and a dialog sized for content it does not have reads as a mistake. */
.dn-modal{ max-width:360px; }
.dn-form{ margin:0; }
.dn-label{
  display:block; margin:4px 0 8px;
  font-size:12px; letter-spacing:.6px; color:var(--muted);
}
.dn-input{
  width:100%; box-sizing:border-box; height:44px; padding:0 14px;
  background:#fff; border:1px solid var(--frame-border); border-radius:7px;
  font-family:inherit; font-size:20px; letter-spacing:.4px; color:#111;
}
.dn-input:focus{ outline:none; border-color:#B6D6FF; box-shadow:0 0 0 3px #CCE2FF; }
/* Left, under the field it describes, rather than centred like the notes in
   the confirm dialogs — this one is a caption on a control, not a sentence
   addressed to the reader. */
.dn-form .dup-note{ margin:9px 2px 0; text-align:start; }
/* A saved design's name can run to fifteen letters. It is held on one line and
   cut rather than allowed to wrap: everything under the name is pinned to the
   y it was drawn at, so a second line would run into the specimen instead of
   pushing it down. 159 is the room to where the rename and delete controls
   begin; while the card is hovered *and* in use those controls have stepped
   inboard of the tick, so the name yields the difference for as long as the
   cursor is on it. Fifteen average letters measure about 142, so this cuts
   only the widest of them, and only while they are being acted on. */
.dcard-name{
  display:block; margin:0; max-width:159px;
  font-size:19px; line-height:1; letter-spacing:.19px;
  color:var(--dc-ink, #000);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.dslot:hover .dcard.on .dcard-name{ max-width:125px; }
/* Everything below the name is pinned to the y Tomer drew it at, not flowed.
   A design with two faces would otherwise push its own specimen down into the
   colour strip, and the two cards would no longer line up with each other —
   which is exactly what a card whose job is comparison must never do. */
.dcard-fonts{
  position:absolute; top:60px; inset-inline-start:29px;
  margin:0; padding:0;
  font-size:11px; line-height:1.52; letter-spacing:.55px;
  color:var(--dc-ink, #000);
}
.dcard-fonts li{ list-style:disc; }

/* The specimen: the display face on the left, the capsules at a fixed x so
   they agree across cards however wide the face's "Aa" turns out to be. */
.dcard-aa{
  position:absolute; top:96px; inset-inline-start:16px;
  font-family:var(--dc-font, inherit);
  font-size:55px; line-height:1; letter-spacing:0;
  color:var(--dc-ink, #000);
}
.dcard-pills{
  position:absolute; top:110.64px; inset-inline-start:101px;
  display:flex; gap:3px;
}
.dcard-pill{
  font-family:var(--dc-font, inherit);
  font-size:19px; line-height:1; letter-spacing:0;
  padding:7.86px 15.71px;
  border-radius:var(--dc-pill-r, 100px);
  color:var(--dc-ink, #000);
  white-space:nowrap;
}
.dcard-pill.accent{ background:var(--dc-accent-pill, #d7ffd7); }
.dcard-pill.neutral{ background:var(--dc-neutral, #ededed); }


/* The palette, flush to the bottom corners — the card clips them round. */
.dcard-strip{
  position:absolute; inset-inline:0; bottom:0;
  display:flex; height:29px;
}
.dcard-strip i{ flex:1 1 0; min-width:0; border-inline-start:1px solid #E2E2E2; }
.dcard-strip i:first-child{ border-inline-start:0; }

/* Room for the designs still to come. It says the shelf grows without
   claiming anything about what arrives in it. */
.dcard-slot{
  flex:none; width:255px; height:191px; box-sizing:border-box;
  border-radius:7px; background:#F7F7F7; border:1px solid #C0C0C0;
}

/* ---- what a shelf card is for, on hover ----
   Drawn on the body rather than inside the card: the shelf list clips its
   contents and fades them at both ends, so a tooltip placed within it would be
   cut off or faded away. It sits to the *left* of the card, pointing right —
   the shelf is against the right edge of the window, so there is no room that
   side. Dark on light, the same treatment as the tooltips on the proposals
   list. */
.be-tip{
  position:fixed; z-index:70; pointer-events:none;
  padding:7px 11px; border-radius:7px;
  background:#111; color:#fff;
  font-family:inherit; font-size:12px; line-height:1.3; letter-spacing:.2px;
  max-width:230px;
  box-shadow:0 10px 24px rgba(0,0,0,.25);
  opacity:0; transition:opacity .12s ease;
}
.be-tip.on{ opacity:1; }
.be-tip::after{
  content:''; position:absolute; top:50%; inset-inline-start:100%;
  transform:translateY(-50%);
  border:6px solid transparent; border-inline-end-width:0;
  border-inline-start-color:#111;
}

/* ---- carrying a component across to the rail ----
   The card stays where it is and a copy of it follows the pointer, because the
   card came from a catalogue and the catalogue does not lose an entry when one
   is used. */
.be-shelfcard.is-lifting{ opacity:.5; }
/* The card in flight is 30% bigger than the one in the shelf, so what is being
   carried — and where it is going in the rail — is easy to read.
   Every measurement is the rail card's own, multiplied by 1.3, rather than a
   CSS transform on the whole thing: a scaled-up iframe composites blurry,
   while a bigger one renders its slide sharp at the new size. */
.be-drag-ghost{
  position:fixed; z-index:60; pointer-events:none;
  width:218.4px;                                 /* 168 × 1.3 */
  box-shadow:0 22px 38px rgba(0,0,0,.22), 0 8px 13px rgba(0,0,0,.14);
  border-radius:11.05px;
}
.be-drag-ghost .be-slidecard{
  padding:9.1px; gap:7.8px; border-radius:11.05px;   /* 7 · 6 · 8.5, ×1.3 */
  background:#fff; border-color:var(--green-border);
  opacity:1;                                     /* never inherit the source card's fade */
}
.be-drag-ghost .be-slidename{ font-size:14.95px; color:var(--green); }
.be-drag-ghost .be-slidethumb{
  width:200.2px; height:112.613px; border-radius:4.42px;   /* 154 · 86.625 · 3.4, ×1.3 */
}
.be-drag-ghost .be-slidethumb iframe{ transform:scale(.10426); }   /* .0802 × 1.3 */

/* Where it would land. Sits in the rail's own gap, so it reads as a place
   rather than as an overlay. */
.be-drop-line{
  flex:none; height:3px; border-radius:2px; background:var(--green);
  box-shadow:0 0 10px rgba(23,49,122,.35);
}
.be-rail.is-receiving{ outline:1px dashed #B6D6FF; outline-offset:8px; border-radius:10px; }

/* While something is being carried, the slide stops answering the pointer.
   Pointer capture already routes the events to the card being dragged; this
   also keeps the slide from reacting to a cursor that is only passing over it,
   and stops the drag turning into a text selection. */
.be-work.is-carrying{ user-select:none; }
.be-work.is-carrying #be-frame{ pointer-events:none; }

.be-slide{
  position:relative; width:1317.618px; height:741.16px;
  background:#fff; border-radius:12.764px; overflow:hidden;
  box-shadow:0 10px 34px rgba(0,0,0,.12);
}
.be-slide iframe{
  position:absolute; top:0; left:0; width:1920px; height:1080px; border:0;
  transform:scale(.68626); transform-origin:top left;
}

/* ---- Deleted builds ---- */
/* The bin's icons are a touch smaller than the rail art they sit beside. */
/* No size of its own any more: the bin is the same 38x38 chip artwork as the
   other tabs, so it takes .tab-ico's own 30.493px like they do. The 26px it
   used to be was for the old chipless glyph. */

.dbuild-list{ display:flex; flex-direction:column; gap:12px; margin-top:26px; }
.dbuild-row{
  display:flex; align-items:center; gap:24px;
  background:#fff; border:.561px solid var(--frame-border); border-radius:8.734px;
  padding:16px 22px;
}
.dbuild-name{ font-size:20px; color:#111; flex:1; min-width:0; }
.dbuild-when{ font-size:14px; color:#8a8a8a; white-space:nowrap; }
.dbuild-left{ font-size:14px; color:var(--muted); white-space:nowrap; }
.dbuild-left.urgent{ color:var(--red); }
.dbuild-actions{ display:flex; gap:12px; }
.dbuild-actions form{ margin:0; }
.dbuild-btn{
  height:38px; padding:0 16px; font-size:14px;
  color:#4a4a4a; cursor:pointer;
}
.dbuild-btn:hover{ border-color:#B6D6FF; color:var(--green); }
.dbuild-btn.danger{ color:var(--red); border-color:#f0b4b0; }
.dbuild-btn.danger:hover{ border-color:var(--red); color:var(--red); }
.dbuild-note{ margin-top:20px; color:var(--red); font-size:14px; }


/* A button that carries a glyph as well as words. The gap is the only new
   value; everything else the chip already had. The glyph follows `color`,
   so it is white on the navy primary and navy on a ghost without either
   being stated twice. */
.btn > svg{ flex:none; width:17px; height:17px; }
.btn:has(> svg){ gap:9px; }

/* ---------- the design theme on the settings page ----------
   A row that names the theme, and a list behind it. It began as the shelf's
   own cards laid out in a wrapping row, which took a column of the page for
   a choice that is made once: a list of rows in a dialog says the same thing in a
   field's worth of space, and the page keeps the shape it had when a colour
   picker sat here.

   The closed row borrows the password field's frame, because it is the same
   thing — a value you look at and a control that opens somewhere else — and
   the page should not grow a second idea of what that looks like. */
.theme-open{
  display:flex; align-items:center; gap:14px; width:100%;
  margin-top:4px; padding:10px 14px;
  border:.3px solid var(--frame-border); border-radius:8.33px;
  background:#f6f6f6; box-shadow:var(--frame-shadow);
  cursor:pointer; text-align:left; font:inherit; color:inherit;
  transition:background .15s, border-color .15s;
}
.theme-open:hover{ background:#fff; border-color:#d8d8d8; }

/* The design's own card. Same shape and same three states as the editor's
   shelf card — 1px grey at rest, #B6D6FF under the cursor, a 2px #2A5FC4 ring
   and a lift when it is the one in use — because it is the same decision seen
   twice, and a second idea of "chosen" would be one too many.

   Everything inside is sized in per cent of the box, so one set of rules draws
   the big card in the list and the small one on the closed row. The name sits
   optically centred in the paper: the box's own bottom padding is the strip's
   height, so centring the flex child centres it in what is left. */
.dt-thumb{
  position:relative; box-sizing:border-box; flex:none;
  width:100%; aspect-ratio:19 / 10;
  border-radius:8px; overflow:hidden;
  background:var(--dc-ground, #fff);
  border:1px solid #C0C0C0;
  display:block;
  transition:border-color .15s, box-shadow .15s;
}
/* The name fills the paper above the strip and centres in it. Absolute, not a
   flex child with bottom padding: a percentage padding resolves against the
   box's WIDTH, so on a card twice as wide as it is tall it reserved twice the
   room the strip takes and pushed the name off the top. */
.dt-t-name{
  position:absolute; inset-inline:7%; top:0; bottom:26%;
  display:flex; align-items:center; justify-content:center;
  font-family:var(--dc-font, inherit);
  color:var(--dc-ink, #111);
  font-size:38px; line-height:1.1; letter-spacing:.2px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* Ground, ink, accent, accent tint, neutral — the card's own five, in the card's
   own order, full width and flush to the foot. */
.dt-t-swatches{
  position:absolute; inset-inline:0; bottom:0; display:flex; height:26%;
}
.dt-t-swatches i{ flex:1 1 0; min-width:0; }

/* The closed row: the same card, smaller, with the chevron out to the right. */
.theme-open .dt-thumb{ width:172px; border-radius:6px; }
.theme-open .dt-t-name{ font-size:17px; }
.dt-caret{ flex:none; margin-inline-start:auto; width:12px; height:12px; color:#8A8A8A; }
.dt-caret svg{ width:12px; height:12px; display:block; stroke-width:1.3; }

/* ---- the list ----
   The pairings list's rules, at the width a design's name and its faces need. */
.dt-modal{ max-width:460px; }
.dt-list{ list-style:none; margin:26px 0 0; padding:0 4px; max-height:62vh; overflow-y:auto; }
.dt-list li + li{ margin-top:16px; }
/* The row is nothing but the card: no background of its own, no separators, no
   tick. Every state it has, the card wears. */
.dt-row{
  display:block; width:100%; padding:2px; margin:0;
  background:none; border:0; cursor:pointer;
}
.dt-row:hover .dt-thumb{ border-color:#B6D6FF; }
.dt-row.on{ cursor:default; }
.dt-row.on .dt-thumb{
  border-color:transparent;
  outline:2px solid #2A5FC4; outline-offset:0;
  box-shadow:0 2px 10px rgba(0,0,0,.10);
}
.dt-list.is-busy{ pointer-events:none; opacity:.55; }


/* ---------- the account's picture ----------
   The circle is the label for a hidden file input, so the whole thing is one
   target and the keyboard reaches it without script. The + sits on its lower
   right, which is where every application that does this puts it. */
.avatar-row{ display:flex; align-items:center; gap:18px; margin:6px 0 18px; }
.avatar-pick{
  position:relative; flex:none; display:block;
  width:78px; height:78px; border-radius:50%;
  background:#F6F6F4; border:1px solid #E4E4E4;
  cursor:pointer; overflow:visible;
}
.avatar-pick:hover{ border-color:#B6D6FF; }
.avatar-pick:focus-within{ outline:2px solid #17317A; outline-offset:2px; }
.avatar-pick input[type="file"]{
  position:absolute; inset:0; width:100%; height:100%;
  opacity:0; cursor:pointer;
}
/* Both the picture and the initial are clipped to the circle; the + is not,
   which is why the clipping lives on these two and not on the label. */
.avatar-img, .avatar-blank{
  position:absolute; inset:0; width:100%; height:100%;
  border-radius:50%; object-fit:cover;
}
.avatar-img[hidden], .avatar-blank[hidden]{ display:none; }
.avatar-blank{
  display:flex; align-items:center; justify-content:center;
  font-size:30px; font-weight:500; color:#9A9A9A; letter-spacing:.5px;
}
.avatar-plus{
  position:absolute; right:-2px; bottom:-2px;
  display:flex; align-items:center; justify-content:center;
  width:26px; height:26px; border-radius:50%;
  background:#17317A; color:#fff;
  border:2px solid #fff;
  pointer-events:none;
}
.avatar-plus svg{ width:13px; height:13px; display:block; }
.avatar-plus svg path{ stroke:#fff; }
.avatar-side{ min-width:0; }
.avatar-name{ margin:0 0 2px; font-size:16px; }
.avatar-side .hint{ margin:0 0 8px; }

/* ---------- placing the picture ----------
   The stage is square and the mask is the circle the picture will be shown
   in: what you see inside the ring is exactly what is saved, so there is
   nothing to learn about how the crop will come out. The image is moved with
   a transform rather than by changing its box, so dragging costs nothing. */
.ac-modal{ max-width:420px; }
.ac-hint{ margin:18px 0 14px; }
.ac-stage{
  position:relative; width:260px; height:260px; margin:0 auto;
  overflow:hidden; border-radius:10px; background:#EFEFEC;
  cursor:grab; touch-action:none; user-select:none;
}
.ac-stage.is-dragging{ cursor:grabbing; }
.ac-stage img{
  position:absolute; left:0; top:0;
  transform-origin:0 0; display:block; max-width:none;
  -webkit-user-drag:none; user-select:none;
}
/* The circle. A ring of white spread out from a 256px disc and clipped by the
   stage — the oldest trick there is for this, and the only one that needs no
   mask support and no second element over the picture, so nothing ever sits
   between the pointer and the drag. */
.ac-mask{ position:absolute; inset:0; pointer-events:none; }
.ac-mask::before{
  content:""; position:absolute; left:50%; top:50%;
  width:256px; height:256px; margin:-128px 0 0 -128px;
  border-radius:50%; box-shadow:0 0 0 999px rgba(255,255,255,.78);
}
.ac-mask::after{
  content:""; position:absolute; left:50%; top:50%;
  width:256px; height:256px; margin:-128px 0 0 -128px;
  border-radius:50%; box-shadow:inset 0 0 0 1px rgba(23,49,122,.35);
}
.ac-zoom{ display:flex; align-items:center; gap:12px; width:260px; margin:16px auto 0; }
.ac-zoom-cap{ flex:none; width:12px; text-align:center; font-size:15px; color:#8A8A8A; }
.ac-zoom input[type="range"]{ flex:1; min-width:0; accent-color:#17317A; }
.ac-acts{ display:flex; justify-content:flex-end; gap:12px; margin-top:22px; }
.ac-swap{
  position:relative; display:block; width:260px; margin:16px auto 0;
  text-align:center; cursor:pointer;
  font-size:13px; line-height:1.2; color:#17317A; text-decoration:underline;
  text-underline-offset:3px; white-space:nowrap;
}
.ac-swap:hover{ color:#0F2359; }
.ac-swap input[type="file"]{
  position:absolute; inset:0; width:100%; height:100%; opacity:0; cursor:pointer;
}
.ac-modal.is-busy{ pointer-events:none; opacity:.6; }

/* ---------- the account's picture, under the logo ----------
   Not a link and not a rail item: it does not answer to hover, it has no label
   and it never lights up. A thin blue ring is the whole of its interface.

   The geometry is fixed against the rail as it was before this existed. The
   logo's own 62px margin and the column's 40px gap put the first menu button
   102px below the logo, and the two margins here take the picture out of that
   distance rather than adding to it, so the first button lands exactly where
   it always did. The picture is centred in what is left: 102 less its own 38
   is 64, so 32px of air above it and 32 below — −70 of the 102 above,
   −8 of the 40 below. Measured: logo 20–58, picture 90–128, first button 160.
   With no account there is no .rail-me and nothing moves. */
.rail-me{
  display:flex; justify-content:center;
  margin-top:-70px; margin-bottom:-8px;
}
.rail-me-pic{
  flex:none; display:flex; align-items:center; justify-content:center;
  width:38px; height:38px; border-radius:50%;
  background:#ECECED; overflow:hidden;
  border:1px solid #9CBFEF;
}
.rail-me-pic img{ width:100%; height:100%; object-fit:cover; display:block; }
.rail-me-initial{ font-size:15px; font-weight:500; color:#6B6B6B; }

/* the client link's switch, under the link itself */
.link-switch { margin-top: 14px; }
.link-switch .link-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.sign-records { margin: 6px 0 0; padding-left: 18px; font-size: 13px; line-height: 1.5; }

/* User page, Security: the client-link settings */
.client-links-form { display: flex; flex-direction: column; gap: 10px; margin: 6px 0 18px; }
.client-links-form .hint { margin: 0; }
.check-row { display: flex; gap: 10px; align-items: flex-start; cursor: pointer; line-height: 1.4; }
.check-row input { margin-top: 3px; accent-color: #ff3c02; flex: none; }
#security-card h3 { margin: 18px 0 4px; font-size: 15px; }

/* User page: Delete my account */
.delete-card { border-color: #f3c3b5; }
.delete-card h2 { color: #c0391b; }
.delete-form { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; }
.delete-note { margin: 0 0 10px; padding: 10px 12px; border-radius: 10px; background: #fdecea; color: #a12a12; font-size: 13.5px; line-height: 1.45; }
.delete-note.ok { background: #eef7ee; color: #1f6b2a; }
.btn.danger { background: #c0391b; border-color: #c0391b; color: #fff; }
.btn.danger:hover { background: #a12a12; border-color: #a12a12; }
/* sign-in style pages: the last delete button */
.signin-submit.signin-danger { background: #c0391b; color: #fff; }

/* "Forgot your password?" under the password on the delete card */
.delete-forgot{ margin:-4px 0 2px; font-size:13px; }
.delete-forgot a{ color:var(--muted); text-decoration:underline; }
.delete-forgot a:hover{ color:var(--ink, #111); }

/* a card title with its mark in front (Google sign-in) */
.form-card h2.h-ico{ display:flex; align-items:center; gap:9px; }
.form-card h2.h-ico svg{ width:18px; height:18px; flex:none; }
.google-card .hint:last-child{ margin-bottom:0; }

/* ---- User page side cards, tightened (Oct 2026): less padding, the state on
   the title's own line, small notes under fields, label and select on one row */
.card.tight{ padding:20px 22px; }
.card.tight h2{ font-size:16px; margin-bottom:8px; }
.card.tight .hint{ font-size:12.5px; line-height:1.45; margin:0 0 10px; }
.card.tight .field{ gap:5px; }
.card.tight .field > span{ font-size:13px; }
.card.tight h3{ margin:12px 0 6px; font-size:14px; }
.google-card h2{ margin-bottom:0; }
.h-state{ display:inline-flex; align-items:center; gap:4px; margin-left:2px; font-size:inherit; font-weight:400; color:var(--muted); }
.google-card .btn{ margin-top:12px; }
.h-state.on{ color:#2f7a3a; }
.h-state .ok-ico{ display:block; }
.row-field{ flex-direction:row; align-items:center; gap:12px; }
.row-field > span{ flex:none; }
.row-field select{ flex:1; min-width:0; height:40px; }
.card.tight .client-links-form{ gap:8px; margin:4px 0 12px; }
.card.tight .check-row{ font-size:14px; }
.card.tight .btn.full-w{ padding-top:10px; padding-bottom:10px; }
