/* Map the supplied Light outlines explicitly; its internal weight label is inconsistent. */
@font-face{font-family:'Invitation Helvetica';src:url('assets/NeueHelvetica45-Light.otf') format('opentype');font-style:normal;font-weight:300;font-display:swap}
:root{
  /* The card sits ~6% deeper than before. It used to render at L 244 under the old
     gradient's bright corner, leaving 11 of 255 levels above it, so the lit side of an
     impression could not read as brighter than the paper at all. Note the old
     background-color did nothing: assets/paper-transport.jpg is opaque and covered it.
     --paper is now only the behind-colour and the .skip/.button fill; the card's own tone
     is --card-light/--card-mid/--card-dark below, which the grain multiplies over. */
  --canvas:#eceded;--paper:#e5e4de;--ink:#4b4b49;--pressed:#5a5a56;--muted:#646462;--rule:#cfcec3;
  /* These three read cool because .paper MULTIPLIES the cotton grain over them and that
     JPEG is warm (mean 238,234,225 - a 1 : .983 : .944 cast). The product is the card:
     L 232 at the lit corner falling to 223, in the same hue the card had before. Edit the
     multiplied result, not these stops, by eye. */
  --card-light:#fafdff;--card-mid:#f6f9fc;--card-dark:#f1f3f4;--header-rgb:236,237,237;
  /* Header height (matches .site-header at each breakpoint) and the 4px vertical scale every
     recurring relationship sits on: label-to-value 12, group gap 36, crease clearance 24. */
  --header-h:78px;
  --font-family:'Invitation Helvetica','Helvetica Neue',Arial,sans-serif;
  --display:var(--font-family);--sans:var(--font-family);--heading-tracking:.06em;--heading-stroke:.01em;
  --ink-floor:.9;
  --gutter:24px;--paper-shift:0px;--sketch-shift:0px;
}
*{box-sizing:border-box}
/* Anchored sections park exactly one header plus one spacing step below the top - the same
   distance the resting layout uses - instead of an arbitrary fixed offset. */
html{scroll-behavior:smooth;scroll-padding-top:calc(var(--header-h) + 24px)}
body{margin:0;background:var(--canvas);color:var(--ink);font:300 14px/1.8 var(--sans);font-synthesis:none;isolation:isolate;-webkit-font-smoothing:antialiased}
/* Grayscale is baked into the tile (paper-transport-grey.jpg) so the viewport-sized fixed
   layer carries no live filter. The -96px overdraw is the desk-drift travel budget: the
   scroll timeline below moves this layer up to 90px, and travel must stay under overdraw. */
.paper-backdrop{position:fixed;inset:-96px 0;z-index:-1;pointer-events:none;background:url('assets/paper-transport-grey.jpg') repeat;background-size:650px;opacity:.14;transform:translate3d(0,var(--paper-shift),0)}
.material-filters{position:absolute;overflow:hidden;pointer-events:none}
a{color:inherit;text-decoration:none}
button,input,select,textarea{font:inherit}
a:focus-visible{outline:2px solid var(--ink);outline-offset:5px}
p,h1,h2,h3,figure{margin:0}
h1,h2,h3{font-weight:300}
.skip{position:fixed;left:16px;top:-100px;z-index:20;padding:12px 20px;border:1px solid var(--ink);border-radius:24px;background:var(--paper)}
.skip:focus{top:12px}
.site-header{position:sticky;top:0;z-index:10;height:78px;display:grid;place-items:center;background:rgba(var(--header-rgb),.94);border-bottom:1px solid rgba(210,210,200,.3)}
/* The frosted glass is desktop-only: re-blurring the backdrop every scroll frame is a top
   mobile jank source and Chromium-mobile's best-known black-surface bug class. Phones get
   the .94 solid, which is also what prefers-reduced-transparency users always got. */
@media(pointer:fine){
  @supports(backdrop-filter:blur(10px)){.site-header{background:rgba(var(--header-rgb),.80);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}}
}
.site-header nav{display:flex;align-items:center;justify-content:center;gap:clamp(12px,4vw,42px)}
/* The nav speaks the suite's language: small tracked capitals like every label on the stock. */
.site-header a{display:grid;place-items:center;position:relative;min-height:44px;padding:0 12px;font-size:13px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink)}
/* No graphite filter here: the rule is 1px tall, so its filter region was shorter than one
   vertical noise period and sampled an arbitrary row, and the compositor then stretched that
   cached output sideways through the scaleX transition. The gradient alone is the texture. */
.site-header a::after{content:'';position:absolute;bottom:3px;left:12px;right:calc(12px + .16em);height:1px;background:linear-gradient(90deg,#9b9c92,#5d6053 28%,#74776a 78%,#acada4);transform:scaleX(0);transform-origin:left;transition:transform .28s ease}
/* The current-page state was a no-op: the colour landed on the <a> while .sketch-text set its
   own. Target the span so [aria-current] actually reads darker than its neighbours. */
.site-header a[aria-current]{color:var(--ink)}
.site-header a[aria-current] .sketch-text{color:var(--ink)}
.site-header a[aria-current]::after,.site-header a:hover::after{transform:scaleX(1)}
.page{width:min(100% - var(--gutter)*2,960px);margin:24px auto 0;display:grid;gap:64px}
/* Suppress the ring only for programmatic focus (skip link, nav targets); a real keyboard
   focus still gets one. */
.page:focus:not(:focus-visible),.paper:focus:not(:focus-visible){outline:none}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.paper{position:relative;isolation:isolate;padding:50px 34px 44px;background-color:var(--paper);background-image:url('assets/paper-transport.jpg'),linear-gradient(145deg,var(--card-light),var(--card-mid) 48%,var(--card-dark));background-size:420px,auto;background-blend-mode:multiply,normal;border:0;border-radius:0;box-shadow:0 1px 2px rgba(77,73,58,.13),1px 5px 7px -4px rgba(77,73,58,.23),3px 16px 24px -14px rgba(77,73,58,.21);scroll-margin-top:0}
/* Broad upper-left illumination is painted with the grain: no separate top edge. */
/* The continuous sheet: index.html wraps the three sections in .sheet, which carries the
   stock (grain, tone, shadow) once so it runs unbroken across them; the sections keep .paper
   for padding and for the standalone pages (404) that still render as a lone card. */
.sheet{position:relative;isolation:isolate;background-color:var(--paper);background-image:url('assets/paper-transport.jpg'),linear-gradient(160deg,var(--card-light),var(--card-mid) 48%,var(--card-dark));background-size:420px,auto;background-blend-mode:multiply,normal;box-shadow:0 1px 2px rgba(77,73,58,.13),1px 5px 7px -4px rgba(77,73,58,.23),3px 16px 24px -14px rgba(77,73,58,.21)}
.sheet .paper{background:none;box-shadow:none}
/* Creases are pre-rendered rasters of the blind press (assets/crease-h.png, 3x, transparent
   ground), not live filters: a full-width filtered strip was the prime candidate for the
   black-bar artifacts on phones, and the raster is pixel-identical at a thousandth the cost.
   border-image keeps the soft end caps fixed while the middle stretches. */
.sheet-crease,.details-grid::before,.registry-status::before{height:7px;border-style:solid;border-color:transparent;border-width:0 12px;border-image:url('assets/crease-h.png') 0 36 fill stretch}
.sheet-crease{margin:0 34px}
.eyebrow{font:300 12px/1.9 var(--sans);letter-spacing:.18em;text-transform:uppercase}
/* One impression and one light source across all letter sizes. The colour and depth of
   the relief live on the feFlood primitives in index.html so the filters still read
   correctly with no stylesheet; the -webkit-text-stroke below is not paint, it only
   widens the alpha the filter presses, standing in for the spread of a real die.
   There is deliberately no text-shadow fallback: any shadow would be part of
   SourceAlpha and would be pressed into the paper along with the glyph, and a
   text-shadow:none sitting beside a dropped filter declaration still applies, so such
   a fallback cannot fire anyway. --pressed carries that job instead. It is near-ink:
   while the filter works it is never painted, so the value costs nothing, and when the
   filter is dropped or its reference cannot be resolved the text degrades to flat
   readable lettering rather than to a ghost. */
.pressed,.embossed-copy{
  color:var(--pressed);
  -webkit-text-stroke:var(--letter-weight,.2px) currentColor;
}
.pressed{filter:url('#paper-press')}
.button{
  position:relative;isolation:isolate;display:inline-flex;align-items:center;justify-content:center;gap:16px;
  min-height:48px;max-width:100%;margin-top:27px;padding:12px 24px;border:1px solid transparent;border-radius:14px;
  background:var(--paper);color:var(--ink);
  box-shadow:-1px -1px 2px rgba(255,255,250,.8),1px 2px 2px rgba(93,92,72,.24),2px 5px 7px -3px rgba(93,92,72,.20);
  font-size:15px;line-height:1.5;letter-spacing:.025em;text-shadow:none;
  transition:box-shadow .16s ease,transform .16s ease;
}
/* The button uses the card's cotton fibers; relief is a separate matte layer. */
.button::before{content:'';position:absolute;inset:-1px;z-index:-2;pointer-events:none;border-radius:inherit;background:url('assets/paper-transport.jpg') repeat;background-size:460px;filter:saturate(.55) brightness(.98);opacity:.40}
.button::after{
  content:'';position:absolute;inset:-1px;z-index:-1;pointer-events:none;border-radius:inherit;
  background:linear-gradient(155deg,rgba(255,255,250,.30),rgba(255,255,250,.06) 45%,rgba(105,104,84,.07));
  box-shadow:inset 1px 2px 2px rgba(255,255,250,.85),inset -1px -2px 3px rgba(106,105,83,.17);
  transition:box-shadow .16s ease;
}
.arrow{font:300 21px/1 var(--sans);letter-spacing:0;transition:transform .18s ease}
@media(hover:hover){.button:hover{box-shadow:-1px -1px 3px rgba(255,255,250,.85),1px 2px 2px rgba(93,92,72,.27),2px 5px 8px -3px rgba(93,92,72,.24)}.button:hover .arrow{transform:translateX(2px)}}
.button:active{box-shadow:0 1px 1px rgba(255,255,250,.85);transform:translateY(1px);transition-duration:.06s}
.button:active::after{background:linear-gradient(155deg,rgba(105,104,84,.12),transparent 65%,rgba(255,255,250,.18));box-shadow:inset 2px 3px 5px rgba(82,81,62,.34),inset -1px -2px 3px rgba(255,255,250,.92);transition-duration:.06s}
.button:active .arrow{transform:translateX(1px)}
.invitation{text-align:left}
.invitation-names{text-align:left;min-width:0}
/* One letterfit at every size: the old per-breakpoint grades ran tightest-when-smallest,
   the inverse of optical practice, and matched the printed card at only one width. */
.names{--name-tracking:.04em;--letter-weight:max(.35px,.008em);display:grid;justify-items:start;gap:14px;font:300 clamp(26px,7vw,58px)/1.2 var(--display);letter-spacing:var(--name-tracking);text-transform:uppercase}
.section-title{--letter-weight:var(--heading-stroke)}
/* Names, the ampersand and event information share the card's left text edge. */
.person{display:block;white-space:nowrap}
.join{font-size:20px;line-height:1;letter-spacing:.05em;-webkit-text-stroke:.3px currentColor}
.invitation-info{min-width:0;margin-top:36px}
.invitation-date{--letter-weight:.012em;font:300 30px/1.5 var(--display);letter-spacing:.16em;white-space:nowrap}
.invitation-venue{margin-top:24px}
.invitation-venue .eyebrow{font-size:17px;letter-spacing:.12em;line-height:1.7}
.invitation-venue .supporting{margin-top:8px}
.section-heading{margin-bottom:36px}
.section-title{font:300 32px/1.35 var(--display);letter-spacing:var(--heading-tracking);text-transform:uppercase;margin-top:20px;max-width:100%;text-wrap:balance}
.section-date{margin-top:12px;font:300 13px/2 var(--sans);letter-spacing:.16em;text-transform:uppercase}
/* The card stock carries no printed rules any more: each old 1px border is now a crease
   blind-pressed by the same filter as the letters. The ribbon is 2px of --rule paint inside a
   7px box - the paint is never shown while the filter runs (only its alpha is pressed), and
   when the filter is dropped it degrades to a plain hairline. */
.details-grid{padding-top:0}
.details-grid::before{content:'';display:block;margin:0 0 24px}
/* "Venue" is the h3 and "Barnes Foundation" the value, matching how Schedule/Dress code/Travel
   are structured. Styled by class so the heading list reads correctly without a visual change. */
.venue .venue-name{font:300 17px/1.7 var(--sans);letter-spacing:.12em;text-transform:uppercase;margin-top:12px}
.supporting{font-size:13px;line-height:2;letter-spacing:.16em;text-transform:uppercase}
.venue>.supporting{margin-top:8px}
.venue-art{margin:36px 0}
.sketch-window{overflow:hidden;aspect-ratio:760/507}
/* The overscale is the parallax's entire travel budget: the image and the window share an aspect
   ratio, so nothing else gives. 1.08 leaves 4% at each edge. SKETCH_OVERSCALE in site.js clamps
   the shift to it and must be kept in step with this number. */
.venue-sketch{display:block;width:100%;height:100%;object-fit:contain;mix-blend-mode:multiply;transform:translate3d(0,var(--sketch-shift),0) scale(1.08)}
/* The rules between detail blocks are gone entirely; air does the separating. */
.detail{margin-top:36px}
.detail:first-child{margin-top:0}
.detail-title{font:300 14px/1.8 var(--sans);letter-spacing:.14em;text-transform:uppercase;margin-top:12px}
.detail .supporting{margin-top:8px}

.registry .section-heading{margin-bottom:36px}
.gift-note{max-width:28ch;font:300 15px/1.9 var(--sans);letter-spacing:.12em;text-transform:uppercase}
.registry-status{margin-top:36px}
.registry-status::before{content:'';display:block;margin:0 0 24px}
.site-footer{width:min(100% - var(--gutter)*2,960px);margin:58px auto 0;padding:23px 0 28px;border-top:1px solid var(--rule);display:flex;flex-direction:column;align-items:flex-start;gap:16px;font-size:13px;letter-spacing:.025em;color:var(--muted)}
.site-footer p span{padding:0 5px}
.back-to-top{display:inline-flex;align-items:center;justify-content:center;gap:12px;min-height:44px;margin:0;padding:9px 4px;font-size:14px}
/* The underline is part of SourceAlpha, so while the filter runs the ink flood covers it at the
   same opacity as the letters and this colour is not what you see. It is what the contrast,
   print and forced-colors paths fall back to, so it tracks the ink rather than the old grey. */
.back-to-top:hover{text-decoration:underline;text-decoration-color:var(--ink);text-underline-offset:5px}
/* Larger copy takes a fractionally wider impression, as a deeper press would. */
.eyebrow.embossed-copy{--letter-weight:.18px}
.venue .venue-name.embossed-copy,.invitation-venue .eyebrow{--letter-weight:.24px}
span.embossed-copy{display:inline-block}
/* The practical copy is inked as well as pressed. A blind impression measures about
   1.1:1 against the card - faithful to the printed piece, but not readable at 14-16px, and
   this is the text carrying the venue, the date and the details. The display type (.pressed)
   stays blind, so the suite still reads as one piece: deboss for the display, inked
   letterpress for the copy, which is how most invitation suites actually work.
   --ink-floor is the face opacity, and the ink is the last node of the merge so nothing
   paints over it. .9 measures 5.2:1 against the darkest corner of the card and 5.6:1 at
   the lit one - .85 would be 4.6:1, which clears AA against the grain's MEAN but leaves
   only about four levels before its darker fibres drop under 4.5:1. .55 gives 2.5:1, and
   0 restores the blind impression exactly. Set it on :root - a filter primitive does NOT
   inherit custom properties from the element being filtered, only from its own
   ancestors. */
.embossed-copy{filter:url('#paper-press-copy')}
/* On card stock the copy is blind, like the printed piece; only the footer - which sits on the
   desk, not on stock - keeps its ink. */
.paper .embossed-copy{filter:url('#paper-press-blind')}
/* One filter surface per run of adjacent lines instead of one per line (index.html adds
   .press-group where a block is text-only): identical output, a fraction of the GPU cost.
   Children must drop their own filter or they would be pressed twice. */
.press-group{filter:url('#paper-press-blind')}
.press-group .embossed-copy{filter:none}
#paper-press-copy .ink-floor{flood-color:var(--ink,#4b4b49);flood-opacity:var(--ink-floor,.9)}
/* The selection highlight is painted inside the filtered subtree, so it lands in SourceAlpha -
   the shape the whole impression is derived from. Left alone it replaces the letters with an
   embossed outline of the selection rectangle, and on the inked copy the flood fills that
   rectangle solid. Making it transparent keeps the impression intact; the text still selects
   and copies, it just has no visible highlight, which is the better half of the trade. */
.pressed::selection,.pressed *::selection,
.embossed-copy::selection,.embossed-copy *::selection{background:transparent}
.sketch-text{color:#55574e;-webkit-text-stroke:.08px currentColor;text-shadow:none;filter:url('#graphite-pencil')}
span.sketch-text{display:inline-block}
@media(min-width:600px){
  :root{--gutter:40px}
  .page{margin-top:38px;gap:76px}
  .paper{padding:62px 48px 54px}
  .sheet-crease{margin-inline:48px}
  
  .invitation-date{font-size:36px}
  .mobile-break{display:none}
  .section-title{font-size:38px}
  .button{padding-inline:25px}
  .venue .venue-name{font-size:18px}
  .site-footer{flex-direction:row;align-items:center;justify-content:space-between;padding-top:18px}
}
@media(min-width:900px){
  :root{--header-h:86px}
  .site-header{height:86px}
  .page{margin-top:46px;gap:88px}
  .paper{padding:72px 72px 58px}
  .sheet-crease{margin-inline:72px}
  .button{padding-inline:23px;gap:14px}
  .section-title{font-size:40px}
  .details-grid{display:grid;grid-template-columns:1.15fr 1fr;gap:36px 55px;padding-top:0}
  .details-grid::before{grid-column:1/-1;margin-bottom:0}
  .venue-art{margin:36px 0 0}
  .registry{display:grid;grid-template-columns:1.15fr 1fr;gap:55px;align-items:center}
  .registry .section-heading{margin:0}
  /* The old printed border-left, now a static raster of the pressed crease, centred in the
     55px gutter so the note's text lands on the same edge as the details column. */
  .registry-note{position:relative;padding-left:0}
  .registry-note::before{content:'';position:absolute;left:-32px;top:4px;bottom:4px;width:9px;border-style:solid;border-color:transparent;border-width:12px 0;border-image:url('assets/crease-v.png') 36 0 fill stretch}
  .gift-note{font-size:16px}
}
@media(max-width:359px){
  :root{--gutter:16px}
  .paper{padding-inline:16px}
  /* .person is nowrap, so below about 300px the longest name overflowed the card and the page
     scrolled sideways. The clamp floor of 26px has to give way before the card does. */
  .names{font-size:min(26px,8.6vw)}
  .invitation-date{font-size:28px}
  .section-title{font-size:28px}
  .button{padding-inline:18px;gap:12px}
}
/* Phones are a still desk: no live nav filter, and offscreen sections skip raster entirely
   so their filter surfaces are never allocated until needed. All scroll-linked motion below
   is scoped to (pointer:fine), so it simply never declares on touch devices. */
@media(hover:none) and (pointer:coarse){
  .sketch-text{filter:none}
}
@media(max-width:599px){
  .details,.registry{content-visibility:auto;contain-intrinsic-size:auto 900px}
}
/* Scroll-driven motion, compositor-only. The desk grain recedes at one constant slow rate
   for the whole scroll (site.js's rAF write is the fallback for engines without timelines),
   and the venue sketch drifts behind its mat on a view timeline. Travel budgets: desk 90px
   against the backdrop's 96px overdraw; sketch 3.7% against the 4% overscale slack. */
@media(pointer:fine){
  @supports(animation-timeline:scroll()){
    .paper-backdrop{animation:desk-drift linear both;animation-timeline:scroll(root)}
  }
  @supports(animation-timeline:view()){
    .venue-sketch{animation:sketch-drift linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
  }
}
@keyframes desk-drift{from{transform:translate3d(0,0,0)}to{transform:translate3d(0,-90px,0)}}
@keyframes sketch-drift{from{transform:translate3d(0,3.7%,0) scale(1.08)}to{transform:translate3d(0,-3.7%,0) scale(1.08)}}
@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation:none!important;transition:none!important}
  .paper-backdrop{transform:none}.venue-sketch{transform:none}
}
@media(prefers-reduced-transparency:reduce),(prefers-contrast:more){
  .site-header{background:var(--canvas);backdrop-filter:none;-webkit-backdrop-filter:none}
  .button{background:var(--paper);border-color:var(--ink)}.button::before,.button::after{display:none}.pressed,.join,.embossed-copy,.press-group,.sketch-text{text-shadow:none;filter:none;-webkit-text-stroke:0!important;color:var(--ink)}
  .site-header a::after{filter:none;background:var(--ink)}
}
/* Printing drops the card background, which takes the white highlight with it, and no
   glyph face is ever painted - without this a printed save-the-date comes out blank. */
@media print{
  .pressed,.join,.embossed-copy,.press-group,.sketch-text{filter:none;text-shadow:none;-webkit-text-stroke:0!important;color:#3a3a38}
  .site-header,.skip,.back-to-top,.paper-backdrop{display:none}
  /* Crease rasters would print as pale smears; a hairline says the same thing in print. */
  .sheet-crease,.details-grid::before,.registry-status::before{border-image:none;border-width:0;background:linear-gradient(var(--rule),var(--rule)) 0 50%/100% 1px no-repeat}
  .registry-note::before{border-image:none;border-width:0;background:linear-gradient(90deg,var(--rule),var(--rule)) 50% 0/1px 100% no-repeat}
  .paper,.sheet{box-shadow:none}
  .page{gap:36px}
}
@media(forced-colors:active){
  .paper-backdrop{display:none}
  .paper{border:1px solid CanvasText;box-shadow:none;background:Canvas}.pressed,.join,.embossed-copy,.press-group,.sketch-text{text-shadow:none;filter:none;-webkit-text-stroke:0!important;color:CanvasText}
  /* One border on the sheet, not three nested ones. */
  .sheet{border:1px solid CanvasText;box-shadow:none;background:Canvas}.sheet .paper{border:0}
  .site-header{background:Canvas}.site-header a[aria-current]{text-decoration:underline}
  .site-header a::after{filter:none;background:CanvasText}
  /* Forced colors does not reset mix-blend-mode, so multiply against a black Canvas turned the
     whole illustration into a solid black rectangle in dark High Contrast themes. */
  .venue-sketch{mix-blend-mode:normal;filter:none}
  .sketch-window{border:1px solid CanvasText}
  /* Forced colors strips background gradients, so the creases vanish; give the two horizontal
     dividers their borders back and drop the pseudos. */
  .details-grid{border-top:1px solid CanvasText;padding-top:24px}
  .registry-status{border-top:1px solid CanvasText;padding-top:20px}
  .details-grid::before,.registry-status::before,.registry-note::before{display:none}
  .sheet-crease{border-image:none;border-width:0;height:1px;background:CanvasText}
  .button{border-color:ButtonText;box-shadow:none;text-shadow:none}.button::before,.button::after{display:none}
}
