Plan step 19, containment.md C-27 and C-28. `.inner-body` was 24px, a value not on M3's 0/4/8/12/16/20/28/32/48 scale; it is 28px, the extra-large corner of the dialog and bottom-sheet surface it stands in for. `.header a` is title-large at weight 400, which its own section comment and the `h2` under it already said. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
427 lines
9.8 KiB
PHP
427 lines
9.8 KiB
PHP
{{-- The Markdown mail theme, as CSS rendered from the application's colour scheme.
|
|
|
|
Select it with `mail.markdown.theme` = `livewire-material::mail.theme` (MAIL_MARKDOWN_THEME),
|
|
or `$theme` / `->theme()` on one mailable or MailMessage. Laravel renders a namespaced theme
|
|
as a view (Illuminate\Mail\Markdown::render) and hands the result to CssToInlineStyles, which
|
|
writes it onto every element's `style` — so the colours are the app's light scheme, read
|
|
from `livewire-material.scheme` (Support\Scheme, falling back to the package's default), and
|
|
a regenerated scheme reaches the next mail without a copy to keep in step.
|
|
|
|
What a mail client can take, and so what this is:
|
|
- Hexes only. No custom properties, no `color-mix()`, no alpha: a client resolves none of
|
|
them, and Outlook drops an alpha channel.
|
|
- Light only. `Css\Processor::doCleanup()` strips every `@media` block from the theme before
|
|
inlining, so a `prefers-color-scheme` rule here would be deleted silently; it could only
|
|
live in a `<style>` in a published layout, and the framework layout declares
|
|
`color-scheme: light` in two meta tags besides. Clients that force dark invert the light
|
|
tones, which are mid-tone enough to survive it.
|
|
- M3's typescale on the bare tags CommonMark writes (`h1` headline-sm, `p` body-lg, a table
|
|
head title-sm), in px, with a system font stack: web fonts and
|
|
`font-variation-settings` reach too few clients to be worth an asset.
|
|
- No elevation. The card separates from the page by tone, as M3 surfaces do.
|
|
- The button is M3's filled button at the `md` size (56px, title-md label, full corners),
|
|
written as borders because padding on an `<a>` is not honoured everywhere. Outlook
|
|
Classic squares the corners; it is still a button. `x-mail::button`'s `color` takes any
|
|
role with a filled pair — primary, secondary, tertiary, error, success, warning, info —
|
|
and the framework's blue, green and red. --}}
|
|
|
|
@php
|
|
$role = \NoNameWeb\LivewireMaterial\Support\Scheme::light();
|
|
|
|
$page = $role['surface-container'];
|
|
$card = $role['surface-container-lowest'];
|
|
$tint = $role['surface-container'];
|
|
|
|
$buttons = [
|
|
'primary' => ['primary', 'on-primary'],
|
|
'secondary' => ['secondary', 'on-secondary'],
|
|
'tertiary' => ['tertiary', 'on-tertiary'],
|
|
'error' => ['error', 'on-error'],
|
|
'success' => ['success', 'on-success'],
|
|
'warning' => ['warning', 'on-warning'],
|
|
'info' => ['info', 'on-info'],
|
|
'blue' => ['primary', 'on-primary'],
|
|
'green' => ['success', 'on-success'],
|
|
'red' => ['error', 'on-error'],
|
|
];
|
|
@endphp
|
|
|
|
/* Base */
|
|
|
|
body,
|
|
body *:not(html):not(style):not(br):not(tr):not(code) {
|
|
box-sizing: border-box;
|
|
font-family: -apple-system, BlinkMacSystemFont, Roboto, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
|
position: relative;
|
|
}
|
|
|
|
body {
|
|
-webkit-text-size-adjust: none;
|
|
background-color: {{ $page }};
|
|
color: {{ $role['on-surface-variant'] }};
|
|
height: 100%;
|
|
line-height: 1.5;
|
|
margin: 0;
|
|
padding: 0;
|
|
width: 100% !important;
|
|
}
|
|
|
|
p,
|
|
ul,
|
|
ol,
|
|
blockquote {
|
|
line-height: 1.5;
|
|
text-align: start;
|
|
}
|
|
|
|
a {
|
|
color: {{ $role['primary'] }};
|
|
}
|
|
|
|
a img {
|
|
border: none;
|
|
}
|
|
|
|
/* Typography: M3's typescale */
|
|
|
|
h1 {
|
|
color: {{ $role['on-surface'] }};
|
|
font-size: 24px;
|
|
font-weight: 400;
|
|
letter-spacing: 0;
|
|
line-height: 32px;
|
|
margin-top: 0;
|
|
text-align: start;
|
|
}
|
|
|
|
h2 {
|
|
color: {{ $role['on-surface'] }};
|
|
font-size: 22px;
|
|
font-weight: 400;
|
|
letter-spacing: 0;
|
|
line-height: 28px;
|
|
margin-top: 0;
|
|
text-align: start;
|
|
}
|
|
|
|
h3 {
|
|
color: {{ $role['on-surface'] }};
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
letter-spacing: 0.15px;
|
|
line-height: 24px;
|
|
margin-top: 0;
|
|
text-align: start;
|
|
}
|
|
|
|
p {
|
|
color: {{ $role['on-surface-variant'] }};
|
|
font-size: 16px;
|
|
font-weight: 400;
|
|
letter-spacing: 0.5px;
|
|
line-height: 24px;
|
|
margin-top: 0;
|
|
text-align: start;
|
|
}
|
|
|
|
ul,
|
|
ol {
|
|
color: {{ $role['on-surface-variant'] }};
|
|
font-size: 16px;
|
|
font-weight: 400;
|
|
letter-spacing: 0.5px;
|
|
line-height: 24px;
|
|
margin: 0 0 16px;
|
|
padding-left: 24px;
|
|
}
|
|
|
|
li {
|
|
margin: 0 0 4px;
|
|
}
|
|
|
|
/* A `->line()` per item makes a loose list, each item wrapped in a paragraph with a
|
|
paragraph's margin; without this it reads as numbered paragraphs, not one list. */
|
|
|
|
li p {
|
|
margin: 0;
|
|
}
|
|
|
|
/* Bold stays bold: a 500 would fall to 400 in the many system fonts without a medium weight. */
|
|
|
|
strong {
|
|
color: {{ $role['on-surface'] }};
|
|
}
|
|
|
|
blockquote {
|
|
border-left: 4px solid {{ $role['outline-variant'] }};
|
|
margin: 0 0 16px;
|
|
padding-left: 16px;
|
|
}
|
|
|
|
code {
|
|
background-color: {{ $tint }};
|
|
border-radius: 4px;
|
|
color: {{ $role['on-surface'] }};
|
|
font-size: 14px;
|
|
padding: 2px 4px;
|
|
}
|
|
|
|
hr {
|
|
border: 0;
|
|
border-top: 1px solid {{ $role['outline-variant'] }};
|
|
margin: 24px 0;
|
|
}
|
|
|
|
p.sub {
|
|
font-size: 14px;
|
|
letter-spacing: 0.25px;
|
|
line-height: 20px;
|
|
}
|
|
|
|
img {
|
|
max-width: 100%;
|
|
}
|
|
|
|
/* Layout */
|
|
|
|
.wrapper {
|
|
-premailer-cellpadding: 0;
|
|
-premailer-cellspacing: 0;
|
|
-premailer-width: 100%;
|
|
background-color: {{ $page }};
|
|
margin: 0;
|
|
padding: 0;
|
|
width: 100%;
|
|
}
|
|
|
|
.content {
|
|
-premailer-cellpadding: 0;
|
|
-premailer-cellspacing: 0;
|
|
-premailer-width: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
width: 100%;
|
|
}
|
|
|
|
/* Header: the app name in title-lg (22/28 at weight 400), or the configured logo */
|
|
|
|
.header {
|
|
padding: 32px 0 24px;
|
|
text-align: center;
|
|
}
|
|
|
|
.header a {
|
|
color: {{ $role['on-surface'] }};
|
|
font-size: 22px;
|
|
font-weight: 400;
|
|
letter-spacing: 0;
|
|
line-height: 28px;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.logo {
|
|
border: 0;
|
|
display: block;
|
|
height: auto;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/* Body: the card, separated from the page by tone alone, on M3's extra-large corner */
|
|
|
|
.body {
|
|
-premailer-cellpadding: 0;
|
|
-premailer-cellspacing: 0;
|
|
-premailer-width: 100%;
|
|
background-color: {{ $page }};
|
|
border: 0;
|
|
margin: 0;
|
|
padding: 0;
|
|
width: 100%;
|
|
}
|
|
|
|
.inner-body {
|
|
-premailer-cellpadding: 0;
|
|
-premailer-cellspacing: 0;
|
|
-premailer-width: 570px;
|
|
background-color: {{ $card }};
|
|
border: 0;
|
|
border-radius: 28px;
|
|
margin: 0 auto;
|
|
padding: 0;
|
|
width: 570px;
|
|
}
|
|
|
|
/* A long URL breaks where it has to; `break-all`, the framework's value, breaks ordinary words too. */
|
|
|
|
.inner-body a {
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.content-cell {
|
|
max-width: 100vw;
|
|
padding: 32px;
|
|
}
|
|
|
|
/* Subcopy */
|
|
|
|
.subcopy {
|
|
border-top: 1px solid {{ $role['outline-variant'] }};
|
|
margin-top: 24px;
|
|
padding-top: 24px;
|
|
}
|
|
|
|
.subcopy p {
|
|
font-size: 14px;
|
|
letter-spacing: 0.25px;
|
|
line-height: 20px;
|
|
}
|
|
|
|
/* Footer: body-sm */
|
|
|
|
.footer {
|
|
-premailer-cellpadding: 0;
|
|
-premailer-cellspacing: 0;
|
|
-premailer-width: 570px;
|
|
margin: 0 auto;
|
|
padding: 0;
|
|
text-align: center;
|
|
width: 570px;
|
|
}
|
|
|
|
.footer p {
|
|
color: {{ $role['on-surface-variant'] }};
|
|
font-size: 12px;
|
|
font-weight: 400;
|
|
letter-spacing: 0.4px;
|
|
line-height: 16px;
|
|
text-align: center;
|
|
}
|
|
|
|
.footer a {
|
|
color: {{ $role['on-surface-variant'] }};
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* Tables: a title-sm head over an outline-variant rule, body-md cells, a rule between rows */
|
|
|
|
.table table {
|
|
-premailer-cellpadding: 0;
|
|
-premailer-cellspacing: 0;
|
|
-premailer-width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 24px auto;
|
|
width: 100%;
|
|
}
|
|
|
|
.table th {
|
|
border-bottom: 1px solid {{ $role['outline-variant'] }};
|
|
color: {{ $role['on-surface-variant'] }};
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
letter-spacing: 0.1px;
|
|
line-height: 20px;
|
|
margin: 0;
|
|
padding: 0 12px 12px;
|
|
text-align: start;
|
|
}
|
|
|
|
/* A Markdown table's column alignment arrives as an `align` attribute, which an inline
|
|
`text-align` would override; the head follows it here as the cells do by themselves. */
|
|
|
|
.table th[align="center"] {
|
|
text-align: center;
|
|
}
|
|
|
|
.table th[align="right"] {
|
|
text-align: right;
|
|
}
|
|
|
|
.table td {
|
|
border-bottom: 1px solid {{ $role['outline-variant'] }};
|
|
color: {{ $role['on-surface'] }};
|
|
font-size: 14px;
|
|
font-weight: 400;
|
|
font-variant-numeric: tabular-nums;
|
|
letter-spacing: 0.25px;
|
|
line-height: 20px;
|
|
margin: 0;
|
|
padding: 12px;
|
|
}
|
|
|
|
.table tr:last-child td {
|
|
border-bottom: 0;
|
|
}
|
|
|
|
/* Buttons: M3's filled button, md */
|
|
|
|
.action {
|
|
-premailer-cellpadding: 0;
|
|
-premailer-cellspacing: 0;
|
|
-premailer-width: 100%;
|
|
float: unset;
|
|
margin: 32px auto;
|
|
padding: 0;
|
|
text-align: center;
|
|
width: 100%;
|
|
}
|
|
|
|
.button {
|
|
-webkit-text-size-adjust: none;
|
|
border-radius: 9999px;
|
|
display: inline-block;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
letter-spacing: 0.15px;
|
|
line-height: 24px;
|
|
overflow: hidden;
|
|
text-decoration: none;
|
|
}
|
|
|
|
@foreach ($buttons as $name => [$container, $content])
|
|
.button-{{ $name }} {
|
|
background-color: {{ $role[$container] }};
|
|
border-bottom: 16px solid {{ $role[$container] }};
|
|
border-left: 24px solid {{ $role[$container] }};
|
|
border-right: 24px solid {{ $role[$container] }};
|
|
border-top: 16px solid {{ $role[$container] }};
|
|
color: {{ $role[$content] }};
|
|
}
|
|
|
|
@endforeach
|
|
/* Panel: a tinted container, no rule down its edge */
|
|
|
|
.panel {
|
|
-premailer-cellpadding: 0;
|
|
-premailer-cellspacing: 0;
|
|
-premailer-width: 100%;
|
|
border-collapse: separate;
|
|
margin: 24px 0;
|
|
width: 100%;
|
|
}
|
|
|
|
.panel-content {
|
|
background-color: {{ $tint }};
|
|
border-radius: 16px;
|
|
color: {{ $role['on-surface'] }};
|
|
padding: 16px 20px;
|
|
}
|
|
|
|
.panel-content p {
|
|
color: {{ $role['on-surface'] }};
|
|
}
|
|
|
|
.panel-item {
|
|
padding: 0;
|
|
}
|
|
|
|
.panel-item p:last-of-type {
|
|
margin-bottom: 0;
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
/* Utilities */
|
|
|
|
.break-all {
|
|
overflow-wrap: anywhere;
|
|
}
|