/* --------------------------------------------------------------------------
   Fonts, served from this origin.

   Previously these came in through npm packages that the bundler inlined. Here
   they are three files next to the stylesheet, which matters for more than
   tidiness: the Content-Security-Policy allows nothing from another origin, so
   a Google Fonts tag would simply be blocked — and a page that fetches its
   fonts from somewhere else also tells that somewhere else who read it.

   `font-display: swap` shows the text in the fallback face immediately rather
   than leaving a blank paragraph while a 30KB file arrives on a phone.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Cairo Variable";
  font-style: normal;
  font-display: swap;
  font-weight: 200 1000;
  src: url("/assets/fonts/cairo-arabic-wght-normal.woff2") format("woff2-variations");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0898-08E1,
    U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74,
    U+FE76-FEFC;
}

@font-face {
  font-family: "Cairo Variable";
  font-style: normal;
  font-display: swap;
  font-weight: 200 1000;
  src: url("/assets/fonts/cairo-latin-wght-normal.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Space Grotesk Variable";
  font-style: normal;
  font-display: swap;
  font-weight: 300 700;
  src: url("/assets/fonts/space-grotesk-latin-wght-normal.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ==========================================================================
   Unilira — the whole stylesheet.

   Carried over from the previous build unchanged apart from two removals: the
   Tailwind import and the @theme block it fed. Neither was doing anything, as
   the interface is built from the named classes below rather than from utility
   classes — which is why the look survives the move to PHP intact instead of
   being rebuilt and re-argued.

   Dark is the default. The light palette is applied when the visitor picks it:
   a cookie sets data-theme="light" on <html> server-side, so the first paint is
   already correct and there is no flash of the wrong theme.
   ========================================================================== */

/* ==========================================================================
   Unilira design tokens
   Black · white · lemon green · dark gray · dark green · gold
   ========================================================================== */

/* Dark is the default. A light palette is defined below and is applied when
   the visitor picks it (cookie → data-theme="light" on <html>, so the very
   first paint is already correct — no flash). */
:root {
  --ink: #050605;
  --ink-2: #0d100d;
  --surface: #121512;
  --surface-2: #181c18;
  --line: #24291f;
  --line-soft: #1c2019;

  --text: #f3f5f0;
  --text-dim: #9aa295;
  --text-faint: #6b7266;

  /* --lemon is the accent used for TEXT; --lemon-solid is the brand fill. */
  --lemon: #a1d24a;
  --lemon-solid: #a1d24a;
  --lemon-ink: #0b0f0a;
  --lemon-soft: rgba(161, 210, 74, 0.14);
  --lemon-line: rgba(161, 210, 74, 0.3);
  --forest: #0a3d2f;
  --gold: #f6d94c;
  --danger: #e8705f;
  --info: #6fb8e8;

  /*
    What the hero headline drifts through: the two brand colours and the page's
    own ink, and nothing else. Three stops against three words means each word
    is on a different one at any moment — the line is never flat and never a
    colour that is not ours.
  */
  --hue-1: #a1d24a;
  --hue-2: #f6d94c;
  --hue-3: #f2f5ec;

  /* the logo and wordmark: white on dark, black on light */
  --logo: #ffffff;
  --dot-rgb: 161, 210, 74;
  --dot-alpha: 1;
  --header-bg: rgba(5, 6, 5, 0.72);

  --radius: 14px;
  --radius-sm: 10px;

  /*
    One family for the whole site, Arabic and English alike. "Cairo Variable"
    is the bundled file; the plain "Cairo" after it catches a machine that has
    the font installed, and the system stack catches the rest.
  */
  --font-body: "Cairo Variable", "Cairo", "Noto Sans Arabic", ui-sans-serif,
    system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
    sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji";
  /* Headlines and numbers. No Arabic in Space Grotesk, so Arabic headlines
     fall through to Cairo — which is the intended pairing, not an accident. */
  --font-display: "Space Grotesk Variable", "Cairo Variable", "Cairo",
    ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
    "Noto Color Emoji";
  --font-ar: var(--font-body);
  --font-mono: ui-monospace, "SFMono-Regular", "JetBrains Mono", Menlo, Consolas,
    monospace, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji";

  color-scheme: dark;
}

:root[data-theme="light"] {
  /*
     The page ground is deliberately a step deeper than it once was. Cards here
     are white, and against #f7f8f3 the edge between a card and the page came
     down to a single hairline border — on a bright screen the dashboard read as
     one flat sheet with lines drawn on it rather than as panels sitting on a
     surface. The dark theme never had this problem because its cards are
     lighter than its ground by a visible amount; this gives light the same
     separation the other way up.
  */
  --ink: #eef1e7;
  --ink-2: #ffffff;
  --surface: #ffffff;
  --surface-2: #f2f4ec;
  --line: #dfe3d6;
  --line-soft: #e9ece2;

  --text: #12160e;
  --text-dim: #565d4d;
  --text-faint: #7f8674;

  --lemon: #587f18;
  --lemon-solid: #a1d24a;
  --lemon-ink: #0b0f0a;
  --lemon-soft: rgba(88, 127, 24, 0.1);
  --lemon-line: rgba(88, 127, 24, 0.28);
  --gold: #9a7c06;
  --danger: #c0432f;
  --info: #22699c;

  /*
    The same three, at weights that hold their own on white — the dark-theme
    lemon and gold would read as pastel here, and the ink flips to the dark.

    The gold is the hard one: darkening #f6d94c far enough to be legible on a
    near-white ground walks it straight into mud, so it is pushed back towards
    amber instead of simply dimmed. And the ink is a green-black rather than a
    neutral one, so the third word still belongs to the other two.
  */
  --hue-1: #4d7c13;
  --hue-2: #b07d05;
  --hue-3: #1b2412;

  --logo: #0b0d0a;
  --dot-rgb: 88, 127, 24;
  --dot-alpha: 0.85;
  --header-bg: rgba(247, 248, 243, 0.78);

  color-scheme: light;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  /* 100dvh follows the mobile browser's shrinking/growing URL bar; 100vh is
     the fallback for browsers that don't know dvh. */
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--ink);
  color: var(--text);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  line-height: 1.55;
}

/*
  The display face, kept to the things a reader looks at rather than reads:
  headlines, the hero, the wordmark and the big numbers on a dashboard card.
  Body copy, labels, buttons and form fields all stay in Cairo, so the change
  gives the pages a voice without making them harder to read.
*/
h1,
h2,
h3,
.hero-things,
.hero-accent,
.stat-value,
.wordmark {
  font-family: var(--font-display);
}

::selection {
  background: var(--lemon-solid);
  color: var(--lemon-ink);
}

a {
  color: inherit;
  text-decoration: none;
}

/* --------------------------------------------------------------------------
   Animated dot background — simple, slow, lemon green
   -------------------------------------------------------------------------- */

.dots-canvas {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* Beats the canvas element's intrinsic width/height attributes, so the
     backing-store size can never widen the page. */
  width: 100%;
  height: 100%;
}

.page-shell {
  position: relative;
  z-index: 1;
}

/* Full-height shells that behave on a phone. */
.min-screen {
  min-height: 100vh;
  min-height: 100dvh;
}

/* --------------------------------------------------------------------------
   Primitives
   -------------------------------------------------------------------------- */

.container-x {
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  /* Physical left/right on purpose: the notch is on a physical side, so this
     has to stay put when the page flips to RTL. max() keeps the normal gutter
     and only widens it on a notched phone in landscape. */
  padding-left: max(20px, env(safe-area-inset-left));
  padding-right: max(20px, env(safe-area-inset-right));
}

/*
   Make room for the rail.

   The rail is `position: fixed`, so content does not flow around it — it runs
   underneath. On a wide screen the page's own centred max-width leaves a gap
   big enough that nobody notices. Between the width where the rail appears
   (821px) and the width where that gap opens up, it does not: measured on the
   services page, the third card ran about 24px under the rail at 900px, and
   the bottom corner of that card was not clickable.

   This is the page yielding to the rail rather than the rail hiding, because
   the rail is how somebody contacts the studio and the corner of a card is
   not. Below 821px the rail is gone and the gutter goes with it.

   It used to be a flat 62px between 821 and 1240. Then the portfolio went back
   to being a 42px pill, the rail grew from 25px wide to 61px, and both numbers
   were wrong: too small to clear it, and stopping short of the width where the
   overlap now reaches. Rather than pick two new constants that will be wrong
   the next time the rail changes, the gutter is derived.

   Read it right to left: the rail needs 94px of the right-hand edge. On a
   screen wider than the container's 1180px the page's own centred margin has
   already paid part of that, and `(1180px - 100vw) / 2` is exactly how much —
   a negative number that eats into the 94 as the screen grows, reaching zero
   around 1370px where the margin covers the rail on its own. `min` stops it
   over-paying on a narrow screen where there is no margin to count, and `max`
   never lets it fall below the gutter every other edge of the page uses.
*/
@media (min-width: 821px) {
  .container-x {
    padding-right: max(20px, min(94px, calc((1180px - 100vw) / 2 + 94px)));
  }
}

.card {
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  backdrop-filter: blur(6px);
  /* Grid and flex items default to min-width:auto, so a wide table inside a
     card would stretch the whole page instead of scrolling inside its own
     .table-wrap. This is what keeps every screen inside the viewport. */
  min-width: 0;
}

.card-pad {
  padding: 20px;
}

/* A project card is one big link — it should feel like one. */
.project-card {
  transition: border-color 0.16s ease, transform 0.16s ease;
}
.project-card:hover {
  border-color: var(--lemon-line);
  transform: translateY(-3px);
}
.project-card img {
  transition: transform 0.35s ease;
}
.project-card:hover img {
  transform: scale(1.035);
}

.hairline {
  border-color: var(--line);
}

.muted {
  color: var(--text-dim);
}

.faint {
  color: var(--text-faint);
}

.eyebrow {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.lemon {
  color: var(--lemon);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 42px;
  padding-inline: 18px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--text);
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.16s ease, border-color 0.16s ease, color 0.16s ease, transform 0.16s ease;
  white-space: nowrap;
}

.btn:hover:not(:disabled) {
  border-color: var(--lemon-line);
  background: var(--lemon-soft);
}

.btn:active:not(:disabled) {
  transform: translateY(1px);
}

.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.btn-primary {
  background: var(--lemon-solid);
  border-color: var(--lemon-solid);
  color: var(--lemon-ink);
  font-weight: 600;
}

.btn-primary:hover:not(:disabled) {
  background: #b0e055;
  border-color: #b0e055;
}

.btn-danger {
  border-color: rgba(232, 112, 95, 0.4);
  color: var(--danger);
}

.btn-danger:hover:not(:disabled) {
  background: rgba(232, 112, 95, 0.12);
  border-color: rgba(232, 112, 95, 0.6);
}

.btn-sm {
  height: 34px;
  padding-inline: 13px;
  font-size: 13px;
}

.input,
.select,
.textarea {
  width: 100%;
  /* So a field in a flex row shrinks instead of pushing the row off-screen. */
  min-width: 0;
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text);
  padding: 11px 13px;
  font-size: 14px;
  font-family: inherit;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.input:focus,
.select:focus,
.textarea:focus {
  outline: none;
  border-color: var(--lemon-line);
  box-shadow: 0 0 0 3px var(--lemon-soft);
}

.input::placeholder,
.textarea::placeholder {
  color: var(--text-faint);
}

.textarea {
  min-height: 96px;
  resize: vertical;
}

.label {
  display: block;
  font-size: 12.5px;
  color: var(--text-dim);
  margin-bottom: 6px;
}

.field-error {
  color: var(--danger);
  font-size: 12.5px;
  margin-top: 6px;
}

/* Checkboxes ship at 13x13, which is far too small to hit with a thumb. */
input[type="checkbox"],
input[type="radio"] {
  width: 18px;
  height: 18px;
  accent-color: var(--lemon-solid);
  flex: none;
  cursor: pointer;
}
@media (max-width: 820px) {
  input[type="checkbox"],
  input[type="radio"] {
    width: 21px;
    height: 21px;
  }
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.chip-lemon {
  border-color: var(--lemon-line);
  background: var(--lemon-soft);
  color: var(--lemon);
}

.chip-gold {
  border-color: rgba(246, 217, 76, 0.32);
  background: rgba(246, 217, 76, 0.12);
  color: var(--gold);
}

.chip-danger {
  border-color: rgba(232, 112, 95, 0.32);
  background: rgba(232, 112, 95, 0.1);
  color: var(--danger);
}

.chip-info {
  border-color: rgba(111, 184, 232, 0.3);
  background: rgba(111, 184, 232, 0.1);
  color: var(--info);
}

.chip-mute {
  color: var(--text-dim);
}

.dot {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: currentColor;
  display: inline-block;
}

/*
 * The booking flow while it is fetching the next step.
 *
 * The step used to arrive as a whole new page, which is its own announcement:
 * the tab spins, the page blanks, something is clearly happening. Swapping one
 * element in place is faster and says nothing, so this says it — the panel
 * dims for as long as the answer is in the air, and presses are ignored while
 * it is, which stops a second date landing on top of the first.
 */
#booking[aria-busy="true"] {
  opacity: 0.55;
  pointer-events: none;
}

#booking {
  transition: opacity 0.12s ease;
}

@media (prefers-reduced-motion: reduce) {
  #booking {
    transition: none;
  }
}

/* Slot buttons ------------------------------------------------------------ */

.slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 10px 4px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--ink-2);
  color: var(--text);
  font-size: 13.5px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: all 0.14s ease;
}

.slot:hover:not(:disabled) {
  border-color: var(--lemon-line);
  background: var(--lemon-soft);
}

.slot[data-selected="true"] {
  background: var(--lemon-solid);
  border-color: var(--lemon-solid);
  color: var(--lemon-ink);
  font-weight: 600;
}

.slot:disabled {
  cursor: not-allowed;
  color: var(--text-faint);
  background: transparent;
  border-style: dashed;
  border-color: var(--line-soft);
}

/* Calendar ---------------------------------------------------------------- */

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
}

.cal-day {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-soft);
  background: var(--ink-2);
  color: var(--text);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: all 0.14s ease;
}

.cal-day:hover:not(:disabled) {
  border-color: var(--lemon-line);
}

.cal-day[data-selected="true"] {
  background: var(--lemon-solid);
  border-color: var(--lemon-solid);
  color: var(--lemon-ink);
  font-weight: 600;
}

.cal-day[data-today="true"] {
  box-shadow: inset 0 0 0 1px var(--lemon-line);
}

.cal-day:disabled {
  cursor: not-allowed;
  color: var(--text-faint);
  background: transparent;
  border-color: transparent;
}

.cal-day .marks {
  display: flex;
  gap: 3px;
  height: 5px;
}

.cal-day .mark {
  width: 5px;
  height: 5px;
  border-radius: 999px;
}

/* Step rail --------------------------------------------------------------- */

.steps {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.step {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 12.5px;
  color: var(--text-faint);
}

.step-num {
  width: 22px;
  height: 22px;
  border-radius: 999px;
  border: 1px solid var(--line);
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 600;
}

.step[data-state="active"] {
  color: var(--text);
}

.step[data-state="active"] .step-num {
  background: var(--lemon-solid);
  border-color: var(--lemon-solid);
  color: var(--lemon-ink);
}

.step[data-state="done"] {
  color: var(--lemon);
}

.step[data-state="done"] .step-num {
  border-color: var(--lemon-line);
  background: var(--lemon-soft);
  color: var(--lemon);
}

/* Tables ------------------------------------------------------------------ */

.table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}

table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
}

table.data th {
  text-align: left;
  font-weight: 500;
  color: var(--text-faint);
  font-size: 11.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

table.data td {
  padding: 12px;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
}

table.data tbody tr:hover {
  background: rgba(161, 210, 74, 0.04);
}

/* A cell that must not push the table wider than the screen, and the one-line
   clamp inside it. Both are undone on a phone, where the row is a card with
   room for the whole value — see the phone block further down. */
/* The row of numbers at the top of a list page. */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(136px, 1fr));
  gap: 12px;
}

.cell-narrow {
  max-width: 220px;
}

.cell-clip {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Misc -------------------------------------------------------------------- */

.stat {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.stat-value {
  font-size: 28px;
  font-weight: 600;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.divider {
  height: 1px;
  background: var(--line-soft);
  border: 0;
  margin: 16px 0;
}

.timeline-line {
  border-left: 1px solid var(--line);
  padding-left: 16px;
  margin-left: 5px;
}

.scroll-y {
  overflow-y: auto;
}

.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Small screens ----------------------------------------------------------- */

@media (max-width: 640px) {
  .container-x {
    padding-left: max(16px, env(safe-area-inset-left));
    padding-right: max(16px, env(safe-area-inset-right));
  }

  .card-pad {
    padding: 16px;
  }

  /* Two-column admin layouts collapse to one */
  .admin-split {
    grid-template-columns: minmax(0, 1fr) !important;
  }
}

@media (max-width: 900px) {
  .admin-split {
    grid-template-columns: minmax(0, 1fr) !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}

/* --------------------------------------------------------------------------
   Site header — taller, with the wordmark, language and theme controls
   -------------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 30;
  border-bottom: 1px solid var(--line-soft);
  background: var(--header-bg);
  backdrop-filter: blur(12px);
}

.site-header-inner {
  min-height: 104px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding-block: 16px;
}

.site-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-inline-start: auto;
}

/* Phones: the header keeps one row — the pages are behind the menu button, so
   there is nothing left to wrap. The mark is set large for the desktop
   header, and the inline size from the component has to be overridden to
   bring it down. */
@media (max-width: 780px) {
  .site-header-inner {
    min-height: 0;
    padding-block: 12px;
    gap: 8px;
  }
  .site-header-inner .logo-mark {
    width: 46px !important;
    height: 46px !important;
  }
}

.book-short {
  display: none;
}

@media (max-width: 560px) {
  .site-actions .btn-book {
    height: 40px;
    padding-inline: 15px;
    font-size: 13.5px;
  }
  .book-long {
    display: none;
  }
  .book-short {
    display: inline;
  }
}

/* --------------------------------------------------------------------------
   The menu button and its panel
   -------------------------------------------------------------------------- */

.site-menu {
  position: relative;
  display: flex;
}

.menu-button {
  height: 42px;
  padding-inline: 15px 17px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--text);
  display: inline-flex;
  align-items: center;
  gap: 9px;
  cursor: pointer;
  flex: none;
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease;
}

.menu-word {
  line-height: 1;
}

.menu-button:hover,
.menu-button.is-open {
  border-color: var(--lemon-line);
  background: var(--lemon-soft);
  color: var(--lemon);
}

.menu-button:focus-visible {
  outline: 2px solid var(--lemon);
  outline-offset: 2px;
}

.menu-bars {
  width: 18px;
  height: 12px;
  position: relative;
  display: block;
}

.menu-bars i {
  position: absolute;
  inset-inline: 0;
  height: 1.8px;
  border-radius: 2px;
  background: currentColor;
  transition: transform 0.2s ease, opacity 0.15s ease, top 0.2s ease;
}

.menu-bars i:nth-child(1) { top: 0; }
.menu-bars i:nth-child(2) { top: 5.1px; }
.menu-bars i:nth-child(3) { top: 10.2px; }

/* Three bars fold into a cross while it is open. */
.menu-button.is-open .menu-bars i:nth-child(1) {
  top: 5.1px;
  transform: rotate(45deg);
}
.menu-button.is-open .menu-bars i:nth-child(2) {
  opacity: 0;
}
.menu-button.is-open .menu-bars i:nth-child(3) {
  top: 5.1px;
  transform: rotate(-45deg);
}

.menu-panel {
  position: absolute;
  top: calc(100% + 10px);
  inset-inline-end: 0;
  z-index: 40;
  min-width: 210px;
  max-width: min(300px, calc(100vw - 32px));
  padding: 7px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--surface);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.45);
  animation: menu-in 0.16s ease-out;
}

/* The hidden attribute has to beat the layout rules above it. */
.menu-panel[hidden] {
  display: none;
}

:root[data-theme="light"] .menu-panel {
  box-shadow: 0 18px 44px rgba(24, 32, 14, 0.14);
}

@keyframes menu-in {
  from {
    opacity: 0;
    transform: translateY(-6px);
  }
}

.menu-panel nav {
  display: grid;
  gap: 2px;
}

.menu-link {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding-inline: 13px;
  border-radius: var(--radius-sm);
  font-size: 15px;
  color: var(--text-dim);
  transition: color 0.14s ease, background 0.14s ease;
}

.menu-link:hover {
  color: var(--text);
  background: var(--surface-2);
}

.menu-link[aria-current="page"] {
  color: var(--lemon);
  background: var(--lemon-soft);
}

/* The rail down the right edge carries these on a desktop, so the panel only
   shows them once the rail is hidden. */
.menu-contact {
  display: none;
}

@media (max-width: 820px) {
  .menu-contact {
    display: grid;
    gap: 2px;
    margin-top: 7px;
    padding-top: 7px;
    border-top: 1px solid var(--line-soft);
  }
}

/* Tighten the header on a phone so the word "Menu" still fits beside
   everything else. */
@media (max-width: 560px) {
  .site-header-inner,
  .site-actions {
    gap: 6px;
  }
  .site-header-inner .logo-mark {
    width: 44px !important;
    height: 44px !important;
  }
  .menu-button {
    padding-inline: 13px 15px;
    gap: 8px;
  }
}

/* Below this even that will not fit — the three bars still say what the
   button is. */
@media (max-width: 382px) {
  .menu-word {
    display: none;
  }
  .menu-button {
    padding-inline: 13px;
  }
}

/* --------------------------------------------------------------------------
   Footer — the mark on one side, the work on the other, and nothing else
   -------------------------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--line-soft);
  margin-top: 72px;
}

.site-footer-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-block: 28px 22px;
}

/* On a phone the mark and the three links do fit on one row, and they should
   not have to: the row is 310px of text jammed against the right edge with
   the logo marooned at the other end. Stacked, each one gets its own line to
   be read on. */
@media (max-width: 560px) {
  .site-footer-inner {
    flex-direction: column;
    align-items: flex-start;
    gap: 20px;
  }
  .footer-links {
    justify-content: flex-start;
    gap: 16px;
  }
}

/* The two things on the right of the footer: the team's door and the work. */
.footer-links {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* Quieter than everything around it on purpose — the people who need it know
   it is there, and it never competes with the work for a visitor's attention. */
.footer-team {
  font-size: 13px;
  /* Quiet, but --text-faint on the light ground is about 3.3:1 — under the 4.5
     a body-size link needs. Dim is quiet enough and readable on both themes. */
  color: var(--text-dim);
  text-decoration: none;
  padding-block: 6px;
  white-space: nowrap;
}

.footer-team:hover {
  color: var(--lemon);
}

.footer-work {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: var(--text-dim);
  padding-block: 6px;
  transition: color 0.15s ease, gap 0.15s ease;
}

.footer-work:hover {
  color: var(--lemon);
  gap: 11px;
}

/* The arrow points the way the language reads. */
[dir="rtl"] .footer-work span {
  transform: scaleX(-1);
}



.icon-btn {
  width: 40px;
  height: 40px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--text-dim);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: all 0.15s ease;
  font-family: inherit;
  font-size: 13px;
  padding: 0;
}

.icon-btn:hover {
  color: var(--lemon);
  border-color: var(--lemon-line);
  background: var(--lemon-soft);
}

/*
   EN | AR — one control with both sides visible.

   Replaces `.lang-btn`, which was a single pill showing the language you were
   not reading. Two sides in one ring: the current one filled, the other one
   quiet and pressable. The ring is the object; the sides are inside it, which
   is why there is no gap and no second border between them.
*/
.lang-switch {
  display: inline-flex;
  align-items: center;
  /* 2px of inner padding so the filled side sits inside the ring rather than
     against it — without it the fill and the border share an edge and the
     control reads as two buttons stuck together. */
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: color-mix(in srgb, var(--surface) 70%, transparent);
}

.lang-side {
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.02em;
  padding: 6px 11px;
  border-radius: 999px;
  cursor: pointer;
  transition: color 0.16s ease, background 0.16s ease;
}

.lang-side:hover {
  color: var(--text);
}

.lang-side.is-on {
  background: var(--lemon-soft);
  color: var(--lemon);
}

.lang-side:focus-visible {
  outline: 2px solid var(--lemon);
  outline-offset: 2px;
}

.wordmark {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--logo);
  line-height: 1;
}

/* --------------------------------------------------------------------------
   Social rail — one hairline down the right edge

   REBUILT, not adjusted. The previous version was four 42px discs, each with a
   1px border, a blurred translucent backdrop and a hover that lifted and
   scaled it, sitting above a filled lemon pill. Five separate buttons in a
   column: bulky, and with no visual relationship between any two of them.

   The concept here is a single line. One hairline runs down the edge of the
   page; the marks hang off it at even intervals; the portfolio sits at the
   bottom of the same line. No element has a container, a ring, a fill or a
   shadow — take the line away and there is nothing holding them together,
   which is the point.

   Hover is a 1px lemon segment appearing on the line next to the entry under
   the cursor, plus the mark coming up to full strength. That is a navigation
   marking its current position, not a button reacting to being pressed.
   -------------------------------------------------------------------------- */

.social-rail {
  position: fixed;
  /* Physical, not logical: the rail stays on the right edge in Arabic too, so
     a logical property here would flip it into the middle of an RTL page. */
  right: 20px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 25;
}

.rail-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* Scaled with the marks: at 15px the four sat 16px apart, and at 20px that
     same 16px closed the set up into a strip. 18px keeps the rhythm the
     smaller version had — a little under the height of one mark. */
  gap: 18px;
  padding-left: 18px;
  border-left: 1px solid var(--line-soft);
}

.rail-link {
  position: relative;
  display: flex;
  align-items: center;
  /*
     42px wide with the glyph centred in it — the width of the portfolio
     button below, not the width of the mark.

     The marks used to be flush with the line and the button ran 22px past
     them on the right, so the rail had two different left-to-right centres
     and read as two stacks that happened to be touching. Giving every entry
     the same 42px track puts all five on one axis.

     It is a track, not a container: no border, no fill, no radius, and the
     box is invisible. The mark inside it is exactly as big as it was.
  */
  width: 42px;
  justify-content: center;
  color: var(--text-dim);
  text-decoration: none;
}

/*
   The segment that lights up on the line.

   -18px puts it back on the border this stack is drawn with, and the height is
   the icon's, so the lit part of the line is exactly as tall as the thing it
   belongs to.
*/
.rail-link::before {
  content: "";
  position: absolute;
  left: -19px;
  top: 50%;
  width: 1px;
  height: 22px;
  transform: translateY(-50%) scaleY(0.35);
  background: var(--lemon);
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.rail-link:hover::before,
.rail-link:focus-visible::before {
  opacity: 1;
  transform: translateY(-50%) scaleY(1);
}

/*
   Every mark renders at its exact official colour. Nothing dims it.

   This used to carry `opacity: 0.66` at rest, to keep the rail quiet until
   somebody reached for it. The fill was the official hex — but opacity is
   applied at paint time, so #0866FF at 0.66 over this page's near-black ground
   lands on screen as roughly rgb(8, 70, 171). The declared colour was right
   and the colour a visitor actually saw was not, which is the only kind that
   counts. Facebook blue is Facebook blue or it is some other blue.

   So there is no opacity on these any more, at rest or on hover. Hover is
   carried entirely by the lemon segment lighting up on the line and the two
   pixels of travel below — neither of which touches the logo.
*/
.rail-link .mark {
  transition: transform 0.2s ease;
}

.rail-link:hover .mark,
.rail-link:focus-visible .mark {
  /* Two pixels towards the line. Enough to feel, not enough to see move. */
  transform: translateX(-2px);
}

/*
   The envelope is the one of the four with no official colour to match,
   because there is no company called Email — the link opens whatever mail
   client the reader has. It takes the page's own text colour, at full
   strength like the other three, so the set reads at one weight.
*/
.rail-email { color: var(--text); }

/*
   The portfolio: the button it always was.

   Everything above it on this line leaves the site. This one does not, and it
   is the only thing on the rail that is asking to be pressed — so it is the
   only thing on the rail that looks pressable. The tint is the same lemon the
   studio uses everywhere else; hover fills it in rather than adding anything
   new.

   The label lives INSIDE the button, on its own line under the glyph. That is
   what keeps the rail 42px wide with the word still readable, instead of the
   100px it took when the word sat beside the glyph — which is why there is no
   longer a breakpoint that throws the word away to buy room.
*/
.rail-work {
  position: relative;
  width: 42px;
  display: grid;
  justify-items: center;
  gap: 5px;
  padding: 9px 4px 8px;
  border-radius: 16px;
  border: 1px solid var(--lemon-line);
  background: var(--lemon-soft);
  color: var(--lemon);
  text-decoration: none;
  /* Further from the four than they are from each other: a different kind of
     destination deserves a breath before it. */
  margin-top: 10px;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}

.rail-work svg {
  width: 16px;
  height: 16px;
  flex: none;
}

.rail-work span {
  font-size: 8.5px;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: center;
  max-width: 34px;
}

.rail-work:hover,
.rail-work:focus-visible {
  background: var(--lemon-solid);
  border-color: var(--lemon-solid);
  color: var(--lemon-ink);
}

.rail-link:focus-visible {
  outline: none;
}

.rail-work:focus-visible {
  outline: 2px solid var(--lemon);
  outline-offset: 2px;
}

/* Somebody who asked for less movement keeps the lit segment and loses the
   nudge — the segment is the part that carries the information. */
@media (prefers-reduced-motion: reduce) {
  .rail-link:hover .mark,
  .rail-link:focus-visible .mark {
    transform: none;
  }
}

/*
   The word stays at every width now.

   It used to be dropped below 1400px, because with "OUR WORK" set beside the
   glyph the rail was 100px wide and the rail is fixed — content does not flow
   around it, it runs underneath, and the services page's third card reached it
   at about that width. Stacking the word under the glyph put the whole rail
   back inside 60px, so there is nothing left to buy and the label never has to
   disappear.
*/

@media (max-width: 1080px) {
  .social-rail {
    right: 12px;
  }
  .rail-stack {
    gap: 20px;
    padding-left: 14px;
  }
  .rail-link::before {
    left: -15px;
  }
}

/* The rail would sit on top of the content on a phone — the menu panel carries
   the same four there. */
@media (max-width: 820px) {
  .social-rail {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   Task progress — reported by the person doing the work
   -------------------------------------------------------------------------- */

.progress-track {
  height: 6px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}

.progress-track > span {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--lemon-solid);
  transition: width 0.25s ease;
}

/* A task nobody has reported on yet reads as an empty track, not a thin
   lemon sliver. */
.progress-track > span[style*="width: 0%"] {
  background: transparent;
}

.progress-range {
  width: 100%;
  accent-color: var(--lemon-solid);
  height: 26px;
  cursor: pointer;
}

/* --------------------------------------------------------------------------
   Admin header — one row on a desktop, two on a phone
   -------------------------------------------------------------------------- */

.admin-header-inner {
  min-height: 58px;
  display: flex;
  align-items: center;
  gap: 14px;
}

.admin-header-actions {
  margin-inline-start: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  /* Never let the bell and avatar push the header wider than the screen. */
  min-width: 0;
}

/*
   The navigation, down the side.

   Three shapes in a year, and the reason for each is worth keeping:

   One row that scrolled sideways, with the trailing edge faded to suggest
   there was more. At 1920px five of the twenty tabs were past the fade and on
   a phone sixteen were, reachable only by dragging a scrollbar that had been
   styled invisible — which is the same as not reachable.

   Then the same tabs, wrapped, so every one of them was on screen. Nothing was
   hidden any more and a new problem took its place: twenty tabs across three
   rows is twenty words to read past at the top of every single screen, before
   reaching the page you actually came for.

   Now the five headings ARE the navigation and the tabs live inside them. Shut
   it is five short lines down the side, open it is one group and nothing else,
   and the group holding the page you are on opens itself so "where am I" never
   needs a press. The top of every screen is the screen again.

   The column is 210px because the longest heading in Arabic — العملاء
   والمبيعات — has to sit on one line; a heading that wraps stops looking like
   a heading.
*/
.admin-body {
  display: grid;
  grid-template-columns: 210px minmax(0, 1fr);
  gap: 30px;
  align-items: start;
}

.admin-side {
  display: grid;
  gap: 2px;
  padding-block: 26px 0;
  /* Stays put while a long page scrolls under it. 70px clears the sticky
     header, which is 58px tall plus its rule. */
  position: sticky;
  top: 70px;
}

/*
   A group heading. This one IS pressable, unlike the label it replaced, so it
   is bigger, it lights up under the cursor, and it carries a mark showing
   which way it will go.
*/
.nav-group-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  user-select: none;
  /* Both spellings: the triangle is drawn below instead. */
  list-style: none;
  transition: color 0.14s ease, background 0.14s ease;
}

.nav-group-head::-webkit-details-marker {
  display: none;
}

.nav-group-head:hover,
.nav-group[open] > .nav-group-head {
  color: var(--text);
  background: var(--surface-2);
}

.nav-group-name {
  min-width: 0;
}

/*
   The mark: two borders and a rotation, pointing down while the group is shut
   and up while it is open. Physical sides on purpose — a chevron pointing down
   points down in Arabic too, and logical sides would swing it sideways.
*/
.nav-group-mark {
  margin-inline-start: auto;
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px);
  transition: transform 0.16s ease;
  opacity: 0.7;
}

.nav-group[open] > .nav-group-head .nav-group-mark {
  transform: rotate(-135deg) translate(-1px, -1px);
}

/*
   The count on a SHUT group.

   A closed group must not be able to hide the fact that something is waiting
   inside it, so its badges are added up on the heading. Open it and the number
   goes: the tabs underneath are now carrying their own.
*/
.nav-group[open] > .nav-group-head .nav-group-count {
  display: none;
}

.nav-links {
  display: grid;
  gap: 1px;
  padding: 2px 0 8px;
}

@media (prefers-reduced-motion: reduce) {
  .nav-group-mark {
    transition: none;
  }
}

/*
   A tab.

   Its own class rather than `.btn.btn-sm` with four inline styles. The old
   version set the border, the background and the colour from PHP on every
   single tab, which meant the look of the navigation lived in a template and
   could not be changed here at all.
*/
.admin-tab {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 5px;
  /* Indented from the heading, because that indent is what says "inside". */
  padding: 6px 10px 6px 20px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.4;
  text-decoration: none;
  transition: color 0.14s ease, background 0.14s ease, border-color 0.14s ease;
}

/* The indent is on the side the reading starts from, both ways round. */
.ltr-island[dir="rtl"] .admin-tab {
  padding-right: 20px;
  padding-left: 10px;
}

.admin-tab:hover {
  color: var(--text);
  background: var(--surface-2);
}

.admin-tab.is-active {
  color: var(--lemon);
  border-color: var(--lemon-line);
  background: var(--lemon-soft);
}

.admin-tab-badge {
  font-size: 10px;
  padding: 0 6px;
}

@media (max-width: 560px) {
  .admin-header-inner {
    min-height: 0;
    padding-block: 10px;
    gap: 8px;
  }
  /* The role chip is the first thing to go — the nav already says where you
     are, and the avatar already says who you are. */
  .admin-header-inner > .chip {
    display: none;
  }
}

/* Breathing room above the first heading on a page. A phone header already
   takes two rows, so it needs much less. */
.page-top {
  padding-top: 76px;
}
@media (max-width: 780px) {
  .page-top {
    padding-top: 34px;
  }
}

/* The search box that sits at the end of a filter row: pushed right when
   there is room, full width on its own line when there is not. */
.filter-search {
  display: flex;
  gap: 6px;
  margin-inline-start: auto;
  width: 240px;
}
.filter-search .input {
  height: 34px;
  font-size: 13px;
}
@media (max-width: 700px) {
  .filter-search {
    width: 100%;
    margin-inline-start: 0;
  }
  .filter-search .input {
    height: 42px;
  }
}

/* --------------------------------------------------------------------------
   Touch and small screens
   -------------------------------------------------------------------------- */

@media (max-width: 820px) {
  /* iOS Safari zooms the page in when a focused field is under 16px. !important
     because several fields set a smaller size inline for desktop density. */
  .input,
  .select,
  .textarea {
    font-size: 16px !important;
  }

  /* Comfortable thumb targets without redrawing the desktop density.
     min-height rather than height, so it also lifts the few buttons that set
     a shorter height inline. */
  .btn-sm {
    height: 38px;
  }
  .btn {
    min-height: 36px;
  }

  /* A button that is the whole point of the screen — sign in, send, confirm —
     gets the full 44px a thumb is measured against, rather than the 42 that is
     fine beside a mouse. */
  .btn-primary {
    min-height: 46px;
  }

  /* Footer and inline text links get a bigger hit area without moving. */
  footer a {
    display: inline-block;
    padding-block: 5px;
  }

  /* A long email or URL wraps rather than widening its card. break-word
     (not anywhere) so it only kicks in for a word that genuinely does not
     fit, and never shrinks normal prose. */
  .card {
    overflow-wrap: break-word;
  }

  /* A data table keeps a readable width and scrolls sideways inside its
     .table-wrap. Letting it squeeze into 350px breaks names apart letter by
     letter, which is worse than scrolling. */
  table.data {
    min-width: 620px;
  }

  /* Phone numbers, references and times read as one unit — never split them
     across lines to save a few pixels the table can scroll instead. */
  table.data td.mono {
    white-space: nowrap;
  }
}

/* --------------------------------------------------------------------------
   Arabic / RTL
   -------------------------------------------------------------------------- */

/* Cairo is already the face everywhere, Latin and Arabic alike, so there is
   nothing to swap here any more — form controls only need reminding, because a
   browser gives them its own font unless told otherwise. */
[dir="rtl"] .input,
[dir="rtl"] .select,
[dir="rtl"] .textarea {
  font-family: var(--font-body);
}

/* clock times, references and phone numbers stay left-to-right */
[dir="rtl"] .mono {
  direction: ltr;
  unicode-bidi: isolate;
}

[dir="rtl"] .eyebrow {
  text-transform: none;
  letter-spacing: 0;
  font-size: 12.5px;
}

[dir="rtl"] .wordmark {
  letter-spacing: 0;
}

/* The dashboard carries its own direction, set from the account of whoever is
   signed in rather than inherited from the page around it. An Arabic-reading
   employee gets a right-to-left dashboard without flipping the public site,
   and somebody previewing the Arabic site keeps their English dashboard. */
.ltr-island {
  font-family: var(--font-body);
}

.ltr-island[dir="ltr"] {
  direction: ltr;
  text-align: left;
}

.ltr-island[dir="rtl"] {
  direction: rtl;
  text-align: right;
}

/* --------------------------------------------------------------------------
   Logo — two supplied files, one per theme (white on dark, black on light)
   -------------------------------------------------------------------------- */

.logo-mark {
  display: block;
  flex: none;
  position: relative;
}

.logo-mark img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Specificity matters here: the rule above is `.logo-mark img`, so the
   show/hide rules have to match it or they are simply overridden. */
.logo-mark img.logo-on-light {
  display: none;
}

:root[data-theme="light"] .logo-mark img.logo-on-dark {
  display: none;
}

:root[data-theme="light"] .logo-mark img.logo-on-light {
  display: block;
}

/* --------------------------------------------------------------------------
   Services — three families, each opening a popup with the kinds inside it
   -------------------------------------------------------------------------- */

.family-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px;
}

.family-card {
  display: grid;
  gap: 8px;
  align-content: start;
  text-align: start;
  padding: 22px 20px 18px;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  backdrop-filter: blur(6px);
  color: var(--text);
  font: inherit;
  cursor: pointer;
  min-width: 0;
  transition: border-color 0.16s ease, transform 0.16s ease;
}

.family-card:hover {
  border-color: var(--lemon-line);
  transform: translateY(-3px);
}

.family-card:focus-visible {
  outline: 2px solid var(--lemon);
  outline-offset: 3px;
}

.family-mark {
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  border-radius: 11px;
  background: var(--lemon-soft);
  border: 1px solid var(--lemon-line);
  color: var(--lemon);
  margin-bottom: 4px;
}

.family-mark svg {
  width: 23px;
  height: 23px;
}

.family-name {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.015em;
}

.family-tagline {
  color: var(--text-dim);
  font-size: 13.5px;
  line-height: 1.55;
}

.family-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 8px;
}

.family-cta {
  color: var(--lemon);
  font-size: 13.5px;
  font-weight: 600;
}

/* ---------------------------------------------------------------- the popup */

dialog.picker {
  /* Spelled out rather than left to the UA sheet: giving the dialog a width
     makes some engines drop the centring `margin: auto` and pin it to the
     top-left corner. */
  position: fixed;
  inset: 0;
  margin: auto;
  padding: 0;
  border: 0;
  background: transparent;
  max-width: min(560px, calc(100vw - 28px));
  width: 100%;
  max-height: min(86dvh, 760px);
  color: var(--text);
  overflow: visible;
}

dialog.picker::backdrop {
  background: rgba(4, 6, 4, 0.62);
  backdrop-filter: blur(3px);
}

:root[data-theme="light"] dialog.picker::backdrop {
  background: rgba(18, 22, 14, 0.42);
}

.picker-panel {
  display: flex;
  flex-direction: column;
  max-height: min(86dvh, 760px);
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.45);
}

.picker-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  padding: 18px 18px 14px;
  border-bottom: 1px solid var(--line);
  flex: none;
}

.picker-head h2 {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 6px 0 0;
}

.picker-close {
  flex: none;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--text-dim);
  cursor: pointer;
}

.picker-close:hover {
  color: var(--text);
  border-color: var(--lemon-line);
}

.picker-close svg {
  width: 17px;
  height: 17px;
}

.picker-list {
  display: grid;
  gap: 8px;
  padding: 14px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.picker-item {
  display: grid;
  gap: 3px;
  padding: 13px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  text-decoration: none;
  color: var(--text);
  transition: border-color 0.14s ease, background 0.14s ease;
}

.picker-item:hover {
  border-color: var(--lemon-line);
  background: var(--surface-2);
}

.picker-item-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.picker-item-name {
  font-size: 15.5px;
  font-weight: 600;
}

.picker-item-alt {
  font-size: 12.5px;
  color: var(--text-faint);
}

.picker-item-text {
  font-size: 13px;
  color: var(--text-dim);
  line-height: 1.55;
}

.picker-item-go {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--lemon);
  margin-top: 4px;
}

.picker-unsure {
  display: grid;
  gap: 2px;
  padding: 13px 18px 15px;
  border-top: 1px solid var(--line);
  background: var(--surface-2);
  text-decoration: none;
  flex: none;
}

.picker-unsure strong {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}

.picker-unsure span {
  font-size: 12.5px;
  color: var(--text-dim);
}

.picker-unsure:hover strong {
  color: var(--lemon);
}

@media (max-width: 560px) {
  .family-card {
    padding: 18px 16px 16px;
  }
  dialog.picker {
    max-width: calc(100vw - 20px);
  }
  .picker-head h2 {
    font-size: 17.5px;
  }
}

/* --------------------------------------------------------------------------
   Contact form — the public one, on /contact
   -------------------------------------------------------------------------- */

.field {
  margin-bottom: 14px;
  min-width: 0;
}

/* Two fields side by side on a wide screen, stacked on a phone — the wrap is
   automatic rather than a breakpoint, so it also holds inside a narrow card. */
.field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0 12px;
}

.form-error {
  color: var(--danger);
  font-size: 13px;
  margin: 4px 0 10px;
}

/* Off the screen but still in the page: a screen reader and the keyboard can
   both still reach it, which is what makes it right for the real file input
   sitting behind a styled label. `display: none` would break both. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The CV chooser: a button that is really a label, and the chosen filename
   beside it. Wraps rather than truncating on a phone. */
.cv-picker {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* The input is off-screen, so tabbing to it would otherwise show no ring at
   all. The label that follows it wears one instead. */
.cv-picker input:focus-visible + label {
  outline: 2px solid var(--lemon);
  outline-offset: 2px;
}

.cv-name {
  font-size: 13px;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The support client page is two columns on a desktop and one on anything
   narrower — the diary is the important half, so it goes first. */
@media (max-width: 900px) {
  .support-grid {
    grid-template-columns: minmax(0, 1fr) !important;
  }
}

/* --------------------------------------------------------------------------
   Data tables on a phone — one card per row, not a sideways scroll
   --------------------------------------------------------------------------

   A table with eight columns cannot be read on a 390px screen. Between 640 and
   820 it keeps its shape and scrolls sideways, which is fine on a tablet. Below
   640 each row becomes its own card: the first cell is the heading, and every
   other cell shows its column name beside the value, taken from `data-label`.
   The markup stays a real table — same DOM, same semantics for a screen reader
   on a desktop — and no page has to render two versions of its own list.
   -------------------------------------------------------------------------- */

@media (max-width: 640px) {
  .table-wrap {
    /* nothing overflows any more, so nothing needs to scroll */
    overflow-x: visible;
  }

  table.data {
    display: block;
    min-width: 0;
    font-size: 14px;
  }

  table.data thead {
    /* the column names now live on each cell */
    display: none;
  }

  table.data tbody {
    display: grid;
    gap: 10px;
  }

  table.data tr {
    display: grid;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: 12px 14px;
  }

  table.data tbody tr:hover {
    background: none;
  }

  table.data td {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: flex-end;
    gap: 2px 10px;
    padding: 4px 0;
    border-bottom: 0;
  }

  table.data td::before {
    content: attr(data-label);
    flex: none;
    /* pushes itself to the left edge and everything else to the right, so a
       cell holding several spans keeps them together as one value instead of
       spreading them across the card */
    margin-inline-end: auto;
    font-size: 11.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-faint);
    /* the label is a repeat of the hidden header, not new information */
    speak: never;
  }

  /* Nothing scrolls sideways any more, so a value with no spaces in it — an
     audit-log id, a long reference — has to be allowed to break, or it pushes
     the whole page wider than the phone. There is room for a phone number to
     stay on one line here without being told to. */
  table.data td {
    min-width: 0;
    overflow-wrap: anywhere;
  }

  table.data td.mono {
    white-space: normal;
  }

  /* There is room for the whole value here, so nothing is clamped. */
  .cell-narrow {
    max-width: none;
  }

  .cell-clip {
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
    text-align: right;
  }

  /* The first cell names the row, so it reads as the card's heading. */
  table.data td:first-child {
    display: block;
    font-size: 15.5px;
    font-weight: 600;
    padding: 0 0 8px;
  }

  table.data td:first-child::before {
    display: none;
  }

  /* A cell holding an action rather than a value — no label, full width. */
  table.data td:not([data-label]) {
    justify-content: flex-start;
    padding-top: 10px;
  }

  table.data td:not([data-label])::before {
    display: none;
  }

  /* An empty cell would otherwise leave a gap inside the card. */
  table.data td:empty {
    display: none;
  }

  /* The month grid: seven columns is seven columns whatever the screen, so
     the room has to come from somewhere. The per-day count goes, the dots
     stay, and each day gets back the height to be tapped comfortably. */
  .cal-count {
    display: none;
  }

  .cal-day {
    gap: 3px;
  }

  /* Two columns of numbers, and an odd one out takes the full width rather
     than sitting alone in half of it with a hole beside it. */
  .stat-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .stat-row > :last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
}

/* --------------------------------------------------------------------------
   Hero — the three things we sell, said first and said big
   -------------------------------------------------------------------------- */

.hero {
  /* Centred only where the row has collapsed to one column — see the phone
     rules at the end of this section. Beside the figure the words are set
     against the reading edge. */
  text-align: start;
}

.hero-title {
  margin: 0;
  font-weight: 600;
  line-height: 1.02;
  display: grid;
  gap: 2px;
}

/*
  One word at the start of the line, one in the middle, one at the end — the
  headline reads as the full width of the page rather than as three words
  huddled in the centre. A three-column grid rather than `space-between`,
  because that would centre the middle word between its NEIGHBOURS' edges, and
  "Websites" is far wider than "POS" — so the middle one would sit visibly off
  centre. Equal columns put it in the true middle of the page.
*/
.hero-things {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: baseline;
  gap: 0 clamp(8px, 1.6vw, 24px);
  /*
    Sized so all three words sit on ONE line. The limit is the container
    (1180px), not the viewport, so the vw factor has to be small enough that
    the line still fits at the width where the container stops growing.
    Measured at seventeen widths in both languages before these numbers were
    settled — Arabic sets wider than the English, so Arabic is the binding one.
  */
  font-size: clamp(30px, 7.2vw, 110px);
  letter-spacing: -0.04em;
}

/* First word hugs the left edge, last word the right, middle one centred. */
.hero-thing:nth-child(1) { justify-self: start; }
.hero-thing:nth-child(2) { justify-self: center; }
.hero-thing:nth-child(3) { justify-self: end; }

.hero-thing {
  white-space: nowrap;
  /*
    The three words drift through the palette, each one a beat behind the one
    before it, so the line is never all the same colour and never all changing
    at once. Slow on purpose — 15 seconds a lap is something you notice on the
    second look, not something that flickers while you read.
  */
  color: var(--hue-1);
  animation: hero-hue 15s linear infinite;
}

.hero-thing:nth-child(2) {
  animation-delay: -5s;
}

.hero-thing:nth-child(3) {
  animation-delay: -10s;
}

@keyframes hero-hue {
  0%,
  100% {
    color: var(--hue-1);
  }
  33.333% {
    color: var(--hue-2);
  }
  66.666% {
    color: var(--hue-3);
  }
}

/* Anyone who has asked their system for less movement gets the line standing
   still — but still on three colours rather than collapsed into one, which is
   exactly the state the animation passes through at the top of every lap. */
@media (prefers-reduced-motion: reduce) {
  .hero-thing {
    animation: none;
    color: var(--hue-1);
  }
  .hero-thing:nth-child(2) {
    color: var(--hue-2);
  }
  .hero-thing:nth-child(3) {
    color: var(--hue-3);
  }
}

.hero-accent {
  font-size: clamp(26px, 6.4vw, 62px);
  letter-spacing: -0.03em;
}

.hero-lead {
  font-size: 17px;
  max-width: 620px;
  margin: 22px 0 0;
}

.hero-cta {
  display: flex;
  gap: 12px;
  margin-top: 32px;
  flex-wrap: wrap;
}

/*
  The line under the headline: what we do and the two buttons on one side, the
  figure on the other.

  Laid out with `start`/`end` rather than left and right, so the whole row
  mirrors itself on the Arabic page without a single rule written twice — the
  sentence takes the reading side and the figure the far one, in both
  languages. The headline above stays the full width of the page; only this
  row is in two columns.
*/
/*
  The hero as two columns: everything that is words on one side, the figure on
  the other.

  The headline used to run the full width of the page with the three words
  spread across it. Beside a figure there is no full width to run across, so the
  words stack — one per line, hard against the reading edge — which is the shape
  the phone layout already had and which reads as a list of three things rather
  than as a sentence that got squeezed.

  Laid out with `start`/`end` rather than left and right, so the whole row
  mirrors itself on the Arabic page without a single rule written twice.
*/
.hero-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(240px, 32vw, 400px);
  align-items: center;
  gap: clamp(16px, 4vw, 48px);
  text-align: start;
}

/* The three words, stacked and hugging the reading edge. Overrides the
   three-column spread the headline has when it is on its own. */
.hero-body .hero-things {
  grid-template-columns: 1fr;
  justify-items: start;
  align-items: start;
  /* Sized against the COLUMN, not the page: the figure takes up to 400px of
     the row, so the words have roughly 60% of the container to sit in. */
  font-size: clamp(34px, 5.4vw, 86px);
  gap: 0;
  line-height: 1.02;
}

.hero-body .hero-thing:nth-child(1),
.hero-body .hero-thing:nth-child(2),
.hero-body .hero-thing:nth-child(3) {
  justify-self: start;
}

.hero-body .hero-accent {
  font-size: clamp(22px, 3.4vw, 52px);
  /* The words above are set tight; the line that finishes the sentence needs
     air above it or it reads as a fourth item in the list. */
  margin-top: 8px;
}

.hero-body .hero-title:lang(ar) .hero-things {
  /* Cairo's ascenders and descenders are taller than the Latin face's, so the
     same 1.02 would have the three Arabic words touching. */
  line-height: 1.24;
}

.hero-figure {
  position: relative;
  justify-self: end;
  /* The figure is the widest thing in its column and must never push the row
     wider than the container. */
  max-width: 100%;
}

/*
  A soft wash of the brand colour behind the figure so it is standing in the
  page rather than pasted on it. Behind the image, never over it, and it does
  not take clicks.
*/
.hero-figure::before {
  content: "";
  position: absolute;
  inset: 8% -12% 2%;
  z-index: 0;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(166, 230, 68, 0.18),
    rgba(166, 230, 68, 0.05) 60%,
    transparent 100%
  );
  pointer-events: none;
}

.hero-figure img {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: auto;
  /* A drop-shadow rather than a box-shadow: the file is a cut-out, and a box
     shadow would draw a rectangle around a figure that has no rectangle. */
  filter: drop-shadow(0 24px 40px rgba(0, 0, 0, 0.45));
  animation: hero-float 7s ease-in-out infinite;
}

@keyframes hero-float {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-10px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero-figure img {
    animation: none;
  }
}

/* On a narrow screen the row becomes one column: the sentence and the buttons
   first, because they are what the page is for, and the figure under them at a
   size that does not push the rest of the page off the screen. */
/*
  The phone.

  The row becomes one column, and every size in it comes down — not for taste
  but for a measurement: laid out at desktop sizes the hero came to 936px on a
  844px screen, so the first and only thing anybody saw on opening the site was
  three words. The buttons were below the fold and the figure was below them.

  The numbers below are set so that on a 390×844 phone the sentence, both
  buttons and the top of the figure are all on the first screen. Nothing here is
  decoration; each rule is removing height.
*/
@media (max-width: 820px) {
  .hero {
    /* Overrides the 64px the template sets inline for the desktop layout. */
    padding-bottom: 28px !important;
  }
  .hero-body {
    grid-template-columns: 1fr;
    justify-items: center;
    text-align: center;
    gap: 22px;
  }
  /*
    The order on a phone: the three words, then the figure, then the sentence,
    then the buttons.

    `display: contents` takes the copy block out of the way so its three
    children become rows of the hero grid in their own right — which is the
    only way the figure can sit BETWEEN the headline and the sentence without
    the markup being written twice, once per layout. The source order stays the
    one that reads correctly with no stylesheet at all.
  */
  .hero-copy {
    display: contents;
  }
  .hero-body .hero-title  { order: 1; width: 100%; }
  .hero-figure            { order: 2; }
  .hero-body .hero-lead   { order: 3; }
  .hero-body .hero-cta    { order: 4; }

  /* One word to the start of the line, one to the middle, one to the end —
     the same three positions the headline has across the full width of a
     desktop, here spread down three lines instead of across one. */
  .hero-body .hero-things {
    grid-template-columns: 1fr;
    justify-items: stretch;
    font-size: clamp(30px, 10.5vw, 46px);
    line-height: 1.06;
  }
  .hero-body .hero-title:lang(ar) .hero-things {
    line-height: 1.3;
  }
  .hero-body .hero-thing:nth-child(1) { justify-self: start; }
  .hero-body .hero-thing:nth-child(2) { justify-self: center; }
  .hero-body .hero-thing:nth-child(3) { justify-self: end; }
  .hero-body .hero-accent {
    font-size: clamp(20px, 6.6vw, 30px);
    margin-top: 6px;
  }
  .hero-lead {
    margin: 14px auto 0;
    font-size: 15px;
  }
  .hero-cta {
    justify-content: center;
    margin-top: 20px;
  }
  .hero-figure {
    justify-self: center;
    width: min(260px, 62vw);
  }
  /* The wash behind the figure is sized for a figure three times this big. */
  .hero-figure::before {
    inset: 10% -8% 4%;
  }
}

/* A small phone has less of everything, including room for a picture. */
@media (max-width: 380px) {
  .hero-figure {
    width: min(220px, 60vw);
  }
}

/* Arabic sets tighter and has no need for the Latin negative tracking. Cairo
   also carries taller ascenders and deeper descenders than the Latin face, so
   the Latin line-height of 1.02 would have the two lines almost touching. */
.hero-title:lang(ar) {
  line-height: 1.16;
}

.hero-title:lang(ar) .hero-things,
.hero-title:lang(ar) .hero-accent {
  letter-spacing: 0;
}

/* On a phone the three words stack, one per line. */
@media (max-width: 560px) {
  .hero-things {
    font-size: clamp(38px, 15vw, 64px);
    gap: 2px;
    /* Stacked on a phone, so the three columns collapse to one and every word
       is centred again — spreading them across 360px would just look broken. */
    grid-template-columns: 1fr;
    /* The base rule aligns on the baseline, which in a column reads as a hard
       left edge inside a centred block. */
    align-items: center;
    justify-items: center;
  }
  .hero-accent {
    font-size: clamp(24px, 8.5vw, 40px);
  }
}

/* --------------------------------------------------------------------------
   Careers — the roles list and one role's page
   -------------------------------------------------------------------------- */

.job-grid {
  display: grid;
  /* min(320px, 100%) rather than a bare 320px: on a 320px phone the content
     box is only ~288px once the gutter is taken out, so a track with a hard
     320px floor is wider than the page it sits in and the whole page scrolls
     sideways. The min() lets the track give way on the narrowest screens and
     changes nothing above them. */
  grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
  gap: 14px;
}

/* The whole card is the link, so nothing inside it needs its own. */
.job-card {
  display: grid;
  gap: 10px;
  align-content: start;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.16s ease, transform 0.16s ease;
}

.job-card:hover {
  border-color: var(--lemon-line);
}

.job-title {
  font-size: 18px;
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.015em;
}

:lang(ar) .job-title {
  letter-spacing: 0;
}

.job-summary {
  font-size: 13.5px;
  margin: 0;
}

.job-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

/* Pushed to the far end of the row it shares with the chips. */
.job-go {
  margin-inline-start: auto;
  font-size: 13px;
  white-space: nowrap;
}

/* The role on the left, the form on the right — one column on anything narrow,
   with the form last, because nobody applies before reading what the job is. */
.job-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: 28px;
  align-items: start;
}

.job-apply {
  position: sticky;
  top: 90px;
}

.job-h2 {
  font-size: 16px;
  font-weight: 600;
  margin: 26px 0 8px;
}

.job-list {
  margin: 0;
  padding-inline-start: 18px;
  display: grid;
  gap: 6px;
  color: var(--text-dim);
  font-size: 14.5px;
}

@media (max-width: 860px) {
  .job-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: 20px;
  }

  /* Sticky inside a single column just pins the form over the text it is
     meant to follow. */
  .job-apply {
    position: static;
  }
}

/* A label/value list — the facts beside a record, without a table's weight. */
.kv {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 8px 16px;
  margin: 0;
  font-size: 13.5px;
}

.kv dt {
  color: var(--text-faint);
  font-size: 11.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  align-self: baseline;
  padding-top: 2px;
}

.kv dd {
  margin: 0;
  min-width: 0;
}

@media (max-width: 420px) {
  /* The two columns become label-over-value rather than squeezing a long
     email into a third of the screen. */
  .kv {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }

  .kv dd {
    margin-bottom: 10px;
  }
}


/* -------------------------------------------------------------------------- */
/* Chat — threads and team rooms                                              */
/* -------------------------------------------------------------------------- */

/*
  Shaped like the messenger everybody on the team already uses all day, because
  a chat that needs learning does not get used. The grammar is borrowed — a
  tailed bubble, the clock and the ticks tucked inside it, a patterned ground,
  a pill to type in — but every colour is Unilira's own.
*/

.chat {
  display: grid;
  /* The composer stays put and the messages scroll under it. That is the whole
     difference between a chat and a page of messages. */
  grid-template-rows: minmax(0, 1fr) auto;
  gap: 0;
  height: calc(100dvh - 206px);
  min-height: 400px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--chat-ground);
}

/* The ground. A faint lemon weave rather than a flat panel, so the bubbles
   read as sitting on something — and it is drawn in CSS, so it costs nothing
   to load and cannot fail to arrive. */
:root {
  --chat-ground: #070906;
  --chat-in: #1e2519;
  --chat-out: #2b4220;
  --chat-out-line: rgba(161, 210, 74, 0.28);
  --chat-stamp: rgba(242, 245, 236, 0.42);
  --chat-bar: #101409;
}

:root[data-theme="light"] {
  --chat-ground: #e7eadf;
  --chat-in: #ffffff;
  --chat-out: #d7ecb4;
  --chat-out-line: rgba(88, 127, 24, 0.24);
  --chat-stamp: rgba(18, 22, 14, 0.45);
  --chat-bar: #e4e7dc;
}

.chat-scroll {
  overflow-y: auto;
  overscroll-behavior: contain;
  display: grid;
  align-content: start;
  gap: 2px;
  padding: 16px 14px 20px;
  scroll-behavior: smooth;
  background-image:
    radial-gradient(circle at 12px 12px, rgba(161, 210, 74, 0.05) 1.2px, transparent 1.3px),
    radial-gradient(circle at 42px 42px, rgba(161, 210, 74, 0.035) 1.2px, transparent 1.3px);
  background-size: 60px 60px, 60px 60px;
}

/* ---------------------------------------------------------------- the date */

.chat-day {
  justify-self: center;
  margin: 10px 0 8px;
}

.chat-day span {
  display: inline-block;
  background: var(--chat-in);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  padding: 4px 13px;
  font-size: 11.5px;
  letter-spacing: 0.02em;
  color: var(--text-dim);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}

/* -------------------------------------------------------------- the bubble */

.chat-bubble {
  position: relative;
  justify-self: start;
  max-width: min(520px, 82%);
  background: var(--chat-in);
  border-radius: 8px;
  /* The corner the tail hangs off is square; the other three are round. */
  border-start-start-radius: 0;
  padding: 6px 9px 6px 10px;
  margin-top: 8px;
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.22);
  font-size: 14px;
}

.chat-bubble.mine {
  justify-self: end;
  background: var(--chat-out);
  border-start-start-radius: 8px;
  border-start-end-radius: 0;
}

/*
  The tail: a small square clipped to a triangle, hanging off the bubble's
  square corner and painted the bubble's own colour. A clipped box rather than
  the classic border trick, because the border version inherits colours in a
  way that is easy to get subtly wrong and impossible to read in a diff.
*/
.chat-bubble::after {
  content: "";
  position: absolute;
  top: 0;
  width: 9px;
  height: 11px;
  background: var(--chat-in);
}

.chat-bubble:not(.mine)::after {
  left: -9px;
  clip-path: polygon(100% 0, 100% 100%, 0 0);
}

.chat-bubble.mine::after {
  right: -9px;
  background: var(--chat-out);
  clip-path: polygon(0 0, 100% 0, 0 100%);
}

/* A second line from the same person moments later is part of the same turn:
   it keeps the tight gap, loses the tail, and rounds the corner back. */
.chat-bubble.joined {
  margin-top: 2px;
  border-start-start-radius: 8px;
  border-start-end-radius: 8px;
}

.chat-bubble.joined::after { display: none; }

.chat-who {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.5;
  margin-bottom: 1px;
}

.chat-body {
  margin: 0;
  font-size: 14px;
  line-height: 1.45;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--text);
}

/*
  An invisible copy of the stamp, sitting at the end of the text. It reserves
  exactly the room the clock needs, so a short message keeps the time on the
  same line and a long one wraps it onto its own — without measuring anything
  in JavaScript.
*/
.chat-spacer {
  display: inline-block;
  width: 0;
  overflow: hidden;
  visibility: hidden;
  white-space: nowrap;
  font-size: 11px;
  padding-inline-start: 58px;
}

.chat-stamp {
  float: inline-end;
  display: flex;
  align-items: center;
  gap: 3px;
  height: 15px;
  margin-top: -14px;
  margin-inline-start: 8px;
  font-size: 10.5px;
  line-height: 1;
  color: var(--chat-stamp);
  font-variant-numeric: tabular-nums;
  user-select: none;
}

.chat-ticks {
  display: flex;
  color: var(--chat-stamp);
}

/* The one moment of colour in the whole thread: it has actually been read. */
.chat-ticks.read { color: var(--lemon); }

/* --------------------------------------------------- what rides along */

.chat-call {
  border-radius: 7px;
  border: 1px solid var(--chat-out-line);
  background: rgba(0, 0, 0, 0.18);
  padding: 9px 11px;
  margin-top: 6px;
  display: grid;
  gap: 4px;
}

:root[data-theme="light"] .chat-call { background: rgba(255, 255, 255, 0.5); }

.chat-file {
  display: flex;
  gap: 9px;
  align-items: center;
  font-size: 12.5px;
  padding: 8px 10px;
  margin-top: 6px;
  border-radius: 7px;
  background: rgba(0, 0, 0, 0.2);
  color: var(--text);
}

:root[data-theme="light"] .chat-file { background: rgba(0, 0, 0, 0.05); }

.chat-file:hover { background: rgba(0, 0, 0, 0.32); }

.chat-file-icon {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 6px;
  display: grid;
  place-items: center;
  background: var(--lemon-soft);
  border: 1px solid var(--lemon-line);
  color: var(--lemon);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
}

.chat-file-name {
  display: block;
  overflow-wrap: anywhere;
}

/* A picture is shown at the size it wants, capped so one screenshot cannot
   take over the whole conversation. */
.chat-photo {
  display: block;
  margin-top: 6px;
  border-radius: 7px;
  overflow: hidden;
  line-height: 0;
  background: rgba(0, 0, 0, 0.2);
  max-width: 320px;
}

.chat-photo img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 340px;
  object-fit: cover;
}

/* ------------------------------------------------------------ emoji picker */

.emoji-pop {
  position: absolute;
  bottom: calc(100% + 10px);
  inset-inline-start: -6px;
  z-index: 40;
  width: 292px;
  background: var(--chat-in);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.42);
  overflow: hidden;
}

.emoji-tabs {
  display: flex;
  border-bottom: 1px solid var(--line-soft);
}

.emoji-tab {
  flex: 1;
  height: 38px;
  border: 0;
  background: transparent;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.5;
  border-bottom: 2px solid transparent;
}

.emoji-tab.on {
  opacity: 1;
  border-bottom-color: var(--lemon);
  background: var(--lemon-soft);
}

.emoji-grid {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 2px;
  padding: 8px;
  max-height: 208px;
  overflow-y: auto;
}

.emoji-cell {
  height: 32px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  font-size: 19px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}

.emoji-cell:hover { background: var(--lemon-soft); }

.emoji-cell:focus-visible {
  outline: 2px solid var(--lemon);
  outline-offset: -2px;
}

.chat-empty {
  justify-self: center;
  align-self: center;
  background: var(--chat-in);
  border-radius: 8px;
  padding: 12px 18px;
  font-size: 13px;
  color: var(--text-dim);
  max-width: 340px;
  text-align: center;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}

/* ------------------------------------------------------------- the composer */

.chat-composer {
  background: var(--chat-bar);
  border-top: 1px solid var(--line-soft);
  padding: 9px 11px;
}

.chat-bar {
  display: flex;
  gap: 8px;
  align-items: flex-end;
}

.chat-field {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: flex-end;
  gap: 4px;
  background: var(--chat-in);
  border: 1px solid var(--line-soft);
  border-radius: 22px;
  padding: 3px 6px 3px 4px;
}

.chat-field:focus-within { border-color: var(--lemon-line); }

.chat-clip {
  flex: none;
  width: 36px;
  height: 36px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--text-faint);
  display: grid;
  place-items: center;
  cursor: pointer;
}

.chat-clip:hover { color: var(--lemon); background: var(--lemon-soft); }

.chat-input {
  flex: 1;
  min-width: 0;
  min-height: 36px;
  max-height: 140px;
  /* Grows with the text rather than showing a scrollbar at one line. */
  resize: none;
  border: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 14px;
  line-height: 1.45;
  padding: 8px 8px 8px 6px;
  outline: none;
}

.chat-input::placeholder { color: var(--text-faint); }

.chat-send {
  flex: none;
  width: 42px;
  height: 42px;
  border: 0;
  border-radius: 999px;
  /* Painted rather than taken from the button tokens: this one sits on the
     composer bar in both themes and has to carry the arrow at full contrast. */
  background: #4e7a14;
  color: #f2f5ec;
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: transform 0.12s ease, opacity 0.12s ease;
}

.chat-send:hover:not(:disabled) { transform: scale(1.05); }

.chat-send:disabled {
  opacity: 0.4;
  cursor: default;
}

.chat-sending {
  width: 16px;
  height: 16px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 999px;
  animation: chat-spin 0.7s linear infinite;
}

@keyframes chat-spin {
  to { transform: rotate(360deg); }
}

.chat-hint {
  font-size: 11px;
  margin: 7px 2px 0;
}

.chat-error {
  color: var(--danger);
  font-size: 12.5px;
  margin: 0 0 7px;
}

.chat-picked {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

/* --------------------------------------------------------- the room header */

.chat-head {
  display: flex;
  gap: 11px;
  align-items: center;
  background: var(--chat-bar);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 9px 13px;
}

.chat-head-who {
  flex: 1;
  min-width: 0;
}

/* One line, always. A long room topic must not push the composer off a phone. */
.chat-head-meta {
  display: block;
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-head-faces {
  display: flex;
  flex: none;
  align-items: center;
}

/* The room list down the side of the messages page. */
.room-row {
  display: flex;
  gap: 11px;
  align-items: center;
  padding: 11px 13px;
}

.room-glyph {
  width: 38px;
  height: 38px;
  border-radius: 999px;
  display: grid;
  place-items: center;
  font-size: 16px;
  flex: none;
  background: var(--lemon-soft);
  border: 1px solid var(--lemon-line);
  color: var(--lemon);
}

@media (max-width: 640px) {
  .chat {
    /* The dashboard header is sticky, so the room has to stop above it rather
       than push the page into a second scrollbar. */
    height: calc(100dvh - 236px);
    border-radius: var(--radius-sm);
  }
  .chat-bubble { max-width: 88%; }
  /* The popover is anchored to a button near the left edge, so on a phone it
     is pinned to the composer's width instead of overflowing past it. */
  .emoji-pop { width: calc(100vw - 32px); inset-inline-start: -8px; }
  .chat-photo { max-width: 240px; }
  .chat-hint { display: none; }
  .chat-scroll { padding: 12px 10px 16px; }
  /* On a phone the header earns its space back: the faces go, and the name and
     one line of status are all that is left. */
  .chat-head-faces { display: none; }
  .chat-head { padding: 8px 11px; }
}

@media (prefers-reduced-motion: reduce) {
  .chat-scroll { scroll-behavior: auto; }
  .chat-send:hover:not(:disabled) { transform: none; }
  .chat-sending { animation-duration: 2s; }
}

/* -------------------------------------------------------------------------- */
/* Task cards — pinning and handing work out from the board                   */
/* -------------------------------------------------------------------------- */

.task-card-title {
  color: inherit;
  display: block;
}

.task-card-title:hover strong {
  color: var(--lemon);
}

/* The controls are quiet until the card is looked at: a board of forty cards
   each shouting two buttons is a board nobody can read. */
/*
  Two rows, not one: a board column is about 300px wide, and an assignee
  picker sharing that with three buttons shrinks to "Moham…", which is not a
  name anybody can pick from.
*/
.task-card-tools {
  display: grid;
  gap: 6px;
  padding-top: 9px;
  border-top: 1px solid var(--line-soft);
  opacity: 0.55;
  transition: opacity 0.14s ease;
}

.task-card:hover .task-card-tools,
.task-card:focus-within .task-card-tools {
  opacity: 1;
}

.task-card-assign {
  display: flex;
  gap: 6px;
  min-width: 0;
}

.task-card-assign .select-sm {
  flex: 1;
  min-width: 0;
}

.task-card-pins {
  display: flex;
  gap: 4px;
  justify-content: flex-end;
}

.select-sm {
  height: 30px;
  padding: 0 8px;
  font-size: 12px;
}

/* A touch screen has no hover, so the controls are simply always there. */
@media (hover: none) {
  .task-card-tools { opacity: 1; }
}

/* The role key under the "add a team member" form. */
.role-key {
  margin: 0;
  display: grid;
  gap: 5px;
  font-size: 12px;
  padding: 10px 12px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}

.role-key div {
  display: flex;
  gap: 8px;
  align-items: baseline;
}

.role-key dt {
  font-weight: 600;
  flex: none;
  min-width: 62px;
  color: var(--lemon);
}

.role-key dd {
  margin: 0;
  color: var(--text-dim);
}

/* The open-work count beside a person's name. */
.work-count {
  display: inline-flex;
  gap: 4px;
  align-items: center;
  flex: none;
}

/* A list row that carries a pin: the row is the link, the pin sits beside it. */
.pin-row {
  display: flex;
  align-items: stretch;
  overflow: hidden;
}

.pin-row-main {
  flex: 1;
  min-width: 0;
}

.pin-row-action {
  display: flex;
  flex: none;
  align-items: center;
  padding-inline-end: 8px;
}

.pin-btn {
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  /* Off by default is a grey pin, not a missing one — you have to be able to
     see where to click before you have ever pinned anything. */
  filter: grayscale(1);
  opacity: 0.35;
  transition: opacity 0.14s ease, filter 0.14s ease;
}

.pin-row:hover .pin-btn { opacity: 0.7; }

.pin-btn.on {
  filter: none;
  opacity: 1;
}

.pin-btn:focus-visible {
  outline: 2px solid var(--lemon);
  outline-offset: -2px;
}

/* -------------------------------------------------------------------------- */
/* Every conversation in the company — the admin's read-only view             */
/* -------------------------------------------------------------------------- */

.all-threads {
  display: grid;
  grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}

.all-threads-read {
  display: grid;
  gap: 9px;
  max-height: 62dvh;
  overflow-y: auto;
}

/* Deliberately NOT bubbles. This is a transcript being reviewed, not a chat
   being taken part in, and it should not look like somewhere to reply. */
.read-line {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 4px 10px;
  align-items: baseline;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 13.5px;
}

.read-who {
  font-weight: 600;
  font-size: 12.5px;
}

.read-body {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.read-when {
  font-size: 10.5px;
  color: var(--text-faint);
}

.read-files {
  grid-column: 2 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  font-size: 11.5px;
}

@media (max-width: 860px) {
  .all-threads {
    grid-template-columns: minmax(0, 1fr);
  }
  .read-line {
    grid-template-columns: minmax(0, 1fr);
  }
  .read-when {
    grid-row: 1;
    justify-self: start;
  }
  .read-files {
    grid-column: 1;
  }
}

/* -------------------------------------------------------------------------- */
/* The task board's filters                                                   */
/* -------------------------------------------------------------------------- */

/*
  The panel is a class, not a `hidden` attribute or a `<details>`: a closed
  `<details>` is hidden with `content-visibility` in current browsers and no
  media query can open it again for a wide screen. A class can be overridden.
*/
.board-filters-toggle {
  display: none;
}

.board-filters-panel {
  display: grid;
  gap: 8px;
}

@media (max-width: 760px) {
  .board-filters-toggle {
    display: flex;
    width: 100%;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    font: inherit;
    font-size: 13px;
    color: var(--text);
    text-align: start;
    padding: 10px 13px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    margin-bottom: 8px;
  }

  .board-filters-arrow {
    margin-inline-start: auto;
    color: var(--text-faint);
    transition: transform 0.15s ease;
  }

  .board-filters-toggle[aria-expanded="true"] .board-filters-arrow {
    transform: rotate(180deg);
  }

  /* Closed on a phone, so the board is the first thing under the heading. */
  .board-filters-panel {
    display: none;
  }

  .board-filters-panel.is-open {
    display: grid;
  }
}

/* ==========================================================================
   Added with the PHP build.

   Everything above this line came across from the previous stylesheet
   untouched. These are the handful of classes the server-rendered markup needs
   that the React components used to cover with inline styles — the sticky
   admin header, the sign-in card, the flash strip and the error page.
   ========================================================================== */

.admin-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--header-bg);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line-soft);
}

/*
   How wide the dashboard is allowed to get.

   1440px was too wide, and the number was not the real problem — the measure
   was. On a 1536px screen it left 48px of margin, so every screen in the
   system ran almost edge to edge and each row stretched the full 1400: a
   person's name at the far left of a card and their task count 1300px away at
   the far right, with nothing in between to carry the eye across. Long rows
   like that are read twice, once in each half.

   1240 is the public site's measure plus its gutters, so the dashboard and the
   site now read at the same width, and there is real margin either side of the
   content instead of a hairline.

   Wider screens do not get a wider page. They get more air.
*/
.admin-header-inner,
.admin-body {
  max-width: 1240px;
}

.admin-main {
  padding-block: 26px;
}

/*
   Narrow: the column becomes a block above the page.

   Shut, that block is five lines — shorter than the three wrapped rows of tabs
   it replaced, and it does not need a hamburger to be out of the way, because
   it already is.
*/
/* The button that reaches it on a phone. There is nothing to reach on a
   desktop, where the column is simply present. */
.nav-toggle {
  display: none;
}

@media (max-width: 900px) {
  .admin-body {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

  .nav-toggle {
    display: inline-flex;
  }

  /*
     Folded away rather than stacked above the page.

     Left in the flow it would put five headings — and the open group's tabs —
     between the top of the screen and the first line of every page. That is
     the wrapped strip's problem again in a taller shape.
  */
  .admin-side {
    display: none;
    position: static;
    padding-block: 10px;
    border-bottom: 1px solid var(--line-soft);
  }

  .admin-side.is-open {
    display: grid;
  }

  .admin-main {
    padding-block: 20px 26px;
  }
}

/*
   Every card in the dashboard has an inside.

   `.card` draws a frame and nothing else — the padding lives in a separate
   `.card-pad` class you have to remember. Most screens remembered. The three
   on the dashboard did not, and their text sat ONE pixel from the border:
   "Who is carrying what" welded to the frame, the row under it starting on
   the line itself.

   It was worse than a missing class, because the components inside are built
   for a padded parent. A row in `.row-list` is `padding: 9px 10px` with
   `margin-inline: -10px` — the negative margin exists so the hover block
   reaches a little past the text into the card's padding. With no padding to
   reach into, the row hangs 10px OUTSIDE the card and the avatar lands on the
   border.

   So the padding belongs on the card, not on a class beside it. A card with
   nothing but a table keeps its table edge to edge — pulled back out by the
   same 20px, so those screens look exactly as they did. `.card-pad` still
   works and is still the same 20px, so nothing double-pads; it is simply not
   required any more.

   Scoped to the dashboard on purpose: a project card on the public site is a
   cover photo that fills its frame, and padding would inset the photo.
*/
.admin-main .card {
  padding: 20px;
}

.admin-main .card > .table-wrap:only-child {
  margin: -20px;
}

/* --- stacked form fields -------------------------------------------------- */

.stack {
  display: grid;
  gap: 14px;
}

.stack > .btn {
  justify-content: center;
}

/* --- sign-in -------------------------------------------------------------- */

.bare-shell {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  /* A gutter on a phone, and room for a notch. */
  padding: 24px max(16px, env(safe-area-inset-left)) 40px;
}

.login-card {
  width: min(400px, 100%);
  padding: 30px 26px 24px;
  display: grid;
  gap: 14px;
}

.login-card h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 26px;
  letter-spacing: -0.02em;
}

.login-card > .muted {
  margin: -6px 0 4px;
  font-size: 13.5px;
  line-height: 1.6;
}

.login-logo {
  display: inline-flex;
  width: max-content;
}

.login-back {
  margin: 4px 0 0;
  font-size: 13px;
}

.login-back a {
  color: inherit;
  text-decoration: none;
}

.login-back a:hover {
  color: var(--lemon);
}

/* --- flash messages ------------------------------------------------------- */

.flash-stack {
  display: grid;
  gap: 8px;
  margin-bottom: 16px;
}

.flash {
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface);
  font-size: 13.5px;
  line-height: 1.55;
}

.flash-ok {
  border-color: var(--lemon-line);
  background: var(--lemon-soft);
  color: var(--lemon);
}

/*
 * "Deleted — undo".
 *
 * The way back sits inside the sentence that says what happened, because that
 * is the one second somebody is looking at it. A row of its own further down
 * the page would be read after the moment it is for.
 */
.flash {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.flash-undo {
  margin-inline-start: auto;
}

/*
 * The delete button, wherever it is drawn.
 *
 * It is a form — a POST with a token, never a link, because a link that
 * deletes can be followed by anything that touches the page. Inline so it sits
 * in a row of buttons or inside a table cell without taking a line of its own.
 */
.delete-form {
  display: inline-flex;
  margin: 0;
}

.flash-error {
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  color: var(--danger);
}

.flash-info {
  border-color: color-mix(in srgb, var(--info) 40%, transparent);
  background: color-mix(in srgb, var(--info) 10%, transparent);
  color: var(--info);
}

/* --- a button that has already been pressed ------------------------------- */

.btn.is-busy {
  opacity: 0.6;
  pointer-events: none;
}

/* --- the error page ------------------------------------------------------- */

.error-page {
  display: grid;
  place-items: center;
  min-height: 100dvh;
  margin: 0;
  padding: 24px;
  background: var(--ink);
  color: var(--text);
  font-family: var(--font-body);
}

.error-box {
  text-align: center;
  display: grid;
  gap: 10px;
  max-width: 420px;
}

.error-code {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.18em;
  color: var(--lemon);
}

.error-box h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 30px;
  letter-spacing: -0.02em;
}

.error-message {
  margin: 0 0 8px;
  color: var(--text-dim);
  line-height: 1.6;
}

.error-box .btn {
  justify-self: center;
}

/* ==========================================================================
   Layout primitives.

   The React build expressed these as inline style objects on each component —
   1311 of them. Written once here instead, so every page in the dashboard
   lines up with every other one and a change to, say, the spacing of a list
   happens in a single place rather than in forty templates.

   The vocabulary is small on purpose:
     .page-head    the title row at the top of a page
     .tile-grid    the row of numbers
     .split-2/3    side-by-side cards that stack on a phone
     .row-list     a list of linked rows — the workhorse
     .data-table   a real table, scrollable sideways on a phone
     .form-grid    labelled fields
     .toolbar      filters and actions above a list
     .empty-state  what a page says when there is nothing in it
   ========================================================================== */

/* --- page heading --------------------------------------------------------- */

.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 22px;
}

.page-head h1 {
  margin: 2px 0 4px;
  font-family: var(--font-display);
  font-size: clamp(22px, 4.4vw, 30px);
  letter-spacing: -0.02em;
}

.page-head .muted {
  margin: 0;
  font-size: 13.5px;
}

.page-head-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* --- number tiles --------------------------------------------------------- */

.tile-grid {
  display: grid;
  /* minmax(0, …) rather than auto: a long label must wrap instead of widening
     its column and pushing the grid past the viewport.

     The floor is 200px, not the 150 it was. At 150 the dashboard's eight
     numbers fitted on one line and each one got a 166px column — a figure and
     a two-word label crammed into a box narrower than the label wanted to be,
     eight of them in a strip. Eight numbers in a row is a strip to scan, not a
     summary to read. At 200 the row breaks into five and three, every label
     fits on its line, and the pages that show five tiles get their five on one
     clean row. */
  grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 1fr));
  gap: 10px;
  margin-bottom: 22px;
}

/* A phone is the one place the old 150px floor was right. At 200 a 390px
   screen only fits one column, and eight numbers become eight full-width
   blocks to scroll past before the page starts. Two columns keeps the set
   readable as a set. */
@media (max-width: 640px) {
  .tile-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr));
  }
}

.tile {
  display: grid;
  gap: 2px;
  padding: 14px 15px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: inherit;
  text-decoration: none;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.tile:hover {
  border-color: var(--lemon-line);
  background: var(--surface-2);
}

.tile-value {
  font-family: var(--font-display);
  font-size: 26px;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.tile-label {
  font-size: 12.5px;
  color: var(--text-dim);
}

.tile-warn .tile-value {
  color: var(--gold);
}

/* --- side by side --------------------------------------------------------- */

.split-2,
.split-3 {
  display: grid;
  gap: 14px;
  margin-bottom: 18px;
}

@media (min-width: 900px) {
  .split-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .split-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/*
   Each card is as tall as what is inside it.

   Grid stretches items to the tallest in the row by default, which meant
   "Today's calendar" with one meeting in it was drawn seven hundred pixels
   tall to match the overdue list beside it — a box mostly full of nothing,
   which reads as data that failed to load rather than as a quiet day.
*/
.split-2 > *,
.split-3 > * {
  align-self: start;
}

/* --- cards ---------------------------------------------------------------- */

.card-title {
  margin: 0 0 4px;
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: -0.01em;
}

.card-note {
  margin: 0 0 12px;
  font-size: 12.5px;
}

/* --- a list of linked rows ------------------------------------------------ */

.row-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
}

.row-list > li > a,
.row-list > li > .row-static {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  margin-inline: -10px;
  border-radius: var(--radius-sm);
  color: inherit;
  text-decoration: none;
}

.row-list > li > a:hover {
  background: var(--surface-2);
}

.row-main {
  /* min-width: 0 is what lets the text inside actually truncate instead of
     forcing the row wider than its container. */
  min-width: 0;
  flex: 1;
  display: grid;
  gap: 1px;
}

.row-main strong {
  font-weight: 600;
  font-size: 13.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row-main .faint {
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row-time {
  font-size: 12.5px;
  color: var(--text-dim);
  flex: none;
  width: 42px;
}

/* --- who is carrying what ------------------------------------------------- */

.load-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
}

.load-list > li > a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  margin-inline: -10px;
  border-radius: var(--radius-sm);
  color: inherit;
  text-decoration: none;
}

.load-list > li > a:hover {
  background: var(--surface-2);
}

.load-count {
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
}

.load-count strong {
  font-family: var(--font-display);
  font-size: 16px;
}

/* --- avatars and presence ------------------------------------------------- */

.avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  color: #0b0f0a;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.01em;
}

.avatar-sm { width: 24px; height: 24px; font-size: 10px; }
.avatar-lg { width: 44px; height: 44px; font-size: 15px; }

.dot-online {
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-inline-start: 5px;
  border-radius: 50%;
  background: var(--lemon-solid);
  vertical-align: middle;
}

/* --- tables --------------------------------------------------------------- */

.table-wrap {
  /* A table is the one thing that genuinely cannot reflow to 390px, so it
     scrolls sideways inside its own box rather than dragging the page with it. */
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin-inline: -4px;
  padding-inline: 4px;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  white-space: nowrap;
}

.data-table th,
.data-table td {
  padding: 9px 12px;
  text-align: start;
  border-bottom: 1px solid var(--line-soft);
}

.data-table th {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.data-table tbody tr:hover {
  background: var(--surface-2);
}

.data-table td.wrap {
  white-space: normal;
  min-width: 220px;
}

/* --- forms ---------------------------------------------------------------- */

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: 12px;
}

.form-grid .full {
  grid-column: 1 / -1;
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}

/* --- toolbar above a list ------------------------------------------------- */

.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.toolbar .input,
.toolbar select {
  min-width: 0;
}

.toolbar-grow {
  flex: 1;
  min-width: 160px;
}

/* --- nothing here --------------------------------------------------------- */

.empty-state {
  display: grid;
  gap: 6px;
  justify-items: center;
  text-align: center;
  padding: 34px 18px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  color: var(--text-dim);
}

.empty-state strong {
  font-family: var(--font-display);
  font-size: 15px;
  color: var(--text);
}

/* --- header widgets: the bell and the account menu ------------------------ */

.bell,
.user-menu {
  position: relative;
}

.bell > summary,
.user-menu > summary {
  list-style: none;
  cursor: pointer;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface);
  font-size: 15px;
}

.bell > summary::-webkit-details-marker,
.user-menu > summary::-webkit-details-marker {
  display: none;
}

.user-menu > summary {
  border: none;
  background: none;
  padding: 0;
}

.bell-dot {
  position: absolute;
  top: 5px;
  inset-inline-end: 6px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--gold);
}

.bell-pop,
.user-menu-pop {
  position: absolute;
  top: calc(100% + 8px);
  inset-inline-end: 0;
  z-index: 60;
  width: min(320px, calc(100vw - 32px));
  padding: 12px;
  display: grid;
  gap: 8px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.35);
}

.bell-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.bell-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
  max-height: 340px;
  overflow-y: auto;
}

.bell-list > li > a,
.bell-list > li > span {
  display: grid;
  gap: 1px;
  padding: 7px 8px;
  margin-inline: -8px;
  border-radius: var(--radius-sm);
  color: inherit;
  text-decoration: none;
  font-size: 12.5px;
}

.bell-list > li > a:hover {
  background: var(--surface-2);
}

.bell-list > li.is-unread > a,
.bell-list > li.is-unread > span {
  border-inline-start: 2px solid var(--lemon);
}

.bell-empty {
  margin: 0;
  font-size: 12.5px;
}

.user-menu-name {
  margin: 0;
  font-weight: 600;
  font-size: 13.5px;
}

.user-menu-mail {
  margin: 0 0 4px;
  font-size: 11.5px;
}

.btn-icon {
  width: 34px;
  padding: 0;
  justify-content: center;
}

/* --- the public site shell — DELETED, not moved ----------------------------

   A second copy of the header and footer rules lived here, ported from the
   old build and never removed. Because it came later in the file it quietly
   won: the footer was a three-column grid, not the two-item row defined at
   the top of this file, and `.site-header-inner` was capped at 1200px while
   every other row on the page was capped at 1180.

   Nobody saw it while the footer had three children. The moment the social
   row came out, the third column stayed — and the remaining links stopped
   an inch short of the right edge with a column of nothing beside them.

   The rules that render are .site-header / .site-header-inner / .site-actions
   (near the top of this file) and .site-footer / .site-footer-inner with
   them. The classes the copy also styled — .site-brand, .site-nav,
   .site-header-actions, .site-main, .site-contact, .site-copyright — are not
   in any template, which is why deleting them changes nothing.
   -------------------------------------------------------------------------- */

/* --- for screen readers only ---------------------------------------------- */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   The task board (views/admin/tasks.php) and the team pages
   (views/admin/team.php, team-member.php, people.php).

   These are the classes the server-rendered markup needs that the React
   components used to carry as inline styles. Nothing above this comment was
   changed to add them.
   -------------------------------------------------------------------------- */

/* --- the board ------------------------------------------------------------ */

/*
  The columns scroll sideways as a group rather than the page doing it, so the
  header and the filters stay put while the board moves under them.
*/
.board-scroll {
  overflow-x: auto;
  padding-bottom: 8px;
  margin-bottom: 12px;
}

.board-columns {
  display: grid;
  grid-auto-flow: column;
  /* minmax(0, …) and not `auto`: an auto track holding a long title widens
     past the viewport and takes the whole page with it. */
  grid-auto-columns: minmax(250px, 1fr);
  gap: 12px;
  min-width: 1180px;
}

.board-column {
  display: grid;
  gap: 10px;
  align-content: start;
  min-width: 0;
}

.board-column-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  font-size: 12.5px;
}

.board-column-empty {
  font-size: 12px;
  margin: 2px 4px;
}

/* On a phone the board is one column per screen, swiped through. */
@media (max-width: 760px) {
  .board-columns {
    grid-auto-columns: minmax(250px, 84vw);
    min-width: 0;
  }
}

/* --- a card on the board -------------------------------------------------- */

.task-card {
  display: grid;
  gap: 8px;
  padding: 13px;
  min-width: 0;
}

.task-card-top {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  font-size: 10.5px;
}

.task-card-ref {
  margin-inline-start: auto;
  font-size: 10.5px;
}

.task-card-progress {
  display: flex;
  align-items: center;
  gap: 8px;
}

.task-card-progress .progress-track {
  flex: 1;
  min-width: 0;
}

.progress-track-thin {
  height: 4px;
}

.task-card-percent {
  flex: none;
  min-width: 30px;
  text-align: end;
  font-size: 11px;
  font-weight: 600;
}

.task-card-foot {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  font-size: 11.5px;
}

.task-card-who {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.task-card-meta {
  margin-inline-start: auto;
  display: flex;
  gap: 8px;
  align-items: center;
  font-size: 11px;
}

/* --- one task ------------------------------------------------------------- */

.task-detail {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}

@media (max-width: 900px) {
  .task-detail {
    grid-template-columns: minmax(0, 1fr);
  }
}

.task-steps {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
}

.task-steps > li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
  border-bottom: 1px solid var(--line-soft);
  min-width: 0;
}

.task-steps .step-label {
  flex: 1;
  min-width: 0;
  font-size: 13.5px;
  overflow-wrap: anywhere;
}

.task-steps .step-done {
  color: var(--text-faint);
  text-decoration: line-through;
}

/* The tick box is a submit button, because ticking a step is a write. */
.step-box {
  width: 18px;
  height: 18px;
  flex: none;
  border-radius: 5px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--lemon-ink);
  cursor: pointer;
  display: grid;
  place-items: center;
  font-size: 11px;
  padding: 0;
}

.step-box.is-done {
  border-color: var(--lemon-solid);
  background: var(--lemon-solid);
}

.step-remove {
  flex: none;
  border: none;
  background: none;
  color: var(--text-faint);
  cursor: pointer;
  font-size: 13px;
  padding: 2px 6px;
}

.task-note {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin: 0;
}

.comment-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 10px;
}

.comment-list > li {
  padding: 11px 13px;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  min-width: 0;
}

/* A status line is the task talking about itself — quieter than a person. */
.comment-list > li.is-system {
  padding: 4px 0;
  background: transparent;
  border: none;
  color: var(--text-faint);
  font-size: 12.5px;
}

.comment-head {
  display: flex;
  gap: 8px;
  align-items: baseline;
  flex-wrap: wrap;
  font-size: 12.5px;
}

.file-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 9px 12px;
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  font-size: 13px;
}

.file-row > a {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
}

.detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 14px;
}

.detail-grid > div {
  min-width: 0;
}

.detail-grid p {
  margin: 4px 0 0;
  font-size: 13.5px;
  overflow-wrap: anywhere;
}

.progress-big {
  font-size: 26px;
  font-weight: 600;
  color: var(--lemon);
}

/* --- the team page -------------------------------------------------------- */

.people-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 12px;
}

.dept-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 4px 0 2px;
}

.dept-swatch {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  flex: none;
}

.member-head {
  display: flex;
  gap: 11px;
  align-items: center;
  min-width: 0;
}

.member-head > div {
  flex: 1;
  min-width: 0;
}

.member-email {
  display: block;
  font-size: 11px;
  overflow-wrap: anywhere;
}

.member-metrics {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin: 14px 0 10px;
}

.member-metrics .mono {
  font-size: 17px;
  font-weight: 600;
}

.member-metrics span.faint {
  display: block;
  font-size: 10.5px;
}

.is-inactive {
  opacity: 0.55;
}

/* A green ring on the avatar, rather than a second element beside the name. */
.avatar-wrap {
  position: relative;
  flex: none;
  display: inline-grid;
}

.avatar-wrap .presence-dot {
  position: absolute;
  inset-inline-end: -1px;
  bottom: -1px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--text-faint);
  border: 2px solid var(--ink);
}

.avatar-wrap .presence-dot.is-online {
  background: var(--lemon-solid);
}

.working-now {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.working-now > a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 13px;
  border-radius: 999px;
  border: 1px solid var(--lemon-line);
  background: var(--lemon-soft);
  color: inherit;
  font-size: 13px;
  text-decoration: none;
}

/* --- hours, last fortnight ------------------------------------------------ */

.hours-chart {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  height: 96px;
}

.hours-chart > div {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.hours-bar {
  width: 100%;
  border-radius: 4px 4px 0 0;
  background: var(--lemon-solid);
}

.hours-bar.is-empty {
  background: var(--line);
  opacity: 0.6;
}

/* ==========================================================================
   The public marketing site — home, services, work, contact
   (views/site/home.php · services.php · portfolio.php · contact.php)
   and the portfolio admin (views/admin/portfolio.php).

   The React original carried these as inline `style` objects on each element.
   They are classes here because a template in this codebase is not allowed to
   invent layout inline, and because the same project card is drawn by two
   different pages — one definition means they cannot drift apart.
   ========================================================================== */

/* A Latin run inside Arabic prose: the stack list in the hero lead, a domain
   quoted mid-sentence. Without the isolate, the full stop that ends the
   sentence is rendered at the wrong end of the Latin run — the bidi algorithm
   is doing the right thing with the wrong assumption about where the sentence
   ends, and only the isolate can tell it. */
.ltr {
  direction: ltr;
  unicode-bidi: isolate;
}

/* --- the heading block a page or a section opens with --------------------- */

.page-title {
  font-size: clamp(32px, 5vw, 50px);
  line-height: 1.08;
  letter-spacing: -0.03em;
  font-weight: 600;
  margin: 10px 0 0;
}

[dir="rtl"] .page-title {
  letter-spacing: 0;
}

.page-lead {
  font-size: 16.5px;
  max-width: 620px;
  margin-top: 14px;
}

.section-head {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 26px;
}

.section-title {
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 10px 0 0;
}

[dir="rtl"] .section-title {
  letter-spacing: 0;
}

/* --- the project grid ----------------------------------------------------- */

/* minmax(0, …) rather than the bare 290px the original used: a track sized
   only by its content stretches past a 390px viewport the moment a summary
   line refuses to wrap, and the page grows a horizontal scrollbar. */
.work-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(290px, 100%), 1fr));
  gap: 16px;
}

.project-card {
  display: block;
  overflow: hidden;
  padding: 0;
  color: inherit;
  text-decoration: none;
}

.project-cover {
  aspect-ratio: 16 / 10;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line-soft);
  display: grid;
  place-items: center;
  overflow: hidden;
}

.project-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* No screenshot yet. The tile carries the project's own initials over the
   brand wash rather than a grey hole, so a portfolio part-way through being
   filled in still looks deliberate. */
.project-fallback {
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 4px;
  background: radial-gradient(120% 100% at 20% 0%, var(--lemon-soft) 0%, transparent 62%);
}

.project-initials {
  font-size: 34px;
  font-weight: 600;
  letter-spacing: -0.03em;
  color: var(--lemon);
  opacity: 0.7;
  line-height: 1;
}

.project-fallback-note {
  font-size: 10px;
  letter-spacing: 0.1em;
}

.project-body {
  padding: 15px 16px 17px;
  display: grid;
  gap: 7px;
  min-width: 0;
}

.project-meta {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
}

.project-title {
  font-size: 17px;
  font-weight: 600;
  margin: 0;
  line-height: 1.25;
}

.project-client {
  font-size: 12px;
}

.project-summary {
  font-size: 13.5px;
  margin: 0;
  line-height: 1.55;
}

.project-tags {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 2px;
}

.project-link {
  font-size: 12.5px;
  margin-top: 4px;
  display: flex;
  gap: 6px;
  align-items: center;
  /* A long domain must wrap inside the card rather than widen it. */
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The filter row above the grid. */
.filter-chips {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 26px;
}

.filter-chips .btn {
  color: var(--text-dim);
}

.filter-chips .btn.is-active {
  border-color: var(--lemon-line);
  color: var(--lemon);
}

/* --- the services picker, opened by a <details> rather than a dialog ------- */

.family-pop > summary.family-card {
  /* A summary is a list item by default; the marker would sit in the middle of
     a grid whose first cell is the icon. */
  list-style: none;
}

.family-pop > summary.family-card::-webkit-details-marker {
  display: none;
}

/* Open, the family takes the whole row: the kinds inside need more room than
   a 240px column, and reading them in a narrow gutter is the one thing the
   original dialog was avoiding. */
.family-pop[open] {
  grid-column: 1 / -1;
}

.family-pop > .picker-panel {
  margin-top: 10px;
  max-height: none;
}

/* The panel heading is an h3 here rather than an h2 — the page already has an
   h1 and an h2 above it, and a heading order that skips a level is a real
   navigation problem for anyone moving by headings. */
.picker-head h3 {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 6px 0 0;
}

[dir="rtl"] .picker-head h3 {
  letter-spacing: 0;
}

/* --- how booking works ---------------------------------------------------- */

.step-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  gap: 14px;
}

.step-number {
  font-size: 12px;
}

.step-title {
  font-size: 15.5px;
  font-weight: 600;
  margin: 8px 0 6px;
}

.step-text {
  font-size: 13px;
  margin: 0;
}

.closing-cta {
  margin-top: 32px;
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  align-items: center;
  justify-content: space-between;
  border-color: var(--lemon-line);
}

.closing-cta-title {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
}

.closing-cta-text {
  margin: 4px 0 0;
  font-size: 13.5px;
}

/* --- services: the stack ------------------------------------------------- */

.stack-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

/* --- contact -------------------------------------------------------------- */

.contact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 14px;
  margin-top: 36px;
  align-items: start;
}

.contact-primary {
  border-color: var(--lemon-line);
}

/* The line under a field that explains it, as opposed to .field-error, which
   says what went wrong with it. */
.field-hint {
  font-size: 12px;
  margin: 5px 0 0;
}

.hours-list {
  list-style: none;
  padding: 0;
  margin: 12px 0 0;
  font-size: 13.5px;
}

.hours-list li {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px solid var(--line-soft);
}

/* --- the portfolio admin -------------------------------------------------- */

.checkline {
  display: flex;
  gap: 8px;
  align-items: center;
  font-size: 13px;
  margin-top: 10px;
}

/* An unpublished project is dimmed rather than moved, so the order an admin
   is about to change is the order they can see. */
.is-hidden-row {
  opacity: 0.62;
}

.admin-project {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  align-items: flex-start;
}

.admin-project-cover {
  width: 108px;
  height: 72px;
  flex: none;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--surface-2);
  display: grid;
  place-items: center;
}

.admin-project-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.admin-project-main {
  flex: 1;
  min-width: min(220px, 100%);
}

.admin-project-title {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  font-size: 15.5px;
}

.admin-project-summary {
  font-size: 13px;
  margin: 5px 0 0;
}

.admin-project-facts {
  font-size: 12px;
  margin: 6px 0 0;
  overflow-wrap: anywhere;
}

.admin-project-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: flex-start;
}

.admin-project-edit {
  margin-top: 12px;
}

.admin-project-edit > summary {
  font-size: 12.5px;
  cursor: pointer;
}

.admin-project-danger {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  flex-wrap: wrap;
}

/* -------------------------------------------------------------------------- */
/* The chat composer's emoji button — /admin/messages/* only                  */
/* -------------------------------------------------------------------------- */

/*
  .emoji-pop is positioned absolutely against the button that opens it, so the
  button needs a positioned box of its own to hang off. Without this the popover
  anchors to the nearest positioned ancestor — the page — and opens in the top
  corner instead of above the composer.
*/
.emoji-wrap {
  position: relative;
  flex: none;
}

/*
  The chat is drawn on the server and the pieces that are not needed yet carry
  the `hidden` attribute. That attribute is only a UA `display: none`, so any
  class rule with a `display` of its own silently beats it — which is how all
  four emoji groups ended up stacked on top of each other instead of one at a
  time. These are the elements in the chat that are both hidden and displayed.
*/
.emoji-grid[hidden],
.chat-picked[hidden],
.chat-recording[hidden],
.chat-mic[hidden],
.chat-bar[hidden] {
  display: none;
}



/* --------------------------------------------------------------------------
   The theme button's two icons.

   Both are in the markup and the selector picks one, so the icon follows
   `data-theme` the instant the switch flips it — no reload, and no moment where
   a sun is sitting on a light page. Same approach as the logo above.
   -------------------------------------------------------------------------- */

.theme-btn .theme-moon { display: none; }
.theme-btn .theme-sun  { display: block; }

:root[data-theme="light"] .theme-btn .theme-sun  { display: none; }
:root[data-theme="light"] .theme-btn .theme-moon { display: block; }

/* --------------------------------------------------------------------------
   Two ways out of a service kind: WhatsApp, or book a meeting.

   The kind used to be one big link straight to the booking form. Booking is a
   commitment — a name, a number and a time — and most people who have just
   worked out which of twelve things they want have one question first. So the
   row is now a container with two actions, and WhatsApp comes first because it
   is the cheaper of the two for the visitor.

   `.picker-item` is no longer an <a>, so its old hover no longer fires on its
   own; the rule below restores it on the container.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   One kind, inside the services popup

   The row is a single link to WhatsApp, so the whole row is the target — the
   earlier version put two buttons inside a row that was itself not clickable,
   which meant the biggest thing on screen did nothing when pressed.
   -------------------------------------------------------------------------- */

.picker-item-go {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 8px;
  /* WhatsApp's own green, on both themes. The mark is recognised by its
     colour; a lemon-tinted one would read as ours rather than theirs. */
  color: #25D366;
}

.picker-item-go svg {
  width: 17px;
  height: 17px;
  flex: none;
}

.picker-item:hover .picker-item-go {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* The booking line at the foot of the popup: one row, not the two-line block
   the "not sure which one?" version was. */
.picker-book-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-dim);
}

.picker-book-line:hover {
  color: var(--lemon);
}

/* What a reader with scripts off gets instead of the popups: the same rows,
   laid out plainly down the page. */
.picker-plain section {
  margin-top: 28px;
}

.picker-plain h2 {
  font-size: 17px;
  margin: 0 0 10px;
}

.picker-plain .picker-list {
  padding: 0;
  overflow: visible;
}

/* --------------------------------------------------------------------------
   Files kept with a record — a meeting, a prospect, a place, a support client.

   Deliberately quieter than the chat's attachment tiles: here a file is a line
   in a list somebody scans, not a thing being sent in a conversation.
   -------------------------------------------------------------------------- */

.attach-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--text-dim);
}

.attach-form {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}

/* The file input takes the room that is left; the button never wraps under it
   on a desktop and always sits full width on a phone. */
.attach-form .input {
  flex: 1;
  min-width: 180px;
  padding: 7px 10px;
  font-size: 13px;
}

.attach-note {
  margin: 9px 0 0;
  font-size: 12px;
  line-height: 1.6;
}

@media (max-width: 480px) {
  .attach-form { flex-direction: column; align-items: stretch; }
  .attach-form .btn { justify-content: center; }
}

/* ========================================================================== */
/* Voice notes                                                                */
/* ========================================================================== */

/*
 * The player sits inside a bubble, so it takes the bubble's colours rather
 * than painting its own: a note you sent and a note you received are the same
 * control on two different grounds, and `currentColor` is what keeps them
 * readable on both without a second set of rules.
 */
.chat-voice {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 6px;
  padding: 7px 10px 7px 7px;
  border-radius: 14px;
  background: rgba(0, 0, 0, 0.22);
  /* 320px is a real phone, and this control has four things side by side. */
  min-width: min(220px, 100%);
  max-width: 100%;
}

:root[data-theme="light"] .chat-voice { background: rgba(0, 0, 0, 0.05); }

/* The element itself is only the engine — the controls above it are the UI.
   It stays in the DOM and keeps working for a reader with no JavaScript, who
   gets the browser's own controls because the script never removed them. */
.chat-voice audio { display: none; }
.chat-voice:not([data-wired="1"]) audio {
  display: block;
  flex: 1;
  min-width: 0;
  max-width: 100%;
  height: 34px;
}

.chat-voice-play {
  flex: none;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.14);
  color: inherit;
  display: grid;
  place-items: center;
  cursor: pointer;
}

:root[data-theme="light"] .chat-voice-play { background: rgba(0, 0, 0, 0.08); }

.chat-voice-play:hover { background: var(--lemon-soft); color: var(--lemon); }
.chat-voice-play svg { width: 17px; height: 17px; }

.chat-voice-bar {
  flex: 1;
  min-width: 40px;
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.2);
  cursor: pointer;
  position: relative;
}

:root[data-theme="light"] .chat-voice-bar { background: rgba(0, 0, 0, 0.14); }

.chat-voice-bar i {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  border-radius: 999px;
  background: currentColor;
}

.chat-voice-time {
  flex: none;
  font-size: 11.5px;
  opacity: 0.75;
  /* The label counts down, so the digits must not shuffle the layout. */
  font-variant-numeric: tabular-nums;
}

.chat-voice-save {
  flex: none;
  color: inherit;
  opacity: 0.55;
  display: grid;
  place-items: center;
}

.chat-voice-save:hover { opacity: 1; color: var(--lemon); }

/* ----------------------------------------------------- while recording --- */

.chat-recording {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 8px 5px 6px;
  border: 1px solid var(--lemon-line);
  border-radius: 22px;
  background: var(--chat-in);
}

.chat-rec-drop,
.chat-rec-send {
  flex: none;
  width: 36px;
  height: 36px;
  border: 0;
  border-radius: 999px;
  display: grid;
  place-items: center;
  cursor: pointer;
}

.chat-rec-drop {
  background: transparent;
  color: var(--text-faint);
}

.chat-rec-drop:hover { color: #e0543f; background: rgba(224, 84, 63, 0.12); }

.chat-rec-send {
  width: 42px;
  height: 42px;
  background: #4e7a14;
  color: #f2f5ec;
}

.chat-rec-dot {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 999px;
  background: #e0543f;
  animation: chat-rec-pulse 1.1s ease-in-out infinite;
}

.chat-rec-time {
  flex: none;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* Not a real waveform — reading the microphone to draw one would mean holding
   an analyser open for the whole recording to decorate a bar. This says the
   same thing the recording light says: it is running. */
.chat-rec-wave {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  /* Centred: the bars are capped at 3px each, so left-aligning them leaves
     the group huddled against the timer with empty bar to the right. */
  justify-content: center;
  gap: 4px;
  height: 22px;
  overflow: hidden;
}

.chat-rec-wave i {
  flex: 1;
  min-width: 2px;
  max-width: 3px;
  height: 30%;
  border-radius: 999px;
  background: var(--lemon);
  opacity: 0.6;
  animation: chat-rec-bar 0.9s ease-in-out infinite;
}

.chat-rec-wave i:nth-child(2n) { animation-delay: 0.12s; }
.chat-rec-wave i:nth-child(3n) { animation-delay: 0.26s; }
.chat-rec-wave i:nth-child(5n) { animation-delay: 0.4s; }

@keyframes chat-rec-pulse {
  50% { opacity: 0.25; }
}

@keyframes chat-rec-bar {
  50% { height: 90%; }
}

/* Somebody who has asked for less movement gets a steady bar and a steady
   dot. Neither carries information the animation was the only way to give. */
@media (prefers-reduced-motion: reduce) {
  .chat-rec-wave i,
  .chat-rec-dot {
    animation: none;
  }

  .chat-rec-wave i { height: 60%; }
}

.chat-mic {
  flex: none;
  width: 42px;
  height: 42px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--text-faint);
  display: grid;
  place-items: center;
  cursor: pointer;
}

.chat-mic:hover { color: var(--lemon); background: var(--lemon-soft); }

/* A bubble whose only content is a recording: the paragraph stays, because
   the clock sits inside it, but it has no line of its own to take up. */
.chat-body.is-voice-only {
  min-height: 0;
  margin-bottom: 0;
}


/* --------------------------------------------------------------------------
   The four marks

   Facebook, Instagram, WhatsApp and email, drawn by app/Core/Marks.php from
   the official glyphs. Everything about how they SIT is here; nothing about
   how they LOOK is, because the glyph and its colour come with the mark and
   this file is not allowed to restyle either. No filters, no shadows, no
   gradients, no border-radius — a rounded corner on an SVG root clips the
   logo, and a clipped logo is a different logo.
   -------------------------------------------------------------------------- */

.mark {
  flex: none;
  display: block;
}

/* Inside a button or a link that also carries a word. */
.btn .mark,
.menu-link .mark,
.picker-item-go .mark {
  display: inline-block;
  vertical-align: -0.22em;
  margin-inline-end: 7px;
}

/*
   The footer's row of four, in 38px discs, used to be styled here.

   It is gone, with the footer row it dressed. The page shows the set in
   exactly one place now — the rail on a desktop, the menu panel on a phone —
   and rules for a row nothing renders are only a way for the two copies to
   drift apart later. Deleted rather than commented out for the same reason.
*/

/* The menu panel on a phone: the mark, then the word, on one baseline. */
.menu-link-mark {
  display: flex;
  align-items: center;
  gap: 10px;
}

.menu-link-mark .mark {
  margin-inline-end: 0;
  vertical-align: baseline;
}

/* The contact card's four buttons, each with its mark. */
.contact-reach .btn {
  display: inline-flex;
  align-items: center;
}

.contact-reach .btn .mark {
  vertical-align: baseline;
}

/* ==========================================================================
   "How to use this section"

   The panel an admin gets at the top of every screen. Closed on arrival: it
   is read once and then known, and a permanently open box of instructions
   above the work becomes furniture within a week.

   It is deliberately quieter than a card — a hairline and a tint, no border
   on all four sides — because it is an aside about the page rather than a
   part of it.
   ========================================================================== */

.how-to {
  margin-bottom: 18px;
  border-inline-start: 2px solid var(--lemon-line);
  background: color-mix(in srgb, var(--surface) 60%, transparent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.how-to > summary {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 10px 14px;
  cursor: pointer;
  font-size: 13px;
  color: var(--text-dim);
  /* The default triangle is replaced by the mark below, which sits on the
     left in both directions rather than flipping sides with the text. */
  list-style: none;
}

.how-to > summary::-webkit-details-marker {
  display: none;
}

.how-to > summary:hover {
  color: var(--text);
}

.how-to[open] > summary {
  color: var(--lemon);
}

.how-to-mark {
  flex: none;
  width: 17px;
  height: 17px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  border: 1px solid var(--lemon-line);
  color: var(--lemon);
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
}

.how-to-body {
  padding: 0 14px 14px 40px;
  display: grid;
  gap: 10px;
  /* Instructions are read, not scanned, so they get a reading measure rather
     than the full width of the page. */
  max-width: 78ch;
}

.how-to-what {
  margin: 0;
  font-size: 13.5px;
  color: var(--text);
}

.how-to-steps {
  margin: 0;
  padding-inline-start: 18px;
  display: grid;
  gap: 5px;
  font-size: 13px;
  color: var(--text-dim);
}

.how-to-watch {
  margin: 0;
  font-size: 12.5px;
  color: var(--text-dim);
  padding-top: 8px;
  border-top: 1px solid var(--line-soft);
}

.how-to-watch strong {
  color: var(--danger);
}

/* ==========================================================================
   The permission grid

   Sections down, verbs across. Laid out with grid rather than a table because
   it has to collapse on a phone, and a table that collapses stops being a
   table half way through.
   ========================================================================== */

.perm-open {
  grid-column: 1 / -1;
  border-top: 1px solid var(--line-soft);
  padding-top: 12px;
}

/*
 * Setting up email, folded away under the card that reports it.
 *
 * Open by itself while mail is unconfigured — that is the one state where the
 * fields are the point of the page — and shut once it works, because then the
 * page is an outbox and these are settings nobody needs to see again.
 */
.mail-setup {
  margin-top: 14px;
  border-top: 1px solid var(--line-soft);
  padding-top: 12px;
}

.mail-setup > summary {
  cursor: pointer;
  font-size: 13.5px;
  color: var(--text-dim);
  padding-block: 4px;
}

.mail-setup > summary:hover {
  color: var(--text);
}

.perm-open > summary {
  cursor: pointer;
  font-size: 13px;
  color: var(--text-dim);
  padding-block: 4px;
}

.perm-open > summary:hover {
  color: var(--text);
}

.perm {
  margin-top: 12px;
}

.perm-admin-note {
  margin: 0;
  font-size: 12.5px;
}

/* One row: the name, then a square per verb. The four columns are fixed so
   every tick sits under its own heading all the way down the list. */
.perm-head,
.perm-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) repeat(4, 46px);
  align-items: center;
  gap: 4px;
}

/* Not sticky. It was, and the header row floated out of the card and up
   behind the navigation — `position: sticky` sticks to the nearest scrolling
   ancestor, which here is the page, not the list it belongs to. A heading
   that leaves its own table is worse than one that scrolls away. */
.perm-head {
  padding: 0 2px 6px;
  border-bottom: 1px solid var(--line-soft);
}

.perm-head-section,
.perm-head-action {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.perm-head-action {
  text-align: center;
}

.perm-group {
  margin: 14px 0 4px;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.perm-row {
  padding: 4px 2px;
  border-radius: var(--radius-sm);
}

.perm-row:hover {
  background: var(--surface-2);
}

.perm-name {
  font-size: 13px;
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}

.perm-always,
.perm-changed {
  font-size: 9.5px;
}

.perm-cell {
  display: grid;
  place-items: center;
}

.perm-toggle {
  width: 16px;
  height: 16px;
  accent-color: var(--lemon-solid);
  cursor: pointer;
}

.perm-toggle:disabled {
  cursor: default;
  opacity: 0.55;
}

/* A verb this section does not have. Not a switch that happens to be off. */
.perm-none {
  color: var(--text-faint);
  font-size: 12px;
}

.perm-yes {
  color: var(--lemon);
  font-size: 13px;
}

.perm-no {
  color: var(--text-faint);
  font-size: 15px;
  line-height: 1;
}

.perm-foot {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}

.perm-foot-note {
  font-size: 12px;
}

/* --- the consequence, under the decision --------------------------------- */

.perm-preview,
.view-preview {
  margin-top: 14px;
  display: grid;
  gap: 10px;
}

.perm-preview {
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}

.perm-preview-title {
  margin: 0;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.perm-preview-body,
.view-preview {
  display: grid;
  gap: 10px;
}

.perm-preview-group,
.view-preview-group {
  display: grid;
  gap: 4px;
}

.perm-preview-label,
.view-preview-label {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.perm-preview-tabs,
.view-preview-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

/* The preview draws the real tab, not an approximation of one. */
.perm-preview-tab {
  display: inline-flex;
  align-items: center;
  padding: 4px 9px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  color: var(--text-dim);
  font-size: 12.5px;
}

/* Phones: the four columns would leave nothing for the name. */
@media (max-width: 560px) {
  .perm-head,
  .perm-row {
    grid-template-columns: minmax(0, 1fr) repeat(4, 34px);
  }

  .perm-name {
    font-size: 12.5px;
  }
}


/* --------------------------------------------------------------------------
   The dashboard, right to left
   --------------------------------------------------------------------------

   Short, because the stylesheet was written in logical properties nearly
   everywhere — `padding-inline`, `margin-inline`, flex and grid — and all of
   that mirrors on its own the moment the direction changes. What is below is
   the handful of places that name a physical side and therefore cannot.

   Everything is scoped to `.ltr-island[dir="rtl"]` rather than to the document,
   so switching the dashboard to Arabic never reaches the public pages and the
   public site being in Arabic never reaches the dashboard. */

.ltr-island[dir="rtl"] table.data th {
  text-align: right;
}

/* The timeline's spine moves to the other side of the column it marks. */
.ltr-island[dir="rtl"] .timeline-line {
  border-left: 0;
  border-right: 1px solid var(--line);
  padding-left: 0;
  padding-right: 16px;
  margin-left: 0;
  margin-right: 5px;
}

/* A chat tail points back at whoever is speaking, so both swap sides and the
   triangle itself is mirrored — a tail that kept its shape would point into
   the bubble instead of out of it. */
.ltr-island[dir="rtl"] .chat-bubble:not(.mine)::after {
  left: auto;
  right: -9px;
  clip-path: polygon(0 0, 0 100%, 100% 0);
}

.ltr-island[dir="rtl"] .chat-bubble.mine::after {
  right: auto;
  left: -9px;
  clip-path: polygon(100% 0, 0 0, 100% 100%);
}

/* Headings fall back to the body face: the display font has no Arabic, so an
   Arabic heading in it is silently drawn in something else at a different
   weight, and the page ends up with two typefaces where it had one. */
.ltr-island[dir="rtl"] h1,
.ltr-island[dir="rtl"] h2,
.ltr-island[dir="rtl"] h3,
.ltr-island[dir="rtl"] .page-title,
.ltr-island[dir="rtl"] .card-title,
.ltr-island[dir="rtl"] .stat-value {
  font-family: var(--font-body);
  letter-spacing: 0;
}

/*
   No tracking, and no small capitals, on Arabic.

   Both are English typographic habits that do damage here. Arabic is joined:
   the letters of a word are drawn as one connected run, and tracking prises
   that run apart — it does not make a label look considered, it makes the word
   look broken. And there are no capitals at all, so `text-transform` has
   nothing to act on and only leaves the rule lying about what it is doing.
*/
.ltr-island[dir="rtl"] .eyebrow,
.ltr-island[dir="rtl"] .label,
.ltr-island[dir="rtl"] .nav-group-head,
.ltr-island[dir="rtl"] .admin-nav-label {
  letter-spacing: 0;
  text-transform: none;
}

/* Times, phone numbers, references and money stay left-to-right inside an
   Arabic sentence. `12:30` reads as `30:12` otherwise, which is not a styling
   detail — it is the wrong time. */
.ltr-island[dir="rtl"] .mono,
.ltr-island[dir="rtl"] time,
.ltr-island[dir="rtl"] input[type="date"],
.ltr-island[dir="rtl"] input[type="time"],
.ltr-island[dir="rtl"] input[type="email"],
.ltr-island[dir="rtl"] input[type="tel"],
.ltr-island[dir="rtl"] input[type="url"] {
  direction: ltr;
  unicode-bidi: isolate;
  text-align: right;
}

/* The language switch in the account menu: two halves, the one you are
   reading marked, the other the thing to press — the same control the public
   site uses, so it is the same thing to learn twice. */
.lang-switch {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--line);
  border-radius: 9px;
  overflow: hidden;
  width: 100%;
}

.lang-switch button {
  flex: 1;
  padding: 7px 10px;
  font: inherit;
  font-size: 12.5px;
  line-height: 1.2;
  color: var(--text-dim);
  background: transparent;
  border: 0;
  cursor: pointer;
}

.lang-switch button:hover {
  color: var(--text);
  background: var(--surface-2);
}

.lang-switch button[aria-pressed="true"] {
  color: var(--lemon);
  background: var(--lemon-soft);
  cursor: default;
}

@media (max-width: 720px) {
  .ltr-island[dir="rtl"] .cell-clip {
    text-align: left;
  }
}

/* ==========================================================================
   The dashboard, wider and in a bigger type size.

   Asked for in those words: "kbr el kalam w hato ala el traf" — make the words
   bigger and put them out to the edges.

   Both halves are one change, not two. The dashboard was held to 1240px
   because a long row read badly: a name at the far left and its number a
   thousand pixels away at the far right, with dead space in between. Widening
   it alone would make that worse. Widening it while the type grows does not —
   bigger words fill more of the line, so the same row carries the eye across
   instead of splitting in two halves that get read separately.

   Only from 901px up. Below that the page is already one column at the full
   width of the screen, and the sizes there were set against a phone in a hand
   rather than a monitor on a desk; making those bigger would cost lines of
   text, not gain them.

   The cap is 2000px rather than none at all. On his screen — and on any laptop
   or ordinary monitor — the page now runs to the edges with the same gutter
   every other page uses. The cap only ever applies on something much wider
   than a desk monitor, where a single row would otherwise run a metre.
   ========================================================================== */

@media (min-width: 901px) {
  .admin-header-inner,
  .admin-body {
    max-width: min(100%, 2000px);
  }

  /*
     The column of headings grows with the words in it. 210px was measured
     against 13px tabs; at 14.5px the longest Arabic heading — العملاء
     والمبيعات — needs the extra room or it wraps, and a heading that wraps
     stops looking like a heading.
  */
  .admin-body {
    grid-template-columns: 244px minmax(0, 1fr);
    gap: 34px;
  }

  .nav-group-head {
    font-size: 12.5px;
  }

  .admin-tab {
    font-size: 14.5px;
    padding-block: 7px;
  }

  /* --- the page itself -------------------------------------------------- */

  .ltr-island .page-head h1 {
    font-size: clamp(26px, 4.4vw, 34px);
  }

  .ltr-island .page-head .muted {
    font-size: 15px;
  }

  .ltr-island .eyebrow {
    font-size: 12px;
  }

  .ltr-island .card-title {
    font-size: 17px;
  }

  .ltr-island .card-note {
    font-size: 14px;
  }

  .ltr-island .muted,
  .ltr-island .faint {
    font-size: 14px;
  }

  /*
     A number tile is read from across the desk, so it gains more than the
     prose does — and its label gains with it, because a 26px figure over an
     11px word reads as a figure with a footnote rather than a labelled number.
  */
  .ltr-island .tile-value {
    font-size: 30px;
  }

  .ltr-island .tile-label {
    font-size: 13.5px;
  }

  .ltr-island .stat-value {
    font-size: 30px;
  }

  /* --- the things you press and fill in ---------------------------------- */

  .ltr-island .btn-sm {
    height: 36px;
    font-size: 14px;
  }

  .ltr-island .chip {
    font-size: 12.5px;
    padding: 4px 11px;
  }

  .ltr-island .label {
    font-size: 13.5px;
  }

  .ltr-island .input,
  .ltr-island .select,
  .ltr-island .textarea {
    font-size: 15px;
    padding: 12px 14px;
  }

  .ltr-island .field-hint,
  .ltr-island .field-error {
    font-size: 13px;
  }

  /* --- rows of records --------------------------------------------------- */

  .ltr-island table.data,
  .ltr-island .data-table {
    font-size: 14.5px;
  }

  .ltr-island table.data th,
  .ltr-island .data-table th {
    font-size: 12px;
  }

  .ltr-island table.data td,
  .ltr-island .data-table td {
    padding: 11px 12px;
  }

  /*
     The one place the extra width is spent on something other than longer
     rows: a cell that was clamped to 220px so it could not push the table off
     a 1240px screen has a great deal more room now.
  */
  .ltr-island .cell-narrow {
    max-width: 380px;
  }
}

/* --------------------------------------------------------------------------
   Reviews — what clients said, on the home page and on their own page
   -------------------------------------------------------------------------- */

.review-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 14px;
  /* Cards in a row are one object: the quote can be two lines or ten, and the
     name has to sit on the same line across all of them whichever it is. */
  align-items: stretch;
}

.review-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.review-stars {
  margin: 0;
  /* The stars are read left to right in both languages — a rating is a scale,
     not a sentence, and mirroring it puts the filled ones at the wrong end. */
  direction: ltr;
  font-size: 15px;
  letter-spacing: 2px;
  color: var(--lemon);
}

[dir="rtl"] .review-stars {
  text-align: right;
}

.review-body {
  margin: 0;
  /* Pushes the name to the bottom of the card whatever the quote's length. */
  flex: 1;
  font-size: 15px;
  line-height: 1.7;
  color: var(--text);
}

.review-by {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 8px;
  border-top: 1px solid var(--line);
  padding-top: 12px;
}

.review-name {
  font-weight: 600;
  font-size: 14px;
}

.review-role {
  font-size: 12.5px;
}

/* The form is a single column in the middle of the page rather than the full
   width of it: a 1180px-wide text box is a box nobody wants to write in. */
.review-form-wrap {
  max-width: 720px;
  margin-inline: auto;
}

/*
  The rating: five radios, drawn as five rows of stars.

  The input itself stays in the page rather than being display:none — a hidden
  input is not focusable, and this control has to be usable from a keyboard. It
  is placed under its own stars and revealed by the focus ring.
*/
.review-rating {
  border: 0;
  margin: 0;
  padding: 0;
}

.rating-choice {
  display: grid;
  gap: 6px;
  margin-top: 8px;
}

.rating-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.rating-option:hover {
  border-color: var(--lemon-line);
}

/* `:has()` rather than a class toggled by script: the checked state belongs to
   the input, and the page must be able to show it with no JavaScript at all. */
.rating-option:has(input:checked) {
  border-color: var(--lemon-line);
  background: var(--lemon-soft);
}

.rating-option:has(input:focus-visible) {
  outline: 2px solid var(--lemon);
  outline-offset: 2px;
}

.rating-stars {
  direction: ltr;
  color: var(--lemon);
  letter-spacing: 2px;
  font-size: 15px;
}

.rating-word {
  font-size: 13px;
  color: var(--text-dim);
}

/* A review as the dashboard shows it: the whole thing, in the language it was
   written in, not trimmed to a preview. */
.review-read {
  margin: 12px 0 0;
  padding-inline-start: 12px;
  border-inline-start: 2px solid var(--lemon-line);
  font-size: 14px;
  line-height: 1.75;
  white-space: pre-wrap;
}
