Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified site/public/brand/companion/broken.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/public/brand/companion/debug.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/public/brand/companion/hint.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/public/brand/companion/info.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/public/brand/companion/invalid.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/public/brand/companion/pitfall.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/public/brand/companion/success.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/public/brand/companion/warning.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
115 changes: 72 additions & 43 deletions site/src/components/Callout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
/**
* Callout — a labelled aside for the eight Guide Companion states.
*
* Each state pairs the painterly companion sprite (a trailing-edge badge) with a
* state-coloured glyph + label. The glyph and label carry the meaning, so the
* callout reads for colour-blind readers and in high-contrast modes; the sprite
* and its state-coloured screen-glow are enrichment.
* Each state pairs the painterly companion sprite (a trailing-edge badge, sliced
* and chroma-keyed from the 8-state sheet) with a state-coloured icon chip +
* label. The icon and label carry the meaning, so the callout reads for
* colour-blind readers and in high-contrast modes; the sprite and its
* state-coloured screen-glow are enrichment.
*
* Usage in MDX:
* import Callout from '../../../components/Callout.astro';
Expand All @@ -19,7 +20,7 @@
* </Callout>
*
* Colour exclusivity: amber=warning, mint=success, gray=invalid, red=broken;
* info/hint/pitfall/debug share the companion cyan and are told apart by glyph.
* info/hint/pitfall/debug share the companion cyan and are told apart by icon.
*/

type CalloutType = 'info' | 'hint' | 'warning' | 'pitfall' | 'success' | 'debug' | 'invalid' | 'broken';
Expand All @@ -30,27 +31,40 @@ interface Props {
title?: string;
}

const META: Record<CalloutType, { label: string; glyph: string }> = {
info: { label: 'Good to know', glyph: 'i' },
hint: { label: 'Tip', glyph: '✦' },
warning: { label: 'Heads up', glyph: '!' },
pitfall: { label: 'Common pitfall', glyph: '?' },
success: { label: "You're set", glyph: '✓' },
debug: { label: 'Debug tip', glyph: '[]' },
invalid: { label: 'Not supported', glyph: 'Ø' },
broken: { label: 'Runtime error', glyph: '✕' },
const LABELS: Record<CalloutType, string> = {
info: 'Good to know',
hint: 'Tip',
warning: 'Heads up',
pitfall: 'Common pitfall',
success: "You're set",
debug: 'Debug tip',
invalid: 'Not supported',
broken: 'Runtime error',
};

// Lucide-style line icons (24 viewBox, currentColor), one per state.
const ICONS: Record<CalloutType, string> = {
info: '<circle cx="12" cy="12" r="9"/><path d="M12 11v5"/><path d="M12 8h.01"/>',
hint: '<path d="M9 18h6"/><path d="M10 21h4"/><path d="M12 3a6 6 0 0 0-4 10.5c.7.7 1 1.3 1 2.5h6c0-1.2.3-1.8 1-2.5A6 6 0 0 0 12 3Z"/>',
warning: '<path d="m10.3 4 -8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.7-3l-8-14a2 2 0 0 0-3.4 0Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
pitfall: '<circle cx="12" cy="12" r="9"/><path d="M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3"/><path d="M12 17h.01"/>',
success: '<circle cx="12" cy="12" r="9"/><path d="m8.5 12 2.5 2.5 4.5-5"/>',
debug: '<path d="m15 8 4 4-4 4"/><path d="m9 8-4 4 4 4"/>',
invalid: '<circle cx="12" cy="12" r="9"/><path d="m5.6 5.6 12.8 12.8"/>',
broken: '<circle cx="12" cy="12" r="9"/><path d="m14.5 9.5-5 5"/><path d="m9.5 9.5 5 5"/>',
};

const { type = 'info', title } = Astro.props;
const meta = META[type];
const heading = title ?? meta.label;
const heading = title ?? LABELS[type];
const base = import.meta.env.BASE_URL;
---

<aside class="callout" data-callout={type} role="note">
<div class="callout__main">
<p class="callout__head">
<span class="callout__glyph" aria-hidden="true">{meta.glyph}</span>
<span class="callout__icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" set:html={ICONS[type]}></svg>
</span>
<span class="callout__label">{heading}</span>
</p>
<div class="callout__body">
Expand All @@ -62,8 +76,8 @@ const base = import.meta.env.BASE_URL;
src={`${base}brand/companion/${type}.webp`}
alt=""
aria-hidden="true"
width="64"
height="64"
width="168"
height="138"
loading="lazy"
decoding="async"
/>
Expand All @@ -78,45 +92,51 @@ const base = import.meta.env.BASE_URL;
--callout-accent: oklch(80% 0.1 195);
--callout-bg: color-mix(in oklab, var(--callout-accent), transparent 92%);
display: flex;
align-items: center;
gap: var(--s-4);
align-items: stretch;
gap: var(--s-3);
margin: var(--s-5) 0;
padding: var(--s-3) var(--s-4);
border: 1px solid var(--line-soft);
border-left: 3px solid var(--callout-accent);
border-radius: var(--r-3);
background: var(--callout-bg);
/* The companion is an upper-body crop seated on the bottom edge; clip
whatever extends past the rounded box so it reads as rising from it. */
overflow: hidden;
}

.callout__main {
flex: 1 1 auto;
min-width: 0;
align-self: center;
}

.callout__head {
display: flex;
align-items: center;
gap: var(--s-2);
margin: 0 0 var(--s-1);
font-size: 12px;
margin: 0 0 var(--s-2);
font-size: 13px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
letter-spacing: 0.02em;
color: var(--callout-accent);
}

.callout__glyph {
.callout__icon {
display: inline-grid;
place-items: center;
flex: 0 0 auto;
min-width: 18px;
height: 18px;
padding: 0 4px;
width: 26px;
height: 26px;
border-radius: var(--r-pill);
border: 1px solid var(--callout-accent);
font-size: 11px;
line-height: 1;
font-family: var(--f-mono);
background: color-mix(in oklab, var(--callout-accent), transparent 84%);
color: var(--callout-accent);
}

.callout__icon svg {
width: 16px;
height: 16px;
display: block;
}

.callout__body :global(p) {
Expand All @@ -136,11 +156,25 @@ const base = import.meta.env.BASE_URL;
in for the screen-glow that carries meaning at small sizes. */
.callout__companion {
flex: 0 0 auto;
align-self: center;
width: 64px;
height: 64px;
align-self: flex-end;
width: 168px;
height: auto;
/* Reach the right + bottom edges; overflow:hidden crops the cut base so
the companion sits in the callout. scaleX flips it to face the text. */
margin: 0 calc(-1 * var(--s-4)) calc(-1 * var(--s-3));
object-fit: contain;
filter: drop-shadow(0 0 12px color-mix(in oklab, var(--callout-accent), transparent 52%));
object-position: bottom;
/* Flip to face the text. pitfall/debug carry an asymmetric face glyph
(? and [ ]) that would mirror, so they keep their native orientation. */
transform: scaleX(-1);
filter: drop-shadow(0 0 16px color-mix(in oklab, var(--callout-accent), transparent 50%));
}

.callout[data-callout='pitfall'] .callout__companion,
.callout[data-callout='debug'] .callout__companion {
transform: none;
/* native orientation seats props on the right, so nudge it to that edge */
margin-right: calc(-1 * var(--s-2));
}

.callout[data-callout='info'],
Expand All @@ -167,13 +201,8 @@ const base = import.meta.env.BASE_URL;
}

@media (max-width: 560px) {
.callout {
gap: var(--s-3);
}

.callout__companion {
width: 48px;
height: 48px;
width: 116px;
}
}
</style>