/* ============================================================================= AFRICA MULTIPLE · UNIVERSITÄT BAYREUTH — reveal.js deck theme "BROADSHEET" — a bold scholarly broadsheet, not a slide template. The type hierarchy is INVERTED from a conventional academic deck: the newspaper-gothic (Libre Franklin, pushed to its black weights) is the loud display voice — set big, tight, confident — and the humanist book serif (EB Garamond) is the quiet counter-voice for prose, leads and quotations. Colour is spent as flat structural FIELDS and heavy rules, not as a timid accent: the Bayreuth green leads, navy is the deep pole, gold the spark. Near-black ink on a cool, crisp near-white. High contrast, real punch. Contents 1. Design tokens (palette, neutrals, type scale, space, motion) 2. reveal.js variable contract + viewport frame 3. Slide canvas, typography & prose primitives 4. Editorial components (overline, note, ledger, figure, cite, index) 5. Layout classes (.cover .section .statement .cols .media .closing …) 6. Brand chrome injected by deck.js (running head, footer, progress) 7. Table-of-contents overlay 8. Code (light highlight.js token theme) 9. Fragments, transitions, motion 10. Print to PDF · reduced motion · small screens ============================================================================= */ @import "fonts/fonts.css"; /* ---------------------------------------------------------------------------- 1. DESIGN TOKENS The six Bayreuth corporate colours, then a cool near-white / near-black neutral ramp. Colour here is a confident plane and a heavy rule — the green leads, navy is the structural deep, gold the single spark. ---------------------------------------------------------------------------- */ :root { /* — Bayreuth corporate palette (exact, as supplied) — */ --green: #009260; /* Uni-Grün · lead colour */ --amber: #f59c08; /* Gelb */ --brown: #d57912; /* Braun */ --gold: #cca352; /* Gold */ --sky: #44b8f2; /* Hellblau */ --navy: #00268a; /* Dunkelblau · structural deep colour */ /* derived, perceptually in OKLCH so the steps look even */ --green-deep: color-mix(in oklch, var(--green), #06140d 26%); /* AA green for text/links on light */ --green-field: color-mix(in oklch, var(--green), #04140d 14%); /* the saturated green plane */ --green-bright: color-mix(in oklch, var(--green), white 8%); --navy-field: color-mix(in oklch, var(--navy), #03081c 8%); /* the saturated navy plane */ --navy-deep: color-mix(in oklch, var(--navy), #04060f 30%); --brown-deep: color-mix(in oklch, var(--brown), #140a02 44%); /* AA brown for small text on light */ --gold-bright: color-mix(in oklch, var(--gold), white 24%); /* AA gold on dark fields */ --green-wash: color-mix(in oklch, var(--green), var(--paper) 90%); --navy-wash: color-mix(in oklch, var(--navy), var(--paper) 92%); --amber-wash: color-mix(in oklch, var(--amber), var(--paper) 88%); --warm-field: color-mix(in oklch, var(--brown), #160b02 30%); /* the warm "stakes" field */ --duo-ground: #04140d; /* duotone plate backing */ /* — Neutrals: a cool, crisp near-white; a cool near-black ink. High contrast. — */ --paper: oklch(0.991 0.001 250); /* page / slide background */ --panel: oklch(0.972 0.002 250); /* faintly raised surfaces */ --sunken: oklch(0.945 0.004 250); /* insets, code, table stripes */ /* The rule tier is set by the hall, not the page. A hairline at 1.4:1 is a printer's hairline: it exists at 40cm and is simply gone on a hazy beamer, which quietly turns "ruled, not boxed" into floating text. Each tier is pinned to a measured floor instead — hairlines carry 2.2:1, and the rules that draw a control (chip outlines, nav buttons, plate borders) clear the 3:1 that WCAG 2.2 asks of a component boundary. */ --line: oklch(0.744 0.005 250); /* hairlines 2.22:1 */ --line-strong: oklch(0.623 0.010 256); /* control + plate borders 3.52:1 */ --rule: oklch(0.165 0.014 258); /* heavy editorial rule (near-black) */ --ink: oklch(0.205 0.016 258); /* primary text */ --ink-bold: oklch(0.150 0.016 262); /* big display headlines */ --ink-soft: oklch(0.405 0.018 258); /* secondary text */ --ink-faint: oklch(0.530 0.016 256); /* tertiary, captions, chrome — 5.15:1 on paper, and 4.51:1 on --sunken, where code line numbers and zebra rows put it */ /* — Semantic roles — */ --accent: var(--green); --accent-ink: var(--green-deep); --accent-2: var(--navy); --accent-warm: var(--brown); --highlight: var(--amber); /* — The on-dark register — the dark fields are TWO registers, not one. Measured against Paper, the DEEP pole has room for a tonal ramp and is where gold belongs: the navy field reaches 13.1:1 and the duotone ground 18.4:1. The BRIGHT fields have no such room — the green field tops out at 4.97:1 against Paper and the brown at 5.71:1, so past ~7% transparency Paper itself falls under AA, and nothing else in the palette clears even 3:1 on green (gold 2.17, gold-bright 2.71, amber 2.34, sky 2.28). A bright field therefore spends Paper and nothing else, and separates its tiers by WEIGHT rather than by alpha. The values below are the bright ones, so any dark surface is safe without opting in; the deep pole re-opens the ramp in the block after :root. */ --ond-text: var(--paper); /* primary text on a dark field */ --ond-quiet: color-mix(in oklch, var(--paper), transparent 5%); /* secondary text, glosses */ --ond-chrome: var(--paper); /* the footer's own quieter tier */ --ond-mark: var(--paper); /* the spark: plate rule, ticks */ --ond-rule: color-mix(in oklch, var(--paper), transparent 30%); /* rules and control borders */ --ond-mark-ink: var(--ink-bold); /* text sitting ON the spark */ --focus-ring: var(--green); /* 3.87:1 on paper */ /* — Type — news gothic LEADS (display), book serif is the counter-voice — */ --font-display: "Libre Franklin", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --font-label: "Libre Franklin", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --font-serif: "EB Garamond", "Iowan Old Style", Georgia, "Times New Roman", serif; --font-mono: "JetBrains Mono", ui-monospace, "Cascadia Code", "SF Mono", "Segoe UI Mono", Menlo, Consolas, monospace; --font-body: var(--font-serif); /* compatibility alias */ /* Modular scale (rem @ 16px root; reveal's transform scales the canvas to fit). The gothic display runs large and tight; the serif body sits a touch big because EB Garamond's small x-height reads small in a hall. */ /* The label voice splits in two. A label printed on paper is read at 40cm; a label projected in a hall is read at 20m, and the hall is the primary reader. --fs-footer stays print-small and is now reserved for TRUE CHROME (the deck footer and runhead, which sit outside the scaled canvas, plus the browser-frame simulations that are representational rather than read). Any label INSIDE the canvas that carries meaning — what a figure counts, which decade a bar sits in, what a column holds — takes --fs-label. */ --fs-footer: 0.80rem; /* ~13px chrome + fine print only */ --fs-label: 1.05rem; /* ~17px in-slide labels (hall) */ --fs-kicker: var(--fs-label); /* alias: every kicker is in-slide */ --fs-caption: 1.00rem; /* ~16px captions, citations */ --fs-small: 1.18rem; /* ~19px asides */ --fs-body: 1.52rem; /* ~24px body (serif) */ --fs-lead: 2.00rem; /* ~32px lead paragraph (serif) */ --fs-h3: 2.20rem; /* ~35px sub-heading (gothic) */ --fs-h2: 3.50rem; /* ~56px slide title (gothic) */ --fs-h1: 5.30rem; /* ~85px cover title (gothic) */ --fs-section: 6.10rem; /* ~98px section title (gothic) */ --fs-display: 5.60rem; /* ~90px statement / big number */ --fs-mega: 9.50rem; /* ~152px oversized folio / metric */ --fs-watermark: 14rem; /* ~224px the ghosted folio behind content — a decorative role, deliberately past --fs-mega */ --lh-tight: 0.98; --lh-snug: 1.12; --lh-body: 1.46; --track-label: 0.18em; /* uppercase labels at the small chrome size */ --track-hall: 0.10em; /* eased tracking for --fs-label: the larger the cap, the less air it needs between letters */ --track-tight: -0.022em; /* negative tracking for big gothic display */ /* — Space (4pt scale, semantic) — */ --space-2xs: 0.25rem; --space-xs: 0.5rem; --space-sm: 0.75rem; --space-md: 1rem; --space-lg: 1.5rem; --space-xl: 2rem; --space-2xl: 3rem; --space-3xl: 4rem; /* — Geometry — sharp by default; the broadsheet leans on rules, not cards — */ --radius-sm: 3px; --radius-md: 5px; --radius-lg: 8px; --hair: 1px solid var(--line); --bar: 4px; /* the green title-marker thickness */ /* — Shadows: tight, neutral; used only for true overlays (lightbox, QR) — Two, not three. --shadow-1 was retired once its last two users turned out to be the retired card in disguise (book covers and a QR, both now flat plates): a depth token nobody spends is a token the next author spends wrongly, and this system has exactly two things that float. */ --shadow-2: 0 4px 14px oklch(0.2 0.02 258 / 0.10), 0 14px 40px oklch(0.2 0.02 258 / 0.12); --shadow-pop: 0 22px 60px oklch(0.16 0.03 258 / 0.30); /* — Motion — */ --ease: cubic-bezier(0.22, 1, 0.36, 1); /* ease-out-quint */ --ease-soft: cubic-bezier(0.4, 0, 0.2, 1); --dur: 220ms; --draw: 560ms; /* the "ink-on-paper" signature: rules draw in, bars grow from the baseline */ --count: 900ms; /* the signature's third movement — figures counting up (run in deck.js). Longer than --draw on purpose: a numeral needs legible frames to be read as counting, where a rule only needs to be seen arriving. */ /* THE ONE STILLER. Every signature animation takes its duration from here, and everything that wants the page to arrive still — print, the ?print-pdf preview, prefers-reduced-motion, a .no-draw deck or slide — sets this to 0s rather than restating the list of animated marks. deck.js reads the same property off the slide, so the CSS half and the JS half cannot disagree. */ --draw-run: var(--draw); /* — Slide geometry — */ --slide-pad-x: 4.8rem; --slide-pad-y: 3.8rem; --footer-h: 3rem; /* The tallest a figure or code block may stand inside the canvas. It has to be rem: the canvas is a fixed 1280×720 stage that reveal scales as a whole, so a vh cap here would measure the *window* instead — the same slide would hold a different amount of image on a laptop, a hall projector and a phone. 28rem is the 62vh this replaces, taken at the 720px reference height. */ --block-max-h: 28rem; /* ----- reveal.js variable contract (replaces stock theme) ----- */ --r-background: oklch(0.165 0.012 258); /* graphite frame around the slide */ --r-background-color: var(--paper); --r-main-font: var(--font-serif); --r-main-font-size: var(--fs-body); --r-main-color: var(--ink); --r-block-margin: 0.8em; --r-heading-margin: 0 0 0.5em 0; --r-heading-font: var(--font-display); --r-heading-color: var(--ink-bold); --r-heading-line-height: var(--lh-tight); --r-heading-letter-spacing: var(--track-tight); --r-heading-text-transform: none; --r-heading-text-shadow: none; --r-heading-font-weight: 800; --r-heading1-text-shadow: none; --r-heading1-size: var(--fs-h1); --r-heading2-size: var(--fs-h2); --r-heading3-size: var(--fs-h3); --r-heading4-size: var(--fs-lead); --r-code-font: var(--font-mono); --r-link-color: var(--green-deep); --r-link-color-dark: var(--navy); --r-link-color-hover: var(--green); --r-selection-background-color: color-mix(in oklch, var(--green), transparent 68%); --r-selection-color: var(--ink); --r-overlay-element-bg-color: 18 26 22; --r-overlay-element-fg-color: 244 243 238; } /* The DEEP pole re-opens the tonal ramp and takes the gold spark: the navy divider, the closing field, and full-bleed media (whose scrim is a true deep ground, guaranteed by the theme — see section.media below). deck.js mirrors the same distinction onto the footer, the printed imprint and the viewport, which all live outside the slide, as data-field="deep". Every other dark surface — the green and brown dividers, and any slide an author marks dark by hand — keeps the bright defaults, which are safe on all of them. */ .reveal section.section.navy, .reveal section.closing, .reveal section.media, .deck-footer.on-dark[data-field="deep"], .pdf-imprint[data-field="deep"], .reveal-viewport.deck-dark[data-field="deep"] { --ond-quiet: color-mix(in oklch, var(--paper), transparent 18%); /* 9.18:1 on navy */ --ond-chrome: color-mix(in oklch, var(--paper), transparent 35%); /* 6.22:1 on navy */ --ond-mark: var(--gold-bright); /* 7.13:1 on navy */ --ond-rule: color-mix(in oklch, var(--paper), transparent 55%); /* 3.05:1 on navy */ --ond-mark-ink: var(--navy-deep); } /* Every dark field flips the focus ring. The green ring measures 1.28:1 on its own field and 1.47:1 on the brown one; Paper never drops below 4.97:1 on any of them, so one inverted ring serves all three. */ .reveal section.section, .reveal section.closing, .reveal section.media, .deck-footer.on-dark { --focus-ring: var(--paper); } /* ---------------------------------------------------------------------------- 2. VIEWPORT FRAME ---------------------------------------------------------------------------- */ /* Light slides are transparent, so the VIEWPORT colour is the slide colour. Keep it paper by default; deck.js flips it to a deep graphite frame on dark slides (section / closing / media) so the framing stays consistent either way. */ .reveal-viewport { background-color: var(--paper); /* !important is load-bearing, and only here. Reveal writes `transition: transform 0.8s` INLINE on this element while it initialises, and an inline declaration outranks any stylesheet rule — so the cross-fade declared on this line never ran even once: the frame around the canvas cut instantly between the paper ground and the graphite one, on every section divider, while the slide it surrounds faded. Reveal's own transform transition is restated rather than dropped; the viewport measures `transform: none` in every state this deck uses (normal, overview, fullscreen), so the restatement costs nothing and takes nothing away. */ transition: background-color 360ms var(--ease), transform 0.8s !important; } .reveal-viewport.deck-dark { background-color: oklch(0.165 0.012 258); } ::selection { background: var(--r-selection-background-color); color: var(--r-selection-color); } /* ---------------------------------------------------------------------------- 3. CANVAS, TYPOGRAPHY & PROSE ---------------------------------------------------------------------------- */ .reveal { font-family: var(--font-serif); font-size: var(--r-main-font-size); font-weight: 500; color: var(--ink); line-height: var(--lh-body); -webkit-font-smoothing: antialiased; font-feature-settings: "kern", "liga", "calt"; letter-spacing: 0; } .reveal .slides { text-align: left; } /* Each top-level slide is a padded editorial column, content from the top. */ .reveal .slides > section, .reveal .slides > section > section { inset: 0; width: 100%; height: 100%; padding: var(--slide-pad-y) var(--slide-pad-x) calc(var(--footer-h) + var(--space-md)); display: flex; flex-direction: column; justify-content: flex-start; align-items: stretch; box-sizing: border-box; overflow: visible; } /* PDF export (?print-pdf): reveal's print view re-parents every slide into a .pdf-page wrapper and hard-resets it (padding: 0 !important, display: block !important — see the html.reveal-print rules in reveal.css), expecting the theme to restate its slide layout for print. Without this restatement the rule above no longer matches, so every PDF page rendered flush against the page edge with a collapsed height — trimmed slides. justify-content is left alone so the centred layouts (.cover, .section, .statement, …) keep their own value. Ordinary slides keep the screen's block flow: a fixed-height flex column would shrink image rows and overlap their captions. */ html.reveal-print .reveal .slides .pdf-page > section { height: 100% !important; box-sizing: border-box !important; padding: var(--slide-pad-y) var(--slide-pad-x) calc(var(--footer-h) + var(--space-md)) !important; display: block !important; flex-direction: column; overflow: visible; } /* The hero layouts centre via a .present-scoped rule that reveal's print re-parenting also defeats (the page wrapper, not the section, is what is "present") — restate it for pdf pages so export matches the screen. */ html.reveal-print .reveal .slides .pdf-page > section.cover, html.reveal-print .reveal .slides .pdf-page > section.section, html.reveal-print .reveal .slides .pdf-page > section.statement, html.reveal-print .reveal .slides .pdf-page > section.closing, html.reveal-print .reveal .slides .pdf-page > section.metric, html.reveal-print .reveal .slides .pdf-page > section.center, html.reveal-print .reveal .slides .pdf-page > section.balance { display: flex !important; justify-content: center; } /* Scroll view (?view=scroll): the same failure as print, one wrapper deeper. Reveal's scroll view re-parents every slide into .scroll-page > .scroll-page-sticky > .scroll-page-content, so the `.slides > section` child combinator above stops matching and the slide loses its padding, its flex column and its border-box sizing in one go: measured on the catalogue, the 1280px canvas became a 1376px content box clipped 48px at each edge, with the copy flush into the top-left corner. Auto-activation stays off (scrollActivationWidth: null in deck.js) — this view is only ever reached by asking for it in the URL. The declarations are the ones above, verbatim, and `display` is deliberately NOT !important here: reveal writes `display: block` inline on every loaded slide, which is why a non-hero slide is a block box on screen and why the hero list needs !important to win. Forcing flex on every slide instead put the whole deck in a fixed-height flex column, where a `.cols` row grows to the safe area and an over-long slide silently shrinks its children to fit — measured on the Erlangen deck, twelve slides came out exactly 595px tall and the two the fit engine scales in the normal view (×0.979, ×0.903) stopped being detected as overflowing at all. Sizing is left to reveal, which !importants width, height, top and left in this view. */ .reveal-scroll .reveal .slides .scroll-page-content > section { padding: var(--slide-pad-y) var(--slide-pad-x) calc(var(--footer-h) + var(--space-md)); display: flex; flex-direction: column; justify-content: flex-start; align-items: stretch; box-sizing: border-box; overflow: visible; } /* The third copy of the hero list. Reveal does mark the section `.present` in this view, so the `.present` rule in 03-layouts.css still matches its class part — but it is written against `.slides > section` too, so it matches nothing here either. Like the print list it drops `.present`: a slide you have scrolled past keeps its composition rather than snapping to the top. All three lists are edited together. */ .reveal-scroll .reveal .slides .scroll-page-content > section.cover, .reveal-scroll .reveal .slides .scroll-page-content > section.section, .reveal-scroll .reveal .slides .scroll-page-content > section.statement, .reveal-scroll .reveal .slides .scroll-page-content > section.closing, .reveal-scroll .reveal .slides .scroll-page-content > section.metric, .reveal-scroll .reveal .slides .scroll-page-content > section.center, .reveal-scroll .reveal .slides .scroll-page-content > section.balance { display: flex !important; flex-direction: column; justify-content: center; } /* Headings — GOTHIC display, heavy weight, tight optical letterfit */ .reveal h1, .reveal h2, .reveal h3 { font-family: var(--font-display); color: var(--ink-bold); line-height: var(--lh-tight); letter-spacing: var(--track-tight); font-weight: 800; margin: 0 0 0.5em; text-transform: none; text-wrap: balance; } /* h4 stays in the serif register — it reads as an emphatic lead, not a label */ .reveal h4 { font-family: var(--font-serif); font-weight: 600; font-style: normal; color: var(--ink); line-height: var(--lh-snug); letter-spacing: 0; margin: 0 0 0.5em; text-wrap: balance; } .reveal h1 { font-size: var(--fs-h1); font-weight: 800; letter-spacing: -0.028em; } .reveal h2 { font-size: var(--fs-h2); font-weight: 800; } .reveal h3 { font-size: var(--fs-h3); font-weight: 700; letter-spacing: -0.014em; } .reveal h4 { font-size: var(--fs-lead); } /* The standard slide title — gothic, with the signature heavy green marker above it. (.no-rule kept as a harmless no-op so existing decks stay valid.) */ .reveal .slide-title { position: relative; margin-bottom: var(--space-lg); max-width: 22ch; padding-top: calc(var(--bar) + var(--space-md)); } .reveal .slide-title::before { content: ""; position: absolute; top: 0; left: 0; width: 2.6rem; height: var(--bar); border-radius: 1px; background: var(--green); } .reveal .slide-title.no-rule::before { display: none; } .reveal .slide-title.no-rule { padding-top: 0; } /* Prose — the serif counter-voice */ .reveal p { margin: 0 0 var(--r-block-margin); max-width: 62ch; text-wrap: pretty; } .reveal .lead { font-family: var(--font-serif); font-size: var(--fs-lead); line-height: 1.26; color: var(--ink); max-width: 30ch; font-weight: 500; letter-spacing: -0.002em; } .reveal .muted { color: var(--ink-soft); } .reveal small, .reveal .small { font-size: var(--fs-small); } .reveal strong, .reveal b { font-weight: 700; color: var(--ink-bold); } /* On the dark layouts the default ink is illegible — lift it to paper. */ .reveal section.section strong, .reveal section.section b, .reveal section.closing strong, .reveal section.closing b, .reveal section.media strong, .reveal section.media b { color: var(--paper); } .reveal em, .reveal i { font-style: italic; } /* Inline highlight — a solid gold underline-block behind a phrase */ .reveal mark, .reveal .hl { background: linear-gradient(transparent 58%, color-mix(in oklch, var(--gold), white 18%) 58%); /* A highlighter stroke overshoots the word it marks; at 0.06em (1.4px) the wash stopped flush against the first and last glyph and read as a printing error. The negative margin gives the ink its overhang without moving the surrounding text. */ color: inherit; padding: 0 0.2em; margin: 0 -0.14em; border-radius: 0; } .reveal .accent { color: var(--green-deep); } .reveal .accent-navy { color: var(--navy); } /* Links — green is functional wayfinding, bolder underline */ .reveal a { color: var(--green-deep); text-decoration: none; text-underline-offset: 0.16em; text-decoration-color: color-mix(in oklch, var(--green), transparent 50%); text-decoration-thickness: 1.5px; transition: color var(--dur) var(--ease), text-decoration-color var(--dur) var(--ease); } /* A link on a slide carries its rule at rest, not only on hover: green against the ink around it measures 1.4–2.9:1, under the 3:1 at which colour alone may mark a link (WCAG 1.4.1), so the rule is what tells it from the words beside it — in a paragraph, a list or a callout alike. The links that stand alone as components keep their own treatment: contact rows, the frame bar's open link and the offline fallback's. */ .reveal .slides a:not(.site-frame-open, .liveshot-open, .contact a, .frame-fallback a) { text-decoration-line: underline; } .reveal a:hover { color: var(--green); text-decoration-line: underline; text-decoration-color: var(--green); } .reveal a:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; border-radius: var(--radius-sm); } /* Lists — hanging green tick markers (the colour earns a small structural role) */ .reveal ul, .reveal ol { display: block; margin: 0 0 var(--r-block-margin); padding: 0; max-width: 56ch; } .reveal ul { list-style: none; } .reveal ul > li { position: relative; padding-left: 1.5em; margin: 0 0 0.5em; } .reveal ul > li::before { content: ""; position: absolute; left: 0; top: 0.58em; width: 0.7em; height: 3px; border-radius: 1px; background: var(--green); } /* Use the native `list-item` counter so the HTML start="" attribute is honoured. */ .reveal ol { list-style: none; } .reveal ol > li { position: relative; padding-left: 2.1em; margin: 0 0 0.5em; } .reveal ol > li::before { content: counter(list-item, decimal-leading-zero); position: absolute; left: 0; top: 0.30em; font-family: var(--font-label); font-size: 0.6em; font-weight: 800; color: var(--green-deep); letter-spacing: 0.02em; font-variant-numeric: tabular-nums; } .reveal li::marker { content: none; } .reveal li ul, .reveal li ol { margin: 0.45em 0 0; } .reveal li ul > li::before { background: var(--ink-faint); } /* nested: neutral tick */ /* dark fields: ticks lift to the field's spark — gold on the deep pole, Paper on the bright fields, where no gold clears 3:1 */ .reveal section.section ul > li::before, .reveal section.closing ul > li::before { background: var(--ond-mark); } /* Blockquote / pull quote — the serif's italic is the whole effect */ .reveal blockquote { border: none; box-shadow: none; background: none; font-family: var(--font-serif); font-style: italic; font-size: var(--fs-lead); line-height: 1.28; color: var(--ink); margin: var(--space-md) 0; padding: 0; max-width: 32ch; font-weight: 500; } .reveal blockquote p { max-width: none; margin: 0; } /* Tables — heavy gothic small-caps headers, ruled, neutral zebra */ .reveal table { margin: var(--space-md) 0; border-collapse: collapse; font-size: var(--fs-small); width: 100%; } /* The horizontal padding is measured against the TABLE's font-size, not each cell's. `0.9em` resolved against the header's own 0.82em size and the body cell's full size — 12.4px against 17.0px — so a right-aligned header sat 4.6px inboard of the numerals it labelled. Vertical padding stays in em, where a smaller header genuinely wants a shorter row. */ .reveal table th, .reveal table td { text-align: left; padding: 0.55em calc(0.9 * var(--fs-small)); border: none; border-bottom: var(--hair); } .reveal table th { font-family: var(--font-label); font-weight: 700; color: var(--ink-bold); text-transform: uppercase; letter-spacing: var(--track-hall); font-size: 0.82em; border-bottom: 3px solid var(--rule); } .reveal table tbody tr:nth-child(even) { background: color-mix(in oklch, var(--sunken), transparent 30%); } .reveal table td.num, .reveal table th.num { text-align: right; font-variant-numeric: tabular-nums; } /* Images / figures — a defined plate border, sharp */ .reveal img { max-width: 100%; height: auto; display: block; } .reveal figure { margin: 0; } .reveal figure img { border-radius: var(--radius-sm); border: 1px solid var(--line-strong); } .reveal figcaption { font-size: var(--fs-caption); color: var(--ink-soft); margin-top: var(--space-sm); line-height: 1.4; max-width: 52ch; font-style: italic; font-family: var(--font-serif); } /* ---------------------------------------------------------------------------- 4. EDITORIAL COMPONENTS ---------------------------------------------------------------------------- */ /* Overline / eyebrow — a heavy gothic small-caps label, green by spirit. Neutral by default; warm/navy/green variants tint when categorising. */ .reveal .kicker { display: inline-flex; align-items: center; gap: 0.55em; font-family: var(--font-label); font-weight: 700; font-size: var(--fs-kicker); text-transform: uppercase; letter-spacing: var(--track-hall); color: var(--ink-soft); /* The label grew for the hall, so its trailing space shrinks to match: a bigger cap needs proportionally less air beneath it, and the tighter gap binds the kicker to the title it introduces. Net height is unchanged, which keeps dense archived slides on the right side of the fit threshold. */ margin: 0 0 var(--space-xs); } .reveal .kicker::before { content: ""; width: 1.4em; height: 3px; border-radius: 1px; background: var(--green); flex: none; } .reveal .kicker.warm { color: var(--brown-deep); } .reveal .kicker.warm::before { background: var(--brown); } .reveal .kicker.navy { color: var(--navy); } .reveal .kicker.navy::before { background: var(--navy); } .reveal .kicker.green { color: var(--green-deep); } /* Editorial note — a ruled aside marked by a heavy top rule + gothic label. */ .reveal .callout { border-top: 3px solid var(--rule); padding: var(--space-sm) 0 0; margin: var(--space-lg) 0 var(--space-md); font-size: var(--fs-small); max-width: 60ch; } .reveal .callout .callout-label { display: block; font-family: var(--font-label); font-weight: 700; font-size: var(--fs-label); text-transform: uppercase; letter-spacing: var(--track-hall); color: var(--ink-bold); margin-bottom: 0.3em; } /* A note closing a ledger takes the ledger's column, not its own prose measure. Both are opened by the same 3px rule, and at 60ch the note's rule stopped 32px past the column above it — two identical devices that almost line up read as a mistake, where a clear difference would have read as a decision. */ .reveal .stat-grid + .callout { max-width: calc((100% - var(--space-3xl)) / 2); } .reveal .callout.warm { border-top-color: var(--brown); } .reveal .callout.warm .callout-label { color: var(--brown-deep); } .reveal .callout.navy { border-top-color: var(--navy); } .reveal .callout.navy .callout-label { color: var(--navy); } .reveal .callout.green { border-top-color: var(--green); } .reveal .callout.green .callout-label { color: var(--green-deep); } /* Stat — an editorial figure. Big gothic numeral, gothic label. */ .reveal .stat { display: flex; flex-direction: column; gap: 0.05em; } .reveal .stat .stat-num { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-h2); line-height: 0.92; color: var(--ink-bold); letter-spacing: var(--track-tight); font-variant-numeric: lining-nums tabular-nums; } .reveal .stat .stat-label { font-family: var(--font-label); font-size: var(--fs-label); color: var(--ink-soft); text-transform: uppercase; letter-spacing: var(--track-hall); font-weight: 600; max-width: 22ch; } /* Figure ledger — "in figures" as a ruled index: label left, figure right, a heavy top rule, hairlines between. Reads like a colophon, not a card grid. */ .reveal .stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--space-3xl); margin-top: var(--space-md); } .reveal .stat-grid .stat { flex-direction: row; align-items: baseline; justify-content: space-between; gap: var(--space-md); padding: 0.55em 0; border-bottom: var(--hair); } .reveal .stat-grid .stat:nth-child(1), .reveal .stat-grid .stat:nth-child(2) { border-top: 3px solid var(--rule); } .reveal .stat-grid .stat .stat-num { font-size: var(--fs-h3); order: 2; color: var(--green-deep); } .reveal .stat-grid .stat .stat-label { order: 1; max-width: none; align-self: center; } /* Citation line / source */ .reveal .cite, .reveal .source { font-size: var(--fs-caption); color: var(--ink-faint); font-style: italic; font-family: var(--font-serif); } /* Chips / tags — heavy gothic small-caps, solid hairline outline */ .reveal .chip { display: inline-block; font-family: var(--font-label); font-size: var(--fs-label); font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; padding: 0.22em 0.6em; border-radius: var(--radius-sm); background: transparent; border: 1.5px solid var(--line-strong); color: var(--ink-soft); } .reveal .chip.green { border-color: var(--green); color: var(--green-deep); } .reveal .chip.solid { background: var(--green); border-color: var(--green); color: var(--paper); } /* Query-mix list — plain-language asks, divided by hairlines (no per-item card). */ .reveal .query-list { list-style: none; margin: 0 0 var(--space-md); padding: 0; max-width: none; display: grid; gap: 0; } .reveal .query-list > li { margin: 0; padding: var(--space-sm) 0; display: grid; gap: 0.28em; border-bottom: var(--hair); } .reveal .query-list > li:first-child { border-top: 3px solid var(--rule); } .reveal .query-list > li::before { content: none; } .reveal .query-list .q { font-family: var(--font-serif); font-style: italic; font-weight: 500; font-size: var(--fs-body); line-height: 1.24; color: var(--ink); } .reveal .query-list .q strong { font-style: normal; font-weight: 700; } .reveal .query-list .q-map { display: flex; align-items: center; gap: 0.6em; font-family: var(--font-label); font-size: var(--fs-label); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600; color: var(--ink-faint); } /* Two-depth chooser — Brief / Extended as a pair of hairline-bordered blocks. The active path is marked by a heavy green left bar + green label. */ .reveal .depths { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-md); max-width: none; margin: 0 0 var(--space-md); } .reveal .depth { display: flex; flex-direction: column; gap: 0.1em; padding: var(--space-sm) var(--space-md); background: transparent; border: 1px solid var(--line); border-left: var(--bar) solid var(--line-strong); border-radius: 0 var(--radius-md) var(--radius-md) 0; } .reveal .depth.is-full { border-color: var(--line); border-left-color: var(--green); background: var(--green-wash); } .reveal .depth .depth-name { font-family: var(--font-display); font-weight: 700; font-size: var(--fs-small); color: var(--ink-bold); } .reveal .depth.is-full .depth-name { color: var(--green-deep); } .reveal .depth .depth-tag { font-family: var(--font-label); font-size: var(--fs-label); font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-faint); margin-left: 0.5em; vertical-align: 0.08em; } .reveal .depth .depth-desc { font-size: var(--fs-caption); color: var(--ink-soft); font-family: var(--font-serif); } /* Numbered index — a bold contents/agenda grid (new layout helper). Big gothic folio, serif label, heavy rule between rows. */ .reveal .index-list { list-style: none; margin: var(--space-md) 0 0; padding: 0; max-width: none; display: grid; gap: 0; counter-reset: idx; } .reveal .index-list > li { counter-increment: idx; margin: 0; padding: var(--space-md) 0 var(--space-md); padding-left: 0; /* override the generic .reveal ol > li padding */ display: grid; grid-template-columns: 3.4rem 1fr; align-items: baseline; gap: 0.2em var(--space-lg); border-top: 1px solid var(--line); } .reveal .index-list > li:first-child { border-top: 3px solid var(--rule); } .reveal .index-list > li:last-child { border-bottom: 3px solid var(--rule); } .reveal .index-list > li::before { content: counter(idx, decimal-leading-zero); position: static; /* stay an in-flow grid item (the generic ol rule absolutes it) */ grid-column: 1; grid-row: 1; top: auto; left: auto; font-family: var(--font-display); font-weight: 800; font-size: var(--fs-lead); color: var(--green-deep); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; line-height: 1; } .reveal .index-list .idx-title { grid-column: 2; grid-row: 1; font-family: var(--font-display); font-weight: 700; font-size: var(--fs-h3); color: var(--ink-bold); letter-spacing: -0.012em; line-height: 1.04; } .reveal .index-list .idx-desc { grid-column: 2; grid-row: 2; font-family: var(--font-serif); font-size: var(--fs-small); color: var(--ink-soft); margin-top: 0.2em; } /* A heavy full-width rule / divider */ .reveal hr, .reveal .rule { border: none; height: 3px; background: var(--rule); margin: var(--space-lg) 0; } /* ---------------------------------------------------------------------------- 5. LAYOUT CLASSES ---------------------------------------------------------------------------- */ /* Vertically centre content on hero-style slides. This has to win at `.present` specificity. Reveal hard-sets `display: block` on the active slide, which drops the flex context and makes a bare `justify-content` here inert — the layout looked centred in the stylesheet and top-aligned on the screen, and every author who copied a hero recipe inherited the discrepancy. `.balance` opts any *other* sparse slide into the same treatment; on a hero it is now redundant rather than load-bearing. */ .reveal .slides > section.cover.present, .reveal .slides > section.section.present, .reveal .slides > section.statement.present, .reveal .slides > section.closing.present, .reveal .slides > section.metric.present, .reveal .slides > section.center.present, .reveal .slides > section.balance.present { display: flex !important; flex-direction: column; justify-content: center; } .reveal section.balance .cols { flex: 0 0 auto; } /* --- COVER --- a broadsheet masthead: heavy rule, gothic event line, giant gothic title, serif italic subtitle ---------------------------------------- */ .reveal section.cover { padding-right: 6rem; } .reveal section.cover .eyebrow { font-family: var(--font-label); font-weight: 700; text-transform: uppercase; letter-spacing: var(--track-hall); font-size: var(--fs-kicker); color: var(--ink-bold); max-width: 60ch; margin-bottom: var(--space-md); padding-bottom: var(--space-md); border-bottom: 3px solid var(--rule); } .reveal section.cover h1 { font-family: var(--font-display); font-size: var(--fs-h1); font-weight: 800; letter-spacing: -0.03em; color: var(--ink-bold); max-width: 17ch; margin-bottom: var(--space-md); line-height: 0.96; } .reveal section.cover .subtitle { font-family: var(--font-serif); font-style: italic; font-weight: 500; font-size: var(--fs-lead); color: var(--ink-soft); max-width: 40ch; margin-bottom: var(--space-xl); } .reveal .byline { display: flex; flex-direction: column; gap: 0.12em; } .reveal .byline .name { font-family: var(--font-label); font-weight: 700; font-size: var(--fs-small); color: var(--ink-bold); letter-spacing: 0.01em; } .reveal .byline .affil { font-size: var(--fs-caption); color: var(--ink-soft); font-style: italic; font-family: var(--font-serif); max-width: 46ch; } /* Job-title line — sits between the gothic name and the italic-serif affiliation, in the quiet news-gothic "label" voice. */ .reveal .byline .role { font-family: var(--font-label); font-weight: 600; font-size: var(--fs-caption); letter-spacing: 0.02em; color: var(--ink-soft); } .reveal section.closing .byline .role { color: color-mix(in oklch, var(--paper), transparent 25%); } /* Co-presenter byline (.byline.authors): stack each author as a tight name+role+affil block, with a larger gap between authors. */ .reveal .byline.authors { gap: var(--space-sm); } .reveal .byline.authors .author { display: flex; flex-direction: column; gap: 0.12em; } /* Per-author institution logo inside a stacked byline. Monochrome marks flip to white on the dark closing; add .keep-color for full-colour box logos. */ .reveal .byline.authors .author-logo { height: 1.5rem; width: auto; align-self: flex-start; margin-top: 0.45rem; opacity: 0.95; } .reveal section.closing .byline.authors .author-logo:not(.keep-color) { filter: brightness(0) invert(1); opacity: 0.85; } /* Contact row (built by deck.js from DECK_CONFIG.links) */ .reveal .contact { display: flex; flex-wrap: wrap; gap: var(--space-sm) var(--space-lg); margin-top: var(--space-lg); } .reveal .contact a { display: inline-flex; align-items: center; gap: 0.5em; font-family: var(--font-label); font-size: var(--fs-caption); color: var(--ink-soft); font-weight: 500; text-decoration: none; } .reveal .contact a:hover { color: var(--green-deep); } .reveal .contact a svg, .reveal .contact a img { width: 1.1em; height: 1.1em; flex: none; } .reveal .contact a .ico { color: var(--ink-faint); display: inline-flex; } .reveal .contact a:hover .ico { color: var(--green-deep); } /* --- Institution wordmark on hero slides (cover + closing) ------------------ */ .reveal section.cover .org-logo, .reveal section.closing .org-logo { display: block; align-self: flex-start; height: 1.7rem; width: auto; margin-top: var(--space-lg); opacity: 0.9; } .reveal section.closing .org-logo { filter: brightness(0) invert(1); opacity: 0.85; } /* --- SECTION DIVIDER --- a full-bleed saturated GREEN field, gold folio, giant gothic title. Navy variant for the structural deep. ------------------ */ .reveal section.section { --r-main-color: var(--paper); color: var(--paper); background: var(--green-field); } /* The thick plate rule across the top of the field. It takes the field's own spark: gold-bright on the navy deep (7.13:1), Paper on the green and brown fields, where gold measures 2.17:1 and is mud on a projector. */ .reveal section.section::before { content: ""; position: absolute; left: var(--slide-pad-x); right: var(--slide-pad-x); top: var(--slide-pad-y); height: var(--bar); background: var(--ond-mark); } .reveal section.section .sec-no { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-mega); letter-spacing: -0.03em; line-height: 0.82; color: color-mix(in oklch, var(--paper), transparent 76%); margin-bottom: var(--space-sm); font-variant-numeric: tabular-nums; } /* The oversized folio is the only place a divider states its own number, and as a 24% ghost it measured 1.52:1 — gone by about row five. Outlined, it carries the full Paper contrast of the field while keeping almost none of its visual mass, so it still sits under the title. The filled ghost above is the fallback where text-stroke is unsupported. */ @supports (-webkit-text-stroke: 3px currentColor) { .reveal section.section .sec-no { color: transparent; -webkit-text-stroke: 3px var(--paper); } } .reveal section.section h2 { color: var(--paper); font-family: var(--font-display); font-size: var(--fs-section); font-weight: 800; letter-spacing: -0.03em; max-width: 16ch; margin: 0; line-height: 0.94; } .reveal section.section .sec-sub { color: var(--ond-quiet); font-family: var(--font-serif); font-style: italic; font-size: var(--fs-lead); margin-top: var(--space-lg); max-width: 38ch; font-weight: 500; } /* Navy variant of the divider (the structural deep). `.green` kept as an alias so existing `.section.green` decks render as the lead green field. The plate rule needs no override here: the deep register already hands it gold. */ .reveal section.section.navy { background: var(--navy-field); } .reveal section.section.green { background: var(--green-field); } /* --- STATEMENT (big claim / quote) ----------------------------------------- */ /* Default: a GOTHIC claim — heavy, huge, tight. One green accent phrase. */ .reveal section.statement .statement-body { font-family: var(--font-display); font-weight: 800; font-style: normal; font-size: var(--fs-display); line-height: 0.98; letter-spacing: -0.03em; color: var(--ink-bold); max-width: 18ch; text-wrap: balance; } .reveal section.statement .statement-body .accent { color: var(--green-deep); } .reveal section.statement .attribution { margin-top: var(--space-xl); font-family: var(--font-label); font-style: normal; font-size: var(--fs-small); color: var(--ink-soft); font-weight: 600; } /* Quote variant: the human voice returns to the serif italic, with a gold mark. */ .reveal section.statement.quote .statement-body { font-family: var(--font-serif); font-style: italic; font-weight: 500; font-size: var(--fs-display); line-height: 1.08; letter-spacing: -0.01em; color: var(--ink); max-width: 22ch; } .reveal section.statement.quote .statement-body::before { content: "\201C"; font-family: var(--font-serif); font-size: 1.1em; color: var(--gold); line-height: 0; margin-right: 0.06em; vertical-align: -0.18em; } /* --- BIG METRIC --- an oversized single figure (new layout helper) ---------- */ .reveal section.metric .metric-num { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-mega); line-height: 0.86; letter-spacing: -0.035em; color: var(--green-deep); font-variant-numeric: lining-nums tabular-nums; margin: 0; } .reveal section.metric .metric-label { font-family: var(--font-serif); font-style: italic; font-size: var(--fs-lead); color: var(--ink-soft); max-width: 32ch; margin-top: var(--space-md); } /* --- COLUMNS / COMPARISON --------------------------------------------------- */ .reveal .cols { display: grid; grid-template-columns: repeat(var(--cols, 2), 1fr); gap: var(--space-3xl); align-items: start; flex: 1; min-height: 0; } .reveal .cols.ratio-3-2 { grid-template-columns: 3fr 2fr; } .reveal .cols.ratio-2-3 { grid-template-columns: 2fr 3fr; } .reveal .cols.ratio-1-2 { grid-template-columns: 1fr 2fr; } .reveal .col { min-width: 0; } .reveal .col > h3 { margin-bottom: var(--space-sm); } .reveal .col-head { font-family: var(--font-label); font-weight: 700; font-size: var(--fs-kicker); text-transform: uppercase; letter-spacing: var(--track-hall); color: var(--ink-bold); padding-bottom: var(--space-xs); margin-bottom: var(--space-md); border-bottom: 3px solid var(--rule); } .reveal .col-head.alt { color: var(--brown-deep); border-bottom-color: var(--brown); } /* Panel — a ruled sidebar for grouping (a heavy top rule, not a card) */ .reveal .panel { background: transparent; border: none; border-top: var(--bar) solid var(--rule); border-radius: 0; padding: var(--space-md) 0 0; } .reveal .panel.sunken { background: var(--sunken); border-top: none; border-radius: var(--radius-md); padding: var(--space-md) var(--space-lg); } .reveal .panel.green { border-top-color: var(--green); } /* --- MEDIA / FULL-BLEED IMAGE ---------------------------------------------- */ /* The media field is the one dark ground the theme does not own: the image underneath it can be anything. It only earns the deep register if the scrim is a guarantee rather than a gesture, so it is built from two crossed gradients — one up from the caption edge, one in from the reading edge — and the darkest stop is held above 0.85. Against a worst-case white frame that keeps the composited ground under 0.06 luminance, which is deeper than the navy field, so Paper and gold both hold their measured contrast. */ .reveal section.media { padding: 0; } .reveal section.media .media-fill { position: absolute; inset: 0; overflow: hidden; } .reveal section.media .media-fill img { width: 100%; height: 100%; object-fit: cover; border-radius: 0; border: none; } .reveal section.media .media-scrim { position: absolute; inset: 0; background: linear-gradient(0deg, oklch(0.14 0.02 258 / 0.94) 0%, oklch(0.14 0.02 258 / 0.86) 30%, oklch(0.14 0.02 258 / 0.52) 54%, oklch(0.14 0.02 258 / 0.12) 74%, transparent 86%), linear-gradient(90deg, oklch(0.14 0.02 258 / 0.84) 0%, oklch(0.14 0.02 258 / 0.48) 44%, transparent 76%); } /* The footer strip is chrome the author never sees when choosing the image, so the theme protects it itself — present whether or not a .media-scrim was authored. */ .reveal section.media::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; z-index: 1; height: calc(var(--footer-h) + var(--space-md)); pointer-events: none; background: linear-gradient(0deg, oklch(0.14 0.02 258 / 0.94) 0%, oklch(0.14 0.02 258 / 0.86) 62%, transparent 100%); } .reveal section.media .media-caption { position: absolute; left: var(--slide-pad-x); right: var(--slide-pad-x); z-index: 2; bottom: calc(var(--footer-h) + var(--space-lg)); color: var(--paper); max-width: 34ch; } .reveal section.media .media-caption h2 { color: var(--paper); } .reveal section.media .media-caption .kicker { color: var(--ond-mark); } .reveal section.media .media-caption .kicker::before { background: var(--ond-mark); } /* Image + caption side layout */ .reveal .figrow { display: grid; grid-template-columns: 1.1fr 1fr; gap: var(--space-3xl); align-items: center; flex: 1; min-height: 0; } .reveal .figrow.flip { grid-template-columns: 1fr 1.1fr; } /* Documentary figures show whole by default; .crop bleeds true photos. */ .reveal .figrow .fig-frame img { width: 100%; max-height: var(--block-max-h); object-fit: contain; border: 1px solid var(--line-strong); } .reveal .figrow.crop .fig-frame img { object-fit: cover; } /* --- CLOSING / CONTACT --- a deep navy field, gold spark ---------------------- */ .reveal section.closing { --r-main-color: var(--paper); color: var(--paper); background: var(--navy-field); } .reveal section.closing::before { content: ""; position: absolute; left: var(--slide-pad-x); right: var(--slide-pad-x); top: var(--slide-pad-y); height: var(--bar); background: var(--ond-mark); } /* The colophon takes its own measures. A 16ch title and a 30ch lead were drawn for slides that carry a figure beside the type; the closing carries nothing but type, so those measures stacked a narrow column down the left third and left the field empty — which is why three decks waived the fit check with identical boilerplate rather than fixing the composition. Wider measures let the sign-off span the page it actually has. */ .reveal section.closing h2 { color: var(--paper); font-family: var(--font-display); font-size: var(--fs-h1); max-width: 22ch; letter-spacing: -0.028em; line-height: 0.96; } .reveal section.closing .lead { color: var(--ond-quiet); max-width: 46ch; } /* Contributors read across on a colophon, the way a masthead lists them — stacked is right under a cover title, where the byline is a caption, but here it is the credit line itself. */ .reveal section.closing .byline.authors { flex-direction: row; flex-wrap: wrap; align-items: stretch; gap: var(--space-xl); } .reveal section.closing .byline.authors .author { flex: 0 1 auto; max-width: 34ch; } /* Contributors carry different numbers of lines (a role, a second affiliation), so left alone their institution marks land at different heights. Pushing the mark down to the block's foot sits them all on one line, which is the only reason a row reads as a colophon rather than as two stacked cards. */ .reveal section.closing .byline.authors .author-logo { margin-top: auto; padding-top: var(--space-sm); } .reveal section.closing .contact a { color: var(--ond-quiet); } .reveal section.closing .contact a:hover { color: var(--ond-text); } .reveal section.closing .contact a .ico { color: var(--ond-mark); } .reveal section.closing .kicker { color: var(--ond-mark); } .reveal section.closing .kicker::before { background: var(--ond-mark); } .reveal section.closing p a { color: var(--ond-mark); text-decoration-color: color-mix(in oklch, var(--ond-mark), transparent 50%); } .reveal section.closing p a:hover { color: var(--ond-text); text-decoration-color: var(--ond-text); } .reveal section.closing .byline .name { color: var(--ond-text); } .reveal section.closing .byline .affil { color: var(--ond-quiet); } /* ---------------------------------------------------------------------------- 6. BRAND CHROME (injected by deck.js) A running head at the top, the imprint + nav at the foot. Typographic, heavy. ---------------------------------------------------------------------------- */ /* Running head — current section, gothic small-caps, with a heavy rule beneath. */ .deck-runhead { position: absolute; left: var(--slide-pad-x); right: var(--slide-pad-x); top: 1.3rem; height: 1.3rem; z-index: 30; display: flex; align-items: center; justify-content: space-between; gap: 1rem; pointer-events: none; font-family: var(--font-label); font-size: var(--fs-footer); font-weight: 700; text-transform: uppercase; letter-spacing: 0.22em; color: var(--ink-faint); } .deck-runhead::after { content: ""; position: absolute; left: 0; right: 0; top: 1.3rem; height: 2px; background: var(--rule); } .deck-runhead .rh-sec { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 70ch; color: var(--ink-bold); } .deck-runhead[hidden] { display: none; } /* Persistent footer (imprint + navigation) */ .deck-footer { position: absolute; left: 0; right: 0; bottom: 0; height: var(--footer-h); z-index: 30; display: flex; align-items: center; justify-content: space-between; padding: 0 var(--slide-pad-x); pointer-events: none; font-family: var(--font-label); color: var(--ink-soft); font-size: var(--fs-footer); } .deck-footer::before { content: ""; position: absolute; left: var(--slide-pad-x); right: var(--slide-pad-x); top: 0; height: 2px; background: var(--rule); } .deck-footer .foot-left { display: flex; align-items: center; gap: 0.7rem; min-width: 0; } .deck-footer .foot-logo { pointer-events: auto; } .deck-footer .foot-logo:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 4px; } .deck-footer .foot-left img { height: 1.15rem; width: auto; opacity: 0.9; } .deck-footer .foot-title { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.92em; color: var(--ink-faint); font-weight: 600; } .deck-footer .foot-title b { color: var(--ink-bold); font-weight: 700; } /* Footer chrome inverts on dark fields, through the on-dark register rather than one blanket mix of Paper: the old fixed values were tuned on navy, where they read 6.2:1, and were never re-checked against the much lighter green field, where the same three tiers measured 3.0, 2.7 and 4.2:1. On a bright field the tiers collapse onto Paper and the hierarchy is carried by weight (600 against 700) instead. */ .deck-footer.on-dark { color: var(--ond-chrome); } .deck-footer.on-dark::before { background: var(--ond-rule); } .deck-footer.on-dark .foot-title { color: var(--ond-chrome); } .deck-footer.on-dark .foot-title b { color: var(--ond-text); } .deck-footer.on-dark .foot-left img { filter: brightness(0) invert(1); opacity: 0.85; } /* Navigation cluster (right side of footer) */ .deck-nav { display: flex; align-items: center; gap: 0.4rem; pointer-events: auto; flex: none; } .deck-nav .counter { font-family: var(--font-label); font-size: var(--fs-footer); letter-spacing: 0.12em; color: inherit; margin-right: 0.4rem; font-variant-numeric: tabular-nums; font-weight: 600; } .deck-nav .counter .cur { color: var(--green-deep); font-weight: 800; } .deck-footer.on-dark .deck-nav .counter .cur { color: var(--ond-mark); } .deck-btn { -webkit-appearance: none; appearance: none; cursor: pointer; display: inline-grid; place-items: center; width: 2rem; height: 2rem; border-radius: var(--radius-sm); border: 1.5px solid var(--line-strong); background: transparent; color: var(--ink-soft); padding: 0; /* Never `all`: it sweeps in outline-color and outline-offset too, so the focus ring faded up over 220ms instead of appearing when focus lands. */ transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease), transform var(--dur) var(--ease); } .deck-btn svg { width: 1.05rem; height: 1.05rem; } .deck-btn:hover { color: var(--paper); border-color: var(--green); background: var(--green); transform: translateY(-1px); } .deck-btn:active { transform: translateY(0); } .deck-btn:disabled { opacity: 0.3; cursor: default; transform: none; background: transparent; border-color: var(--line); color: var(--ink-faint); } /* The ring follows the field: green measured 1.28:1 on the green divider, so on any dark field --focus-ring is Paper. */ .deck-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; } /* justify-content is for the compact footers, which hide the label and square the button off: without it the lone icon kept the pill's flex-start and sat 1px from the left border with 16px of empty box to its right. */ .deck-btn.toc-btn { display: inline-flex; align-items: center; justify-content: center; width: auto; padding: 0 0.85rem; gap: 0.5rem; font-family: var(--font-label); font-weight: 700; font-size: var(--fs-footer); letter-spacing: 0.1em; text-transform: uppercase; } .deck-btn.toc-btn svg { width: 0.95rem; height: 0.95rem; flex: none; } .deck-footer.on-dark .deck-btn { background: color-mix(in oklch, var(--paper), transparent 90%); border-color: var(--ond-rule); color: var(--ond-text); } .deck-footer.on-dark .deck-btn:hover { background: var(--ond-mark); color: var(--ond-mark-ink); border-color: var(--ond-mark); } /* Copy-link confirmation: the link icon turns to a tick for a moment. Colour and glyph carry it — no movement — so it reads the same under reduced motion, and it takes the mark of whichever field the footer sits on. */ .deck-btn.copy-link.is-copied { color: var(--green-deep); border-color: var(--green); } .deck-footer.on-dark .deck-btn.copy-link.is-copied { color: var(--ond-mark); border-color: var(--ond-mark); } /* Progress bar (reveal built-in, a confident 3px green rule). It sits on the viewport, not the slide, so on a green divider a green bar was drawing itself at 1.28:1 against its own background — the one status signal the room has, invisible on exactly the slides that end a section. On dark it takes the field's spark, and the unfilled track lifts off 1.23:1. */ .reveal .progress { height: 3px; color: var(--green); background: color-mix(in oklch, var(--ink), transparent 78%); } /* Reveal fills the bar with an inline scaleX(), never width: transitioning width replaced reveal's own transform transition with one that never fired, so the bar jumped from slide to slide instead of drawing across. */ .reveal .progress span { background: var(--green); transition: transform 320ms var(--ease); } .reveal-viewport.deck-dark .reveal .progress { background: color-mix(in oklch, var(--paper), transparent 76%); } .reveal-viewport.deck-dark .reveal .progress span { background: var(--ond-mark); } /* Hide reveal's default controls + slide number (we provide our own) */ .reveal .controls { display: none; } .reveal .slide-number { display: none; } /* ---------------------------------------------------------------------------- 7. TABLE OF CONTENTS OVERLAY ---------------------------------------------------------------------------- */ .toc-overlay { position: fixed; inset: 0; z-index: 60; display: grid; background: color-mix(in oklch, oklch(0.16 0.012 258), transparent 12%); opacity: 0; visibility: hidden; transition: opacity 320ms var(--ease), visibility 320ms; } .toc-overlay.open { opacity: 1; visibility: visible; } .toc-panel { /* box-sizing is load-bearing here. The panel sits outside .slides, so it never inherited the border-box the slide canvas sets, and `width: min(92vw, 60rem)` was sizing the CONTENT box — the 2×2rem padding was then added on top. On a 390px phone that laid out a 423px panel: the overlay ran past the screen and the close button sat 14px beyond its right edge, unreachable. */ box-sizing: border-box; position: relative; max-width: 60rem; width: min(92vw, 60rem); margin: auto; max-height: 90vh; overflow: auto; padding: clamp(1.6rem, 3vw, 2.6rem) clamp(2rem, 4vw, 3.4rem); background: var(--paper); color: var(--ink); border-radius: var(--radius-md); box-shadow: var(--shadow-pop); transform: translateY(14px) scale(0.99); opacity: 0; transition: transform 380ms var(--ease), opacity 380ms var(--ease); } .toc-overlay.open .toc-panel { transform: none; opacity: 1; } /* The close button floats over the panel's top-right corner, so the head keeps out from under it — the box overlapped at every viewport, and only the word "Contents" being short kept the collision off the screen. The rule itself still spans the full width: padding insets the text, not the border. */ .toc-head { margin-bottom: clamp(1.1rem, 3vw, 1.8rem); border-bottom: 3px solid var(--rule); padding-bottom: 0.9rem; padding-right: calc(2.4rem + var(--space-sm)); } .toc-head .toc-eyebrow { font-family: var(--font-label); font-weight: 700; text-transform: uppercase; letter-spacing: var(--track-label); font-size: 0.8rem; color: var(--green-deep); } .toc-head .toc-title { font-family: var(--font-display); font-size: clamp(1.8rem, 4vw, 2.7rem); font-weight: 800; color: var(--ink-bold); margin: 0.2rem 0 0; letter-spacing: -0.02em; } /* The overlay is mounted inside .reveal, so the deck's body-prose list styling reaches it: a 56ch measure, a 1.5em indent, and the green tick every slide bullet carries. All three applied here, which is why the rows sat indented from the head's rule on the left, stopped well short of it on the right, and each carried a stray tick aligned for 1.52rem serif rather than for the row. The contents list is furniture, not prose — it opts out of all of it. The .reveal prefix is load-bearing: `.reveal ul` outranks a bare `.toc-list`, so the reset that lived here before never actually applied. */ .reveal .toc-list { list-style: none; margin: 0; padding: 0; max-width: none; } .reveal .toc-list > li { margin: 0; padding-left: 0; position: static; } .reveal .toc-list > li::before { content: none; } .toc-item { display: flex; align-items: baseline; gap: 0.9rem; width: 100%; text-align: left; background: none; border: none; border-bottom: 1px solid var(--line); cursor: pointer; padding: 0.6rem 0.4rem; color: var(--ink-soft); font-family: var(--font-display); font-size: clamp(1.1rem, 2.1vw, 1.5rem); font-weight: 600; letter-spacing: -0.01em; transition: color var(--dur) var(--ease); } .toc-list > li:last-child .toc-item { border-bottom: none; } .toc-item .toc-num { flex: none; width: 2.1rem; font-family: var(--font-label); font-size: 0.62em; font-weight: 800; color: var(--green-deep); letter-spacing: 0.04em; font-variant-numeric: tabular-nums; } /* The row must be able to shrink with the panel: a long section title wraps onto a second line rather than widening the whole overlay. */ .toc-item .toc-label { flex: 0 1 auto; min-width: 0; overflow-wrap: break-word; color: var(--ink-bold); } /* The leader sits just above the shared baseline, the way a printed contents leader does. At 0.32em it floated at about 60% of the x-height, level with the middle of the lowercase — reading as a rule laid over the row rather than as a line running along it. */ .toc-item .toc-dots { flex: 1 1 auto; min-width: 1.5rem; border-bottom: 2px dotted var(--line-strong); position: relative; bottom: 0.12em; } /* The hover nudge moves the number and title in by 0.6rem and takes the same 0.6rem off the start of the leader, so the folio holds still and the gap before the dots keeps its width. It used to animate the row's padding-left, which laid the row out again on every frame and re-wrapped a two-line title mid-slide; transform and clip-path only repaint. The leader starts from inset(0), not none, because clip-path cannot interpolate from none. */ .toc-item .toc-num, .toc-item .toc-label { transition: transform var(--dur) var(--ease); } .toc-item .toc-dots { clip-path: inset(0); transition: clip-path var(--dur) var(--ease); } .toc-item .toc-folio { flex: none; font-family: var(--font-label); font-size: 0.6em; font-weight: 700; color: var(--ink-faint); letter-spacing: 0.06em; font-variant-numeric: tabular-nums; } .toc-item:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; border-radius: var(--radius-sm); } .toc-item:focus-visible .toc-label { color: var(--green-deep); } .toc-item:hover .toc-num, .toc-item:hover .toc-label { transform: translateX(0.6rem); } .toc-item:hover .toc-dots { clip-path: inset(0 0 0 0.6rem); } .toc-item:hover .toc-label { color: var(--green-deep); } .toc-item:hover .toc-dots { border-bottom-color: var(--green); } .toc-item.current .toc-num, .toc-item.current .toc-label, .toc-item.current .toc-folio { color: var(--green-deep); } .toc-item.current .toc-label { font-weight: 800; } .toc-foot { margin-top: clamp(1.1rem,3vw,1.8rem); padding-top: 1rem; border-top: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; gap: 1rem; color: var(--ink-faint); font-family: var(--font-label); font-size: var(--fs-footer); font-weight: 500; } .toc-foot kbd { font-family: var(--font-mono); font-size: 0.82em; padding: 0.12em 0.5em; border-radius: var(--radius-sm); background: var(--sunken); color: var(--ink-soft); border: 1px solid var(--line); } .toc-close { position: absolute; top: clamp(1.2rem,3vw,2rem); right: clamp(1.2rem,3vw,2rem); width: 2.4rem; height: 2.4rem; border-radius: 50%; display: grid; place-items: center; cursor: pointer; background: var(--panel); border: 1.5px solid var(--line-strong); color: var(--ink-soft); transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease), transform var(--dur) var(--ease); } .toc-close:hover { background: var(--green-deep); color: var(--paper); border-color: var(--green-deep); } .toc-close svg { width: 1rem; height: 1rem; } .deck-status { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; } /* ---------------------------------------------------------------------------- 8. CODE — light highlight.js token theme ---------------------------------------------------------------------------- */ .reveal pre { width: 100%; margin: var(--space-md) 0; font-size: 0.78em; line-height: 1.5; /* The Sunken inset is the system's one sanctioned surface, and it carries no border — a hairline around it makes the block a card again. */ box-shadow: none; border-radius: var(--radius-md); background: var(--sunken); border: none; } .reveal pre code { /* display:block is load-bearing: as an inline box the horizontal padding lands on the first and last line boxes only, leaving the middle lines flush against the panel edge. The .scroll-panel variant already had it. */ display: block; font-family: var(--font-mono); padding: 1.1em 1.3em; border-radius: 0; background: transparent; color: var(--ink); max-height: var(--block-max-h); -webkit-font-smoothing: auto; font-feature-settings: "liga" 0; } .reveal code { font-family: var(--font-mono); font-size: 0.86em; background: var(--sunken); padding: 0.1em 0.36em; border-radius: 3px; color: var(--green-deep); } .reveal pre code.hljs { padding: 1.1em 1.3em; } .hljs-comment, .hljs-quote { color: var(--ink-soft); font-style: italic; } .hljs-keyword, .hljs-selector-tag, .hljs-built_in, .hljs-name, .hljs-tag { color: var(--navy); font-weight: 700; } .hljs-string, .hljs-title.class_, .hljs-section, .hljs-attribute { color: var(--green-deep); } .hljs-number, .hljs-literal, .hljs-bullet, .hljs-link { color: var(--brown-deep); } .hljs-title, .hljs-title.function_, .hljs-function .hljs-title { color: color-mix(in oklch, var(--navy), var(--sky) 18%); font-weight: 700; } .hljs-attr, .hljs-variable, .hljs-template-variable, .hljs-property { color: var(--brown-deep); } .hljs-meta { color: var(--ink-soft); } .hljs-type, .hljs-class .hljs-title { color: var(--navy); } .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: 700; } .reveal .hljs-ln-numbers { color: var(--ink-faint); } .reveal pre .hljs-line-highlight, .reveal .highlight-line { background: var(--green-wash); } /* Scrollable file embed — e.g. the live SKILL.md; the presenter scrolls it. No hairline: this is the Sunken inset, whose surface is the fill plus the inset shadow and never either one with an outline on top. `.reveal pre` was brought under that rule in Pass 5; its scrollable sibling was missed. */ .reveal .scroll-panel { position: relative; /* containing block for the paper-cut marks */ flex: 1 1 auto; min-height: 0; max-height: 25rem; width: 100%; overflow-y: auto; overscroll-behavior: contain; background: var(--sunken); border: none; border-radius: var(--radius-md); box-shadow: inset 0 6px 14px -12px oklch(0.2 0.02 258 / 0.5); scrollbar-width: thin; scrollbar-color: var(--green) transparent; } /* Focusable so a keyboard can scroll it (deck.js initScrollRegions); the ring is the theme's own, and :focus-visible keeps it off a mouse click. */ .reveal .scroll-panel:focus-visible, .reveal [data-scroll-region]:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; } .reveal .scroll-panel::-webkit-scrollbar { width: 11px; } .reveal .scroll-panel::-webkit-scrollbar-thumb { background: color-mix(in oklch, var(--green), var(--paper) 18%); border-radius: 999px; border: 3px solid var(--sunken); } .reveal .scroll-panel::-webkit-scrollbar-thumb:hover { background: var(--green); } .reveal .scroll-panel pre { margin: 0; border: none; border-radius: 0; box-shadow: none; background: transparent; width: 100%; } .reveal .scroll-panel pre code { display: block; padding: 1.2em 1.5em; font-size: 0.72em; line-height: 1.62; max-height: none; white-space: pre-wrap; word-break: break-word; color: var(--ink); } .reveal .scroll-hint { margin: var(--space-sm) 0 0; font-family: var(--font-label); font-size: var(--fs-caption); color: var(--ink-soft); font-weight: 500; display: inline-flex; align-items: center; gap: 0.55em; } .reveal .scroll-hint::before { content: ""; width: 0.95em; height: 0.95em; flex: none; background: var(--green-deep); -webkit-mask: no-repeat center/contain; mask: no-repeat center/contain; -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v14M6 13l6 6 6-6'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v14M6 13l6 6 6-6'/%3E%3C/svg%3E"); } /* Framed live embed — a browser-style chrome bar over a fitted viewport. */ .reveal .site-frame { flex: 1 1 auto; min-height: 0; width: 100%; display: flex; flex-direction: column; background: var(--paper); border: 1px solid var(--line-strong); border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-2); } /* THE BROWSER-CHROME BAR — one device, declared once. Two frames carry it and they are two different jobs: .site-frame fills its column with a live page, .chrome (section 12) holds a capture at its natural size. Their bar is the same bar, and it had been written twice — long enough for the two copies to drift 0.05rem of padding and 0.1rem of label inset apart. A near-miss between two copies of one device reads as a mistake, so there is now one copy. */ .reveal .site-frame-bar, .reveal .chrome-bar { flex: none; display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.9rem; background: var(--sunken); border-bottom: 1px solid var(--line); /* The bar must span the frame. If authored as a
, it inherits the generic caption styling — `max-width: 52ch` clamps it to ~413px and truncates the URL, `margin-top` opens a strip of paper above it inside the frame's own rounded border, and the italic serif is a caption voice, not chrome. The bar is the same device whatever element carries it, so neutralise all three here rather than leaving each variant to rediscover them. */ max-width: none; margin-top: 0; font-style: normal; } .reveal .site-frame-bar .dot, .reveal .chrome-dot { width: 0.6rem; height: 0.6rem; border-radius: 50%; flex: none; background: color-mix(in oklch, var(--ink-faint), var(--paper) 25%); } .reveal .site-frame-url, .reveal .chrome-url { margin-left: 0.5rem; font-family: var(--font-mono); font-size: var(--fs-footer); color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* The open link belongs to the live embed alone: a captured screenshot has no page to open. That is the difference between the two frames, not an omission in one of them. Its twin .chrome-open was styled the day .chrome was written (2026-06-29) and no markup ever used it, catalogue included. */ .reveal .site-frame-open { margin-left: auto; flex: none; font-family: var(--font-label); font-size: var(--fs-footer); font-weight: 700; } .reveal .site-frame-view { position: relative; flex: 1 1 auto; min-height: 0; background: var(--paper); } .reveal .site-frame-view > iframe, .reveal .site-frame-view > img { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; object-fit: cover; object-position: top center; } /* "Scan to explore" QR overlay on the live-site frame */ .reveal .site-qr { position: absolute; right: 0.9rem; bottom: 0.9rem; z-index: 2; display: flex; flex-direction: column; align-items: center; gap: 0.3rem; background: #fff; padding: 0.6rem; border-radius: 8px; box-shadow: var(--shadow-2); } .reveal .site-qr img { width: 116px; height: 116px; display: block; } .reveal .site-qr span { font-family: var(--font-label); font-size: var(--fs-footer); font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink); } /* "Scan for these slides" QR — a small labelled code for the cover/closing. */ .reveal .slides-qr { display: inline-flex; align-self: flex-start; align-items: center; gap: 0.85rem; margin-top: var(--space-lg); } /* Pure #fff, not --paper: a QR carries its own quiet-zone contract and scanners read it against white, so this one white is a functional value, not a themed one. */ .reveal .slides-qr > img { width: 86px; height: 86px; display: block; background: #fff; padding: 6px; border-radius: var(--radius-md); border: 1px solid var(--line-strong); } .reveal .slides-qr .qr-label { display: flex; flex-direction: column; gap: 0.1em; text-align: left; } .reveal .slides-qr .qr-label b { font-family: var(--font-label); font-weight: 700; font-size: var(--fs-small); color: var(--ink-bold); } .reveal .slides-qr .qr-label .qr-url { font-family: var(--font-mono); font-size: var(--fs-footer); color: var(--ink-faint); } .reveal .slides-qr.corner { position: absolute !important; top: calc(var(--slide-pad-y) + var(--space-md)); right: var(--slide-pad-x); margin: 0; z-index: 3; flex-direction: column; align-items: center; gap: 0.4rem; } .reveal .slides-qr.corner .qr-label { text-align: center; align-items: center; } /* dark fields (section divider / closing): lift the label to paper */ .reveal section.section .slides-qr .qr-label b, .reveal section.closing .slides-qr .qr-label b { color: var(--paper); } .reveal section.section .slides-qr .qr-label .qr-url, .reveal section.closing .slides-qr .qr-label .qr-url { color: color-mix(in oklch, var(--paper), transparent 36%); } /* Fallback note injected by deck.js when a live iframe fails to load (offline, or the site refuses framing): a quiet centred notice with the original link. */ .reveal .frame-fallback { position: absolute; inset: 0; z-index: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-sm); text-align: center; padding: var(--space-lg); background: var(--sunken); } .reveal .frame-fallback[hidden] { display: none; } .reveal .frame-fallback p { margin: 0; font-family: var(--font-serif); font-style: italic; font-size: var(--fs-small); color: var(--ink-soft); max-width: 40ch; } .reveal .frame-fallback a { font-family: var(--font-label); font-weight: 700; font-size: var(--fs-caption); } /* Saved-demonstration frame — a captured app/chat run in browser chrome, displayed whole (no crop) and height-capped so the slide never overflows. The frame hugs the image width and centres in the leftover height. */ .reveal .demo-wrap { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-md); } .reveal .demo-shot { width: fit-content; max-width: 100%; margin: 0; display: flex; flex-direction: column; min-height: 0; border: var(--hair); border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-2); background: var(--paper); } /* The demo-shot URL bar is a
, so generic figcaption styles leak in (top margin → white gap, italic → slanted URL, max-width:52ch → narrow bar). Reset. */ .reveal .demo-shot .site-frame-bar { flex: none; margin-top: 0; max-width: none; font-style: normal; } /* A warm near-black letterbox behind app screenshots of any aspect ratio. It is deliberately not an ink token: it belongs to the captured artifact, like the browser chrome around it, not to the page's own neutral ramp. */ .reveal .demo-shot-view { min-height: 0; line-height: 0; background: #211f1d; } .reveal .demo-shot-view img { display: block; width: auto; height: auto; max-height: 336px; max-width: 100%; } .reveal .demo-wrap .small.muted { max-width: 64ch; text-align: center; margin: 0; } /* Framed screenshot inside a content column */ .reveal .shot { display: block; width: 100%; height: auto; border: 1px solid var(--line-strong); border-radius: var(--radius-sm); box-shadow: none; } .reveal .shot.tall { width: auto; max-width: 100%; max-height: 348px; margin-inline: auto; } .reveal .col-head .mcp-mark { display: inline-block; height: 1.4em; width: auto; vertical-align: -0.32em; margin-right: 0.4em; } /* Image lightbox — click a figure/screenshot (or focus it and press Enter) to view it full-screen. Zoomable images are keyboard controls. */ .reveal .slides .shot.is-zoomable, .reveal .slides .site-frame-view > img.is-zoomable { cursor: zoom-in; } .reveal .slides .is-zoomable:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 3px; } .deck-lightbox { position: fixed; inset: 0; z-index: 80; display: flex; align-items: center; justify-content: center; padding: clamp(1.5rem, 4vw, 3.5rem); background: color-mix(in oklch, oklch(0.14 0.012 258), #02060f 40%); opacity: 0; visibility: hidden; cursor: zoom-out; transition: opacity 260ms var(--ease), visibility 260ms; } .deck-lightbox.open { opacity: 1; visibility: visible; } .deck-lightbox .lightbox-figure { margin: 0; display: flex; flex-direction: column; align-items: center; gap: var(--space-md); max-width: 92vw; max-height: 88vh; transform: scale(0.985); transition: transform 300ms var(--ease); } .deck-lightbox.open .lightbox-figure { transform: none; } .deck-lightbox img { max-width: 92vw; max-height: 80vh; width: auto; height: auto; border-radius: var(--radius-sm); box-shadow: var(--shadow-pop); background: var(--paper); } .deck-lightbox figcaption { font-family: var(--font-serif); font-style: italic; font-size: var(--fs-caption); color: color-mix(in oklch, var(--paper), transparent 26%); max-width: 60ch; text-align: center; line-height: 1.4; } .deck-lightbox .lightbox-close { position: absolute; top: clamp(1rem, 3vw, 2rem); right: clamp(1rem, 3vw, 2rem); width: 2.6rem; height: 2.6rem; border-radius: 50%; display: grid; place-items: center; cursor: pointer; background: color-mix(in oklch, var(--paper), transparent 86%); border: 1px solid color-mix(in oklch, var(--paper), transparent 74%); color: var(--paper); transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease), transform var(--dur) var(--ease); } .deck-lightbox .lightbox-close:hover { background: var(--gold); color: var(--navy-deep); border-color: var(--gold); } .deck-lightbox .lightbox-close svg { width: 1.1rem; height: 1.1rem; } /* THE PAPER CUT. A scroll panel cannot scroll on paper, and under the one-page-per-slide contract a 190-line file was never going to fit a 1280×720 page — so the excerpt is unavoidable. What was avoidable is the silence: the PDF used to hard-cut mid-sentence with no mark at all, under authored copy still telling the reader to scroll. The cut is now drawn in the theme's own vocabulary — the text fades into the Sunken fill, the heavy near-black rule that opens a block is used here to close one, and a gothic label names what the reader is holding. Both marks and the suppressed hint hang off the same pair of properties, declared once per paper context. */ .reveal .scroll-panel::after, .reveal .scroll-panel::before { display: var(--excerpt-mark, none); position: absolute; left: 0; right: 0; pointer-events: none; -webkit-print-color-adjust: exact; print-color-adjust: exact; } .reveal .scroll-panel::after { /* the fade, seated on the band */ content: ""; bottom: 2.05em; height: 3.6em; background: linear-gradient(to bottom, transparent, var(--sunken)); } .reveal .scroll-panel::before { /* the band that closes the panel */ content: "Excerpt"; bottom: 0; padding: 0.4em 1.5em; background: var(--sunken); border-top: 2px solid var(--rule); font-family: var(--font-label); font-size: var(--fs-footer); font-weight: 700; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* A panel that already declares where its file came from names it on paper. data-source-url is an existing authoring attribute — deck.js falls back to it when the embed cannot load — so the band reuses the author's own answer instead of asking for a second one. It is also what keeps the suppressed scroll-hint from taking a URL down with it. The source variant carries its caps in the string and turns text-transform off, because uppercasing a URL shouts it and makes the path segments harder to read; its tracking eases for the same reason. The label keeps its voice, the address stays an address. */ .reveal .scroll-panel[data-source-url]::before { content: "EXCERPT · " attr(data-source-url); text-transform: none; letter-spacing: 0.06em; } html[lang="fr"] .reveal .scroll-panel::before { content: "Extrait"; } html[lang="fr"] .reveal .scroll-panel[data-source-url]::before { content: "EXTRAIT · " attr(data-source-url); } .reveal .scroll-hint { display: var(--scroll-hint-display, inline-flex); } /* The two paper contexts: the real print run, and the ?print-pdf preview the author actually looks at. Same pair of declarations, same reason. */ @media print { .reveal { --excerpt-mark: block; --scroll-hint-display: none; } } html.reveal-print .reveal { --excerpt-mark: block; --scroll-hint-display: none; } /* ---------------------------------------------------------------------------- 9. FRAGMENTS & TRANSITIONS ---------------------------------------------------------------------------- */ /* Reveal ships `.reveal .fragment { transition: all .2s }` — the one declaration the theme's own rule forbids, since `all` sweeps in outline-color and outline-offset and fades the focus ring up on any fragment that can take focus (a zoomable figure, a link). Named here instead: the four properties reveal's fragment vocabulary actually moves — opacity for the plain and fading kinds, colour for .highlight-*, transform for .grow/.shrink, and visibility, which holds the element on screen long enough for a backward step to fade out. The plain .fragment is the scaffold's default disclosure, so this is the path most decks take. */ .reveal .fragment { transition: opacity var(--dur) var(--ease), color var(--dur) var(--ease), transform var(--dur) var(--ease), visibility var(--dur); } .reveal .fragment.fade-up { transform: translateY(14px); transition: transform 420ms var(--ease), opacity 420ms var(--ease); } .reveal .fragment.fade-up.visible { transform: none; } .reveal .slides section .fragment.highlight-green.visible { color: var(--green-deep); } /* Explicit opt-in remains usable when a remote document cannot prove readiness. */ .frame-show { font: inherit; color: var(--green-deep); background: var(--paper); border: 1px solid var(--line-strong); padding: var(--space-xs) var(--space-sm); cursor: pointer; } .frame-show:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; } /* ---------------------------------------------------------------------------- 10. PRINT TO PDF · REDUCED MOTION · SMALL SCREENS ---------------------------------------------------------------------------- */ /* PDF export: the live footer is one element for the whole deck. Reveal's print view parks it at the tail of the last page still stamped with whatever folio was current when the export ran — so one page carried a wrong number and the other sixteen carried none. Print gets its own imprint instead: one per page, numbered from that page's own position (built by deck.js). */ html.reveal-print .deck-footer, html.reveal-print .deck-runhead { display: none !important; } html.reveal-print .reveal .slides .pdf-page { position: relative; } .pdf-imprint { position: absolute; left: var(--slide-pad-x); right: var(--slide-pad-x); bottom: var(--space-md); z-index: 3; display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-md); font-family: var(--font-label); font-size: var(--fs-footer); font-weight: 700; text-transform: uppercase; letter-spacing: 0.18em; line-height: 1.2; color: var(--ink-faint); border-top: 2px solid var(--rule); padding-top: 0.5rem; -webkit-print-color-adjust: exact; print-color-adjust: exact; } .pdf-imprint .pi-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .pdf-imprint .pi-folio { color: var(--green-deep); white-space: nowrap; font-variant-numeric: lining-nums tabular-nums; } .pdf-imprint .pi-folio span { color: var(--ink-faint); font-weight: 400; } /* Dark pages take the same two registers as the screen chrome. */ .pdf-imprint.on-dark { color: var(--ond-chrome); border-top-color: var(--ond-rule); } .pdf-imprint.on-dark .pi-folio { color: var(--ond-mark); } .pdf-imprint.on-dark .pi-folio span { color: var(--ond-chrome); } /* SCROLL VIEW (?view=scroll): the deck read as one page instead of presented. The chrome has to answer for what it can still be true about. The footer is the deck's identification and it stays — but `position: absolute` measured it against a `.reveal` one viewport tall, so it scrolled off after the first screen and never came back; fixed, it holds its band, and the slide's own `--footer-h` clearance already keeps content out from under it. The nav cluster goes: reveal re-parents every slide out of `.slides > section`, which is the selector getHorizontalSlides() reads, so the counter has no counted total to print (it froze at 01 / 01 on every slide) and the contents list has no entries to build — the scrollbar is this view's index. The running head goes with them: it names the section you are standing in, and in a scroll there is no standing. */ .reveal-viewport.reveal-scroll .deck-footer { position: fixed; } .reveal-viewport.reveal-scroll .deck-nav, .reveal-viewport.reveal-scroll .deck-runhead { display: none !important; } @media print { .deck-footer, .deck-nav, .deck-runhead { color: var(--ink-faint) !important; } .toc-overlay, .deck-btn { display: none !important; } .reveal section.section, .reveal section.closing, .reveal section.media .media-scrim, .reveal section.media::after { -webkit-print-color-adjust: exact; print-color-adjust: exact; } .reveal .slides section.section, .reveal .slides section.closing, .reveal .slides section.media { height: 100% !important; } } /* REDUCED MOTION — take the movement away, not the feedback. The rule here used to zero every transition-duration on every element, which read as thorough and was not: it also flattened the viewport's 360ms cross-fade between the paper ground and the graphite one into an instant full-screen flip between near-white and near-black, on every section divider — a hard luminance cut delivered to exactly the audience least able to take one. A transition that only changes colour or opacity carries no motion vector and stays at its authored timing; what goes is displacement. */ @media (prefers-reduced-motion: reduce) { /* The signature stills through the switch print and .no-draw already use. */ .reveal { --draw-run: 0s; } /* Backstop for any keyframe animation the switch does not own. */ *, *::before, *::after { animation-duration: 0.001ms !important; scroll-behavior: auto !important; } /* Overlays still fade in — they just stop travelling to get there. */ .toc-panel, .deck-lightbox .lightbox-figure, .reveal .fragment.fade-up { transform: none !important; } /* Hover and press feedback keeps its colour change and loses its 1px lift. */ .deck-btn:hover, .deck-btn:active { transform: none; } .toc-item:hover .toc-num, .toc-item:hover .toc-label { transform: none; } .toc-item:hover .toc-dots { clip-path: inset(0); } /* The progress bar jumps to its new position rather than sliding across. */ .reveal .progress span { transition: none; } } /* The reveal canvas is scaled to fit, so chrome (runhead/footer/toc) is what needs small-screen care when viewed on a phone. */ @media (max-width: 640px) { :root { --slide-pad-x: 1.6rem; --footer-h: 2.6rem; } .deck-footer .foot-title { display: none; } .deck-runhead { display: none !important; } /* Icon-only, the contents button joins the row of square buttons, so its glyph takes their size too; the 0.95rem it carries beside the label left it visibly smaller than the arrows either side. */ .deck-btn.toc-btn span { display: none; } .deck-btn.toc-btn { width: 2rem; padding: 0; } .deck-btn.toc-btn svg { width: 1.05rem; height: 1.05rem; } } /* SHALLOW screens (a phone held landscape): the scaled canvas is height-limited and would otherwise fill the viewport right down behind the persistent footer, which then covers the last lines of content. Reserve a real strip for a compact footer *outside* the area reveal scales into: the canvas scales to (viewport − strip), the footer sits in the strip, and nothing overlaps. Desktop (≥ 560px tall) is untouched. */ @media screen and (max-height: 560px) { :root { --footer-reserve: 2.4rem; } .reveal { height: calc(100% - var(--footer-reserve)); } .deck-footer { height: var(--footer-reserve); bottom: calc(-1 * var(--footer-reserve)); } .deck-footer::before { left: 0; right: 0; } .deck-footer .foot-title { display: none; } .deck-runhead { display: none !important; } .deck-btn { width: 1.8rem; height: 1.8rem; } .deck-btn.toc-btn span { display: none; } .deck-btn.toc-btn { width: 1.8rem; padding: 0; } .deck-btn.toc-btn svg { width: 1.05rem; height: 1.05rem; } } /* ============================================================================= 11. DATA AS DESIGN — native, on-brand charts (direction-board · Move 1) A corpus has a shape, so the system must be able to DRAW it: bars, a timeline, a share breakdown — set in the green/navy fields with gothic labels, tabular figures and a single heavy editorial baseline. Not a chart library; the broadsheet's own type and rules, holding numbers. ============================================================================= */ /* A captioned chart block: gothic over-label, the plot, an italic source line. */ .reveal .chart { margin: var(--space-md) 0; max-width: none; display: flex; flex-direction: column; } .reveal .chart-head { font-family: var(--font-label); font-weight: 700; font-size: var(--fs-label); letter-spacing: var(--track-hall); text-transform: uppercase; color: var(--ink-faint); margin-bottom: var(--space-md); } .reveal .chart-src { font-family: var(--font-serif); font-style: italic; font-size: var(--fs-caption); color: var(--ink-faint); margin: var(--space-sm) 0 0; } /* COLUMN CHART — a row of bars on a heavy baseline. Height per bar via --h (a %). Bars grow up on slide entry; values can count up (data-count, see deck.js). */ .reveal .barchart { display: flex; align-items: flex-end; gap: var(--space-md); height: var(--chart-h, 14rem); border-bottom: 3px solid var(--rule); margin: 0; } .reveal .barchart .bar { flex: 1 1 0; min-width: 0; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; } .reveal .barchart .bar-v { font-family: var(--font-label); font-weight: 800; font-size: var(--fs-caption); color: var(--ink-faint); font-variant-numeric: tabular-nums; line-height: 1; margin-bottom: 0.45em; } .reveal .barchart .bar::after { content: ""; display: block; width: 100%; max-width: 3.4rem; height: var(--h, 0%); background: var(--green); border-radius: 1px 1px 0 0; } .reveal .barchart .bar.is-peak .bar-v { color: var(--ink-bold); } .reveal .barchart .bar.is-peak::after { background: var(--green-field); } .reveal .barchart .bar.is-mute::after { opacity: 0.45; } .reveal .barchart.navy .bar::after { background: var(--navy); } .reveal .barchart.navy .bar.is-peak::after { background: var(--navy-field); } /* the x-axis tick labels — a matching flex row beneath the baseline */ .reveal .bar-axis { display: flex; gap: var(--space-md); margin-top: 0.55em; max-width: none; list-style: none; padding: 0; } .reveal .bar-axis > * { flex: 1 1 0; text-align: center; margin: 0; font-family: var(--font-label); font-weight: 600; font-size: var(--fs-label); letter-spacing: 0.04em; color: var(--ink-faint); } .reveal .bar-axis > *::before { content: none; } /* kill the generic ul/ol marker */ /* SHARE BARS — horizontal breakdown. Width per row via --p (a %). Navy by default (data's structural deep); .green tips it to the lead colour. Fills draw in. */ .reveal .share-bars { list-style: none; margin: 0; padding: 0; max-width: none; display: flex; flex-direction: column; gap: var(--space-md); } .reveal .share-bars > li { margin: 0; padding: 0; } .reveal .share-bars > li::before { content: none; } .reveal .share-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-md); margin-bottom: 0.34em; } .reveal .share-head > span { font-family: var(--font-serif); font-size: var(--fs-small); color: var(--ink); } .reveal .share-head > b { font-family: var(--font-label); font-weight: 800; font-size: var(--fs-caption); color: var(--navy); font-variant-numeric: tabular-nums; } .reveal .share-track { height: 9px; background: var(--sunken); border-radius: 1px; overflow: hidden; } .reveal .share-track::before { content: ""; display: block; height: 100%; width: var(--p, 0%); background: var(--navy); } .reveal .share-bars.green .share-head > b { color: var(--green-deep); } .reveal .share-bars.green .share-track::before { background: var(--green); } .reveal .share-bars > li.is-mute .share-head > b { color: var(--ink-faint); } .reveal .share-bars > li.is-mute .share-track::before { opacity: 0.5; } /* ============================================================================= 12. ONE IMAGE TREATMENT — duotone plates + browser chrome (Move 2) Two rules, never broken: documentary photographs get a green duotone behind a ruled "plate" caption; live UI keeps its true colour but always inside real browser chrome. A scavenged pile of JPEGs starts to read as one authored object. The #duo-green / #duo-navy SVG filters are injected once by deck.js. ============================================================================= */ /* The duotone utility — drop on any deck img, or use the .plate figure below. */ .reveal img.duotone, .reveal .duotone > img { filter: url(#duo-green); } .reveal img.duotone.navy, .reveal .duotone.navy > img { filter: url(#duo-navy); } /* PLATE — a documentary photo as a printed plate: duotone image on the ink ground, a heavy ruled caption with a gothic "Fig." mark and serif gloss. */ .reveal .plate { margin: 0; display: flex; flex-direction: column; min-height: 0; } .reveal .plate .plate-img { background: var(--duo-ground); border: 1px solid var(--line-strong); line-height: 0; overflow: hidden; } .reveal .plate .plate-img img { display: block; width: 100%; height: 100%; object-fit: cover; filter: url(#duo-green); border: 0; border-radius: 0; } .reveal .plate.navy .plate-img { background: var(--navy-deep); } .reveal .plate.navy .plate-img img { filter: url(#duo-navy); } .reveal .plate figcaption { display: flex; gap: 0.7em; margin-top: 0.55em; padding-top: 0.5em; border-top: 2px solid var(--rule); max-width: none; font-style: normal; } .reveal .plate .fig-no { flex: none; font-family: var(--font-label); font-weight: 800; font-size: var(--fs-label); letter-spacing: 0.12em; text-transform: uppercase; color: var(--green-deep); } .reveal .plate.navy .fig-no { color: var(--navy); } .reveal .plate .fig-cap { font-family: var(--font-serif); font-style: italic; font-size: var(--fs-caption); line-height: 1.34; color: var(--ink-soft); } /* CHROME — an app/UI screenshot in real browser chrome, at its natural size (the filling, flex-driven version is .site-frame; this drops into any column). Only the box and the view are its own: .chrome-bar, .chrome-dot and .chrome-url are the shared browser-chrome bar, declared once beside .site-frame in 05-embeds-code.css. */ .reveal .chrome { margin: 0; border: 1px solid var(--line-strong); border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-2); background: var(--paper); } .reveal .chrome-view { line-height: 0; background: var(--paper); } .reveal .chrome-view img { display: block; width: 100%; height: auto; object-fit: cover; border: 0; border-radius: 0; } /* ============================================================================= 13. PUSH THE BROADSHEET FURTHER — editorial signature (Move 4) Versal, margin note, dateline masthead, ghosted folio, colophon. Not decoration — the newspaper logic the system already chose, taken all the way. ============================================================================= */ /* VERSAL / drop-cap — opens an argument. Green on paper, gold on the dark fields. */ .reveal p.dropcap::first-letter { float: left; font-family: var(--font-display); font-weight: 800; font-size: 3.4em; line-height: 0.66; padding: 0.06em 0.14em 0 0; color: var(--green-deep); } .reveal p.dropcap.gold::first-letter { color: var(--gold); } .reveal section.section p.dropcap::first-letter, .reveal section.closing p.dropcap::first-letter { color: var(--ond-mark); } /* MARGIN NOTE — the serif counter-voice as true marginalia: a heavy rule, a gothic label, an italic gloss. Sits in the narrow column of a ratio split. */ /* The rule needs air above it. With none, a marginnote set under a chart put a heavy full-width rule directly beneath the last bar, where it read as one more — unlabelled, near-black — bar in the series rather than as the opening of an aside. A rule that opens a block has to be clearly above that block. */ .reveal .marginnote { margin-top: var(--space-lg); } .reveal .marginnote:first-child { margin-top: 0; } .reveal .marginnote { border-top: 3px solid var(--rule); padding-top: 0.55em; max-width: 34ch; font-family: var(--font-serif); font-style: italic; font-size: var(--fs-small); line-height: 1.4; color: var(--ink-soft); } .reveal .marginnote .mn-label { display: block; font-family: var(--font-label); font-style: normal; font-weight: 800; font-size: var(--fs-label); letter-spacing: var(--track-hall); text-transform: uppercase; color: var(--ink-bold); margin-bottom: 0.3em; } .reveal .marginnote.warm { border-top-color: var(--brown); } .reveal .marginnote.warm .mn-label { color: var(--brown-deep); } .reveal section.section .marginnote, .reveal section.closing .marginnote { border-top-color: var(--ond-mark); color: var(--ond-quiet); } .reveal section.section .marginnote .mn-label, .reveal section.closing .marginnote .mn-label { color: var(--ond-mark); } /* DATELINE — a masthead row: the part/strand on the left, the venue on the right, on a heavy rule. Gothic small-caps; gold-keyed on dark fields. */ .reveal .dateline { display: flex; justify-content: space-between; align-items: baseline; gap: 1em; font-family: var(--font-label); font-weight: 700; font-size: var(--fs-kicker); text-transform: uppercase; letter-spacing: var(--track-hall); color: var(--ink-faint); padding-bottom: 0.6em; margin: 0 0 var(--space-lg); border-bottom: 3px solid var(--rule); max-width: none; } .reveal .dateline .dl-part { color: var(--green-deep); } .reveal .dateline .dl-meta { font-weight: 600; letter-spacing: 0.12em; } .reveal section.section .dateline, .reveal section.closing .dateline { border-bottom-color: var(--ond-mark); color: var(--ond-quiet); } .reveal section.section .dateline .dl-part, .reveal section.closing .dateline .dl-part { color: var(--ond-mark); } /* GHOSTED FOLIO — an oversized faint gothic number anchoring a field, like a pressroom plate. :has() scopes the stacking fix to slides that use one. */ /* Oversized but bounded: line-height 1 keeps the glyph inside its own box, and the box is anchored within the canvas so it never inflates the slide's scrollHeight (which would trip the ?check guard or split a PDF page). */ .reveal .folio-ghost { position: absolute; right: var(--slide-pad-x); bottom: calc(var(--footer-h) + var(--space-md)); z-index: 0; font-family: var(--font-display); font-weight: 800; font-size: var(--fs-watermark); line-height: 1; letter-spacing: -0.04em; color: color-mix(in oklch, var(--ink), transparent 94%); font-variant-numeric: tabular-nums; pointer-events: none; user-select: none; } .reveal section.section .folio-ghost, .reveal section.closing .folio-ghost { color: color-mix(in oklch, var(--paper), transparent 90%); } .reveal section:has(> .folio-ghost) > :not(.folio-ghost) { position: relative; z-index: 1; } /* COLOPHON — a printer's imprint to close on: label·value pairs on a rule. */ .reveal .colophon { display: flex; flex-wrap: wrap; gap: 0.35em 1.6em; align-items: baseline; max-width: none; border-top: 2px solid var(--rule); padding-top: var(--space-sm); margin-top: var(--space-md); } .reveal .colophon .col-item { display: inline-flex; align-items: baseline; gap: 0.5em; font-family: var(--font-serif); font-style: italic; font-size: var(--fs-caption); color: var(--ink-soft); } .reveal .colophon .col-item b { font-family: var(--font-label); font-style: normal; font-weight: 800; font-size: var(--fs-footer); letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-bold); } .reveal section.closing .colophon { border-top-color: var(--ond-rule); } .reveal section.closing .colophon .col-item { color: color-mix(in oklch, var(--paper), transparent 30%); } .reveal section.closing .colophon .col-item b { color: color-mix(in oklch, var(--paper), transparent 8%); } /* EVIDENCE — keep a source beside its reading, with equal visual status. The original remains whole and zoomable; readable observations are native text, never a tiny crop that a room must decipher. */ .reveal .evidence-pair > .col + .col { border-left: 3px solid var(--rule); padding-left: var(--space-xl); } .reveal .evidence-reference { margin: var(--space-lg) 0 0; } .reveal .evidence-reference .shot { max-height: 9rem; width: auto; max-width: 100%; } .reveal .evidence-reference figcaption { font-family: var(--font-serif); font-size: var(--fs-caption); line-height: 1.35; color: var(--ink-soft); margin-top: var(--space-xs); } .reveal .evidence-reading { margin: 0 0 var(--space-lg); } .reveal .evidence-reading > div { border-top: 3px solid var(--rule); padding: var(--space-sm) 0; } .reveal .evidence-reading dt { font-family: var(--font-label); font-size: var(--fs-caption); font-weight: 700; text-transform: uppercase; letter-spacing: var(--track-hall); color: var(--navy); } .reveal .evidence-reading dd { margin: var(--space-xs) 0 0; font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 800; line-height: 1.15; } .reveal .evidence-reading dd span { display: block; margin-top: var(--space-xs); font-family: var(--font-serif); font-size: var(--fs-body); font-weight: 400; color: var(--ink-soft); } /* ============================================================================= 14. WIDEN THE PALETTE · DIAGRAMS OVER LISTS (Move 6) The brown/amber pole carries the critical "counter-thread" (the stakes, the blind spot); and a process is better drawn than bulleted. ============================================================================= */ /* A warm section divider — for the critical "stakes" strand (use sparingly). It is a bright field like the green one (5.71:1 against Paper), so it keeps the bright register and its plate rule needs no override: gold-bright sat at 3.12:1 here, a hair over the floor and muddy against the brown. */ .reveal section.section.warm { background: var(--warm-field); } /* A warm statement accent (the claim worth pausing on, in the critical key). */ .reveal section.statement.warm .statement-body .accent { color: var(--brown-deep); } .reveal section.statement.warm.quote .statement-body::before { color: var(--brown); } /* gold/navy/warm chips, to round out the set */ .reveal .chip.navy { border-color: var(--navy); color: var(--navy); } .reveal .chip.warm { border-color: var(--brown); color: var(--brown-deep); } /* FLOW — a process as a diagram: numbered steps joined by arrows. Generalises the per-deck "semflow"; flex so it takes any number of steps. */ /* The steps are ruled columns, not cards. Three bordered radiused boxes joined by arrows is the one composition on this deck an unrelated product could ship unchanged — and it sat on the slide captioned "A process, drawn — not bulleted". Each step now opens on the same heavy rule .panel uses, and the separation comes from the gutter rather than from a border. */ .reveal .flow { display: flex; align-items: stretch; gap: var(--space-xl); margin: var(--space-lg) 0; max-width: none; } .reveal .flow-step { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 0.4em; border-top: var(--bar) solid var(--rule); padding: var(--space-sm) 0 0; } .reveal .flow-step .step-n { font-family: var(--font-label); font-weight: 600; font-size: var(--fs-label); letter-spacing: 0.12em; text-transform: uppercase; color: var(--navy); } .reveal .flow-step .step-h { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-body); line-height: 1.08; color: var(--ink); } .reveal .flow-step p { margin: 0; font-size: var(--fs-caption); line-height: 1.4; color: var(--ink-soft); max-width: none; } .reveal .flow-step .step-q { font-family: var(--font-display); font-style: italic; font-size: var(--fs-small); color: var(--ink); } .reveal .flow-step .step-vec { align-self: flex-start; font-family: var(--font-mono); font-size: var(--fs-caption); color: var(--brown-deep); background: var(--sunken); border-radius: var(--radius-sm); padding: 0.2em 0.5em; } .reveal .flow-step ul { margin: 0; } .reveal .flow-step li { margin: 0 0 0.25em; } .reveal .flow-arrow { flex: none; align-self: flex-start; display: flex; align-items: center; height: var(--bar); margin-top: calc(var(--bar) / -2 + 0.5px); color: var(--green-deep); font-weight: 700; font-size: var(--fs-small); } .reveal .flow.tight { gap: 0; } .reveal .flow.tight .flow-arrow { padding: 0 0.15em; } /* ============================================================================= 15. MOTION SIGNATURE — rules that draw, bars that grow (Move 3, CSS half) One ink-on-paper idea, repeated and restrained: the heavy markers and rules lay themselves down as a slide arrives; the chart fills rise from the baseline. Numbers count up in deck.js. Nothing here restates which marks are animated in order to still them — they all read their duration from --draw-run, and print, the ?print-pdf preview, prefers-reduced-motion and .no-draw each set that one property to 0s. ============================================================================= */ @keyframes rule-draw { from { transform: scaleX(0); } } @keyframes grow-up { from { height: 0; } } /* Everything the signature draws from the left is one device and one keyframe: the green marker over a title, the kicker's tick, the plate rule that opens a section or the colophon — and the share-bar fill, which is a rule whose length is the number. It used to animate `width` under its own `grow-right` keyframe; scaling it instead is exact (nothing is laid out against the fill, which sits alone inside an overflow-hidden track) and takes the whole set off the layout path onto the compositor. */ .reveal section.present .slide-title::before, .reveal section.present .kicker::before, .reveal section.section.present::before, .reveal section.closing.present::before, .reveal section.present .share-track::before { transform-origin: left center; animation: rule-draw var(--draw-run) var(--ease) both; } /* The column chart is the one animation that stays on the layout path, and it is a decision rather than an oversight: each bar's value numeral is laid out ON TOP of the growing fill, so the height is what carries the numeral up with its bar (measured: 447px → 247px on the peak bar). Scaling the fill instead would strand all eight numerals at their final height while the bars rose to meet them. Measured cost of the ride, on the catalogue's data slide: 36 layouts / ~28ms — against 52 layouts / ~40ms for the count-up running beside it, which stays either way. */ .reveal section.present .barchart .bar::after { animation: grow-up var(--draw-run) var(--ease) both; } /* THE STILLERS. A zero duration with `both` fill lands each mark on its end state, so the slide simply arrives finished. The opt-out reads at deck level (
) and at slide level (
); it used to reach five of the six marks in three different selector grammars, leaving a section divider's plate rule drawing itself on a deck that had asked for stillness. */ .reveal.no-draw, .reveal section.no-draw { --draw-run: 0s; } html.reveal-print .reveal { --draw-run: 0s; } /* The scroll view (?view=scroll) is a read-through, and it takes the switch for the same reason print does. Reveal adds and removes `.present` as each page crosses the scroll trigger, so every mark redraws — and every numeral rolls from zero again — each time a slide passes the fold, in both directions. An arrival animation needs an arrival; scrolling back up is not one. */ .reveal-viewport.reveal-scroll .reveal { --draw-run: 0s; } /* Print: ink the new dark fields, and never hold a half-drawn rule on paper. */ @media print { .reveal { --draw-run: 0s; } .reveal section.section.warm { -webkit-print-color-adjust: exact; print-color-adjust: exact; } }