/*
  EduNation, the edition as a web page. resources/views/pages/edunation/read.blade.php

  THE DESIGN IS THE PRINTED ONE

  Laid out after tests/edunation.pdf, which is the published newsletter: a teal
  masthead, the theme in Arabic over the theme in English, and then a run of stories
  where each one is a coloured headline bar with the school's logo beside it, its
  Arabic headline over its English one, and a photograph with that same colour as a
  strip beneath it.

  Every colour below is sampled from that file rather than matched by eye.

  ONE APPEARANCE, AND IT IS THE LIGHT ONE

  The chrome around this page follows the visitor's theme; the edition itself does
  not. It is a printed piece with photographs and Arabic set on paper white, and a
  dark version would be a second design nobody is reviewing. So the surfaces here
  are literal colours rather than tokens, and they are the same in both themes. The
  same decision as the email, for the same reason, and the two now agree.

  WIDTHS

  The measure is the whole design. 940px is about 65 characters of the body face,
  which is where a justified bilingual column stops being readable, and the story
  grid inside it is a 300px picture beside whatever is left. Below 860px that grid
  becomes one column and the picture goes full width.
*/

/*
  THE PAGE, WHICH IS NOT A PAGE OF THE SITE

  read.blade.php does not extend the shared layout, so the reset and the ground that
  qf-chrome.css used to provide are here. A newsletter opened from an email wears no
  site header: see the note at the top of that file.
*/
/* NOT .edu-web-body: that name was already taken by the grid row inside a story,
   and giving the page the same class made the whole document a grid. */
.edu-web-page {
  margin: 0;
  padding: 0 0 48px;
  background: #ececec;
  -webkit-font-smoothing: antialiased;
}

.edu-web-page *,
.edu-web-page *::before,
.edu-web-page *::after {
  box-sizing: border-box;
}

.edu-web {
  --edu-teal: #00b8b1;
  --edu-teal-dark: #007d78;
  --edu-orange: #f26727;
  --edu-ink: #53555a;
  --edu-ink-strong: #2a2c2e;
  --edu-paper: #ffffff;
  --edu-ground: #f6f6f6;

  max-width: 940px;
  margin: 0 auto;
  background: var(--edu-paper);
  color: var(--edu-ink);
  /* Stated here rather than inherited from the chrome, which this page no longer
     wears. QF is the house face and qf-tokens.css registers it. */
  font-family: "QF", "Segoe UI", system-ui, -apple-system, sans-serif;
  font-size: 16px;
  line-height: 1.6;

  /*
    NOTHING A SCHOOL TYPES MAY BREAK THE PAGE

    A body or a headline is free text, and free text includes a hundred and fifty
    characters with no space in them: a keyboard mash, a pasted URL, a German
    compound. None of those offer a wrap opportunity, so without this the run
    escapes its grid track and crosses the photograph, the margin and the edge of
    the page. Seen in production with an Arabic mash in a story description.

    `anywhere` rather than `break-word`, deliberately: only `anywhere` also drops
    the min-content contribution, so the run cannot force a grid track or a flex
    item wider than the space it was given. `break-word` wraps the text and still
    stretches the column.

    Set once on the edition rather than per block, because every text on this page
    is somebody's typing.
  */
  overflow-wrap: anywhere;
}

.edu-web :is(h1, h2, h3, p) {
  margin: 0;
}

/*
  HEADINGS TAKE THE EDITION'S INK, NOT THE VISITOR'S THEME

  qf-chrome.css carries `.qf-page :where(h1..h6) { color: var(--text-heading) }`,
  which matches every heading on this page DIRECTLY. A directly matching rule beats
  an inherited value whatever the specificity, so the headline colour set on the
  coloured bar never reached the <h3> inside it: the story headlines followed the
  visitor's theme instead, near-black in light and near-white in dark, which is
  illegible on the yellow and the pale olive bars.

  So inheritance is restored here explicitly, and every heading that wants its own
  colour states it below. This is the same decision as the rest of the file: the
  chrome around the edition follows the theme, the edition does not.

  :where() AND NOT :is(), WHICH IS THE WHOLE POINT

  :is() takes the specificity of its most specific argument, so `.edu-web :is(h1..h6)`
  is (0,1,1) and outranks every single-class rule below it: the first version of this
  fix repaired the story headlines and greyed out the theme on the cover. :where()
  contributes nothing, leaving this at (0,1,0), which is enough to beat the chrome's
  rule on source order alone and not enough to beat anything of ours.
*/
.edu-web :where(h1, h2, h3, h4, h5, h6) {
  color: inherit;
}

/* Arabic runs a little larger and a lot looser: at the same size it reads smaller
   than Latin, and the two languages are meant to feel equal rather than measure
   equal. */
.edu-web [lang="ar"] {
  font-size: 1.05em;
  line-height: 2;
}

/* ------------------------------------------------------------------ Masthead */

/*
  The theme artwork asks for QF-Bold BY NAME.

  qf-tokens.css registers the family as "QF" with weights, so `font-family: QF-Bold`
  inside the artwork matches nothing and the cover falls back to the browser's
  default face. Providing the name the artwork asks for is less fragile than
  overriding its own rules from out here, and it stays correct if the next edition's
  cover is drawn the same way.
*/
@font-face {
  font-family: "QF-Bold";
  src: url("../fonts/QF-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

.edu-web-mast {
  padding: 32px 40px 24px;
  text-align: right;
}

/* The masthead lockup. A max in px rather than a percentage: it is a wordmark, and
   past about 380px it stops looking like a masthead and starts looking like a
   banner. */
.edu-web-lockup {
  width: min(380px, 78%);
  height: auto;
  margin-left: auto;
}

/* The drawn cover, inlined. It carries its own teal band and its own margins, so it
   sits full-bleed inside the sheet with nothing around it. */
.edu-web-theme-art svg {
  display: block;
  width: 100%;
  height: auto;
}

/* --------------------------------------------------------------------- Theme */

.edu-web-theme-ar {
  background: var(--edu-teal);
  color: #fff;
  padding: 14px 40px;
  font-size: clamp(30px, 6vw, 54px);
  font-weight: 700;
  line-height: 1.35 !important;
  text-align: right;
}

.edu-web-theme-en {
  padding: 18px 40px 6px;
  font-family: "Trebuchet MS", "Segoe UI", system-ui, sans-serif;
  font-size: clamp(34px, 8vw, 74px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -1px;
  text-transform: uppercase;
  color: var(--edu-teal);
}

/* The printed cover sets the last word of the theme in orange. Done here with a
   span the template adds rather than by splitting words in CSS, because a rule
   that guesses where a headline breaks is a rule that will be wrong in Arabic. */
.edu-web-theme-en em {
  font-style: normal;
  color: var(--edu-orange);
}

.edu-web-issue {
  padding: 10px 40px 0;
  font-size: 14px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: #8a8d92;
}

/* ---------------------------------------------------------------------- Intro */

/*
  What the newsletter is, for a reader who arrived from a forwarded link. One
  sentence per language, and it stays one sentence: this sits between the cover and
  the editor's letter, and anything longer turns the opening of the edition into two
  pieces of throat-clearing before the first story.

  Set quieter and smaller than the letter on purpose. It is a caption on the
  masthead, not the first thing to read.
*/
/*
  Two columns, Arabic first, the same arrangement as the editor's letter directly
  below it. Stacked and held to a 62ch measure, the two sentences read as one
  language interrupted by another, and the measure squeezed both into four or five
  lines apiece in a space that had the width to spare.

  No wrapper per language, unlike the letter: there is exactly one sentence each, so
  the paragraphs are the grid items.
*/
.edu-web-intro {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* Row gap first, and small: it only applies in the stacked state below 860px,
     where these are still a caption pair and not two paragraphs. */
  gap: 6px 28px;
  padding: 14px 40px 0;
}

.edu-web-intro p {
  margin: 0;
  font-size: 14px;
  line-height: 1.65;
  color: #6d7176;
}

/* Arabic on the right and still first in the source, moved with order rather than
   by reordering the markup. Same reasoning as the letter: a screen reader and a
   copy-paste should both get the Arabic line first. */
.edu-web-intro p[lang="ar"] {
  order: 2;
  text-align: right;
  font-size: 15px;
  line-height: 1.9 !important;
}

/* ------------------------------------------------------------ Editor's letter */

/*
  Two columns on a wide screen, Arabic first. The printed edition puts English on
  the left and Arabic on the right; this puts Arabic first in the source and on the
  left, because that is the order this newsletter is read in and the order every
  other bilingual pair on the page follows. One column below 860px, Arabic still
  first.
*/
.edu-web-letter {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 28px;
  padding: 26px 40px 6px;
}

.edu-web-letter p {
  text-align: justify;
}

.edu-web-letter p + p {
  margin-top: 12px !important;
}

/*
  Arabic on the right, and still first in the source.

  The two languages are columns of one grid, so the Arabic column is moved with
  order rather than by putting English first in the markup: a screen reader and a
  copy-paste should both still get the Arabic letter before the English one, which
  is the order the rest of the edition is set in.
*/
.edu-web-letter [lang="ar"] {
  order: 2;
  text-align: right;
}

/* ----------------------------------------------------------------- Promotions */

/*
  The two standing promotions, above the first section. Two columns, and the copy is
  short on purpose: this strip sits in the most valuable space in the newsletter, and
  anything longer competes with the stories it is meant to introduce.

  Each card is an <a> when it has somewhere to go and a <div> when it does not, so
  the whole card is the target rather than a "Click here" the size of two words.
*/
.edu-web-promos {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  padding: 26px 40px 0;
}

/*
  A COLOURED BAND, NOT A COLOURED EDGE

  The first version of this card carried a 5px teal stripe down its leading edge.
  That is not how this newsletter marks a call-out: the printed edition gives one a
  full band with the title reversed out of it, which is the same device as the
  headline bar on every story. The stripe was a habit borrowed from elsewhere, and it
  read as one.

  So the head is the band, the text sits under it, and the promotions look like the
  rest of the page instead of like a widget dropped into it.
*/
.edu-web-promo {
  display: block;
  background: var(--edu-ground);
  color: inherit;
  text-decoration: none;
}

.edu-web-promo-head {
  background: var(--edu-teal);
  color: #fff;
  padding: 7px 14px;
}

.edu-web-promo:nth-child(even) .edu-web-promo-head {
  background: var(--edu-orange);
}

.edu-web-promo-text {
  padding: 11px 14px 14px;
}

/* Centred throughout, both languages. These are two short announcements rather than
   reading matter, and a centred pair reads as a pair. */
.edu-web-promo {
  text-align: center;
}

.edu-web-promo-ar {
  font-size: 15px;
  font-weight: 700;
  line-height: 1.55 !important;
}

.edu-web-promo-en {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
}

.edu-web-promo-body {
  font-size: 12px;
  line-height: 1.6;
}

.edu-web-promo-body + .edu-web-promo-body {
  margin-top: 8px !important;
}

.edu-web-promo-body[lang="ar"] {
  line-height: 1.8 !important;
}

/* "Click here" in the text is the link: the whole card is the anchor, and this
   marks the words so a reader knows where it goes. See EditionPage::promoBody(). */
.edu-web-promo-link {
  font-weight: 700;
  color: var(--edu-teal-dark);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.edu-web-promo:nth-child(even) .edu-web-promo-link {
  color: #b4470f;
}

/* ------------------------------------------------------------------- Sections */

.edu-web-section-head {
  margin: 40px 0 0;
  background: #1a1a1a;
  color: #fff;
  text-align: center;
  padding: 12px 20px;
}

.edu-web-section-ar {
  font-size: 22px;
  font-weight: 700;
  line-height: 1.4 !important;
}

.edu-web-section-en {
  font-family: "Trebuchet MS", "Segoe UI", system-ui, sans-serif;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
}

/* The drawn section heading. It carries its own bar and its own margins, so it runs
   the full width of the sheet where the set band above is inset. */
.edu-web-section-art {
  margin-top: 40px;
}

.edu-web-section-art svg,
img.edu-web-section-art,
img.edu-web-theme-art {
  display: block;
  width: 100%;
  height: auto;
}

/* -------------------------------------------------------------------- Stories */

/* No side MARGIN, so every story lines up with every other one and with the drawn
   section heading above them: 40px of margin here broke the page's one vertical line.
   The 15px of PADDING is what stops the bars and photographs sitting hard against
   the edge of the sheet now that the margin is gone. */
.edu-web-story {
  margin: 26px 0 0;
  padding: 0 15px;
  /* Every headline in the email lands on one of these. Without the offset the
     story arrives flush with the top of the window and reads as cut off. */
  scroll-margin-top: 24px;
}

/* The headline bar: the school's logo on a pale chip, then the two headlines
   centred on the story's own colour. --edu-bar and --edu-fg are set per story by
   the template. */
.edu-web-bar {
  display: flex;
  align-items: stretch;
}

.edu-web-bar-chip {
  flex: none;
  width: 104px;
  background: var(--edu-ground);
  display: grid;
  place-items: center;
  padding: 8px;
}

.edu-web-bar-chip img {
  max-width: 100%;
  max-height: 46px;
  height: auto;
  display: block;
}

.edu-web-bar-titles {
  flex: 1 1 auto;
  min-width: 0;
  background: var(--edu-bar);
  color: var(--edu-fg);
  padding: 10px 20px;
  text-align: center;
}

.edu-web-bar-titles a {
  color: inherit;
  text-decoration: none;
}

.edu-web-bar-titles a:hover {
  text-decoration: underline;
}

/* The colour is stated on the headings themselves, not left to inherit from the
   bar. See the note at the top of this file. */
.edu-web-title-ar {
  color: var(--edu-fg);
  font-size: 20px;
  font-weight: 700;
  line-height: 1.5 !important;
}

.edu-web-title-en {
  color: var(--edu-fg);
  font-family: "Trebuchet MS", "Segoe UI", system-ui, sans-serif;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.3;
}

/* The body: picture, then the two languages, then the rail of links. */
.edu-web-body {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  gap: 22px;
  padding: 16px 0 0;
  align-items: start;
}

/* The story's colour, carried from the bar down onto its picture. The printed
   edition's one piece of visual glue, and the cheapest thing on this page. It sits
   on the frame rather than the picture so a hover cannot scale it. */
.edu-web-photo-frame {
  border-bottom: 10px solid var(--edu-bar);
  line-height: 0;
}

/* Square, matching the crop the submission form takes. Anything sent at another
   shape is filled to the frame rather than left to set its own height. */
.edu-web-photo {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  height: auto;
  object-fit: cover;
}

/* ------------------------------------------------------ The wait for a picture */

/*
  THE SQUARE IS ALREADY RESERVED, SO NONE OF THIS IS ABOUT LAYOUT

  aspect-ratio holds the shape from the first paint and always has, so a slow
  photograph has never moved the page. What it left was a hole: an empty frame
  that looks the same as a picture that failed. This is the difference between
  the two, and nothing more.

  Driven by data-edu-photo, which pages/edunation/_photo-loading.blade.php writes
  and clears. The attribute is empty in the HTML, so a browser running no script
  gets the photographs exactly as before: there is no rule here that hides an
  image until something else says to show it.
*/

.edu-web-photo-frame[data-edu-photo="loading"] {
  position: relative;
  overflow: hidden;
  background: var(--edu-ground);
}

.edu-web-photo-frame[data-edu-photo="loading"] .edu-web-photo { opacity: 0; }
.edu-web-photo-frame[data-edu-photo="ready"] .edu-web-photo { opacity: 1; }

/* Optional, like every other rule this design animates: a reader who has asked
   for less motion gets the quiet grey square and the picture simply appears. */
@media (prefers-reduced-motion: no-preference) {
  .edu-web-photo { transition: opacity 0.4s ease; }

  .edu-web-photo-frame[data-edu-photo="loading"]::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    background: linear-gradient(
      100deg,
      rgba(255, 255, 255, 0) 20%,
      rgba(255, 255, 255, 0.75) 50%,
      rgba(255, 255, 255, 0) 80%
    );
    animation: edu-photo-wait 1.5s ease-in-out infinite;
  }
}

@keyframes edu-photo-wait {
  from { transform: translateX(-100%); }
  to { transform: translateX(100%); }
}

.edu-web-photo-missing {
  background: var(--edu-ground);
  border-bottom: 10px solid var(--edu-bar);
  color: #9aa5b1;
  font-size: 13px;
  text-align: center;
  padding: 48px 12px;
}

.edu-web-text p {
  text-align: justify;
}

.edu-web-text [lang="ar"] {
  text-align: right;
}

.edu-web-text .edu-web-en {
  margin-top: 14px !important;
}

.edu-web-school {
  font-size: 12px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: #8a8d92;
  margin-bottom: 6px !important;
}

/* One line under the story: the way to read it on the left, the marks for where else
   it appears on the right. Both optional, and the row holds either alone. */
.edu-web-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 14px;
}

/* No boxes. The marks are the smallest thing in the story and should read as a
   footnote to it, not as four buttons. */
.edu-web-links {
  display: flex;
  align-items: center;
  /* The gap is small because each link below is a 44px box around an 18px icon.
     Around nine in ten readers open this on a phone, and an 18px target between two
     other 18px targets is a guess rather than a tap. */
  gap: 2px;
  /* Right, even when there is no "Read the full story" beside them. */
  margin-left: auto;
}

.edu-web-links a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  color: #9aa0a6;
}

.edu-web-links a:hover {
  color: var(--edu-teal-dark);
}

/* It was 14px teal body text sitting in the same row as the social marks, which
   is what body text looks like. The other two designs make this the story's one
   call to action and this one did not, so it now borrows the small uppercase
   label this design already uses for the school name, and the arrow the promo
   cards already use for "there is somewhere to go". */
.edu-web-more {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--edu-teal-dark);
}

.edu-web-more::after {
  content: "\2192";
  font-size: 14px;
  line-height: 1;
}

.edu-web-more:hover,
.edu-web-more:focus-visible {
  color: var(--edu-teal);
}

/* --------------------------------------------------------------------- Close */

.edu-web-end {
  margin-top: 44px;
  background: var(--edu-teal);
  color: #fff;
  text-align: center;
  padding: 44px 40px;
}

.edu-web-end-lockup {
  width: min(300px, 70%);
  height: auto;
  margin: 0 auto 6px;
}

.edu-web-end p + p {
  margin-top: 10px !important;
}

.edu-web-end-qf {
  width: 150px;
  height: auto;
  margin: 24px auto 0;
  display: block;
}

.edu-web-note {
  margin: 0 40px 24px;
  border: 1px dashed var(--edu-orange);
  color: var(--edu-ink);
  background: #fff6f0;
  padding: 12px 16px;
  font-size: 14px;
}

/*
  THE PREVIEW THAT INCLUDES UNDECIDED STORIES

  Deliberately louder than the "not published yet" note above, and deliberately not
  in the edition's palette: what this page is showing is not the edition, and the
  one failure worth designing against is somebody scrolling past the banner and
  reading the rest as though it were.

  Every undecided story carries its own flag as well, because a banner at the top of
  a long page is out of sight by the third section.
*/
.edu-web-note.is-pending {
  border-style: solid;
  border-color: #b3261e;
  background: #fdeceb;
}

.edu-web-flag {
  margin: 0 0 8px;
  padding: 6px 12px;
  border-inline-start: 4px solid #b3261e;
  background: #fdeceb;
  color: #5c1a15;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}


/* ---------------------------------------------------------------------- Motion */

/*
  EVERY RULE IN HERE IS OPTIONAL, AND THAT IS ENFORCED TWICE

  Once by `prefers-reduced-motion: no-preference`, which is the whole block: a
  reader who has asked their system for less motion gets a page that simply is.

  And once by `.js`, which the reveal script adds as its first act. The
  hidden-until-seen state below therefore never applies unless something is running
  that can undo it. An edition that will not show its stories because a script failed
  is not an edition. See the note above that script in read.blade.php.
*/
@media (prefers-reduced-motion: no-preference) {
  /* ----------------------------------------------------------------- Reveal */

  .js .edu-reveal {
    opacity: 0;
    transform: translateY(20px);
  }

  .js .edu-reveal.is-in {
    opacity: 1;
    transform: none;
    transition:
      opacity 0.65s ease,
      transform 0.65s cubic-bezier(0.22, 0.7, 0.28, 1);
    transition-delay: var(--edu-promo-delay, 0ms);
  }

  /* ------------------------------------------------------------------ Intro

    THE FIRST SECOND AND A HALF, IN ORDER

    The top of the edition arrives as a sequence rather than all at once: the sheet
    settles, the masthead rises out of a blur, a hairline draws itself under the
    lockup, the cover wipes open with the artwork settling down to size behind it, a
    single pass of light crosses the cover, and the issue line closes up its tracking
    last. Every step is a fixed offset from load, and they are listed here so the
    thing can be read as a timeline rather than reassembled from six delays scattered
    down the file.

         0ms  the sheet
       120ms  the masthead lockup
       320ms  the hairline under it
       420ms  the cover wipe, and the artwork settling inside it
       620ms  the light across the cover
      1000ms  the issue line

    It is over before a reader has finished the theme, which is the constraint the
    whole sequence is built to: this is a newsletter somebody opened to read, not a
    title card they have to sit through.

    `backwards` RATHER THAN `both`

    Both fills hold the OPENING frame through the delay, which is the part that
    matters: without it every block would paint at full opacity and then jump back to
    hide itself when its turn came. What `both` adds is holding the CLOSING frame
    forever after, and one of these closes on a clip-path; keeping that would leave a
    permanent clip on the cover in exchange for one second of motion. `backwards`
    lets each element go back to being an ordinary element once it has arrived.

    AND NOTHING BELOW IS LOAD-BEARING

    None of it is gated on `.js`, unlike the reveal further down, and it does not need
    to be: an animation that never runs leaves the element exactly as it already was.
    The gate that matters is the one around this whole block.
  */
  .edu-web {
    animation: edu-sheet 0.7s cubic-bezier(0.22, 0.7, 0.28, 1) backwards;
  }

  .edu-web-mast .edu-web-lockup {
    animation: edu-lockup 0.9s cubic-bezier(0.22, 0.7, 0.28, 1) 120ms backwards;
  }

  /*
    A hairline that draws itself out from under the lockup, teal into orange, in the
    direction the masthead is set: the lockup is right-aligned, so the rule grows
    leftwards from the same edge.

    It exists only in here, rather than sitting in the layout with an animation on
    top. A reader who has asked for less motion should get a masthead with nothing
    under it, not a static rule that was only ever there to be drawn.
  */
  .edu-web-mast {
    position: relative;
  }

  .edu-web-mast::after {
    content: "";
    position: absolute;
    right: 40px;
    bottom: 10px;
    left: 40px;
    height: 2px;
    background: linear-gradient(
      to left,
      var(--edu-teal),
      var(--edu-orange) 45%,
      rgba(242, 103, 39, 0)
    );
    transform-origin: 100% 50%;
    animation: edu-hairline 0.9s cubic-bezier(0.22, 0.7, 0.28, 1) 320ms backwards;
  }

  /*
    The cover, wiped open downwards.

    The wipe is a clip-path on the wrapper and the settle is a transform on the
    artwork inside it, which is why the wrapper exists at all: one element cannot be
    clipped along its own moving edge and scaled at the same time without the clip
    scaling too.

    The wrapper also clips, so the artwork can start a few per cent oversize. Without
    that, six per cent of a full-width cover is a horizontal scrollbar on a phone.
  */
  .edu-web-cover {
    position: relative;
    overflow: hidden;
    animation: edu-curtain 1s cubic-bezier(0.65, 0, 0.35, 1) 420ms backwards;
  }

  .edu-web-cover .edu-web-theme-art {
    animation: edu-settle 1.4s cubic-bezier(0.22, 0.7, 0.28, 1) 420ms backwards;
  }

  /* The set cover, for an edition whose theme was never drawn: the same beat, as two
     lines of type instead of a picture, Arabic first as everywhere else. */
  .edu-web-cover .edu-web-theme-ar {
    animation: edu-cover-line 0.9s cubic-bezier(0.22, 0.7, 0.28, 1) 520ms backwards;
  }

  .edu-web-cover .edu-web-theme-en {
    animation: edu-cover-line 0.9s cubic-bezier(0.22, 0.7, 0.28, 1) 640ms backwards;
  }

  /*
    One pass of light across the cover, and only one: it is parked off the left edge
    by the rule itself, so when the animation ends and `backwards` drops its hold the
    sheen returns to where the wrapper's clipping hides it.
  */
  .edu-web-cover::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    transform: translateX(-130%);
    background: linear-gradient(
      100deg,
      rgba(255, 255, 255, 0) 35%,
      rgba(255, 255, 255, 0.45) 50%,
      rgba(255, 255, 255, 0) 65%
    );
    animation: edu-sheen 1.3s ease-out 620ms backwards;
  }

  /* The issue line closes its tracking up as it lands, which is the one place on the
     page where a little typographic theatre costs nothing: it is five words of grey
     caps with no photograph and no Arabic to keep in step. */
  .edu-intro-issue {
    animation: edu-tracking 0.9s cubic-bezier(0.22, 0.7, 0.28, 1) 1s backwards;
  }

  /* --------------------------------------------------------- Hover and dwell */

  /* A photograph leans in a little when somebody is looking at its story. The
     overflow clip is on the frame rather than the image, so the coloured strip
     underneath stays put while the picture moves. */
  .edu-web-photo-frame {
    overflow: hidden;
  }

  .edu-web-photo {
    transition: transform 0.7s cubic-bezier(0.22, 0.7, 0.28, 1);
  }

  .edu-web-story:hover .edu-web-photo {
    transform: scale(1.03);
  }

  .edu-web-promo {
    transition:
      transform 0.3s ease,
      box-shadow 0.3s ease,
      background-color 0.3s ease;
  }

  a.edu-web-promo:hover {
    transform: translateY(-3px);
    background: #fff;
    box-shadow: 0 10px 24px rgba(0, 0, 0, 0.09);
  }

  .edu-web-links a {
    transition:
      background-color 0.25s ease,
      color 0.25s ease;
  }

  .edu-web-bar-titles a {
    transition: opacity 0.25s ease;
  }

  .edu-web-bar-titles a:hover {
    opacity: 0.82;
  }

  .edu-web-more {
    transition: color 0.25s ease;
  }

  .edu-web-more::after {
    display: inline-block;
    transition: transform 0.3s ease;
  }

  .edu-web-more:hover::after {
    transform: translateX(4px);
  }
}

@keyframes edu-sheet {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
}

@keyframes edu-lockup {
  from {
    opacity: 0;
    transform: translateY(18px) scale(0.97);
    filter: blur(8px);
  }
}

@keyframes edu-hairline {
  from {
    opacity: 0;
    transform: scaleX(0);
  }
}

/*
  BOTH ENDS STATED, WHICH THE REST OF THESE DO NOT NEED

  Every other keyframe here gives only its opening frame and lets the closing one be
  the element's own style. That works because `none` is a usable end for opacity and
  transform. It is not one for clip-path: a basic shape cannot interpolate with
  `none`, so an unstated end would make this a jump cut halfway through instead of a
  wipe.
*/
@keyframes edu-curtain {
  from {
    clip-path: inset(0 0 100% 0);
  }

  to {
    clip-path: inset(0 0 0 0);
  }
}

@keyframes edu-settle {
  from {
    transform: scale(1.06);
  }
}

@keyframes edu-cover-line {
  from {
    opacity: 0;
    transform: translateY(18px);
  }
}

@keyframes edu-sheen {
  from {
    transform: translateX(-130%);
  }

  to {
    transform: translateX(130%);
  }
}

@keyframes edu-tracking {
  from {
    opacity: 0;
    letter-spacing: 8px;
    transform: translateY(6px);
  }
}

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

@media (max-width: 860px) {
  .edu-web-letter,
  .edu-web-intro {
    grid-template-columns: 1fr;
  }

  /* One column, so there is no right to move to, and order would only push the
     Arabic letter below the English one. */
  .edu-web-letter [lang="ar"],
  .edu-web-intro p[lang="ar"] {
    order: 0;
  }

  .edu-web-body {
    grid-template-columns: 1fr;
  }

  /* One column. Two of these side by side on a phone are a pair of 148px columns
     carrying a heading, two lines of Arabic and two of English each, which is not a
     call-out, it is a puzzle. */
  .edu-web-promos {
    grid-template-columns: 1fr;
  }

  /* Once the card has the full measure it can afford the body copy the printed
     edition gives it. */
  .edu-web-promo-body {
    font-size: 13px;
  }

  /* The masthead hairline is pinned to the gutter, so it follows the gutter down.
     Inert when it is not drawn: with no `content` the pseudo-element is never
     generated, so this styles nothing for a reader who asked for less motion. */
  .edu-web-mast::after {
    left: 20px;
    right: 20px;
  }

  .edu-web-mast,
  .edu-web-theme-ar,
  .edu-web-theme-en,
  .edu-web-issue,
  .edu-web-intro,
  .edu-web-promos,
  .edu-web-letter {
    padding-left: 20px;
    padding-right: 20px;
  }

  .edu-web-note {
    margin-left: 20px;
    margin-right: 20px;
  }
}

@media (max-width: 560px) {
  .edu-web-bar {
    flex-direction: column;
  }

  .edu-web-bar-chip {
    width: 100%;
    padding: 10px;
  }
}

/* ---------------------------------------------------------------------- Print */

/*
  Somebody will print this, because it is a newsletter. The chrome and the links
  rail are screen furniture; the edition is the page.
*/
@media print {
  .edu-web-links,
  .edu-web-note,
  .edu-web-mast::after,
  .edu-web-cover::after {
    display: none !important;
  }

  /* The exception, and the reason the rule above is not simply `.edu-web-note`: a
     printed preview that has lost the line saying it is a preview is a sheet of
     paper that claims to be the edition. Both this and the per-story flags print. */
  .edu-web-note.is-pending {
    display: block !important;
  }

  /* A sheet printed in the first second and a half would otherwise come out with the
     cover half wiped and the masthead still blurred: `backwards` holds the opening
     frame, and the printer takes whatever is computed at that moment. */
  .edu-web,
  .edu-web *,
  .edu-web *::before,
  .edu-web *::after {
    animation: none !important;
  }

  .edu-web-page {
    background: #fff;
    padding: 0;
  }

  .edu-web {
    max-width: none;
  }

  .edu-web-story {
    break-inside: avoid;
  }
}
