/*
Theme Name: Stoffgeflüster
Theme URI: https://stoffgefluester.at
Author: Daniel
Description: Block theme (Full Site Editing) for a handmade textile label. Brand colours Beere and Mint, self-hosted webfonts, and a printed-paper motif: the whole page is one textured sheet lying on a mint wash. Ships a code-defined menu bar with mobile drawer, a "Werk" custom post type, a card list block and a detail-page carousel block.
Requires at least: 6.5
Tested up to: 6.8
Requires PHP: 8.0
Version: 0.4.4
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: stoffgefluester
Tags: block-theme, full-site-editing, custom-post-type
*/

/* -----------------------------------------------------------------------------
 * WHERE DOES STYLING LIVE IN THIS THEME?
 *
 * 1. theme.json          -> design tokens (colours, fonts, font sizes, spacing,
 *                           layout widths) and defaults for core blocks. Prefer
 *                           this. WordPress turns every entry into a CSS custom
 *                           property such as var(--wp--preset--color--beere)
 *                           and, for fontFace entries, into @font-face rules —
 *                           so there is no font loading code anywhere.
 * 2. blocks/<name>/style.css
 *                        -> styles for one custom block. Only loaded on pages
 *                           where that block actually renders.
 * 3. this file           -> the few global things theme.json cannot express:
 *                           the paper sheet, the seam, focus rings, motion.
 *
 * Rule of thumb: if you can express it as a token, put it in theme.json.
 * -------------------------------------------------------------------------- */

/* =============================================================================
 * THE SHEET
 *
 * The whole page is printed on one card: WordPress wraps every block template
 * in <div class="wp-site-blocks">, so that element — header, content and footer
 * together — is the sheet, and <body> is the mint surface it lies on. No
 * template markup is involved, which is why every template got this for free.
 *
 * MAKING CONTENT LOOK PRINTED RATHER THAN PLACED ON TOP
 * Three rules do almost all the work, and the third is the one people miss:
 *
 *   1. Nothing on the sheet casts an ELEVATION shadow. A soft, wide drop shadow
 *      is the browser saying "this floats above the surface", which is the
 *      exact opposite of ink. Only the sheet itself has one.
 *      The one deliberate exception is the werk cards, which are sewn-on
 *      patches (see blocks/item-list/style.css): a patch genuinely is one layer
 *      up, so it gets a 1px contact shadow describing the thickness of the
 *      material — and nothing softer or wider, or it starts hovering.
 *   2. Nothing is pure white or pure black. The paper is #fffefc and the ink is
 *      #111, so the page never shows a colour a printer could not mix.
 *   3. The grain lies OVER the content, not under it. ::after covers the whole
 *      sheet and multiplies a fine noise across text, buttons and photographs
 *      alike. Because every element receives the same tooth they read as one
 *      printed surface instead of as layers — texture only under the content
 *      leaves the ink looking pasted on afterwards.
 *
 * The two textures are inline SVG feTurbulence, so there is no image request
 * and nothing to go blurry on a retina screen.
 *
 * HOW STRONG THE TEXTURE MAY BE
 * A texture on near-white paper can only ever subtract light — there is no
 * headroom above white to lighten into, which is why soft-light (mid-grey =
 * "leave alone", brighter = lift) produces literally nothing here and multiply
 * is the only workable blend. The entire craft is therefore in how *little* it
 * subtracts. The first version used noise remapped into [0.90, 1] at 0.9
 * opacity: that dims the sheet by 6.9%, which measured RGB 246 against a 254
 * target and read as grey paper — and grey paper makes black text look dirty
 * rather than printed.
 *
 * The values below dim it by about 1.2%, which is still a visible tooth but
 * keeps the sheet bright and warm. Push the intercepts toward 1 for smoother,
 * cleaner stock; drop them for rougher paper, and expect the page to get
 * perceptibly darker as you do. Anything past about 3% starts to look soiled.
 * ========================================================================== */

:root {
	--sg-paper: var(--wp--preset--color--papier, #fefdfb);

	/* Fine tooth of the stock, laid over everything. Range [0.975, 1]. */
	--sg-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n' x='0' y='0' width='160' height='160' filterUnits='userSpaceOnUse'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='0.025' intercept='0.975'/%3E%3CfeFuncG type='linear' slope='0.025' intercept='0.975'/%3E%3CfeFuncB type='linear' slope='0.025' intercept='0.975'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");

	/* Long, soft fibres, laid under the ink. Range [0.993, 1]. */
	--sg-fibre: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='340' height='340'%3E%3Cfilter id='n' x='0' y='0' width='340' height='340' filterUnits='userSpaceOnUse'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.010 0.5' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='0.007' intercept='0.993'/%3E%3CfeFuncG type='linear' slope='0.007' intercept='0.993'/%3E%3CfeFuncB type='linear' slope='0.007' intercept='0.993'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='340' height='340' filter='url(%23n)'/%3E%3C/svg%3E");

	/* The seam: a dashed line that reads like a sewing stitch — the one
	   handmade cue in an otherwise clean layout. */
	--sg-seam: 2px dashed var(--wp--preset--color--mint, #6fc2b6);

	/* The surface of a sewn-on patch (the werk cards). Deliberately not the
	   paper white: a patch has to read as a different piece of material, or the
	   stitching around it looks like a drawn border. Undyed flax rather than a
	   neutral off-white: it is the colour that lets the weave below read as
	   linen instead of as graph paper. */
	--sg-patch: #f9f7ed;

	/*
	 * THE WEAVE. One 60x60 tile of warp and weft threads, drawn as SVG paths
	 * so that they can CURVE. A repeating-linear-gradient can only ever produce
	 * dead straight, perfectly evenly spaced lines, which is precisely what
	 * made the old patch surface look printed rather than woven.
	 *
	 * Three properties do the work:
	 *   - every thread is a curve, `M0 y Q15 y±a 30 y T60 y`. The T command
	 *     mirrors the preceding control point, so the path leaves the tile at
	 *     the same y AND the same slope it entered with — which is what lets a
	 *     wobbly line tile seamlessly. Break that and a kink appears every 60px.
	 *   - the sett is uneven: threads sit 4–7px apart rather than on a grid,
	 *     and a few are drawn fatter. That irregularity is linen's slub, and it
	 *     is the difference between cloth and millimetre paper.
	 *   - the stroke is a warm brown-grey at 3–7% opacity, not black. Grey
	 *     threads on a warm ground read as pencil; brown ones read as flax.
	 *
	 * Regenerate with `python3 bin/gen-linen.py` rather than hand-editing the
	 * data URI: the script is seeded, so it is reproducible, and every path has
	 * to keep the Q/T shape above to stay seamless — easy to lose when nudging
	 * a number by hand.
	 */
	--sg-linen: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60'%3E%3Cg fill='none' stroke='%23594a36' stroke-linecap='round'%3E%3Cpath d='M0 1.2 Q15 0.20 30 1.2 T60 1.2' stroke-opacity='0.05' stroke-width='1'/%3E%3Cpath d='M0 6.0 Q15 6.96 30 6.0 T60 6.0' stroke-opacity='0.03' stroke-width='1'/%3E%3Cpath d='M0 10.6 Q15 11.58 30 10.6 T60 10.6' stroke-opacity='0.06' stroke-width='1.3'/%3E%3Cpath d='M0 17.9 Q15 18.99 30 17.9 T60 17.9' stroke-opacity='0.06' stroke-width='0.8'/%3E%3Cpath d='M0 22.3 Q15 22.97 30 22.3 T60 22.3' stroke-opacity='0.06' stroke-width='1.3'/%3E%3Cpath d='M0 27.5 Q15 28.53 30 27.5 T60 27.5' stroke-opacity='0.05' stroke-width='1'/%3E%3Cpath d='M0 33.3 Q15 32.20 30 33.3 T60 33.3' stroke-opacity='0.06' stroke-width='1.3'/%3E%3Cpath d='M0 40.7 Q15 42.10 30 40.7 T60 40.7' stroke-opacity='0.06' stroke-width='1'/%3E%3Cpath d='M0 47.4 Q15 48.36 30 47.4 T60 47.4' stroke-opacity='0.03' stroke-width='0.8'/%3E%3Cpath d='M0 52.8 Q15 51.54 30 52.8 T60 52.8' stroke-opacity='0.03' stroke-width='0.8'/%3E%3Cpath d='M0 59.8 Q15 60.30 30 59.8 T60 59.8' stroke-opacity='0.07' stroke-width='0.8'/%3E%3Cpath d='M2.0 0 Q2.86 15 2.0 30 T2.0 60' stroke-opacity='0.06' stroke-width='1'/%3E%3Cpath d='M8.7 0 Q9.44 15 8.7 30 T8.7 60' stroke-opacity='0.04' stroke-width='0.8'/%3E%3Cpath d='M15.9 0 Q17.08 15 15.9 30 T15.9 60' stroke-opacity='0.04' stroke-width='0.8'/%3E%3Cpath d='M20.2 0 Q19.28 15 20.2 30 T20.2 60' stroke-opacity='0.04' stroke-width='1'/%3E%3Cpath d='M25.8 0 Q26.47 15 25.8 30 T25.8 60' stroke-opacity='0.05' stroke-width='1.3'/%3E%3Cpath d='M30.4 0 Q31.28 15 30.4 30 T30.4 60' stroke-opacity='0.03' stroke-width='1'/%3E%3Cpath d='M37.8 0 Q38.32 15 37.8 30 T37.8 60' stroke-opacity='0.05' stroke-width='0.8'/%3E%3Cpath d='M42.1 0 Q41.47 15 42.1 30 T42.1 60' stroke-opacity='0.04' stroke-width='1'/%3E%3Cpath d='M49.0 0 Q49.85 15 49.0 30 T49.0 60' stroke-opacity='0.03' stroke-width='1'/%3E%3Cpath d='M53.2 0 Q52.37 15 53.2 30 T53.2 60' stroke-opacity='0.04' stroke-width='1.3'/%3E%3C/g%3E%3C/svg%3E");

	/* The fuzz: fine, slightly horizontally drifting noise laid OVER the weave
	   with multiply. Without it the threads are too clean and the 60px repeat
	   becomes findable; with it the surface reads as fibrous. Range [0.95, 1] —
	   stronger than the paper's grain because cloth is rougher than stock, but
	   the same "subtract little" rule from the sheet applies. */
	--sg-linen-fuzz: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='f' x='0' y='0' width='120' height='120' filterUnits='userSpaceOnUse'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.42 0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='0.05' intercept='0.95'/%3E%3CfeFuncG type='linear' slope='0.05' intercept='0.95'/%3E%3CfeFuncB type='linear' slope='0.05' intercept='0.95'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23f)'/%3E%3C/svg%3E");

	/*
	 * The frayed edge, used as a MASK on the patch: a rounded rect pushed
	 * around by feTurbulence (about ±2.5px) and blurred by half a pixel, so the
	 * cut edge of the cloth wanders a little and is soft rather than
	 * vector-crisp. Authored at roughly card proportions and stretched with
	 * `mask-size: 100% 100%`; at the aspect ratios cards actually take, the
	 * distortion stays well under a pixel, so it needs no slicing.
	 */
	--sg-patch-edge: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='420'%3E%3Cfilter id='e'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.05' numOctaves='3' seed='4' result='n'/%3E%3CfeDisplacementMap in='SourceGraphic' in2='n' scale='5' xChannelSelector='R' yChannelSelector='G'/%3E%3CfeGaussianBlur stdDeviation='0.7'/%3E%3C/filter%3E%3Crect x='4' y='4' width='312' height='412' rx='10' fill='%23fff' filter='url(%23e)'/%3E%3C/svg%3E");

	--sg-radius-sheet: 22px;
	--sg-radius-card: 12px;
	--sg-radius: 10px;
	--sg-radius-pill: 999px;

	/* The only shadow on the site. Tinted with the brand purple rather than
	   grey, which is what makes the sheet look like it is lying on the mint
	   instead of being cut out of it. */
	--sg-shadow-sheet:
		inset 0 1px 0 rgba(255, 255, 255, .9),
		0 1px 2px rgba(17, 17, 17, .05),
		0 34px 64px -34px rgba(123, 59, 114, .45);

	/* Controls are the other exception to "nothing floats": a carousel arrow
	   sitting on top of a photograph is a physical control, not printed
	   content, and it has to separate from whatever image is behind it.
	   Content never gets this. */
	--sg-shadow-control: 0 1px 2px rgba(17, 17, 17, .10), 0 6px 14px -8px rgba(123, 59, 114, .40);

	--sg-transition: 220ms cubic-bezier(.2, .6, .3, 1);
}

.wp-site-blocks {
	position: relative;

	/*
	 * The mint has to show on all four sides at EVERY width — it is the only
	 * thing that says "card lying on a surface" rather than "page".
	 *
	 * `margin: <x> auto` does not achieve that: it sets the vertical margin and
	 * leaves the horizontal one to auto, and auto resolves to zero unless the
	 * element is already narrower than its container. With a 74rem cap that was
	 * true only above 1184px, so every viewport between the phone breakpoint and
	 * a large laptop rendered the sheet edge to edge. Subtracting the gap from
	 * the max-width instead makes the margin structural: the sheet is either
	 * 74rem or the viewport minus a gap on each side, whichever is smaller.
	 */
	--sg-sheet-gap: clamp(.75rem, 3vw, 2.5rem);
	/* border-box, so the padding below counts inside max-width — otherwise the
	   padding is added on top of it and eats half the gap back. */
	box-sizing: border-box;
	max-width: min(74rem, 100% - 2 * var(--sg-sheet-gap));
	margin-block: var(--sg-sheet-gap);
	margin-inline: auto;
	padding-inline: clamp(0rem, 1.5vw, 1.5rem);
	border-radius: var(--sg-radius-sheet);
	background-color: var(--sg-paper);
	background-image: var(--sg-fibre);
	background-blend-mode: multiply;
	box-shadow: var(--sg-shadow-sheet);
}

/*
 * The grain pass. pointer-events:none keeps it from swallowing clicks, and it
 * deliberately carries no z-index: positioned descendants that set one (the
 * mobile drawer at 90, the brand mark at 100) still paint above it.
 *
 * Do NOT give the sheet a filter, transform or backdrop-filter. Any of those
 * would make it the containing block for position:fixed descendants and the
 * mobile drawer would stop covering the viewport. mix-blend-mode on this
 * pseudo-element does not have that effect, which is why it is used here.
 */
.wp-site-blocks::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: inherit;
	pointer-events: none;
	background-image: var(--sg-grain);
	mix-blend-mode: multiply;
	opacity: .7;
}

/*
 * A phone has no room for a wide margin, but the sheet must never bleed to the
 * edge: seeing the mint on all four sides is the whole tell that this is a card
 * lying on a surface rather than the page itself. So the margin shrinks instead
 * of disappearing, and the corner radius comes down with it to stay in
 * proportion. The content keeps its own inset from theme.json's root padding on
 * top of this, so text never runs up against the paper's edge either.
 */
@media (max-width: 30em) {
	.wp-site-blocks {
		--sg-sheet-gap: .75rem;
		border-radius: 16px;
	}
}

body {
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

::selection {
	background: var(--wp--preset--color--mint, #6fc2b6);
	color: var(--wp--preset--color--schwarz, #111);
}

/* -----------------------------------------------------------------------------
 * The seam
 * -------------------------------------------------------------------------- */

/* Section headings get the seam underneath, hanging left so it reads as a
   stitch running past the text rather than as an underline. */
.sg-seam-heading {
	position: relative;
	padding-bottom: .4em;
}

.sg-seam-heading::after {
	content: "";
	position: absolute;
	inset-inline-start: 0;
	bottom: 0;
	width: 3.5rem;
	border-bottom: var(--sg-seam);
}

/* -----------------------------------------------------------------------------
 * Header: the brand mark, printed at the top of the sheet
 *
 * The dense logo sits to the left of the site title, both inside the menu row.
 * Set the logo under Appearance -> Editor -> Styles, or Customizer -> Site
 * Identity; with none set, the title alone carries the brand.
 * -------------------------------------------------------------------------- */

.sg-header .wp-block-site-logo img {
	display: block;
	height: auto;
	border-radius: var(--sg-radius);
}

/* The logo mark reads as heavier than the wordmark, so the pair needs a little
   air between them. */
.sg-header .wp-block-site-logo + .wp-block-site-title {
	margin-inline-start: .15rem;
}

/* The mobile drawer is fixed and covers the viewport (see
   blocks/site-menu/style.css). Lifting the brand mark above it keeps the header
   readable while the menu is open, next to the close button. */
.sg-header .wp-block-site-logo,
.sg-header .wp-block-site-title {
	position: relative;
	z-index: 100;
}

/* -----------------------------------------------------------------------------
 * Footer
 * -------------------------------------------------------------------------- */

.sg-footer {
	color: var(--wp--preset--color--grau, #5b5b66);
}

.sg-footer a {
	color: inherit;
	text-decoration-color: var(--wp--preset--color--mint, #6fc2b6);
	text-underline-offset: .25em;
}

.sg-footer a:hover {
	color: var(--wp--preset--color--beere-dunkel, #7b3b72);
}

/* -----------------------------------------------------------------------------
 * Accessibility floor. Keep these even if you rewrite everything else.
 * -------------------------------------------------------------------------- */

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
	outline: 2px solid var(--wp--preset--color--beere-dunkel, #7b3b72);
	outline-offset: 3px;
	border-radius: var(--sg-radius);
}

.sg-visually-hidden {
	position: absolute !important;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* Locks background scrolling while the mobile drawer is open.
   Set from blocks/site-menu/view.js. */
body.sg-menu-open {
	overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: .01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: .01ms !important;
		scroll-behavior: auto !important;
	}
}
