Spaces:
Running
Running
thibaud frere
commited on
Commit
·
b6281fd
1
Parent(s):
0e04b08
update
Browse files
app/src/components/HtmlEmbed.astro
CHANGED
|
@@ -64,7 +64,7 @@ const mountId = `frag-${Math.random().toString(36).slice(2)}`;
|
|
| 64 |
</script>
|
| 65 |
|
| 66 |
<style>
|
| 67 |
-
.html-embed { margin:
|
| 68 |
.html-embed__title {
|
| 69 |
text-align: left;
|
| 70 |
font-weight: 600;
|
|
@@ -81,6 +81,7 @@ const mountId = `frag-${Math.random().toString(36).slice(2)}`;
|
|
| 81 |
.html-embed__card.is-frameless {
|
| 82 |
background: transparent;
|
| 83 |
border-color: transparent;
|
|
|
|
| 84 |
}
|
| 85 |
.html-embed__desc {
|
| 86 |
text-align: left;
|
|
|
|
| 64 |
</script>
|
| 65 |
|
| 66 |
<style>
|
| 67 |
+
.html-embed { margin: 0; }
|
| 68 |
.html-embed__title {
|
| 69 |
text-align: left;
|
| 70 |
font-weight: 600;
|
|
|
|
| 81 |
.html-embed__card.is-frameless {
|
| 82 |
background: transparent;
|
| 83 |
border-color: transparent;
|
| 84 |
+
padding: 0;
|
| 85 |
}
|
| 86 |
.html-embed__desc {
|
| 87 |
text-align: left;
|
app/src/components/Seo.astro
CHANGED
|
@@ -29,6 +29,7 @@ const jsonLd = {
|
|
| 29 |
image: ogImage ? [ogImage] : undefined,
|
| 30 |
};
|
| 31 |
---
|
|
|
|
| 32 |
<meta name="description" content={description} />
|
| 33 |
<link rel="canonical" href={url} />
|
| 34 |
|
|
|
|
| 29 |
image: ogImage ? [ogImage] : undefined,
|
| 30 |
};
|
| 31 |
---
|
| 32 |
+
<title>{title}</title>
|
| 33 |
<meta name="description" content={description} />
|
| 34 |
<link rel="canonical" href={url} />
|
| 35 |
|
app/src/content/chapters/available-blocks.mdx
CHANGED
|
@@ -358,26 +358,27 @@ import HtmlEmbed from '../components/HtmlEmbed.astro'
|
|
| 358 |
|
| 359 |
You can embed external content in your article using **iframes**. For example, **TrackIO or github code embeds** can be used this way.
|
| 360 |
|
|
|
|
| 361 |
<iframe frameborder="0" scrolling="no" style="width:100%; height:292px;" allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Fhuggingface%2Fpicotron%2Fblob%2F1004ae37b87887cde597c9060fb067faa060bafe%2Fsetup.py&style=default&type=code&showBorder=on&showLineNumbers=on"></iframe>
|
| 362 |
|
| 363 |
-
<
|
|
|
|
|
|
|
|
|
|
| 364 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
|
| 366 |
<small className="muted">Example</small>
|
| 367 |
```mdx
|
| 368 |
<iframe frameborder="0" scrolling="no" style="width:100%; height:292px;" allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Fhuggingface%2Fpicotron%2Fblob%2F1004ae37b87887cde597c9060fb067faa060bafe%2Fsetup.py&style=default&type=code&showBorder=on&showLineNumbers=on"></iframe>
|
| 369 |
<iframe src="https://trackio-documentation.hf.space/?project=fake-training-750735&metrics=train_loss,train_accuracy&sidebar=hidden&lang=en" width="100%" height="600" frameborder="0"></iframe>
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
### Gradio
|
| 373 |
-
|
| 374 |
-
You can also embed **gradio** apps.
|
| 375 |
-
|
| 376 |
-
<gradio-app theme_mode="light" space="gradio/hello_world"></gradio-app>
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
<small className="muted">Example</small>
|
| 381 |
-
```mdx
|
| 382 |
-
<gradio-app theme_mode="light" space="gradio/hello_world"></gradio-app>
|
| 383 |
```
|
|
|
|
| 358 |
|
| 359 |
You can embed external content in your article using **iframes**. For example, **TrackIO or github code embeds** can be used this way.
|
| 360 |
|
| 361 |
+
<small className="muted">Github code embed</small>
|
| 362 |
<iframe frameborder="0" scrolling="no" style="width:100%; height:292px;" allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Fhuggingface%2Fpicotron%2Fblob%2F1004ae37b87887cde597c9060fb067faa060bafe%2Fsetup.py&style=default&type=code&showBorder=on&showLineNumbers=on"></iframe>
|
| 363 |
|
| 364 |
+
<small className="muted">TrackIO embed</small>
|
| 365 |
+
<div className="">
|
| 366 |
+
<iframe src="https://trackio-documentation.hf.space/?project=fake-training-750735&metrics=train_loss,train_accuracy&sidebar=hidden&lang=en" width="100%" height="660" frameborder="0"></iframe>
|
| 367 |
+
</div>
|
| 368 |
|
| 369 |
+
<small className="muted">Gradio embed</small>
|
| 370 |
+
<div className="">
|
| 371 |
+
<iframe
|
| 372 |
+
src="https://gradio-hello-world.hf.space"
|
| 373 |
+
width="100%"
|
| 374 |
+
height="380"
|
| 375 |
+
frameborder="0"
|
| 376 |
+
></iframe>
|
| 377 |
+
</div>
|
| 378 |
|
| 379 |
<small className="muted">Example</small>
|
| 380 |
```mdx
|
| 381 |
<iframe frameborder="0" scrolling="no" style="width:100%; height:292px;" allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Fhuggingface%2Fpicotron%2Fblob%2F1004ae37b87887cde597c9060fb067faa060bafe%2Fsetup.py&style=default&type=code&showBorder=on&showLineNumbers=on"></iframe>
|
| 382 |
<iframe src="https://trackio-documentation.hf.space/?project=fake-training-750735&metrics=train_loss,train_accuracy&sidebar=hidden&lang=en" width="100%" height="600" frameborder="0"></iframe>
|
| 383 |
+
<iframe src="https://gradio-hello-world.hf.space" width="100%" height="380" frameborder="0"></iframe>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
```
|
app/src/content/embeds/d3-line.html
CHANGED
|
@@ -191,6 +191,8 @@
|
|
| 191 |
let runList = [];
|
| 192 |
let runOrder = [];
|
| 193 |
const dataByMetric = new Map(); // metric => { run => [{step,value}] }
|
|
|
|
|
|
|
| 194 |
|
| 195 |
// Scales and layout
|
| 196 |
let width = 800, height = 360;
|
|
@@ -231,14 +233,19 @@
|
|
| 231 |
yScale.range([innerHeight, 0]);
|
| 232 |
|
| 233 |
// Compute integer ticks for Y
|
| 234 |
-
const yDomain = yScale.domain();
|
| 235 |
-
const yMin = Math.min(yDomain[0], yDomain[1]);
|
| 236 |
-
const yMax = Math.max(yDomain[0], yDomain[1]);
|
| 237 |
-
let yStep = Math.max(1, Math.round((yMax - yMin) / 6));
|
| 238 |
-
if (!isFinite(yStep) || yStep <= 0) yStep = 1;
|
| 239 |
let yIntTicks = [];
|
| 240 |
-
|
| 241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
|
| 243 |
// Grid (horizontal)
|
| 244 |
gGrid.selectAll('*').remove();
|
|
@@ -255,7 +262,18 @@
|
|
| 255 |
|
| 256 |
// Axes
|
| 257 |
gAxes.selectAll('*').remove();
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
const yAxis = d3.axisLeft(yScale).tickValues(yIntTicks).tickSizeOuter(0).tickFormat(d3.format('d'));
|
| 260 |
gAxes.append('g')
|
| 261 |
.attr('transform', `translate(0,${innerHeight})`)
|
|
@@ -333,7 +351,13 @@
|
|
| 333 |
});
|
| 334 |
if (!isFinite(minStep) || !isFinite(maxStep)) { return; }
|
| 335 |
xScale.domain([minStep, maxStep]);
|
| 336 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
|
| 338 |
const { innerWidth, innerHeight } = updateScales();
|
| 339 |
|
|
@@ -345,6 +369,7 @@
|
|
| 345 |
.slice()
|
| 346 |
.sort((a,b)=>a.step-b.step)
|
| 347 |
.map(pt => isRankStrict ? { step: pt.step, value: Math.round(pt.value) } : pt)
|
|
|
|
| 348 |
}));
|
| 349 |
const paths = gLines.selectAll('path.run-line').data(series, d=>d.run);
|
| 350 |
const gen = isRank ? lineGenStep : lineGenSmooth;
|
|
|
|
| 191 |
let runList = [];
|
| 192 |
let runOrder = [];
|
| 193 |
const dataByMetric = new Map(); // metric => { run => [{step,value}] }
|
| 194 |
+
let isRankStrictFlag = false;
|
| 195 |
+
let rankTickMax = 1;
|
| 196 |
|
| 197 |
// Scales and layout
|
| 198 |
let width = 800, height = 360;
|
|
|
|
| 233 |
yScale.range([innerHeight, 0]);
|
| 234 |
|
| 235 |
// Compute integer ticks for Y
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
let yIntTicks = [];
|
| 237 |
+
if (isRankStrictFlag) {
|
| 238 |
+
const maxR = Math.max(1, Math.round(rankTickMax));
|
| 239 |
+
for (let v = 1; v <= maxR; v += 1) yIntTicks.push(v);
|
| 240 |
+
} else {
|
| 241 |
+
const yDomain = yScale.domain();
|
| 242 |
+
const yMin = Math.min(yDomain[0], yDomain[1]);
|
| 243 |
+
const yMax = Math.max(yDomain[0], yDomain[1]);
|
| 244 |
+
let yStep = Math.max(1, Math.round((yMax - yMin) / 6));
|
| 245 |
+
if (!isFinite(yStep) || yStep <= 0) yStep = 1;
|
| 246 |
+
for (let v = Math.ceil(yMin); v <= Math.floor(yMax); v += yStep) { yIntTicks.push(v); }
|
| 247 |
+
if (yIntTicks.length === 0) { yIntTicks = [Math.round(yMin), Math.round(yMax)]; }
|
| 248 |
+
}
|
| 249 |
|
| 250 |
// Grid (horizontal)
|
| 251 |
gGrid.selectAll('*').remove();
|
|
|
|
| 262 |
|
| 263 |
// Axes
|
| 264 |
gAxes.selectAll('*').remove();
|
| 265 |
+
let xAxis = d3.axisBottom(xScale).tickSizeOuter(0);
|
| 266 |
+
if (isRankStrictFlag) {
|
| 267 |
+
const [dx0, dx1] = xScale.domain();
|
| 268 |
+
const start = Math.ceil(dx0 / 1000) * 1000;
|
| 269 |
+
const end = Math.floor(dx1 / 1000) * 1000;
|
| 270 |
+
const xTicks = [];
|
| 271 |
+
for (let v = start; v <= end; v += 1000) xTicks.push(v);
|
| 272 |
+
if (xTicks.length === 0) xTicks.push(Math.round(dx0));
|
| 273 |
+
xAxis = xAxis.tickValues(xTicks).tickFormat(d3.format('d'));
|
| 274 |
+
} else {
|
| 275 |
+
xAxis = xAxis.ticks(8);
|
| 276 |
+
}
|
| 277 |
const yAxis = d3.axisLeft(yScale).tickValues(yIntTicks).tickSizeOuter(0).tickFormat(d3.format('d'));
|
| 278 |
gAxes.append('g')
|
| 279 |
.attr('transform', `translate(0,${innerHeight})`)
|
|
|
|
| 351 |
});
|
| 352 |
if (!isFinite(minStep) || !isFinite(maxStep)) { return; }
|
| 353 |
xScale.domain([minStep, maxStep]);
|
| 354 |
+
if (isRank) {
|
| 355 |
+
rankTickMax = Math.max(1, Math.round(maxVal));
|
| 356 |
+
yScale.domain([rankTickMax, 1]);
|
| 357 |
+
} else {
|
| 358 |
+
yScale.domain([0, Math.max(1, maxVal)]).nice();
|
| 359 |
+
}
|
| 360 |
+
isRankStrictFlag = isRankStrict;
|
| 361 |
|
| 362 |
const { innerWidth, innerHeight } = updateScales();
|
| 363 |
|
|
|
|
| 369 |
.slice()
|
| 370 |
.sort((a,b)=>a.step-b.step)
|
| 371 |
.map(pt => isRankStrict ? { step: pt.step, value: Math.round(pt.value) } : pt)
|
| 372 |
+
.filter(pt => !isRankStrict || (pt.step % 1000 === 0))
|
| 373 |
}));
|
| 374 |
const paths = gLines.selectAll('path.run-line').data(series, d=>d.run);
|
| 375 |
const gen = isRank ? lineGenStep : lineGenSmooth;
|
app/src/content/embeds/palettes.html
CHANGED
|
@@ -5,13 +5,24 @@
|
|
| 5 |
/* removed circular badge */
|
| 6 |
.palettes .palette-card__swatches { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); grid-auto-rows: 1fr; gap: 8px; margin: 0; }
|
| 7 |
.palettes .palette-card__swatches .sw { width: 100%; min-width: 0; min-height: 0; border-radius: 8px; border: 1px solid var(--border-color); }
|
| 8 |
-
.palettes .palette-card__content { display: flex; flex-direction:
|
| 9 |
-
.palettes .palette-
|
| 10 |
-
.palettes .palette-
|
|
|
|
|
|
|
| 11 |
/* .palettes .copy-btn { margin: 0; padding: 0 10px; height: 100%; border-radius: 8px; } */
|
| 12 |
/* .palettes .copy-btn:hover { background: var(--primary-color); color: var(--on-primary)!important; border-color: transparent; }
|
| 13 |
.palettes .copy-btn:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; } */
|
| 14 |
.palettes .copy-btn svg { width: 18px; height: 18px; fill: currentColor; display: block; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
@media (max-width: 640px) {
|
| 16 |
.palettes .palette-card { grid-template-columns: 1fr; align-items: stretch; gap: 10px; }
|
| 17 |
.palettes .palette-card__swatches { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
@@ -20,6 +31,37 @@
|
|
| 20 |
}
|
| 21 |
</style>
|
| 22 |
<div class="palettes__grid"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
</div>
|
| 24 |
<script>
|
| 25 |
(() => {
|
|
@@ -154,39 +196,81 @@
|
|
| 154 |
const css = getCssPrimary();
|
| 155 |
const baseHex = css && /^#|rgb|hsl/i.test(css) ? chroma(css).hex() : '#E889AB';
|
| 156 |
|
| 157 |
-
cards.
|
| 158 |
-
const card = document.createElement('div'); card.className = 'palette-card';
|
| 159 |
-
const sw = document.createElement('div'); sw.className = 'palette-card__swatches';
|
| 160 |
const colors = c.generator(baseHex).slice(0, 6);
|
| 161 |
-
colors.
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
});
|
| 180 |
-
|
| 181 |
-
content.appendChild(title); content.appendChild(desc);
|
| 182 |
-
actions.appendChild(btn);
|
| 183 |
-
card.appendChild(actions); card.appendChild(content); card.appendChild(sw);
|
| 184 |
-
grid.appendChild(card);
|
| 185 |
-
});
|
| 186 |
};
|
| 187 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
const bootstrap = () => {
|
| 189 |
render();
|
|
|
|
|
|
|
| 190 |
const mo = new MutationObserver(() => render());
|
| 191 |
mo.observe(document.documentElement, { attributes: true, attributeFilter: ['style'] });
|
| 192 |
};
|
|
|
|
| 5 |
/* removed circular badge */
|
| 6 |
.palettes .palette-card__swatches { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); grid-auto-rows: 1fr; gap: 8px; margin: 0; }
|
| 7 |
.palettes .palette-card__swatches .sw { width: 100%; min-width: 0; min-height: 0; border-radius: 8px; border: 1px solid var(--border-color); }
|
| 8 |
+
.palettes .palette-card__content { display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 6px; min-width: 0; padding-right: 24px; border-right: 1px solid var(--border-color); }
|
| 9 |
+
.palettes .palette-card__content__info { display: flex; flex-direction: column; }
|
| 10 |
+
.palettes .palette-card__title { text-align: left; font-weight: 800; font-size: 15px; }
|
| 11 |
+
.palettes .palette-card__desc { text-align: left; color: var(--muted-color); line-height: 1.5; font-size: 12px; }
|
| 12 |
+
.palettes .palette-card__actions { display: flex; align-items: center; justify-content: flex-start; justify-self: start; align-self: stretch; }
|
| 13 |
/* .palettes .copy-btn { margin: 0; padding: 0 10px; height: 100%; border-radius: 8px; } */
|
| 14 |
/* .palettes .copy-btn:hover { background: var(--primary-color); color: var(--on-primary)!important; border-color: transparent; }
|
| 15 |
.palettes .copy-btn:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; } */
|
| 16 |
.palettes .copy-btn svg { width: 18px; height: 18px; fill: currentColor; display: block; }
|
| 17 |
+
/* Simulation UI */
|
| 18 |
+
.palettes .palettes__select { width: 100%; max-width: 260px; border: 1px solid var(--border-color); background: var(--surface-bg); color: var(--text-color); padding: 8px 10px; border-radius: 8px; }
|
| 19 |
+
.palettes .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 1px, 1px); white-space: nowrap; border: 0; }
|
| 20 |
+
/* Page-wide color vision simulation classes */
|
| 21 |
+
html.cb-grayscale, body.cb-grayscale { filter: grayscale(1) !important; }
|
| 22 |
+
html.cb-protanopia, body.cb-protanopia { filter: url(#cb-protanopia) !important; }
|
| 23 |
+
html.cb-deuteranopia, body.cb-deuteranopia { filter: url(#cb-deuteranopia) !important; }
|
| 24 |
+
html.cb-tritanopia, body.cb-tritanopia { filter: url(#cb-tritanopia) !important; }
|
| 25 |
+
html.cb-achromatopsia, body.cb-achromatopsia { filter: url(#cb-achromatopsia) !important; }
|
| 26 |
@media (max-width: 640px) {
|
| 27 |
.palettes .palette-card { grid-template-columns: 1fr; align-items: stretch; gap: 10px; }
|
| 28 |
.palettes .palette-card__swatches { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
|
|
| 31 |
}
|
| 32 |
</style>
|
| 33 |
<div class="palettes__grid"></div>
|
| 34 |
+
<div class="palettes__simu" role="group" aria-labelledby="cb-sim-title">
|
| 35 |
+
<br/>
|
| 36 |
+
<p ><strong>Use color with care.</strong> Color should rarely be the only channel of meaning. Always pair it with text, icons, shape or position. The simulation below helps you spot palettes and states that become indistinguishable for people with color‑vision deficiencies. Toggle modes while checking charts, legends and interactions to ensure sufficient contrast and redundant cues.</p>
|
| 37 |
+
<label for="cb-select">Color vision simulation</label>
|
| 38 |
+
<select id="cb-select" class="palettes__select">
|
| 39 |
+
<option value="none">None — full color</option>
|
| 40 |
+
<option value="grayscale">Grayscale — no hue (luminance only)</option>
|
| 41 |
+
<option value="protanopia">Protanopia — reduced/absent reds</option>
|
| 42 |
+
<option value="deuteranopia">Deuteranopia — reduced/absent greens</option>
|
| 43 |
+
<option value="tritanopia">Tritanopia — reduced/absent blues</option>
|
| 44 |
+
<option value="achromatopsia">Achromatopsia — no color at all</option>
|
| 45 |
+
</select>
|
| 46 |
+
<!-- Hidden SVG filters used by the page-wide simulation classes -->
|
| 47 |
+
<svg aria-hidden="true" focusable="false" width="0" height="0" style="position:absolute; left:-9999px; overflow:hidden;">
|
| 48 |
+
<defs>
|
| 49 |
+
<!-- Matrices from common color vision deficiency simulations -->
|
| 50 |
+
<filter id="cb-protanopia">
|
| 51 |
+
<feColorMatrix type="matrix" values="0.567 0.433 0 0 0 0.558 0.442 0 0 0 0 0.242 0.758 0 0 0 0 0 1 0"/>
|
| 52 |
+
</filter>
|
| 53 |
+
<filter id="cb-deuteranopia">
|
| 54 |
+
<feColorMatrix type="matrix" values="0.625 0.375 0 0 0 0.7 0.3 0 0 0 0 0.3 0.7 0 0 0 0 0 1 0"/>
|
| 55 |
+
</filter>
|
| 56 |
+
<filter id="cb-tritanopia">
|
| 57 |
+
<feColorMatrix type="matrix" values="0.95 0.05 0 0 0 0 0.433 0.567 0 0 0 0.475 0.525 0 0 0 0 0 1 0"/>
|
| 58 |
+
</filter>
|
| 59 |
+
<filter id="cb-achromatopsia">
|
| 60 |
+
<feColorMatrix type="matrix" values="0.299 0.587 0.114 0 0 0.299 0.587 0.114 0 0 0.299 0.587 0.114 0 0 0 0 0 1 0"/>
|
| 61 |
+
</filter>
|
| 62 |
+
</defs>
|
| 63 |
+
</svg>
|
| 64 |
+
</div>
|
| 65 |
</div>
|
| 66 |
<script>
|
| 67 |
(() => {
|
|
|
|
| 196 |
const css = getCssPrimary();
|
| 197 |
const baseHex = css && /^#|rgb|hsl/i.test(css) ? chroma(css).hex() : '#E889AB';
|
| 198 |
|
| 199 |
+
const html = cards.map((c) => {
|
|
|
|
|
|
|
| 200 |
const colors = c.generator(baseHex).slice(0, 6);
|
| 201 |
+
const swatches = colors.map(col => `<div class="sw" style="background:${col}"></div>`).join('');
|
| 202 |
+
return `
|
| 203 |
+
<div class="palette-card" data-colors="${colors.join(',')}">
|
| 204 |
+
<div class="palette-card__content">
|
| 205 |
+
<div class="palette-card__content__info">
|
| 206 |
+
<div class="palette-card__title">${c.title}</div>
|
| 207 |
+
<div class="palette-card__desc">${c.desc}</div>
|
| 208 |
+
</div>
|
| 209 |
+
<button class="copy-btn button--ghost" type="button" aria-label="Copy palette">
|
| 210 |
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M16 1H4c-1.1 0-2 .9-2 2v12h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
|
| 211 |
+
</button>
|
| 212 |
+
</div>
|
| 213 |
+
<div class="palette-card__actions"></div>
|
| 214 |
+
<div class="palette-card__swatches">${swatches}</div>
|
| 215 |
+
</div>
|
| 216 |
+
`;
|
| 217 |
+
}).join('');
|
| 218 |
+
grid.innerHTML = html;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
};
|
| 220 |
|
| 221 |
+
const MODE_TO_CLASS = { grayscale: 'cb-grayscale', protanopia: 'cb-protanopia', deuteranopia: 'cb-deuteranopia', tritanopia: 'cb-tritanopia', achromatopsia: 'cb-achromatopsia' };
|
| 222 |
+
const CLEAR_CLASSES = Object.values(MODE_TO_CLASS);
|
| 223 |
+
const clearCbClasses = () => {
|
| 224 |
+
const rootEl = document.documentElement;
|
| 225 |
+
CLEAR_CLASSES.forEach(cls => rootEl.classList.remove(cls));
|
| 226 |
+
};
|
| 227 |
+
const applyCbClass = (mode) => {
|
| 228 |
+
clearCbClasses();
|
| 229 |
+
const cls = MODE_TO_CLASS[mode];
|
| 230 |
+
if (cls) document.documentElement.classList.add(cls);
|
| 231 |
+
};
|
| 232 |
+
const currentCbMode = () => {
|
| 233 |
+
const rootEl = document.documentElement;
|
| 234 |
+
for (const [mode, cls] of Object.entries(MODE_TO_CLASS)) { if (rootEl.classList.contains(cls)) return mode; }
|
| 235 |
+
return 'none';
|
| 236 |
+
};
|
| 237 |
+
const setupCbSim = () => {
|
| 238 |
+
const select = document.getElementById('cb-select');
|
| 239 |
+
if (!select) return;
|
| 240 |
+
try { select.value = currentCbMode(); } catch {}
|
| 241 |
+
select.addEventListener('change', () => applyCbClass(select.value));
|
| 242 |
+
};
|
| 243 |
+
|
| 244 |
+
let copyDelegationSetup = false;
|
| 245 |
+
const setupCopyDelegation = () => {
|
| 246 |
+
if (copyDelegationSetup) return;
|
| 247 |
+
const root = document.querySelector('.palettes');
|
| 248 |
+
if (!root) return;
|
| 249 |
+
const grid = root.querySelector('.palettes__grid');
|
| 250 |
+
if (!grid) return;
|
| 251 |
+
grid.addEventListener('click', async (e) => {
|
| 252 |
+
const target = e.target.closest ? e.target.closest('.copy-btn') : null;
|
| 253 |
+
if (!target) return;
|
| 254 |
+
const card = target.closest('.palette-card');
|
| 255 |
+
if (!card) return;
|
| 256 |
+
const colors = (card.dataset.colors || '').split(',').filter(Boolean);
|
| 257 |
+
const json = JSON.stringify(colors, null, 2);
|
| 258 |
+
try {
|
| 259 |
+
await navigator.clipboard.writeText(json);
|
| 260 |
+
const old = target.innerHTML;
|
| 261 |
+
target.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M9 16.2l-3.5-3.5-1.4 1.4L9 19 20.3 7.7l-1.4-1.4z"/></svg>';
|
| 262 |
+
setTimeout(() => target.innerHTML = old, 900);
|
| 263 |
+
} catch {
|
| 264 |
+
window.prompt('Copy palette', json);
|
| 265 |
+
}
|
| 266 |
+
});
|
| 267 |
+
copyDelegationSetup = true;
|
| 268 |
+
};
|
| 269 |
+
|
| 270 |
const bootstrap = () => {
|
| 271 |
render();
|
| 272 |
+
setupCbSim();
|
| 273 |
+
setupCopyDelegation();
|
| 274 |
const mo = new MutationObserver(() => render());
|
| 275 |
mo.observe(document.documentElement, { attributes: true, attributeFilter: ['style'] });
|
| 276 |
};
|
app/src/layouts/ArticleLayout.astro
DELETED
|
@@ -1,70 +0,0 @@
|
|
| 1 |
-
---
|
| 2 |
-
import type { APIContext } from 'astro';
|
| 3 |
-
|
| 4 |
-
interface Props {
|
| 5 |
-
title: string;
|
| 6 |
-
description?: string;
|
| 7 |
-
authors?: string[];
|
| 8 |
-
published?: string; // ISO or human-readable
|
| 9 |
-
tags?: string[];
|
| 10 |
-
image?: string; // Recommended absolute URL
|
| 11 |
-
}
|
| 12 |
-
|
| 13 |
-
const {
|
| 14 |
-
title,
|
| 15 |
-
description = '',
|
| 16 |
-
authors = [],
|
| 17 |
-
published,
|
| 18 |
-
tags = [],
|
| 19 |
-
image,
|
| 20 |
-
} = Astro.props as Props;
|
| 21 |
-
|
| 22 |
-
const url = Astro.url?.toString?.() ?? '';
|
| 23 |
-
const site = (Astro.site ? String(Astro.site) : '') as string;
|
| 24 |
-
const ogImage = image && image.length > 0
|
| 25 |
-
? (image.startsWith('http') ? image : (site ? new URL(image, site).toString() : image))
|
| 26 |
-
: undefined;
|
| 27 |
-
|
| 28 |
-
const jsonLd = {
|
| 29 |
-
'@context': 'https://schema.org',
|
| 30 |
-
'@type': 'Article',
|
| 31 |
-
headline: title,
|
| 32 |
-
description: description || undefined,
|
| 33 |
-
datePublished: published || undefined,
|
| 34 |
-
author: authors.map((name) => ({ '@type': 'Person', name })),
|
| 35 |
-
keywords: tags.length ? tags.join(', ') : undefined,
|
| 36 |
-
mainEntityOfPage: url || undefined,
|
| 37 |
-
image: ogImage ? [ogImage] : undefined,
|
| 38 |
-
};
|
| 39 |
-
---
|
| 40 |
-
<html lang="en">
|
| 41 |
-
<head>
|
| 42 |
-
<meta charset="utf-8" />
|
| 43 |
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 44 |
-
|
| 45 |
-
<title>{title}</title>
|
| 46 |
-
{description && <meta name="description" content={description} />}
|
| 47 |
-
|
| 48 |
-
<link rel="canonical" href={url} />
|
| 49 |
-
|
| 50 |
-
<meta property="og:type" content="article" />
|
| 51 |
-
<meta property="og:title" content={title} />
|
| 52 |
-
{description && <meta property="og:description" content={description} />}
|
| 53 |
-
<meta property="og:url" content={url} />
|
| 54 |
-
{ogImage && <meta property="og:image" content={ogImage} />}
|
| 55 |
-
{published && <meta property="article:published_time" content={published} />}
|
| 56 |
-
{authors.map(a => <meta property="article:author" content={a} />)}
|
| 57 |
-
|
| 58 |
-
<meta name="twitter:card" content={ogImage ? 'summary_large_image' : 'summary'} />
|
| 59 |
-
<meta name="twitter:title" content={title} />
|
| 60 |
-
{description && <meta name="twitter:description" content={description} />}
|
| 61 |
-
{ogImage && <meta name="twitter:image" content={ogImage} />}
|
| 62 |
-
|
| 63 |
-
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
|
| 64 |
-
</head>
|
| 65 |
-
<body>
|
| 66 |
-
<slot />
|
| 67 |
-
</body>
|
| 68 |
-
</html>
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/src/pages/index.astro
CHANGED
|
@@ -55,7 +55,6 @@ const bibtex = `@misc{${bibKey},\n title={${titleFlat}},\n author={${authorsBi
|
|
| 55 |
<head>
|
| 56 |
<meta charset="utf-8" />
|
| 57 |
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 58 |
-
<title>{docTitle}</title>
|
| 59 |
<Seo title={docTitle} description={description} authors={authors} published={published} tags={tags} image={imageAbs} />
|
| 60 |
<script is:inline>
|
| 61 |
(() => {
|
|
|
|
| 55 |
<head>
|
| 56 |
<meta charset="utf-8" />
|
| 57 |
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
|
|
| 58 |
<Seo title={docTitle} description={description} authors={authors} published={published} tags={tags} image={imageAbs} />
|
| 59 |
<script is:inline>
|
| 60 |
(() => {
|