/* Portal styles: only what is the portal's own. Mobile first; one breakpoint at 672px (the factory
   grid's first tier). Everything else comes from the ninben brand, loaded before this file and copied
   from the factory by scripts/sync-brand.mjs: brand/ninben.css (colours, light and dark; the radius;
   the fonts) and brand/ninben-components.css (the text sizes, the 4/8-based spacing scale, the frame
   and the shared components: notices, fields, buttons, actions, rows and status, and the brand mark). Nothing
   here sets a token or uses a loose value (FUN-35). The frame is the base's: the page margin on both sides at every
   width, and not cut before 1440 (MAR-11). */

html { -webkit-text-size-adjust: 100%; }
p, h1, h2, ol, ul { margin: 0; }

/* ---- masthead and page ------------------------------------------------------------------ */

.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  min-height: calc(var(--target-min) + 2 * var(--space-2));
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--line);
}
/* The brand is a link home: its words keep the brand's type (brand/ninben-components.css), the link keeps the tap target. */
.masthead .brand { min-height: var(--target-min); color: inherit; text-decoration: none; }
.masthead-actions { display: flex; gap: var(--space-2); }

main { padding-block: var(--space-6) var(--space-7); }

.page-head h1 { font-size: var(--text-title); line-height: 1.25; font-weight: 600; letter-spacing: -0.02em; }
.page-meta {
  max-width: var(--measure-text);
  margin-top: var(--space-2);
  color: var(--muted);
  overflow-wrap: anywhere;
}

.notices { margin-top: var(--space-between-blocks); }
.notices:empty { display: none; }
.body { margin-top: var(--space-between-blocks); }

.section + .section { margin-top: var(--space-between-blocks); }
.section h2 { font-size: var(--text-body); font-weight: 600; line-height: 1.4; margin-bottom: var(--space-3); }
.section p { max-width: var(--measure-text); }

/* Sections side by side share the width equally; below 672px they stack. */
.split { display: grid; gap: var(--space-between-blocks); }
.split > .section { margin: 0; }
@media (min-width: 672px) {
  .split { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

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

.form { display: grid; gap: var(--space-4); max-width: var(--measure-form); }
/* The code is figures: JetBrains Mono (brand spec §2.2). */
.code-input { font-family: var(--font-mono); font-size: var(--text-title); letter-spacing: 0.3em; font-variant-numeric: tabular-nums; }
.form .button--primary { width: 100%; }
/* What follows a form (sending the code again, another email) goes after it, so its primary action is the form's
   last control (MAR-06); same gap and measure as inside it. */
.form + .secondary-actions { margin-top: var(--space-4); max-width: var(--measure-form); }

.steps { list-style: none; padding: 0; display: grid; gap: var(--space-2); }
.step-number { font-weight: 600; font-variant-numeric: tabular-nums; }

/* ---- home ------------------------------------------------------------------------------- */

.loading { color: var(--muted); }
/* Nothing shared yet: the empty state takes the shape of the list it replaces (the manual's «lista»): the list with its own
   top rule (the base's .rows) and one row (.row-item, below): its title and message at the reading measure, then what asks
   the person to check the email they were invited with, the base's section notice (information, aviso-de-seccion), which
   carries its own action, «Entrar con otro correo», as every notice does (the base's link action in .notice-actions). Right
   after the notice, in the same column at every width, the view's main action, «Actualizar»: what to do while nothing has
   arrived is to look again. It is the base's primary button, as on sign-in (a 48 px target): as wide as the body up to the
   form measure, like the sign-in buttons, and nothing of the block comes after it (MAR-06). */
.empty .row-main { max-width: var(--measure-text); }
.empty .notice { margin-top: var(--space-2); }
.empty .row-title { font-size: var(--text-body); line-height: 1.4; }
.empty p { overflow-wrap: anywhere; }
/* Its margin and the column's gap make the 8 px it always had under the notice. */
.empty .empty-actions { display: grid; justify-items: start; gap: var(--space-4); max-width: var(--measure-form); margin-top: var(--space-1); }
.empty .empty-actions .button--primary { width: 100%; }

/* A project's page: the way back above the title (the manual's «contexto» zone), and the line that says what the
   project's owner sees, first in the body and at the reading measure, one block's gap above the lists. */
.page-context { margin-bottom: var(--space-1); }
/* The project's page has no «volver» on a phone: its destination is home, which the brand in the masthead links to. The
   title takes its place, so a name of two lines (24 px each) still ends the band at 160 px or less (MAR-14). */
main.context-wide { padding-top: var(--space-4); }
.page-context--wide { display: none; }
/* A way back elsewhere (the owner's point of «Decisiones», back to its project) stays on a phone, «← Volver» on its own row right
   under the masthead (its 44 px target the only gap); then the title band reads as every list's: the h1, and the counts under
   it as the list's data line. On a phone the h1 takes the body size, the largest step of the type scale with which a point's
   title of 60 characters (the most the factory publishes) ends at 160 px or less (MAR-14; at the lists' 24 px it ends at
   203 px). At that size it drops the h1's tight tracking, so it never takes the brand name's type (the brand is 16 px, 600 and
   tracked tight: NAV-08). From 672 px, the frame's title band as on every screen. Each count of the data line stays whole on
   one line: the line wraps between two counts. */
main.context-back { padding-top: 0; }
main.context-back .page-head h1 { font-size: var(--text-body); letter-spacing: normal; }
.page-meta .count { white-space: nowrap; }
@media (min-width: 672px) {
  main:is(.context-wide, .context-back) { padding-top: var(--space-6); }
  .page-context--wide { display: block; }
  main.context-back .page-head h1 { font-size: var(--text-title); letter-spacing: -0.02em; }
}
.page-note { max-width: var(--measure-text); color: var(--muted); }
.page-note + .section { margin-top: var(--space-between-blocks); }

/* A row of the list (.rows, .row and .status come from the base): the whole row is the link. An entry of the owner's part
   (.row-item) is laid out the same, its name in the text colour, not the link's: what opens is its own link. */
.row-link, .row-item {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-height: 56px;
  padding-block: var(--space-4);
  color: inherit;
  text-decoration: none;
}
.row-main { display: grid; gap: var(--space-1); }
.row-title { color: var(--signal); font-weight: 600; }
.row-meta { color: var(--muted); font-size: var(--text-small); }
@media (hover: hover) {
  .row-link:hover .row-title { text-decoration: underline; text-underline-offset: 3px; }
}
.row-item .row-title { color: var(--ink); }
@media (min-width: 672px) {
  .row-link, .row-item { flex-direction: row; align-items: center; justify-content: space-between; gap: var(--space-4); }
}
/* What decides on a home row (left to answer, all sent, the owner), on the row's right as a list's state. */
.row-aside { color: var(--muted); font-size: var(--text-small); }

/* One mark per state, all in the base's dot family (COM-13) and the same wherever the state shows; the word says it, the
   dot repeats it: «Por empezar» the base's hollow grey; «La abrió» half filled in ink; «En curso» the ink ring, hollow;
   «Enviada» the green ring; «Recibida» filled green (base); «Reemplazada» the grey ring struck through. The portal never
   draws a state with the base's ink-filled dot (.status--active): inside the pieces that dot means «Con observaciones» or
   «Pidió un cambio», so along the owner's path one colour keeps one meaning. */
.status--opened .status-dot { --dot: var(--ink); --dot-fill: linear-gradient(90deg, var(--ink) 50%, transparent 50%); }
.status--in-progress .status-dot { --dot: var(--ink); }
.status--sent .status-dot { --dot: var(--ok); }
.status--replaced .status-dot { --dot-fill: linear-gradient(135deg, transparent 40%, var(--muted) 40%, var(--muted) 60%, transparent 60%); }

/* The owner's part of a project's page reads, squinting, section, then piece, then person: the section's title as every
   section's, first in the part and followed by its line in muted text; each piece's title in the same type (a section
   title in the base's scale, as a piece is in «Lo que se te pidió»), with its kind; each person's name a step smaller and
   regular, always in the text colour, so a piece and a person differ in size and in weight. A send that can be read carries «Ver lo que envió» next to the name, a plain link
   (the base's link colour and underline) at the name's size: the link colour means a link and nothing else.
   Below 672 px an entry is two lines: the name (and its link) with the state word on its right, as a list row carries its
   state, then the date line across the row. From 672 px it is the list row above (the state on the row's right). */
.owner-lead { color: var(--muted); }
.owner-lead + .owner-piece, .owner-piece + .owner-piece { margin-top: var(--space-5); }
.owner-piece-title { margin: 0; font-size: var(--text-body); font-weight: 600; line-height: 1.4; }
.owner-piece-kind { margin-block: 0 var(--space-3); color: var(--muted); font-size: var(--text-small); }
.owner-piece .row-title { font-size: var(--text-small); font-weight: 400; }
.owner-piece .row-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "name state" "meta meta";
  align-items: center;
  gap: var(--space-1) var(--space-3);
}
.owner-piece .row-item > .row-main { display: contents; }
.owner-piece .owner-name { grid-area: name; }
.owner-piece .row-meta { grid-area: meta; }
.owner-piece .row-item > .status { grid-area: state; }
@media (min-width: 672px) {
  .owner-piece .row-item { display: flex; }
  .owner-piece .row-item > .row-main { display: grid; }
  .owner-piece .owner-name, .owner-piece .row-meta { grid-area: auto; }
}
/* The link is a 44 px target like every other link of the portal (FUN-18): its padding grows what can be touched, and the
   same negative margin keeps its line, and so the entry, at the text's height. Its padding reaches over the date line, so
   it is positioned: it paints, and takes the tap, above that line on its whole box. Its focus ring goes around its words
   (the base's ring on its text), not around the padded box, so it never crosses the date line. */
.owner-name { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--space-3); }
.owner-open { position: relative; display: inline-block; padding-block: var(--space-3); margin-block: calc(-1 * var(--space-3)); font-size: var(--text-small); }
.owner-open:focus-visible { outline: none; }
.owner-open:focus-visible > span { outline: 2px solid var(--signal); outline-offset: 2px; }

/* The «Decisiones» section (app.js decisionsSection: the manual's «formulario» inside the page's «lista»). Its lead at the
   reading measure, then the points as rows of a list (the base's rule between rows), each holding its title in the
   section-title type, its mark, what was decided, the fold «En qué se apoya», when she answered and the answer's form;
   then the proposal form, last. The four choices sit side by side and wrap, each a 48 px target, its words in regular
   weight (they are answers, not field labels). The fold is the pieces' «more» fold: a signal chevron that turns, only the
   words underlined, a 44 px summary; a quote inside it is set off by a rule on its start edge. */
.decisions-lead { margin-bottom: var(--space-4); }
.decision { padding-block: var(--space-4); }
.decision-row, .decision-head, .decision-body { display: grid; gap: var(--space-2); }
.decision-title { margin: 0; font-size: var(--text-body); font-weight: 600; line-height: 1.4; }
.decision-title:focus { outline: none; } /* it takes the focus after a reload of the section; it is not a control */
.decision-mark { font-size: var(--text-small); font-weight: 600; }
.decision-answered { color: var(--muted); font-size: var(--text-small); }
.decision-form { margin-top: var(--space-2); }
/* A point already answered on this version folds its form: the answer line says what she said, and «Cambiar respuesta» (the
   base's secondary button, a 48 px target) opens the form again in its place. */
.decision-change { margin-top: var(--space-2); }
.choices { display: flex; flex-wrap: wrap; column-gap: var(--space-5); min-width: 0; margin: 0; padding: 0; border: 0; }
/* Each choice is a row, as the pieces' options (preguntas.template.html): the whole 44 px row is what you touch and what
   shows the focus (FUN-18, FUN-16); the real radio lies over it, invisible, so a click anywhere chooses and the keyboard
   moves through the group; the mark is drawn. */
.choice { position: relative; display: flex; align-items: center; gap: var(--space-2); min-height: var(--target-min); font-weight: 400; cursor: pointer; }
.choice input { position: absolute; inset: 0; width: 100%; height: 100%; min-height: 0; margin: 0; padding: 0; opacity: 0; cursor: pointer; }
.choice-mark { flex: none; width: calc(var(--space-4) + var(--space-1)); height: calc(var(--space-4) + var(--space-1));
  border: 2px solid var(--muted); border-radius: var(--radius-pill); background: var(--sheet); }
.choice input:checked + .choice-mark { border-color: var(--signal); background: var(--signal); box-shadow: inset 0 0 0 var(--space-1) var(--sheet); }
.choice:has(input:focus-visible) { outline: 2px solid var(--signal); outline-offset: 2px; }
/* With the system's forced colours (a contrast theme) the browser paints every mark as the empty ring: the chosen one keeps its
   fill in the system's highlight colour, and its inner ring in the page's, so the answer chosen still shows. */
@media (forced-colors: active) {
  .choice input:checked + .choice-mark { forced-color-adjust: none; border-color: Highlight; background: Highlight; box-shadow: inset 0 0 0 var(--space-1) Canvas; }
}
@media (hover: hover) { .choice:hover .choice-mark { border-color: var(--signal); } }
.decision-notice:empty { display: none; }
.decision-basis summary { display: inline-flex; align-items: center; gap: var(--space-2); min-height: 44px; color: var(--signal); cursor: pointer; list-style: none; }
.decision-basis summary::-webkit-details-marker { display: none; }
.decision-basis summary span { text-decoration: underline; text-underline-offset: 3px; }
.decision-basis summary svg { flex: none; width: var(--space-4); height: var(--space-4); }
.decision-basis[open] summary svg { transform: rotate(90deg); }
.decision-basis-body { display: grid; gap: var(--space-2); padding-bottom: var(--space-2); }
.decision-basis blockquote { max-width: var(--measure-text); margin: 0; padding-inline-start: var(--space-4); border-inline-start: var(--mark) solid var(--line); }
.decisions-propose, .decisions-propose-main { display: grid; gap: var(--space-3); }
.decisions-propose { margin-top: var(--space-5); }
.decisions-propose h3 { margin: 0; font-size: var(--text-body); font-weight: 600; line-height: 1.4; }
/* «Lo que propusiste», above the form: a small label (as a point's mark), then each proposal as a row of a list (the base's
   rule between rows), its date in muted small text over its text, which keeps its line breaks, the reading measure and
   breaks anywhere (a long address never widens the page). */
.decisions-proposed-title { margin: 0 0 var(--space-2); font-size: var(--text-small); font-weight: 600; }
.decisions-proposed-item { display: grid; gap: var(--space-1); padding-block: var(--space-3); }
.decisions-proposed-text { max-width: var(--measure-text); white-space: pre-line; overflow-wrap: anywhere; }

/* The owner's view of the section (app.js ownerDecisionsPart, showOwnerDecision: the manual's «lista»). On the project's
   page, after the pieces' part, its title and lead, then one row per point as the pieces' rows read: the point's title,
   its counts in muted text, and «Ver respuestas» on the row's right (the owner's link, a 44 px target); then what people
   proposed, each by name and date over its text. A point's screen lists each person: the name in the text colour and regular
   weight, and the answer that counts on its right, small and in bold (the row's one emphasis); the dates, «Ya no se le
   pregunta» and «Respondió a la versión anterior del punto» under them in muted text; the comment under them in the text
   colour, on the row's own start line (MAR-10: no indent of its own); and the earlier answers folded as «En qué se apoya» is
   (from 1056 px the row's three columns, below). Texts keep the reading measure and break anywhere (a long word or address
   never widens the page). */
.owner-decisions .owner-lead { margin-bottom: var(--space-3); }
.owner-proposals { margin-top: var(--space-5); }
.owner-proposals h3 { margin: 0 0 var(--space-3); font-size: var(--text-body); font-weight: 600; line-height: 1.4; }
.owner-proposal { display: grid; gap: var(--space-1); padding-block: var(--space-4); }
.owner-decision-decided { max-width: var(--measure-text); }
.owner-answers { margin-top: var(--space-between-blocks); }
.owner-answer { display: grid; gap: var(--space-1); padding-block: var(--space-4); }
/* The name on the row's left and the answer on its right edge, across the whole row (not the reading measure of a section's
   text): every answer ends on the same x. */
.owner-answers .owner-answer-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; column-gap: var(--space-3); max-width: none; }
/* Squinting, the point's title is the screen's one heading of its size and weight (on a phone it is 16 px): the list's heading
   is a small label, as a point's mark (.decision-mark); each name is in the text's regular weight, as a person in the
   owner's part, and the answer, small and in bold, is the row's one emphasis. */
.owner-answers h2 { font-size: var(--text-small); }
.owner-answer .row-title { color: var(--ink); font-weight: 400; }
.owner-answer-value { font-size: var(--text-small); font-weight: 600; }
.owner-proposal-text, .owner-answer-comment, .owner-decision-decided, .owner-answer-head { overflow-wrap: anywhere; }
.owner-proposal-text, .owner-answer-comment { max-width: var(--measure-text); white-space: pre-line; }
.owner-answer-comment { margin: var(--space-1) 0 0; }
.owner-history-list { list-style: none; margin: 0; padding: 0 0 var(--space-2); display: grid; gap: var(--space-3); }
.owner-history-line { color: var(--muted); font-size: var(--text-small); }

/* From 1056 px a list row is a row of a table: its title (a person's name with «Ver lo que envió» in the owner's part), its
   meta line (people, dates) and its state or aside, in three equal columns of the row (FUN-43: «reparte el ancho con lo que
   ya existe, el grupo tabular como tabla»). Every row of a screen is as wide as the body, so each column falls on the same
   x down every list, and the eye crosses the row in short steps instead of from the far left to the far right. The row
   keeps its markup (a link, or an entry, holding its main part and its state): .row-main only stops being a box. Below
   1056 the row is as above. The empty home is not a list of rows: it keeps its one column. */
@media (min-width: 1056px) {
  .rows:not(.empty) :is(.row-link, .row-item) {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-areas: "title meta state";
    align-items: center;
    gap: var(--space-4);
  }
  .rows:not(.empty) .row-main { display: contents; }
  .rows:not(.empty) .row-main > :first-child { grid-area: title; }
  .rows:not(.empty) .row-meta { grid-area: meta; }
  .rows:not(.empty) :is(.row-aside, .status) { grid-area: state; }
  /* Decided by the list, not by the row: a list in which no row has a meta line (every project is another person's: a name and
     what is left to answer) has two equal columns, title · state, so no column stands empty; as soon as one row has a meta line
     the list keeps its three columns and a row without one leaves that cell empty, so every state still falls on the same x. */
  .rows:not(.empty):not(:has(.row-meta)) :is(.row-link, .row-item) { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-areas: "title state"; }
  /* A person's row on the owner's point of «Decisiones» is a list row too, in the same three equal columns: the name, then the
     dates with the comment and the earlier answers under them, then the answer (the row's state), each on the same x down the
     list. A row with no dates leaves that cell empty. */
  .owner-answers .owner-answer { grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--space-4); align-items: start; }
  .owner-answers .owner-answer-head { display: contents; }
  .owner-answers .owner-answer .row-title { grid-area: 1 / 1; }
  .owner-answers .owner-answer-value { grid-area: 1 / 3; }
  .owner-answers .owner-answer .row-meta { grid-area: 1 / 2; }
  .owner-answers .owner-answer :is(.owner-answer-comment, .owner-history) { grid-column: 2; }
  /* A proposal is not a row of the table: its author and date, then its text, both on the row's start as below 1056 (the
     lists' rule above would send the author line to a column of its own). */
  .owner-proposals .owner-proposal .row-meta, .decisions-proposed .decisions-proposed-item .row-meta { grid-area: auto; }
  /* The «Decisiones» section takes the page's column as the pieces' list does: each point's three parts side by side in the
     same three equal columns, its title (and its mark under it) · what was decided, with the fold «En qué se apoya» and when
     she answered under it · the answer, which starts where the pieces' states do: the part spans its column (MAR-12) and
     its form keeps the form measure at the part's edge (MAR-09); «¿Falta algo?» likewise, its title · its form. The texts
     keep the reading measure inside their column. */
  .decision-row, .decisions-propose { grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--space-4); align-items: start; }
  .decision-answer > :is(.decision-form, .decision-change) { margin-top: 0; }
  .decisions-propose > .decisions-propose-main { grid-column: 3; }
}

/* ---- piece ------------------------------------------------------------------------------ */

/* The piece is as tall as its content and the page scrolls, never the piece (MAR-14): the iframe takes the height the
   piece posts (app.js followPiece), and its box (.piece-main) fills the screen under the frame, so a short piece still
   has the screen. Nothing of the frame is fixed or sticky: on a phone it goes up as the page scrolls. */
.piece-layout { display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }
.piece-layout > .frame { flex: none; }
/* Under the masthead, at every width: «Volver», then the title band (the piece's title and its line: whether the draft
   was saved, or on the owner's screen «Lo que envió … el …»). Both start on the brand's left edge, the frame's. On a phone
   the frame takes the least it can, with nothing cut (MAR-14, FUN-14): at rest, and with «Guardado» showing, the title band
   ends at 160 px or less of a 640 px screen with a title of two lines (the factory's publisher keeps titles to what fits
   there). «← Volver» goes right under the masthead, one line (its name still says where), and the line goes in its row, on
   its right and in small text, so it never takes a row of its own under the title; the title takes the row below, across
   the bar. The heading's box gives way to the grid (the title and the line are its items); a longer line (a save that
   failed, a long name) wraps inside that row and the row grows, nothing is cut. From 672 px «Volver» shows where it goes
   and the line goes under the title. */
.piece-bar {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: var(--space-3);
  padding-block: 0 var(--space-3);
}
.back-link { display: inline-flex; align-items: center; gap: var(--space-1); min-height: 44px; color: var(--signal); font-weight: 600; text-decoration: none; }
.piece-bar .back-link { justify-self: start; }
@media (hover: hover) { .back-link:hover { text-decoration: underline; text-underline-offset: 3px; } }
.back-link-long { display: none; }
.back-link-short { white-space: nowrap; }
.piece-heading { display: contents; font-size: var(--text-body); line-height: 1.3; }
.piece-title { display: block; grid-area: 2 / 1 / 3 / 3; min-width: 0; font-size: var(--text-body); font-weight: 600; line-height: 1.3; }
.piece-status { color: var(--muted); font-size: var(--text-small); }
.piece-status:not([hidden]) { display: block; grid-area: 1 / 2; align-self: center; text-align: end; line-height: 1.15; }
@media (min-width: 672px) {
  .piece-bar { grid-template-columns: none; column-gap: 0; row-gap: var(--space-1); justify-items: start; padding-block: var(--space-1) var(--space-3); }
  .back-link-long { display: inline; }
  .back-link-short { display: none; }
  .piece-heading { display: grid; gap: var(--space-1); min-width: 0; max-width: 100%; }
  .piece-title { grid-area: auto; }
  .piece-status:not([hidden]) { grid-area: auto; align-self: auto; text-align: start; line-height: inherit; }
}
.piece-layout .notices { margin: 0 0 var(--space-3); }
/* A closed piece (received, or a replaced version that was sent): its notices, the earlier change requests and the
   request form, one under the other in the pinned zone; their texts keep the reading measure. */
.closure { display: grid; gap: var(--space-3); }
.change-request-lines { display: grid; gap: var(--space-1); }
.change-request-lines:empty, .change-request-slot:empty { display: none; }
.change-request-lines p, .change-request-sent { max-width: var(--measure-text); }
/* A notice can hold a link and a button side by side (a replaced version: its newer one and «Pedir un cambio»): both
   centre their text in the 44 px target, so they share a baseline. */
.notice-actions a.link-button { display: inline-flex; align-items: center; }
/* The piece takes the frame's box, its margins included, and starts its content at the frame's margin
   itself (the factory's pieces do so inside the portal, with the same 16/24 px margin and the same 672 px
   tier, measured on the iframe, which is as wide as the window up to the frame's cap). So its content
   shares the brand's left edge, and a focus ring at that edge is never cut by the iframe. The rule above
   it spans the frame's content, like the masthead's. */
.piece-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: var(--marco-pagina);
  margin-inline: auto;
  padding: 0;
}
.piece-main::before { content: ""; flex: none; margin-inline: var(--margin-page); border-top: 1px solid var(--line); }
/* Until the piece posts its height, the iframe takes what is left of the screen; then exactly that height. */
.piece-frame { display: block; flex: 1; min-height: 0; width: 100%; border: 0; }
.piece-frame[data-sized] { flex: none; }
