:root{--ink:#0a1723;--navy:#071829;--navy2:#10283d;--gold:#c89a4b;--gold2:#e6c47f;--bg:#f4f6f8;--card:#fff;--line:#dce2e7;--muted:#66717c;--danger:#a92a2a;--ok:#1e7048}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.5 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}a{color:inherit}.admin-shell{display:grid;grid-template-columns:245px 1fr;min-height:100vh}.admin-sidebar{background:linear-gradient(180deg,var(--navy),#091f31);color:#fff;padding:24px 18px;position:sticky;top:0;height:100vh}.admin-brand{display:flex;align-items:center;gap:10px;text-decoration:none;margin-bottom:30px}.admin-brand img{width:35px;height:auto}.admin-brand strong{display:block;font:700 .97rem/1.22 Georgia,serif;letter-spacing:.06em;overflow-wrap:anywhere}.admin-brand-host{display:block;margin-top:3px;font-size:.75rem;letter-spacing:.11em;text-transform:uppercase;color:var(--gold2);overflow-wrap:anywhere}.admin-nav{display:grid;gap:6px}.admin-nav a{padding:11px 13px;text-decoration:none;border-radius:9px;color:#dfe8ee}.admin-nav a:hover,.admin-nav a.is-active{background:rgba(255,255,255,.1);color:#fff}.admin-main{padding:30px clamp(20px,4vw,54px)}.admin-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}.admin-top h1{font:700 clamp(2rem,4vw,3rem)/1.05 Georgia,serif;margin:0}/* LAS TRES PIEZAS DE LA ESQUINA, APOYADAS EN LA MISMA LINEA.
 *
 * `align-items:center` centraba cada pieza por su propia altura, y como el usuario va en negrita y
 * «Salir» es un boton-enlace con otro tamano, las tres letras se apoyaban a alturas distintas. Se
 * notaba sin saber por que: el dueno lo dijo como «pero bien alineados». `baseline` las apoya en la
 * misma linea de escritura, que es lo que el ojo compara.
 *
 * `flex-wrap:wrap` porque en un movil estrecho las tres no caben y sin esto la ultima se salia de la
 * pantalla: mejor dos filas alineadas que una fila cortada. */
.admin-user{display:flex;gap:12px;align-items:baseline;flex-wrap:wrap;color:var(--muted)}
.admin-user-quien{display:flex;gap:8px;align-items:baseline;min-width:0}
.admin-user-quien strong{overflow-wrap:anywhere}
.admin-user-papel{font-size:.86rem;text-transform:uppercase;letter-spacing:.04em;white-space:nowrap}
/* El usuario entregado, con su etiqueta encima y sin que el codigo largo desborde la tarjeta. */
.issued-field{margin:2px 0 0;display:flex;gap:10px;align-items:baseline;flex-wrap:wrap}
.issued-field code{overflow-wrap:anywhere}.admin-user form{margin:0}.link-button{border:0;background:none;color:var(--gold);font-weight:800;cursor:pointer}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}.metric{background:var(--card);padding:22px;border-radius:14px;border:1px solid var(--line)}.metric strong{display:block;font:700 2.2rem Georgia,serif}.metric span{color:var(--muted)}.panel{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:22px;margin-top:18px}.panel h2{font-family:Georgia,serif;margin-top:0}.table-wrap{overflow:auto}.admin-table{width:100%;border-collapse:collapse}.admin-table th,.admin-table td{padding:12px;text-align:left;border-bottom:1px solid var(--line);vertical-align:top}.admin-table th{font-size:.75rem;text-transform:uppercase;letter-spacing:.09em;color:var(--muted)}.badge{display:inline-flex;padding:4px 8px;border-radius:999px;font-size:.75rem;font-weight:800;background:#eef2f4}.badge.published{background:#e4f3ea;color:var(--ok)}.badge.draft{background:#fff0cb;color:#76551c}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start}.field{display:grid;gap:6px}.field.full{grid-column:1/-1}.field label{font-weight:750}.field input,.field textarea,.field select{width:100%;padding:11px 12px;border:1px solid #bbc5ce;border-radius:9px;background:#fff;font:inherit}.field textarea{min-height:110px;resize:vertical}.actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}.btn{display:inline-flex;align-items:center;justify-content:center;min-height:42px;padding:0 16px;border-radius:9px;border:1px solid transparent;text-decoration:none;font-weight:800;cursor:pointer}.btn-primary{background:var(--navy);color:#fff}.btn-gold{background:linear-gradient(135deg,var(--gold2),var(--gold));color:#14202c}.btn-light{background:#fff;border-color:var(--line)}.notice{padding:12px 14px;border-radius:9px;background:#e8f4ed;color:#205d3f;margin-bottom:15px}.error{padding:12px 14px;border-radius:9px;background:#fde9e9;color:#8c2020;margin-bottom:15px}.preview-card{border:1px solid var(--line);border-radius:12px;overflow:hidden;max-width:520px}.preview-card img{width:100%;aspect-ratio:16/9;object-fit:cover;display:block;background:#0a1723}.preview-card div{padding:14px}.login-page{min-height:100vh;display:grid;place-items:center;padding:24px;background-color:#050706;background-image:linear-gradient(115deg,#050706 0%,#0c0e0c 42%,#17130d 72%,#0a0b0a 100%),radial-gradient(circle at 74% 34%,rgba(214,177,109,.17),transparent 56%)}.login-card{width:min(430px,100%);background:rgba(10,11,10,.92);border:1px solid rgba(214,177,109,.28);color:#fff;padding:34px;border-radius:18px;box-shadow:0 30px 90px rgba(0,0,0,.6)}.login-brand{display:flex;align-items:center;gap:13px;padding-bottom:20px;margin-bottom:24px;border-bottom:1px solid rgba(214,177,109,.22)}.login-brand img{width:46px;height:auto}.login-brand h1{font:700 1.45rem/1.2 Georgia,serif;margin:0;color:#fff;overflow-wrap:anywhere}.login-host{margin:4px 0 0;font-size:.75rem;letter-spacing:.16em;text-transform:uppercase;color:#d6b16d;overflow-wrap:anywhere}.login-page .field{margin-bottom:16px}.login-page .field label{font-size:.75rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:#a8aaa5}.login-page .field input{background:rgba(255,255,255,.04);border-color:rgba(214,177,109,.3);color:#fff}.login-page .field input:-webkit-autofill{-webkit-text-fill-color:#fff;-webkit-box-shadow:0 0 0 1000px #14150f inset;caret-color:#fff}.login-page .field input:focus-visible{outline:2px solid #efcd8a;outline-offset:2px;border-color:transparent}.login-submit{width:100%;margin-top:6px}.login-note{margin:20px 0 0;font-size:.8rem;line-height:1.55;color:#a8aaa5}.login-page .error{background:rgba(169,42,42,.2);border:1px solid rgba(220,120,120,.42);color:#f2cfcf}.muted{color:var(--muted)}/* EL VOLCADO DE ANTES/DESPUES PARTE SOLO CUANDO NO CABE.
 *
 * Tenia `word-break: break-word`, que es un alias heredado equivalente a `overflow-wrap: anywhere`: parte
 * por cualquier letra aunque la palabra entrase entera. En una captura a 1440 px, con sitio de sobra, se
 * leia `userna / me`, `ema / il`, `displ / ay`. El volcado es dato de maquina, pero sus claves son
 * palabras y se leen.
 *
 * `overflow-wrap: break-word` parte unicamente cuando la palabra no cabe en su linea, que es la unica
 * vez que hay que partir. */
.audit-json{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.77rem;max-width:360px;white-space:pre-wrap;overflow-wrap:break-word}.quick-links{display:flex;gap:10px;flex-wrap:wrap}@media(max-width:850px){.admin-shell{grid-template-columns:1fr;grid-template-rows:auto 1fr}.admin-sidebar{position:static;height:auto}.admin-nav{grid-template-columns:repeat(3,1fr)}.admin-main{padding:20px}.cards,.form-grid{grid-template-columns:1fr}.field.full{grid-column:auto}.admin-top{align-items:flex-start;gap:14px;flex-direction:column}}@media(max-width:560px){.admin-nav{grid-template-columns:1fr 1fr}.cards{grid-template-columns:1fr}.admin-table{font-size:.88rem}}


.media-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:16px}.media-card{overflow:hidden;border:1px solid var(--line);border-radius:12px;background:#fff}.media-card img{display:block;width:100%;aspect-ratio:16/9;object-fit:cover;background:var(--navy)}.media-card div{padding:12px;display:grid;gap:6px}.media-card small{color:var(--muted)}.media-card a{color:#8a6229;font-weight:800}

/* ---------------------------------------------------------------------------
   Field feedback

   Counters and link checks are written by /js/admin.js. Without scripting the forms are
   unchanged and fully usable, so none of this is load-bearing.
   --------------------------------------------------------------------------- */

.char-count,
.link-check,
.link-open {
  display: inline-block;
  margin-top: 4px;
  font-size: 12px;
  letter-spacing: .04em;
  color: #6f7671;
}

.char-count[data-state="good"],
.link-check[data-state="good"] { color: #2f7d4f; }
.char-count[data-state="short"] { color: #8a6d1f; }
.char-count[data-state="over"],
.link-check[data-state="bad"] { color: #a8352c; }

.link-open { margin-left: 10px; text-decoration: underline; }

/* A secret shown once: given room, made selectable, and deliberately not styled as a
   throwaway line of muted text. */
.issued-panel { border: 1px solid rgba(214,177,109,.45); }
.issued-secret {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin: 12px 0;
}
.issued-secret code {
  font-size: 18px;
  letter-spacing: .08em;
  padding: 10px 14px;
  border-radius: 8px;
  background: rgba(0,0,0,.05);
  user-select: all;
}

.row-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: flex-start; }
.inline-confirm { display: flex; gap: 6px; align-items: center; }
.inline-confirm input { max-width: 150px; }

.notice-attention { border-left: 3px solid var(--gold, #d6b16d); }

/* The handover table on the sites screen: three addresses and a secret, read once and
   copied. Laid out as rows because they are transcribed, not skimmed. */
.handover th { width: 8rem; text-align: left; white-space: nowrap; }
.handover code { user-select: all; }

/* Permanent deletion. Deliberately not styled as a neat inline action: it sits in its own
   block, with its two confirmations stacked, so it reads as a different kind of thing from
   the buttons above it. */
.purge-form { margin-top: 12px; padding: 12px; border: 1px solid rgba(168,53,44,.35); border-radius: 8px; }
.purge-form p { margin: 0 0 10px; }
.purge-form input { display: block; width: 100%; max-width: 260px; margin-bottom: 8px; }
.btn-danger { background: #a8352c; color: #fff; border-color: #a8352c; }
.btn-danger:hover { background: #8f2b23; }

/* The sites table.
 *
 * Four columns of long text — a client's name, two hostnames, a lifecycle and a row of
 * controls — were each being squeezed until every one of them wrapped. The two hostnames now
 * share a column, and the remaining widths are declared rather than left to the browser's
 * guess, so the controls stay on one line instead of stacking into a tower.
 *
 * F-R7.8-05: those widths are a DESKTOP decision, and they were not scoped to one. Below
 * 700 px `.admin-table-apilable` turns each row into a stacked card, but `min-width: 14rem`
 * on the actions column kept demanding 224 px inside a card narrower than that — at 390 px
 * wide the card had nowhere to put it, and the row of buttons packed against the card's own
 * edge. Declared widths that exist to stop a dense table from squeezing its columns have
 * nothing to do once the table is not a row of columns any more. */
.sites-table td { vertical-align: top; }
@media (min-width: 701px) {
  .sites-table .col-site { min-width: 11rem; }
  .sites-table .col-addresses { width: 42%; }
  .sites-table .col-actions { min-width: 14rem; }
  /* A hostname broken mid-label reads as a different hostname — "…cleaning.c" over "om" is
     worse than a scrollbar, and at this width there is a real column to hold it on one line.
     Scoped to desktop ON PURPOSE: unscoped, this rule and the stacked-mode rule below are the
     same specificity (two classes, one element) and whichever sits LATER in the file wins at
     every width below 850px regardless of which `@media` wraps it — not which one is "meant"
     to apply. That is P-R7.8-01: an independent audit measured 51px of horizontal scroll
     inside `.table-wrap` at 390px because this declaration, unscoped, kept winning there too. */
  .sites-table .col-addresses code { font-size: .82rem; white-space: nowrap; }
}
@media (max-width: 700px) {
  /* The stacked card has no 42%-wide column to protect a hostname's shape — it is full-width
     text in a `.admin-table-apilable` cell, which already sets `overflow-wrap: anywhere` on
     every cell it stacks. Restating it here is not redundant: it is what stops the desktop
     rule above from winning by being declared later in a past edit, which is exactly how
     P-R7.8-01 happened once. */
  .sites-table .col-addresses code { white-space: normal; overflow-wrap: anywhere; }
}
.addr { display: flex; gap: 9px; align-items: baseline; }
.addr + .addr { margin-top: 6px; }
.addr-label {
  flex: 0 0 3.4rem;
  font-size: .75rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Lifecycle controls are secondary to the content screens, and there are four of them in a
   row: full-size buttons would push the table wider than the viewport. */
.row-actions .btn { min-height: 34px; padding: 0 11px; font-size: .82rem; }

/* The media library. Cards are sized to show shape at a glance -- a logo that is secretly
   square and a banner that is secretly portrait are both mistakes best seen, not measured. */
/* Two grids, two different jobs, so they are told apart rather than one overriding the
   other. A portfolio still is a 16:9 frame and is cropped to fill. A library file may be a
   logo, a square icon or a banner, and cropping it to 16:9 would hide the very thing the
   operator uploaded it to check -- so it is fitted whole inside the card. */
.library-grid .media-card img { object-fit: contain; }
.media-url { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.media-url code { flex: 1 1 12rem; font-size: .75rem; overflow-wrap: anywhere; user-select: all; background: rgba(0,0,0,.05); padding: 6px 8px; border-radius: 6px; }
.media-url .btn { min-height: 30px; padding: 0 11px; font-size: .78rem; }
.media-missing {
  aspect-ratio: 16/9;
  display: grid;
  place-items: center;
  padding: 14px;
  text-align: center;
  font-size: .82rem;
  color: var(--danger);
  background: #fdf3f3;
}
/* Inside a card the description field has a whole row to itself rather than the 150px a
   search box gets, because what is typed here is a sentence and has to be readable while it
   is being written. */
.media-card .inline-confirm { flex-wrap: wrap; }
.media-card .inline-confirm input { max-width: none; flex: 1 1 8rem; }

/* Adverts. The picture is the identifying thing in a list of campaigns, so it leads each
   row at a size that can actually be recognised. */
.adverts-table .col-order { width: 4rem; font-variant-numeric: tabular-nums; }
.adverts-table .col-advert { display: flex; gap: 12px; align-items: flex-start; min-width: 16rem; }
.advert-thumb { width: 92px; height: 60px; object-fit: contain; background: #0a1723; border-radius: 6px; flex: none; }
.advert-destination { display: block; font-size: .78rem; color: var(--muted); overflow-wrap: anywhere; }

/* Choosing the creative and choosing the sites are both "tick the right one out of a set",
   so they are laid out the same way: the whole card is the label, and the state is visible
   from across the room rather than in a 13px radio button. */
.creative-picker { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 12px; }
.creative-option, .site-option {
  display: block;
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px;
  cursor: pointer;
  background: #fff;
}
.creative-option img { width: 100%; aspect-ratio: 16/9; object-fit: contain; background: #0a1723; border-radius: 6px; margin: 8px 0; }
.creative-option small, .site-option code { color: var(--muted); font-size: .76rem; overflow-wrap: anywhere; }
.creative-option:has(input:checked), .site-option:has(input:checked) { border-color: var(--gold); box-shadow: 0 0 0 2px rgba(200,154,75,.25); }
.creative-option:focus-within, .site-option:focus-within { outline: 2px solid var(--gold2); outline-offset: 2px; }
.site-picker { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.site-option { display: flex; gap: 10px; align-items: flex-start; }
.checkbox-line { display: flex; gap: 8px; align-items: center; font-weight: 400; }
/* One site per line. A separator character that can also appear inside a site's own name is
   not a separator -- "Barbara · USA Luxury Cleaning" joined to another name read as one run. */
.site-list { margin: 0; padding: 0; list-style: none; }
.site-list li + li { margin-top: 4px; }

/* Two boxes per text: the site's own language and its translation.
 *
 * Side by side rather than behind a language tab, because the job is transcription — you are
 * looking at one sentence and writing the other. A tab would mean remembering what you just
 * read, which is how a translation ends up saying something slightly different. */
.bilingual-field { padding: 16px 0; border-bottom: 1px solid var(--line); }
.bilingual-field:last-of-type { border-bottom: 0; }
.bilingual-field h3 { margin: 0 0 4px; font: 700 1rem/1.3 Georgia, serif; }
.bilingual-field > .muted { margin: 0 0 10px; font-size: .84rem; }
.bilingual-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.lang-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 4px;
  background: #eef2f4;
  color: var(--muted);
  font-size: .75rem;
  letter-spacing: .1em;
  font-weight: 800;
}
/* On a phone the two boxes stack, and stacked they read top-to-bottom as original then
   translation, which is the same order as side by side. */
@media (max-width: 760px) { .bilingual-pair { grid-template-columns: 1fr; } }

/* Where each picture goes. A list of PLACES, not of files — the library below is the list of
   files, and confusing the two is what made "which of these is the logo" a memory test. */
.slot { display: grid; grid-template-columns: 210px 1fr; gap: 18px; padding: 18px 0; border-bottom: 1px solid var(--line); }
.slot:last-child { border-bottom: 0; }
.slot-preview img { width: 100%; aspect-ratio: 16/9; object-fit: contain; background: #0a1723; border-radius: 10px; display: block; }
.slot-empty {
  aspect-ratio: 16/9;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px;
  text-align: center;
  border-radius: 10px;
  background:
    linear-gradient(140deg,#0b0d0c 0%,#141210 48%,#090a09 100%),
    radial-gradient(circle at 72% 28%, rgba(214,177,109,.13), transparent 58%);
}
.slot-empty-word {
  font-family: Georgia, serif;
  font-size: .75rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(214,177,109,.55);
}
.slot-empty-rule { width: 34px; height: 1px; background: linear-gradient(90deg, transparent, rgba(214,177,109,.45), transparent); }
.slot-empty-note { font-size: .75rem; letter-spacing: .22em; text-transform: uppercase; color: rgba(214,177,109,.4); }
.slot-body h3 { margin: 0 0 6px; font: 700 1.05rem Georgia, serif; display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.slot-body > .muted { margin: 0 0 12px; font-size: .86rem; }
.slot-form select { padding: 9px 10px; border: 1px solid #bbc5ce; border-radius: 9px; background: #fff; font: inherit; max-width: 22rem; }
@media (max-width: 760px) { .slot { grid-template-columns: 1fr; } .slot-preview { max-width: 260px; } }

/* The site's own address, under the brand rather than squeezed beside it. */
.admin-identity{margin-bottom:30px}
.admin-brand{margin-bottom:8px}
.admin-brand strong{overflow-wrap:anywhere}
.admin-brand-host{display:block;font-size:.75rem;letter-spacing:.05em;text-transform:uppercase;color:var(--gold2);overflow-wrap:anywhere}

/* A field declared to hold thousands of characters is shown a box that can hold them. */
.field textarea.tall{min-height:min(58vh,480px)}

/* A run of fields belonging to one part of a page, on My words. Folded because a service
   page declares 81 words and one column of them is a screen nobody finishes. */
.copy-run{margin:18px 0;border:1px solid var(--line);border-radius:12px;background:#fbfcfd}
.copy-run>summary{padding:14px 16px;font-family:Georgia,serif;font-size:1.02rem;color:var(--ink);cursor:pointer;list-style:none;border-radius:12px}
.copy-run>summary::-webkit-details-marker{display:none}
.copy-run>summary::before{content:"\25B8";display:inline-block;width:1.1em;color:var(--gold);transition:transform .15s}
.copy-run[open]>summary::before{transform:rotate(90deg)}
.copy-run[open]>summary{border-bottom:1px solid var(--line);border-radius:12px 12px 0 0}
.copy-run>.bilingual-field{padding-left:16px;padding-right:16px}
.copy-run>.bilingual-field:last-child{padding-bottom:16px}

/* Choosing a site's look. The swatch is painted from the real palette, generated by
   config/themes.js, so the choice shows the colours rather than describing them. */
.theme-choices{display:grid;gap:12px;margin-top:8px}
.theme-choice{display:grid;grid-template-columns:auto auto 1fr;gap:14px;align-items:center;padding:14px 16px;border:1px solid var(--line);border-radius:12px;cursor:pointer}
.theme-choice.is-current{border-color:var(--gold);background:#fffdf7}
.theme-choice strong{display:block}
.theme-swatch{width:54px;height:36px;border-radius:8px;border:1px solid rgba(0,0,0,.15);background:linear-gradient(135deg,var(--black) 0%,var(--black2) 55%,var(--gold) 100%)}

/* ORDEN CHICAS-D2, punto 2: el alta de 3 pasos, paso Dominio — dos tarjetas de radio con
   título y, en una de ellas, una etiqueta "sin construir todavía". El círculo iba suelto
   encima del título porque ".field{display:grid}" apilaba cada hijo en su propia fila; esto es
   el mismo gesto declarativo que ".admin-main label:has(>input[type=radio])" ya usa en móvil
   (flex, alineado, con espacio) pero sin esperar a ese ancho — el círculo va en línea con el
   título en cualquier tamaño, y la etiqueta nunca se estira más que su propio texto porque
   vive dentro de un span en línea, no de una fila de rejilla. */
.domain-choice-option{grid-column:1/-1;display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;padding:14px 16px;border:1px solid var(--line);border-radius:12px;cursor:pointer}
.domain-choice-option .domain-choice-head{display:inline-flex;align-items:center;gap:.5rem}
.domain-choice-option p{flex:1 0 100%;margin:0}

/* Moving an advert up or down its queue. Two small forms and a number, because the queue is
   the placement rule and typing 20 to mean "second" requires knowing the other numbers. */
.col-order{white-space:nowrap;text-align:center;width:1%}
.order-move{display:block;margin:0}
.order-number{display:block;font-variant-numeric:tabular-nums;font-weight:800;line-height:1.6}
.order-button{display:block;width:100%;min-height:24px;padding:0 6px;border:1px solid var(--line);border-radius:6px;background:#fff;color:var(--ink);font-size:.75rem;cursor:pointer}
.order-button:hover:not(:disabled){border-color:var(--gold);color:var(--gold)}
.order-button:disabled{opacity:.3;cursor:default}

/* A project with no picture, on the admin preview. Says what the page will show rather than
   borrowing a photograph of a different business to stand in for one. */
.preview-empty{display:grid;place-items:center;aspect-ratio:16/9;background:#0a1723;color:#8d98a4;font-size:.85rem;text-align:center;padding:16px}

/* El interruptor de idioma del panel y la explicacion de cada pantalla, anadidos en R7.
   Van al final y no tocan ninguna regla anterior: una hoja de estilos que se reordena para
   meter algo nuevo es una hoja que hay que volver a mirar entera. */
.admin-lang{margin-top:26px;padding-top:18px;border-top:1px solid rgba(255,255,255,.12)}
.admin-lang-label{display:block;font-size:.75rem;letter-spacing:.11em;text-transform:uppercase;color:var(--gold2);margin-bottom:8px}
.admin-lang-set{display:flex;gap:6px}
.admin-lang-set a{min-width:44px;min-height:34px;display:inline-flex;align-items:center;justify-content:center;border-radius:8px;text-decoration:none;font-weight:800;font-size:.8rem;color:#dfe8ee;background:rgba(255,255,255,.08)}
.admin-lang-set a.is-on{background:var(--gold);color:#14202c}
.admin-lang-set a:hover{background:rgba(255,255,255,.18)}
.admin-lang-set a.is-on:hover{background:var(--gold2)}
.admin-help{margin:0 0 20px;border:1px solid var(--line);border-radius:12px;background:var(--card)}
.admin-help>summary{cursor:pointer;padding:13px 16px;font-weight:800;color:var(--navy);min-height:44px;display:flex;align-items:center}
.admin-help>summary:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.admin-help-body{padding:0 16px 14px;display:grid;gap:9px}
.admin-help-body p{margin:0;line-height:1.55}
.admin-help-not{color:#8c2020}
/* El interruptor en la pantalla de acceso, que es oscura y no tiene menu lateral. */
.login-lang{margin-top:22px;padding-top:16px;border-top:1px solid rgba(214,177,109,.22)}
.login-lang .admin-lang-label{color:#d6b16d}
.login-lang .admin-lang-set a{background:rgba(255,255,255,.08);color:#fff}
.login-lang .admin-lang-set a.is-on{background:#d6b16d;color:#14202c}
/* Las tarjetas del Escritorio son enlaces desde R7.1: la ayuda decia que lo eran y no lo eran.
   Un <a> trae subrayado y color heredado del navegador; se retiran para que la tarjeta siga
   leyendose como una tarjeta, y se le da un estado de foco visible, que un <div> no necesitaba. */
a.metric{display:block;text-decoration:none;color:inherit}
a.metric:hover{border-color:var(--gold)}
a.metric:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* Los dos botones de «todos / ninguno» de una campana, anadidos en R7.1. */
.site-picker-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:12px}
.site-picker-actions .btn{min-height:38px}

/* LO ANCHO SE DESPLAZA DENTRO DE SU TARJETA; LA PÁGINA NO SE MUEVE.
 *
 * El panel no tenía NINGÚN `overflow-x`, así que una tabla con contenido ancho —un identificador de 28
 * caracteres, un hostname largo, un nombre de persona completo— empujaba la página entera y el panel se
 * desplazaba de lado. El dueño lo vio en su navegador y lo enseñó con tres capturas; la puerta no lo veía
 * porque medía el panel con la base vacía, y una tabla sin filas no desborda nada.
 *
 * La regla es la de siempre: el contenido ancho se desplaza en su propio contenedor, y el cuerpo de la
 * página nunca lo hace. */
/* `.table-wrap` ESTABA PUESTO EN SIETE PLANTILLAS Y NO TENÍA NINGUNA REGLA.
 *
 * El envoltorio existía —alguien lo puso a propósito para esto— y el CSS nunca le dijo qué hacer. Una
 * clase escrita en las plantillas y ausente de la hoja no se ve en ninguna revisión de código: en el HTML
 * parece resuelto, y en la pantalla no lo está. */
.table-wrap { max-width: 100%; overflow-x: auto; }
.table-wrap > table { min-width: 100%; }

/* Y EL REBOTE ELÁSTICO, QUE NO ES UN DESBORDAMIENTO.
 *
 * En macOS un gesto lateral rebota la página aunque no haya nada que desplazar. El dueño lo describió
 * como «se mueven para los lados aunque estén bien encuadradas», y tenía razón en las dos mitades: se
 * movían, y estaban bien encuadradas. `overscroll-behavior` no aparecía en ninguna hoja del proyecto. */
html, body { overscroll-behavior: none; }

/* UNA COLUMNA DE REJILLA NO SE ENCOGE POR DEBAJO DE SU CONTENIDO SI NO SE LE DICE.
 *
 * Éste es el motivo real de que el panel se desplazara de lado, y explica por qué poner `overflow-x` en
 * `.table-wrap` no bastó: el envoltorio SÍ quería estrecharse, y su columna no le dejaba. Un elemento de
 * rejilla o de caja flexible nace con `min-width:auto`, que significa «no menos que mi contenido». Con una
 * tabla de identificadores largos dentro, ese mínimo era 1547 px en una ventana de 1440.
 *
 * Se vio en la medición, no leyendo el CSS: la puerta imprimió `div.admin-shell ancho=1440 minimo=1547`.
 * Sin ese dato habría que buscarlo a ojo, y buscar a ojo es lo que hace que un defecto así sobreviva
 * rondas enteras. */
.admin-shell, .admin-main { min-width: 0; }
.admin-main > *, .admin-main .card { min-width: 0; }

/* OBJETIVOS TÁCTILES: 34 px DE ALTO EN LOS ANCHOS DE DEDO.
 *
 * La puerta los mide desde hace rondas y nunca los pudo denunciar, porque medía la página de error de
 * Chrome. En cuanto cargó de verdad aparecieron 169: botones de 17 px, campos de 13 px y enlaces de
 * acción de 18 px. En una tableta eso no se pulsa con el dedo, se pulsa con la uña y fallando.
 *
 * `.row-actions .btn` ya tenía la regla —alguien la puso para un caso— y el resto no. Se aplica donde
 * importa: los anchos que la puerta considera de dedo, 768 px hacia abajo.
 *
 * ORDEN R7.10.2-FINAL 6.3: 34 pasa a 44, la misma cifra de la Parte 4 para el sitio público —
 * "las pantallas del panel también cumplen 4.1 y 4.2 en 375". `.admin-brand` (el enlace del logo
 * en la barra lateral) y `.saltar-al-contenido` no estaban en esta lista porque nadie los había
 * medido contra un dedo; la medición de la Parte 6 los encontró a 39 y 43 px. */
@media (max-width: 768px) {
  .admin-main button,
  .admin-main input:not([type=hidden]):not([type=radio]):not([type=checkbox]),
  .admin-main select,
  .admin-main .btn,
  .admin-main a.btn,
  .admin-main button.link-button,
  .admin-main a.advert-destination,
  .admin-brand,
  .saltar-al-contenido {
    min-height: 44px;
  }
  .admin-brand,
  .saltar-al-contenido { display: inline-flex; align-items: center; }
  /* Un botón sin relleno queda en 34 px de caja y el texto pegado al borde: se le da aire. */
  .admin-main button.link-button { padding: 0 6px; }

  /* Y LAS CASILLAS Y LOS RADIOS, QUE LOS HABÍA EXCLUIDO Y ESTABA MAL.
     Los saqué del selector de arriba porque `min-height` sobre una casilla la deforma. Pero una casilla
     de 13 px en una tableta tampoco se puede pulsar: el error era excluirlos, no incluirlos. Se les da
     tamaño con `width`/`height`, que es lo que una casilla entiende, y se agranda su etiqueta, que es
     donde de verdad se pulsa. */
  .admin-main input[type=checkbox],
  .admin-main input[type=radio] { width: 22px; height: 22px; }
  .admin-main label:has(> input[type=checkbox]),
  .admin-main label:has(> input[type=radio]) { min-height: 44px; display: inline-flex; align-items: center; gap: .5rem; }
}

/* LAS REJILLAS DE ANCHO FIJO NO CABEN EN UN MÓVIL.
 *
 * `.slot` es `grid-template-columns: 210px 1fr`. A 390 px de ventana, 210 px de miniatura más el texto y
 * sus espacios no caben, y la pantalla de medios se desplazaba de lado. Y su segunda columna tampoco
 * tenía `min-width: 0`, que es el mismo defecto que ensanchaba el panel entero: una columna de rejilla no
 * se encoge por debajo de su contenido si no se le dice.
 *
 * En los anchos de dedo la rejilla pasa a una sola columna: la miniatura arriba y el texto debajo. */
.slot > * { min-width: 0; }
.slot-body h3, .slot-body > .muted { min-width: 0; overflow-wrap: anywhere; }

@media (max-width: 640px) {
  .slot { grid-template-columns: 1fr; gap: 12px; }
  .slot-preview { max-width: 210px; }
}

/* UNA FILA QUE NO CABE TIENE QUE PODER PARTIRSE.
 *
 * `.inline-confirm` es una caja flexible SIN `flex-wrap`: a 390 px, un desplegable de 150 px más su botón
 * no caben y empujaban la pantalla de medios. Alguien ya lo había visto en un sitio —`.media-card
 * .inline-confirm` sí tiene `wrap`— y no en el resto. Se aplica donde estaba la excepción. */
.inline-confirm { flex-wrap: wrap; }
.inline-confirm select, .inline-confirm input { min-width: 0; max-width: 100%; }

/* UNA PALABRA LARGA NO PUEDE ENSANCHAR LA PÁGINA.
 *
 * Lo que queda son cadenas que no tienen dónde partirse: un identificador, un nombre de fichero, una
 * dirección. En una ventana de 375 px, `strong` y `span` con una palabra de 40 caracteres empujan el
 * documento entero.
 *
 * ERA `anywhere`, Y EL COMENTARIO DECÍA «no cambia nada donde ya cabían». NO ES VERDAD, y se vio mirando
 * el panel: en la tabla de cuentas, a 1440 px de ancho, la palabra **owner** salía partida en «owne / r».
 * La diferencia entre los dos valores no es cosmética:
 *
 *   · `break-word` parte una palabra sólo cuando de otro modo se saldría, y **no** cuenta para el ancho
 *     mínimo del contenido;
 *   · `anywhere` parte igual **y además** hace que el ancho mínimo sea de un carácter.
 *
 * Con `anywhere`, una columna de tabla puede encogerse por debajo de sus palabras, y el navegador las
 * parte porque se lo hemos permitido. Con `break-word` la columna conserva el ancho de su palabra más
 * larga y «owner» no se rompe nunca; si aun así no cabe, quien desplaza es `.table-wrap`, que para eso
 * está. La protección contra la palabra de 40 caracteres se mantiene íntegra.
 *
 * El dueño lo dijo con estas palabras: «los escritos, que las palabras ni estén unidas». Estaban
 * partidas, y era una línea de CSS que prometía no cambiar nada. */
.admin-main strong, .admin-main span, .admin-main td, .admin-main code, .admin-main .muted {
  overflow-wrap: break-word;
}

/* La celda de la fecha no se parte: son dos trozos cortos y cada uno cabe entero. */
.celda-fecha { white-space: nowrap; }
.celda-fecha .muted { font-size: .78rem; }

/* EL TEXTO DE DENTRO DE UN CAMPO TIENE QUE CABER.
 *
 * Los dos campos de «Restaurar credenciales» quedaban en 150 px y su texto de ejemplo salía cortado:
 * «new username (optional…». Un campo que no deja leer para qué es obliga a adivinarlo, y éste es el que
 * decide con qué nombre entrará un cliente.
 *
 * `min-width` en `ch` se mide en anchos de carácter, que es justo la unidad del problema. Va al final de
 * la hoja para ganar a la regla de 150 px por orden, no con `!important`: el final gana por una razón que
 * se lee.
 *
 * Y VA ENVUELTO EN `min(…, 100%)`, que no es adorno. `min-width: 22ch` a secas fijaba un mínimo absoluto:
 * a 375 px el panel pasó a necesitar 446 y empezó a desplazarse a los lados — lo dijo `gate:admin-responsive`
 * en la corrida siguiente, con `mínimo=446` al lado de `ancho=375`. `min(22ch, 100%)` pide 22 caracteres
 * cuando los hay y cede cuando no los hay, que es lo que un mínimo debería haber hecho desde el principio.
 * Un arreglo de legibilidad que rompe la maqueta no es un arreglo. */
.inline-confirm input { min-width: min(22ch, 100%); }

/* DÓNDE SÍ SE PARTE POR CUALQUIER SITIO, y por qué es una lista corta y no una regla general.
 *
 * `break-word` protege la maqueta de una palabra larga PERO cuenta la palabra entera para el ancho
 * mínimo. Con un nombre de fichero guardado —32 caracteres hexadecimales seguidos, sin una sola vocal
 * donde apoyarse— ese mínimo es de 441 px, y a 390 el panel se desplazaba a los lados.
 *
 * `anywhere` resuelve eso y estropea lo otro: hace que el mínimo sea de un carácter, y entonces el
 * navegador parte también «owner» en «owne / r» dentro de una tabla estrecha.
 *
 * Así que cada valor va donde le toca, y la frontera es semántica: **un identificador se parte por donde
 * sea, una palabra no.** Aquí están los sitios donde el panel enseña identificadores —nombre de fichero,
 * dirección de un recurso, id— y son los únicos. Si mañana aparece otro, se añade aquí; lo que no se
 * hace es volver a poner `anywhere` en `td` y `span` y romper las palabras de todo el panel.
 *
 * Las dos mitades están medidas: `gate:admin-responsive` denuncia el desplazamiento lateral, y la
 * inspección visual de R7.5 fue quien vio «owne / r». Ninguna de las dos sola habría bastado. */
.media-card strong,
.media-card small,
.media-url code,
/* El nombre del fichero vive en el , y el  de al lado solo lleva las medidas: cubrir solo
   el  dejaba fuera justo al que empuja. */
.creative-option span,
.creative-option small,
.site-option code,
.advert-destination { overflow-wrap: anywhere; }

/* SE PROBÓ `break-word` AQUÍ Y NO SIRVE. QUEDA ESCRITO PARA NO REPETIRLO.
 *
 * `anywhere` parte aunque la pieza entrase entera, y eso se ve: a 768 px el nombre de fichero
 * `6e3aabc189c37474fe1439d475f182c2.png` sale partido dentro de una tarjeta donde cabía. Parecía
 * gratuito, así que se cambió a `break-word`, que parte sólo cuando no cabe.
 *
 * Lo refutó `gate:admin-responsive`: con `break-word`, en /media a 390 px el contenido de la tarjeta
 * llega a 445 px, 55 más que la pantalla. La diferencia que importa no es cuándo parte, es que `anywhere`
 * pone el ancho mínimo del contenedor en un carácter y `break-word` lo deja en la palabra más larga. Sin
 * eso, la tarjeta no puede encogerse por debajo del nombre del fichero y se sale.
 *
 * La inspección visual NO lo vio: mide `scrollWidth` del documento, y `.media-card` tiene
 * `overflow: hidden`, así que el contenido se recorta sin que la página se desplace. Dos puertas midiendo
 * cosas distintas, y sólo una tenía razón. Partir un identificador que cabe es el precio, y es el barato:
 * lo otro es contenido fuera de la pantalla y sin forma de llegar a él. */

/* El código del registro, debajo de su etiqueta: se lee cuando se busca, no cuando se pasa la vista. */
.codigo-crudo { display:inline-block; font-size:.75rem; color:var(--muted); letter-spacing:.02em; }

/* ============================================================================
   NAVEGACIÓN DEL PANEL EN UN MÓVIL
   ============================================================================

   A 390×844 el panel enseñaba toda su navegación antes del contenido: la barra lateral medía 662 px y
   `<main>` empezaba en y≈682. Casi una pantalla entera de enlaces antes de la tarea que se vino a hacer.
   Lo midió una auditoría independiente (`F-R7.5-07`), y la puerta responsive daba PASS porque medía
   desplazamiento lateral y objetivos táctiles, no dónde empieza lo que se ha venido a leer.

   Dos piezas, y ninguna depende de JavaScript para no empeorar nada:

     · el enlace de salto, invisible hasta que recibe el foco. Con teclado es lo primero que se alcanza;
     · el `<details>`, que el navegador abre y cierra solo, con teclado y anunciando su estado.

   En escritorio el resumen se oculta y todo se ve igual que antes. */

.saltar-al-contenido {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  padding: 10px 16px;
  background: var(--gold);
  color: var(--navy);
  font-weight: 800;
  border-radius: 0 0 8px 0;
}
/* Al recibir el foco se coloca donde se ve. Un enlace de salto que no aparece es el mismo enlace que
   no existe, y es un defecto que nadie nota porque sólo lo usa quien navega con teclado. */
.saltar-al-contenido:focus { left: 0; }
/* `<main>` recibe el foco al saltar, y no se le pinta un recuadro porque no es un control. */
.admin-main:focus { outline: none; }

/* En escritorio el plegable no se nota: el resumen se oculta y la lista queda como estaba. */
.admin-nav-resumen { display: none; }
.admin-nav-plegable > .admin-nav { display: grid; }

@media (max-width: 850px) {
  .admin-nav-resumen {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 44px;
    padding: 8px 4px;
    cursor: pointer;
    list-style: none;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    font-size: .8rem;
    color: var(--gold2);
  }
  .admin-nav-resumen::-webkit-details-marker { display: none; }
  /* Tres rayas dibujadas con bordes: ni una imagen ni una fuente de iconos que cargar. */
  .admin-nav-icono {
    width: 18px; height: 12px;
    border-top: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    position: relative;
  }
  .admin-nav-icono::after {
    content: ""; position: absolute; left: 0; top: 5px;
    width: 100%; border-top: 2px solid currentColor;
  }
  .admin-nav-plegable:not([open]) > .admin-nav { display: none; }

  /* EL SELECTOR DE IDIOMA VIAJA CON EL MENU.
   *
   * Ocupaba 76 px de los 285 que separaban el principio de la pantalla del contenido, y es algo que se
   * toca una vez y no todos los dias. Con el menu plegado se esconde; al abrirlo aparece, que es cuando
   * alguien lo esta buscando.
   *
   * Lo decide el `<details>` y no una clase puesta por JavaScript: una sola fuente para el estado. Si el
   * navegador no entiende `:has()`, el selector se queda visible -- exactamente como estaba antes. */
  .admin-sidebar:has(.admin-nav-plegable:not([open])) .admin-lang { display: none; }
}

/* POR QUE EL CARRIL DEL PANEL LLEVA `grid-template-rows: auto 1fr` EN MOVIL.
 *
 * `.admin-shell` tiene `min-height: 100vh`. Con una sola columna y dos filas automaticas, el sobrante se
 * reparte entre las dos: en una pagina CORTA -- `/portfolio` sin trabajos, por ejemplo -- la barra
 * lateral se estiraba hasta 402 px y empujaba el contenido a y=422, mientras que en una pagina larga
 * empezaba en 183. El mismo panel, dos resultados, segun cuanto contenido hubiera debajo.
 *
 * Lo encontro `gate:admin-responsive` en cuanto empezo a medir donde arranca el contenido: una sola
 * pantalla de 133 fallaba, y era la unica corta. Si la puerta midiera solo `/users`, esto seguiria ahi.
 *
 * `auto 1fr` dice lo que se quiere decir: la barra mide lo que ocupa y el resto del alto es del
 * contenido. */

/* ============================================================================
   TABLAS EN UN TELÉFONO · una ficha por fila, no una tabla encogida
   ============================================================================

   La tabla de cuentas en un móvil era la de escritorio metida en un desplazador horizontal. Se veían dos
   columnas; el usuario, el papel, la fecha y **todas las acciones** quedaban fuera de la pantalla. Las
   filas que llevan botones dejaban además bandas verticales vacías, porque la altura venía de una celda
   que no se veía. Quien administra desde el teléfono no podía desactivar una cuenta ni restaurar unas
   credenciales, y nada en la pantalla decía que esas acciones existieran.

   Por debajo de 700 px cada fila se convierte en una ficha y cada celda lleva delante el nombre de su
   columna, que viene de `data-etiqueta` y está traducido por la misma función que traduce la cabecera.
   No hay segunda plantilla, ni JavaScript, ni contenido duplicado: la misma fila se lee de dos maneras.

   Se aplica sólo a las tablas marcadas `admin-table-apilable`. Las tablas densas de consulta —el registro
   de auditoría— conservan el desplazador, que para ellas es la lectura correcta: se comparan filas entre
   sí, y apilarlas rompería la comparación. Que la decisión sea por tabla y no por anchura es el punto. */
@media (max-width: 700px) {
  .admin-table-apilable thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .admin-table-apilable, .admin-table-apilable tbody, .admin-table-apilable tr, .admin-table-apilable td { display: block; width: auto; }
  .admin-table-apilable tr {
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 4px 0;
    margin-bottom: 12px;
    background: var(--card);
  }
  .admin-table-apilable td {
    border-bottom: 0;
    padding: 8px 14px;
    display: grid;
    grid-template-columns: minmax(7rem, 38%) 1fr;
    gap: 10px;
    align-items: baseline;
  }
  /* La etiqueta de la columna, delante del dato. Se pinta desde el atributo: si mañana se añade una
     columna y nadie le pone etiqueta, aparece vacía y se ve, que es mejor que desaparecer. */
  .admin-table-apilable td::before {
    content: attr(data-etiqueta);
    font-size: .75rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--muted);
  }
  /* EL VALOR PARTE POR DONDE SEA; LA ETIQUETA NO.
   *
   * En la ficha apilada la columna del valor mide el 62 % de 390 px, y ahí no cabe un correo entero ni un
   * identificador de veinte caracteres: `insp.propietario@local.inv…` salía cortado contra el borde. Son
   * identificadores, así que parten por donde sea —es la misma distinción que el panel ya hace en la
   * biblioteca de medios—, mientras que la etiqueta, que es una palabra, no se parte nunca. */
  .admin-table-apilable td { overflow-wrap: anywhere; }
  .admin-table-apilable td::before { overflow-wrap: normal; }
  /* TODO LO QUE NO ES LA ETIQUETA VA EN LA COLUMNA DEL VALOR.
   *
   * La celda es una rejilla de dos columnas y su primer elemento es el `::before` con la etiqueta. Un
   * valor compuesto por varios nodos —la fecha, que son día, un `<br>` y la hora— generaba un elemento
   * más, y ese tercero caía en la fila siguiente, columna uno: la hora aparecía debajo de la etiqueta,
   * como si fuera otro campo. Se ve enseguida en una captura y no se ve leyendo el CSS.
   *
   * `grid-column: 2` en los hijos lo coloca donde pertenece sin tocar la plantilla. */
  .admin-table-apilable td > * { grid-column: 2; }
  .admin-table-apilable td.celda-fecha { white-space: normal; }
  .admin-table-apilable td.celda-fecha br { display: none; }
  .admin-table-apilable td.celda-fecha .muted { margin-left: .5ch; }

  /* La celda de acciones ocupa el ancho entero: son botones, no un dato con etiqueta. */
  .admin-table-apilable td:last-child { grid-template-columns: 1fr; }
}

/* LA ETIQUETA ENCIMA DEL DATO CUANDO LA PANTALLA ES MUY ESTRECHA.
 *
 * A 390 px la ficha apilada funciona: etiqueta a la izquierda, valor a la derecha. A 320 no. Descontado
 * el relleno de la tarjeta quedan unos 290 px, y el reparto de dos columnas deja al valor unos 150: ahí
 * no cabe «insp.propietario», ni «administrador del sitio», ni un correo. Como el valor parte por donde
 * sea —y debe, porque son identificadores—, lo que se leía en la captura era «insp.propiet / ario» y
 * «administrad / or del sitio»: palabras cortadas a mitad. El fallo no era la regla de partir, era el
 * ancho. Una regla de partir que se aplica sobre una columna demasiado estrecha parte lo que sea.
 *
 * Por debajo de 400 px la etiqueta se pone encima y el valor ocupa la tarjeta entera. El dato duplica su
 * sitio y la mayoría de los casos dejan de partirse solos; los que siguen partiéndose son los que no
 * caben de ninguna manera, y ésos sí deben partir.
 *
 * El umbral es 400 y no 320 porque entre ambos el problema ya aparece: a 360 «administrador» tampoco
 * entra. Se elige por donde falla el contenido, no por el nombre de un dispositivo. */
@media (max-width: 400px) {
  .admin-table-apilable td {
    grid-template-columns: 1fr;
    gap: 2px;
    padding: 10px 14px;
  }
  .admin-table-apilable td > * { grid-column: 1; }
  .admin-table-apilable td:last-child::before { display: none; }
  .admin-table-apilable .row-actions { flex-wrap: wrap; gap: 8px; }
  /* Y el desplazador deja de hacer falta para esta tabla. */
  .table-wrap:has(.admin-table-apilable) { overflow-x: visible; }
}
