Compare commits
2 commits
fac5d26220
...
01e09680c8
| Author | SHA1 | Date | |
|---|---|---|---|
| 01e09680c8 | |||
| 7df2542d1e |
10 changed files with 378 additions and 50 deletions
|
|
@ -117,13 +117,17 @@
|
||||||
--text-headline-sm: 1.25rem;
|
--text-headline-sm: 1.25rem;
|
||||||
--text-title-lg: 1.125rem;
|
--text-title-lg: 1.125rem;
|
||||||
--text-title-md: 1rem;
|
--text-title-md: 1rem;
|
||||||
--text-body-xl: 1.25rem; /* long-form reading standard */
|
--text-body-xl: clamp(1.56rem, 1vi + 1.31rem, 2.11rem);
|
||||||
--text-body-lg: 1rem;
|
--text-body-lg: clamp(1.25rem, 0.61vi + 1.1rem, 1.58rem);
|
||||||
--text-body-md: 0.9375rem;
|
--text-body-md: clamp(1rem, 0.34vi + 0.91rem, 1.19rem);
|
||||||
--text-body-sm: 0.875rem;
|
--text-body-sm: clamp(0.8rem, 0.17vi + 0.76rem, 0.89rem);
|
||||||
--text-label-lg: 0.875rem;
|
--text-label-lg: 0.875rem;
|
||||||
--text-label-md: 0.75rem; /* metadata, all-caps */
|
--text-label-md: 0.75rem; /* metadata, all-caps */
|
||||||
--text-label-sm: 0.6875rem;
|
--text-label-sm: 0.6875rem;
|
||||||
|
|
||||||
|
--fs-xl: clamp(1.95rem, 1.56vi + 1.56rem, 2.81rem);
|
||||||
|
--fs-xxl: clamp(2.44rem, 2.38vi + 1.85rem, 3.75rem);
|
||||||
|
--fs-xxxl: clamp(3.05rem, 3.54vi + 2.17rem, 5rem);
|
||||||
|
|
||||||
/* --- Typography: Weights --- */
|
/* --- Typography: Weights --- */
|
||||||
--weight-light: 300;
|
--weight-light: 300;
|
||||||
|
|
@ -138,6 +142,7 @@
|
||||||
--leading-normal: 1.5;
|
--leading-normal: 1.5;
|
||||||
--leading-relaxed: 1.6; /* "Digital Paper" body text minimum */
|
--leading-relaxed: 1.6; /* "Digital Paper" body text minimum */
|
||||||
--leading-loose: 1.8;
|
--leading-loose: 1.8;
|
||||||
|
--leading-xloose: 2.25;
|
||||||
|
|
||||||
/* --- Typography: Letter Spacing --- */;
|
/* --- Typography: Letter Spacing --- */;
|
||||||
--tracking-tight: -0.025em;
|
--tracking-tight: -0.025em;
|
||||||
|
|
|
||||||
29
frontend/src/lib/i8n/index.ts
Normal file
29
frontend/src/lib/i8n/index.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { derived, writable, type Writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export type Locale = 'en' | 'fr';
|
||||||
|
export const locale: Writable<Locale> = writable('en');
|
||||||
|
|
||||||
|
export function makeTranslate<T extends Record<string, any>>(translations: T, locale: Locale) {
|
||||||
|
return function (key: string, vars: Record<string, string> = {}): string {
|
||||||
|
// Keys can be e.g. 'cards.title', so we split by ., and have to access
|
||||||
|
// nested props
|
||||||
|
let translation = '';
|
||||||
|
let localeText = translations[locale];
|
||||||
|
for (const part of key.split('.')) {
|
||||||
|
translation = localeText[part];
|
||||||
|
if (!translation) break;
|
||||||
|
localeText = localeText[part];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!translation) throw new Error(`no translation found for ${locale}.${key}`);
|
||||||
|
|
||||||
|
// Replace any passed in variables in the translation string.
|
||||||
|
// Variables are denoted by {{variableName}} in the translation string.
|
||||||
|
Object.keys(vars).map((k) => {
|
||||||
|
const regex = new RegExp(`{{${k}}}`, 'g');
|
||||||
|
translation = translation.replace(regex, vars[k]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return translation;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,32 +1,88 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageProps } from './$types';
|
import type { PageProps } from './$types';
|
||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
|
import AdventuresList from './AdventuresList.svelte';
|
||||||
|
|
||||||
import { getAdventures } from './getAdventures.remote';
|
import { getAdventures } from './getAdventures.remote';
|
||||||
|
|
||||||
let adventures = $derived(await getAdventures(''));
|
let adventures = $derived((await getAdventures('')) ?? []);
|
||||||
|
|
||||||
const { data }: PageProps = $props();
|
const { data }: PageProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if data.successMessage !== null}
|
{#if data.successMessage !== null}
|
||||||
<div class="alert success">
|
<div class="adventures-success" role="status" aria-live="polite">
|
||||||
{data.successMessage}
|
{data.successMessage}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="app-page">
|
|
||||||
<header class="page-header">
|
|
||||||
<h1 class="page-title">Adventures</h1>
|
|
||||||
|
|
||||||
<a href={resolve('/app/adventures/new')} class="btn">Create</a>
|
<section class="adventures-page">
|
||||||
|
<header class="adventures-page__header">
|
||||||
|
<p class="adventures-page__kicker">Library</p>
|
||||||
|
<h1 class="adventures-page__title">Adventures</h1>
|
||||||
|
|
||||||
|
<a href={resolve('/app/adventures/new')} class="btn btn-primary">Create</a>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#each adventures as adventure (adventure.id)}
|
<AdventuresList {adventures} />
|
||||||
<div class="adventure-card">
|
</section>
|
||||||
<a href={resolve('/app/adventures/[id]', { id: adventure.id })}>
|
|
||||||
<h2>{adventure.title}</h2>
|
<style>
|
||||||
<p>{adventure.description}</p>
|
.adventures-page {
|
||||||
</a>
|
max-width: 92rem;
|
||||||
</div>
|
margin: 0 auto;
|
||||||
{/each}
|
padding: var(--space-16) clamp(var(--space-3), 8vw, 6.5rem) var(--space-10)
|
||||||
</div>
|
clamp(var(--space-3), 5vw, 4rem);
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adventures-page__header {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: end;
|
||||||
|
gap: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adventures-page__kicker {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-label);
|
||||||
|
font-size: var(--text-label-md);
|
||||||
|
font-weight: var(--weight-medium);
|
||||||
|
letter-spacing: var(--tracking-wide);
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: color-mix(in srgb, var(--color-on-surface) 70%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adventures-page__title {
|
||||||
|
margin: var(--space-1) 0 0;
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: clamp(2rem, 1.8rem + 1.6vw, 3.2rem);
|
||||||
|
font-weight: var(--weight-semibold);
|
||||||
|
line-height: 1.04;
|
||||||
|
letter-spacing: var(--tracking-tight);
|
||||||
|
color: var(--color-on-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adventures-success {
|
||||||
|
max-width: 92rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: var(--space-3) clamp(var(--space-3), 5vw, 4rem);
|
||||||
|
font-family: var(--font-label);
|
||||||
|
font-size: var(--text-body-sm);
|
||||||
|
color: color-mix(in srgb, var(--colour-green-700) 82%, var(--color-on-surface));
|
||||||
|
background-color: color-mix(in srgb, var(--colour-green-100) 38%, var(--color-surface));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 56rem) {
|
||||||
|
.adventures-page {
|
||||||
|
padding: var(--space-8) var(--space-3);
|
||||||
|
gap: var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adventures-page__header {
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
99
frontend/src/routes/app/adventures/AdventuresList.svelte
Normal file
99
frontend/src/routes/app/adventures/AdventuresList.svelte
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import AdventuresListItem from './AdventuresListItem.svelte';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
|
||||||
|
type Adventure = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string | null;
|
||||||
|
created_at: string;
|
||||||
|
genres: string[];
|
||||||
|
vibes: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
adventures: Adventure[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const { adventures }: Props = $props();
|
||||||
|
|
||||||
|
const makeEyebrowText = (adventure: Adventure): string => {
|
||||||
|
const genresText = adventure.genres.length > 0 ? adventure.genres.join(', ') : 'No genres';
|
||||||
|
const vibesText = adventure.vibes.length > 0 ? adventure.vibes.join(', ') : 'No vibes';
|
||||||
|
return `${genresText} | ${vibesText}`;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if adventures.length === 0}
|
||||||
|
<section class="adventures-empty" aria-label="No adventures yet">
|
||||||
|
<p class="adventures-empty__label">No adventures yet</p>
|
||||||
|
<p class="adventures-empty__copy">Create one to begin your next story.</p>
|
||||||
|
</section>
|
||||||
|
{:else}
|
||||||
|
<ol class="adventures-list" aria-label="Available adventures">
|
||||||
|
{#each adventures as adventure, index (adventure.id)}
|
||||||
|
<li class="adventures-list__item">
|
||||||
|
<AdventuresListItem
|
||||||
|
href={resolve('/app/adventures/[id]', { id: adventure.id })}
|
||||||
|
title={adventure.title}
|
||||||
|
description={adventure.description}
|
||||||
|
eyebrowText={makeEyebrowText(adventure)}
|
||||||
|
createdAt={new Date(adventure.created_at)}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ol>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.adventures-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-3);
|
||||||
|
background-color: var(--color-surface-container-low);
|
||||||
|
border-radius: var(--radius-xl);
|
||||||
|
padding: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adventures-list__item {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adventures-empty {
|
||||||
|
padding: var(--space-6) var(--space-4);
|
||||||
|
background-color: var(--color-surface-container-low);
|
||||||
|
border-radius: var(--radius-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adventures-empty__label {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-label);
|
||||||
|
font-size: var(--text-label-md);
|
||||||
|
font-weight: var(--weight-medium);
|
||||||
|
letter-spacing: var(--tracking-wide);
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: color-mix(in srgb, var(--color-on-surface) 70%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adventures-empty__copy {
|
||||||
|
margin: var(--space-2) 0 0;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: var(--text-body-xl);
|
||||||
|
line-height: var(--leading-relaxed);
|
||||||
|
color: var(--color-on-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 60rem) {
|
||||||
|
.adventures-list {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 56rem) {
|
||||||
|
.adventures-list {
|
||||||
|
padding: var(--space-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
82
frontend/src/routes/app/adventures/AdventuresListItem.svelte
Normal file
82
frontend/src/routes/app/adventures/AdventuresListItem.svelte
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
<script lang="ts">
|
||||||
|
type Props = {
|
||||||
|
href: string;
|
||||||
|
title: string;
|
||||||
|
description: string | null;
|
||||||
|
eyebrowText: string;
|
||||||
|
createdAt: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { href, title, description, eyebrowText, createdAt }: Props = $props();
|
||||||
|
|
||||||
|
const dateFormatter = Intl.DateTimeFormat(undefined, {
|
||||||
|
dateStyle: 'medium',
|
||||||
|
timeStyle: 'short'
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a class="adventure-item" {href}>
|
||||||
|
<p class="eyebrow">{eyebrowText}</p>
|
||||||
|
<h2 class="title">{title}</h2>
|
||||||
|
{#if description}
|
||||||
|
<p class="description">{description}</p>
|
||||||
|
{/if}
|
||||||
|
<p class="date">{dateFormatter.format(createdAt)}</p>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.adventure-item {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-2);
|
||||||
|
padding: clamp(1rem, 0.85rem + 0.7vw, 1.5rem);
|
||||||
|
background-color: var(--color-surface-container-lowest);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
transition:
|
||||||
|
transform var(--duration-fast) var(--ease-standard),
|
||||||
|
background-color var(--duration-fast) var(--ease-standard);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adventure-item:hover,
|
||||||
|
.adventure-item:focus-visible {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-label);
|
||||||
|
font-size: var(--text-label-md);
|
||||||
|
font-weight: var(--weight-medium);
|
||||||
|
letter-spacing: var(--tracking-wide);
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: color-mix(in srgb, var(--color-on-surface) 72%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: clamp(1.25rem, 1.15rem + 0.5vw, 1.65rem);
|
||||||
|
font-weight: var(--weight-semibold);
|
||||||
|
line-height: 1.15;
|
||||||
|
color: var(--color-on-surface);
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: var(--text-body-lg);
|
||||||
|
line-height: var(--leading-relaxed);
|
||||||
|
color: color-mix(in srgb, var(--color-on-surface) 90%, transparent);
|
||||||
|
text-wrap: pretty;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-label);
|
||||||
|
font-size: var(--text-label-sm);
|
||||||
|
color: color-mix(in srgb, var(--color-on-surface) 70%, transparent);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -18,12 +18,13 @@ export const load: PageServerLoad = async ({ locals, params }) => {
|
||||||
return error(400, `Error loading adventure`);
|
return error(400, `Error loading adventure`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, entries, current_entry_choices } = response.data;
|
const { title, entries, current_entry_choices, language } = response.data;
|
||||||
|
|
||||||
response.data.entries.forEach((e) => console.log(e.story_text));
|
response.data.entries.forEach((e) => console.log(e.story_text));
|
||||||
return {
|
return {
|
||||||
title: title,
|
title: title,
|
||||||
entries,
|
entries,
|
||||||
choices: current_entry_choices
|
choices: current_entry_choices,
|
||||||
|
language: language
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
import type { PageProps } from './$types';
|
import type { PageProps } from './$types';
|
||||||
import LatestEntry from './LatestEntry.svelte';
|
import LatestEntry from './LatestEntry.svelte';
|
||||||
import PreviousEntries from './PreviousEntries.svelte';
|
import PreviousEntries from './PreviousEntries.svelte';
|
||||||
|
import { locale, type Locale } from '$lib/i8n';
|
||||||
|
|
||||||
const { data, params }: PageProps = $props();
|
const { data, params }: PageProps = $props();
|
||||||
const latestEntry = $derived(data.entries[data.entries.length - 1]);
|
const latestEntry = $derived(data.entries[data.entries.length - 1]);
|
||||||
|
|
@ -23,6 +25,16 @@
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
$locale = data.language as Locale;
|
||||||
|
|
||||||
|
// Scroll down to the #latest-story entry, in a nice way
|
||||||
|
const latestStoryElement = document.getElementById('latest-story');
|
||||||
|
if (latestStoryElement) {
|
||||||
|
latestStoryElement.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="adventure-page">
|
<div class="adventure-page">
|
||||||
|
|
@ -33,18 +45,16 @@
|
||||||
|
|
||||||
<PreviousEntries entries={previousEntries} />
|
<PreviousEntries entries={previousEntries} />
|
||||||
|
|
||||||
{#if latestEntry}
|
<LatestEntry
|
||||||
<LatestEntry
|
sourceText={latestEntry.story_text}
|
||||||
sourceText={latestEntry.story_text}
|
translationText={latestEntry.translation}
|
||||||
translationText={latestEntry.translation}
|
audioUrl={latestEntry.audio_url!}
|
||||||
audioUrl={latestEntry.audio_url!}
|
nextStepsOptions={data.choices.map((choice) => ({
|
||||||
nextStepsOptions={data.choices.map((choice) => ({
|
label: choice.text,
|
||||||
label: choice.text,
|
id: choice.id
|
||||||
id: choice.id
|
}))}
|
||||||
}))}
|
adventureId={params.id}
|
||||||
adventureId={params.id}
|
/>
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="latest-story" aria-label="Current story entry">
|
<section class="latest-story" aria-label="Current story entry" id="latest-story">
|
||||||
<header class="latest-story__header">
|
<header class="latest-story__header">
|
||||||
<div class="latest-story__title-group">
|
<div class="latest-story__title-group">
|
||||||
<p class="latest-story__kicker">Current entry</p>
|
<p class="latest-story__kicker">Current entry</p>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { locale, makeTranslate } from '$lib/i8n';
|
||||||
|
import translations from '../translations';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
entries: {
|
entries: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -11,6 +14,8 @@
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const t = $derived(makeTranslate(translations, $locale));
|
||||||
|
|
||||||
const { entries }: Props = $props();
|
const { entries }: Props = $props();
|
||||||
|
|
||||||
function toParagraphs(text: string): string[] {
|
function toParagraphs(text: string): string[] {
|
||||||
|
|
@ -24,15 +29,24 @@
|
||||||
{#if entries.length > 0}
|
{#if entries.length > 0}
|
||||||
<section class="previous-entries" aria-label="Previous story entries">
|
<section class="previous-entries" aria-label="Previous story entries">
|
||||||
<header class="previous-entries__header">
|
<header class="previous-entries__header">
|
||||||
<h2 class="previous-entries__title">Previous entries</h2>
|
<h2 class="previous-entries__title">{t('previousEntries.title')}</h2>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<ol class="previous-entries__list">
|
<ol class="list">
|
||||||
{#each entries as entry, index (entry.id)}
|
{#each entries as entry, index (entry.id)}
|
||||||
<li class="previous-entries__item">
|
<li class="previous-entries__item">
|
||||||
<article class="entry-card" aria-label={`Entry ${index + 1}`}>
|
<article
|
||||||
|
class="entry-card"
|
||||||
|
aria-label={t('previousEntries.entryTitle', {
|
||||||
|
entryNumber: String(index + 1).padStart(2, '0')
|
||||||
|
})}
|
||||||
|
>
|
||||||
<header class="entry-card__header">
|
<header class="entry-card__header">
|
||||||
<p class="entry-card__index">Entry {String(index + 1).padStart(2, '0')}</p>
|
<p class="entry-card__index">
|
||||||
|
{t('previousEntries.entryTitle', {
|
||||||
|
entryNumber: String(index + 1).padStart(2, '0')
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="entry-card__body">
|
<div class="entry-card__body">
|
||||||
|
|
@ -43,16 +57,19 @@
|
||||||
|
|
||||||
{#if entry.possibleChoices.length > 0}
|
{#if entry.possibleChoices.length > 0}
|
||||||
<footer class="entry-card__choices">
|
<footer class="entry-card__choices">
|
||||||
<p class="entry-card__choices-label">Possible choices</p>
|
<p class="entry-card__choices-label">{t('previousEntries.possibleChoices')}</p>
|
||||||
<ul class="entry-card__choices-list" aria-label="Choices for this entry">
|
<ul class="entry-card__choices-list" aria-label="Choices for this entry">
|
||||||
{#each entry.possibleChoices as choice, choiceIndex (choice.id)}
|
{#each entry.possibleChoices as choice, choiceIndex (choice.id)}
|
||||||
<li class="entry-card__choice" class:entry-card__choice--selected={choice.isSelected}>
|
<li
|
||||||
|
class="entry-card__choice"
|
||||||
|
class:entry-card__choice--selected={choice.isSelected}
|
||||||
|
>
|
||||||
<span class="entry-card__choice-index" aria-hidden="true"
|
<span class="entry-card__choice-index" aria-hidden="true"
|
||||||
>{String(choiceIndex + 1).padStart(2, '0')}</span
|
>{String(choiceIndex + 1).padStart(2, '0')}</span
|
||||||
>
|
>
|
||||||
<span class="entry-card__choice-text">{choice.text}</span>
|
<span class="entry-card__choice-text">{choice.text}</span>
|
||||||
{#if choice.isSelected}
|
{#if choice.isSelected}
|
||||||
<span class="entry-card__choice-state">Selected</span>
|
<span class="entry-card__choice-state">{t('options.selected')}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
@ -86,19 +103,24 @@
|
||||||
color: var(--color-on-surface);
|
color: var(--color-on-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.previous-entries__list {
|
.previous-entries .list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-3);
|
gap: var(--space-5);
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry-card {
|
.entry-card {
|
||||||
padding: clamp(0.9rem, 0.8rem + 0.7vw, 1.4rem);
|
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
background-color: var(--previous-surface-elevated);
|
background-color: var(--previous-surface-elevated);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
place-items: center;
|
||||||
|
gap: var(--space-5);
|
||||||
|
padding: var(--space-4) var(--space-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry-card__header {
|
.entry-card__header {
|
||||||
|
|
@ -132,10 +154,11 @@
|
||||||
|
|
||||||
.entry-card__paragraph {
|
.entry-card__paragraph {
|
||||||
font-family: var(--font-body);
|
font-family: var(--font-body);
|
||||||
font-size: clamp(1rem, 0.97rem + 0.2vw, 1.12rem);
|
font-size: var(--text-body-md);
|
||||||
line-height: var(--leading-loose);
|
line-height: var(--leading-xloose);
|
||||||
color: var(--color-on-surface);
|
color: var(--color-on-surface);
|
||||||
text-wrap: pretty;
|
text-wrap: pretty;
|
||||||
|
max-width: 65ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry-card__paragraph + .entry-card__paragraph {
|
.entry-card__paragraph + .entry-card__paragraph {
|
||||||
|
|
@ -144,8 +167,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry-card__choices {
|
.entry-card__choices {
|
||||||
margin-top: var(--space-3);
|
max-width: 65ch;
|
||||||
padding-top: var(--space-2);
|
font-size: var(--text-body-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry-card__choices-label {
|
.entry-card__choices-label {
|
||||||
|
|
@ -169,7 +192,7 @@
|
||||||
.entry-card__choice {
|
.entry-card__choice {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
align-items: start;
|
place-items: center;
|
||||||
gap: var(--space-2);
|
gap: var(--space-2);
|
||||||
padding: var(--space-2);
|
padding: var(--space-2);
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
|
|
@ -185,10 +208,11 @@
|
||||||
|
|
||||||
.entry-card__choice-text {
|
.entry-card__choice-text {
|
||||||
font-family: var(--font-body);
|
font-family: var(--font-body);
|
||||||
font-size: var(--text-body-lg);
|
font-size: var(--text-body-md);
|
||||||
line-height: var(--leading-relaxed);
|
line-height: var(--leading-relaxed);
|
||||||
color: color-mix(in srgb, var(--color-on-surface) 84%, transparent);
|
color: color-mix(in srgb, var(--color-on-surface) 84%, transparent);
|
||||||
text-wrap: pretty;
|
text-wrap: pretty;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry-card__choice-state {
|
.entry-card__choice-state {
|
||||||
|
|
|
||||||
22
frontend/src/routes/app/adventures/translations.ts
Normal file
22
frontend/src/routes/app/adventures/translations.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
export default {
|
||||||
|
en: {
|
||||||
|
previousEntries: {
|
||||||
|
title: 'Previous entries',
|
||||||
|
possibleChoices: 'Possible choices',
|
||||||
|
entryTitle: `Entry {{entryNumber}}`
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
selected: 'Selected'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fr: {
|
||||||
|
previousEntries: {
|
||||||
|
title: 'Entrées précédentes',
|
||||||
|
possibleChoices: 'Choix possibles',
|
||||||
|
entryTitle: `Entrée {{entryNumber}}`
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
selected: 'Sélectionné'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue