What's new popup (#20112)

DES-192

We often hear that people are not aware of the new features we ship.
Ways in which people can find out are social media/changelog/dashboard –
all of these are easy to miss. We'd like to introduce a template for a
simple notification in the sidebar that can be used any time a new and noteworthy feature has
shipped. The purpose of this is simply to notify and will
disappear forever after it's been dismissed.
This commit is contained in:
Peter Zimon 2024-05-21 12:36:28 +02:00 committed by GitHub
parent f01e06153f
commit 5bb945e89b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 491 additions and 49 deletions

View File

@ -189,3 +189,4 @@ add|ember-template-lint|require-iframe-title|27|20|27|20|94e58d11848d5613900c218
add|ember-template-lint|require-iframe-title|42|16|42|16|a3292b469dc37f2f4791e7f224b0b65c8ecf5d18|1712707200000|1723075200000|1728259200000|app/components/modals/email-preview.hbs
add|ember-template-lint|no-autofocus-attribute|21|20|21|20|942419d05c04ded6716f09faecd6b1ab55418121|1712707200000|1723075200000|1728259200000|app/components/modals/new-custom-integration.hbs
add|ember-template-lint|no-invalid-interactive|2|37|2|37|e21ba31f54b631a428c28a1c9f88d0dc66f2f5fc|1712707200000|1723075200000|1728259200000|app/components/modals/search.hbs
remove|ember-template-lint|no-unknown-arguments-for-builtin-components|93|93|93|93|156670ca427c49c51f0a94f862b286ccc9466d92|1712707200000|1723075200000|1728259200000|app/components/gh-nav-menu/footer.hbs

View File

@ -4,17 +4,17 @@
<h4>{{svg-jar "gift" alt="Gift" class="v-mid"}}What's new</h4>
</div>
<div class="gh-dashboard-resource-body">
<div class="gh-dashboard-list {{if this.whatsNew.hasNew "has-new"}}">
<div class="gh-dashboard-list {{if this.whatsNew.hasNew "has-new"}}">
{{#if (not (or this.loading this.error))}}
<div class="gh-dashboard-list-body">
{{#each this.entries as |entry|}}
<div class="gh-dashboard-list-item">
<LinkTo class="gh-dashboard-list-post" @route="whatsnew" @query={{hash entry=entry.slug}}>
<a href={{entry.url}} class="gh-dashboard-list-post" target="_blank" rel="noopener noreferrer">
<span class="gh-dashboard-list-link">
<span>{{entry.title}}</span>
</span>
<div class="gh-dashboard-resource-secondary">{{moment-format entry.published_at "D MMM YYYY"}}</div>
</LinkTo>
</a>
</div>
{{else}}
<div class="gh-dashboard-list-empty">
@ -26,7 +26,7 @@
</div>
</div>
<div class="gh-dashboard-resource-footer">
<LinkTo @route="whatsnew" @query={{hash entry=null}} class="green">View more features &rarr;</LinkTo>
<a href="https://ghost.org/changelog" class="green" target="_blank" rel="noopener noreferrer">View more features &rarr;</a>
</div>
</article>
</section>

View File

@ -0,0 +1,28 @@
{{#if this.showReferralInvite}}
<div class="gh-sidebar-banner gh-referral-toast">
<button class="gh-sidebar-banner-close" type="button" {{on "click" this.dismissReferralInvite}}>&#xd7;</button>
<a href="https://referrals.ghost.org/?ref=admin" target="_blank" rel="noopener noreferrer">
<strong>You qualify for our invite-only referral program.</strong>
<p class="gh-footer-toast-p">Refer people to Ghost and earn a <strong>30% share</strong> of the subscription revenue, every single month.</p>
<div class="gh-btn gh-btn-black gh-sidebar-banner-button" type="button"><span>Find out more &rarr;</span></div>
</a>
</div>
{{/if}}
{{#if (and this.showWhatsNew this.whatsNew.hasNew)}}
{{#let (get this.whatsNew.entries "0") as |entry|}}
<div class="gh-sidebar-banner gh-whatsnew-toast">
<button class="gh-sidebar-banner-close" type="button" {{on "click" this.dismissWhatsNewToast}}>&#xd7;</button>
<a class="gh-sidebar-banner-container" href={{entry.url}} target="_blank" rel="noopener noreferrer" {{on "click" (fn this.openFeaturedWhatsNew entry.url)}}>
<div class="gh-sidebar-banner-head">
{{svg-jar "sparkle-fill" class="gh-sidebar-banner-icon gh-whatsnew-banner-icon"}}
<span class="gh-sidebar-banner-subhead">What's new?</span>
</div>
<div class="gh-sidebar-banner-msg">{{entry.title}}</div>
{{#if entry.custom_excerpt}}
<div class="gh-sidebar-banner-details">{{entry.custom_excerpt}}</div>
{{/if}}
</a>
</div>
{{/let}}
{{/if}}

View File

@ -0,0 +1,89 @@
import Component from '@glimmer/component';
import envConfig from 'ghost-admin/config/environment';
import moment from 'moment-timezone';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
export default class FooterBanner extends Component {
@service session;
@service dashboardStats;
@service feature;
@service membersUtils;
@service modals;
@service settings;
@service whatsNew;
constructor() {
super(...arguments);
this.loadCurrentMRR.perform();
}
get isAdminOrOwner() {
return this.session.user.isAdmin;
}
get isReferralNotificationNotDismissed() {
return !this.feature.accessibility.referralInviteDismissed;
}
get stripeLiveModeEnabled() {
// allow testing mode when not in a production environment
const isDevModeStripeEnabled = envConfig.environment !== 'production' && this.membersUtils.isStripeEnabled;
const isLiveEnabled = this.settings.stripeConnectLivemode;
return isDevModeStripeEnabled || isLiveEnabled;
}
get hasReachedMRR() {
return this.dashboardStats.currentMRR / 100 >= 100;
}
get showReferralInvite() {
// Conditions to see the referral invite
// 1. Needs to be Owner or Admin
// 2. Stripe is setup and enabled in live mode
// 3. MRR is > $100
// 4. Notification has not yet been dismissed by the user
return !this.args.hasThemeErrors && this.isAdminOrOwner && this.isReferralNotificationNotDismissed && this.stripeLiveModeEnabled && this.hasReachedMRR;
}
get showWhatsNew() {
return !this.showReferralInvite && this.whatsNew.hasNewFeatured;
}
@task
*loadCurrentMRR() {
if (this.isAdminOrOwnern) {
try {
yield this.dashboardStats.loadMrrStats();
} catch (error) {
// noop
}
}
}
@action
dismissReferralInvite(event) {
event.preventDefault();
event.stopPropagation();
if (!this.feature.referralInviteDismissed) {
this.feature.referralInviteDismissed = moment().tz(this.settings.timezone);
}
}
@action
dismissWhatsNewToast(event) {
event.preventDefault();
event.stopPropagation();
// Dismiss
this.whatsNew.seen();
}
@action
openFeaturedWhatsNew(href) {
window.open(href, '_blank');
this.whatsNew.seen();
}
}

View File

@ -1,12 +1,19 @@
<div class="gh-nav-bottom">
{{#if this.hasThemeErrors}}
<button type="button" class="gh-footer-toast gh-theme-error-toast" {{on "click" (fn this.openThemeErrors null)}}>
<span class="gh-footer-toast-title gh-notification-title">Your theme contains errors</span>
<span class="gh-footer-toast-p">Some functionality on your site may be limited &rarr;</span>
</button>
<div class="gh-sidebar-banner gh-error-banner">
<button class="gh-sidebar-banner-container" type="button" {{on "click" (fn this.openThemeErrors null)}}>
<div>
{{svg-jar "warning-fill" class="gh-sidebar-banner-icon"}}
</div>
<div>
<span class="gh-sidebar-banner-subhead">Your theme has errors</span>
<p class="gh-sidebar-banner-msg">Some functionality on your site may be limited &rarr;</p>
</div>
</button>
</div>
{{/if}}
<GhReferralInvite @hasThemeErrors={{this.hasThemeErrors}} />
<GhNavMenu::FooterBanner @hasThemeErrors={{this.hasThemeErrors}} />
<div class="flex items-center justify-between">
<div class="pe-all">
@ -14,7 +21,7 @@
<dropdown.Trigger class="outline-0 pointer">
<div class="flex-auto flex items-center">
<div class="gh-user-avatar relative" style={{background-image-style this.session.user.profileImageUrl}}>
{{#if this.whatsNew.hasNew}}<span class="absolute dib ba b--white br-100 gh-whats-new-badge-account"></span>{{/if}}
{{#if (and this.whatsNew.hasNew (not this.whatsNew.hasNewFeatured))}}<span class="absolute dib ba b--white br-100 gh-whats-new-badge-account"></span>{{/if}}
</div>
{{svg-jar "arrow-down" class="w3 mr1 fill-darkgrey"}}
</div>
@ -45,12 +52,12 @@
<li class="divider" role="separator"></li>
{{else}}
<li>
<LinkTo @route="whatsnew" @query={{hash entry=null}} class="dropdown-item" @role="menuitem" tabindex="-1" data-test-nav="whatsnew">
<button class="dropdown-item" tabindex="-1" data-test-nav="whatsnew" type="button" {{on "click" this.openWhatsNew}}>
What's new?
{{#if this.whatsNew.hasNew}}
<div class="flex-grow-1 flex justify-end"><span class="dib w2 h2 top-0 right-0 bg-green br-100"></span></div>
{{/if}}
</LinkTo>
</button>
</li>
{{/if}}

View File

@ -1,5 +1,6 @@
import Component from '@ember/component';
import ThemeErrorsModal from '../modals/design/theme-errors';
import WhatsNew from '../modals/whats-new';
import calculatePosition from 'ember-basic-dropdown/utils/calculate-position';
import classic from 'ember-classic-decorator';
import {action} from '@ember/object';
@ -43,7 +44,7 @@ export default class Footer extends Component {
// filter errors that have other UI to display to users that the functionality is not working
const filteredErrors = errors?.filter((error) => {
if (error.code === 'GS110-NO-MISSING-PAGE-BUILDER-USAGE' && error?.failures?.[0].message.includes(`show_title_and_feature_image`)) {
return false;
return false;
}
return true;
});
@ -60,4 +61,9 @@ export default class Footer extends Component {
return {horizontalPosition, verticalPosition, style};
}
@action
openWhatsNew() {
return this.modals.open(WhatsNew);
}
}

View File

@ -18,7 +18,7 @@
<div class="gh-nav-top">
<ul class="gh-nav-list gh-nav-main">
{{#if (gh-user-can-admin this.session.user)}}
<li class="relative">
<li class="relative gh-nav-list-home">
<LinkTo @route="dashboard" @alt="Dashboard" title="Dashboard" data-test-nav="dashboard">{{svg-jar "house"}} Dashboard</LinkTo>
</li>
{{/if}}

View File

@ -23,10 +23,10 @@ export default class Main extends Component.extend(ShortcutsMixin) {
@service router;
@service session;
@service ui;
@service whatsNew;
@service membersStats;
@service settings;
@service explore;
@service notifications;
@inject config;

View File

@ -0,0 +1,24 @@
<div class="modal-content">
<h1 class="gh-whasnew-modal-title">What's new?</h1>
<section class="gh-whatsnew-modal-entries" {{did-insert (perform this.whatsNew.updateLastSeen)}}>
{{#each this.whatsNew.entries as |entry|}}
<a class="gh-whatsnew-modal-entry" href={{entry.url}} target="_blank" rel="noopener noreferrer">
{{#if entry.feature_image}}
<img class="gh-whatsnew-modal-entry-featureimage" src={{entry.feature_image}} alt={{entry.title}}>
{{/if}}
<div class="gh-whatsnew-modal-entrycontent">
<h2>{{entry.title}}</h2>
{{#if entry.custom_excerpt}}
<p>{{entry.custom_excerpt}}</p>
{{/if}}
<span>{{moment-format entry.published_at "DD MMMM YYYY"}}</span>
</div>
</a>
{{/each}}
</section>
<div class="gh-whatsnew-modal-footer">
<a href="https://ghost.org/changelog/#/portal/signup" class="gh-btn" type="button" target="_blank" rel="noopener noreferrer"><span>Turn on notifications</span></a>
<a class="gh-btn gh-btn-primary" href="https://ghost.org/changelog" target="_blank" rel="noopener noreferrer"><span>All updates &rarr;</span></a>
</div>
</div>

View File

@ -0,0 +1,15 @@
import Component from '@glimmer/component';
import {inject as service} from '@ember/service';
export default class WhatsNewFeatured extends Component {
@service whatsNew;
static modalOptions = {
className: 'fullscreen-modal-action fullscreen-modal-wide fullscreen-modal-whatsnew'
};
willDestroy() {
super.willDestroy(...arguments);
this.whatsNew.seen();
}
}

View File

@ -7,6 +7,8 @@ import {task} from 'ember-concurrency';
export default Service.extend({
session: service(),
store: service(),
response: null,
entries: null,
changelogUrl: 'https://ghost.org/blog/',
@ -39,32 +41,47 @@ export default Service.extend({
return latestMoment.isAfter(lastSeenMoment);
}),
showModal: action(function () {
hasNewFeatured: computed('entries.[]', function () {
if (!this.hasNew) {
return false;
}
let [latestEntry] = this.entries;
return latestEntry.featured;
}),
seen: action(function () {
this.updateLastSeen.perform();
}),
openFeaturedModal: action(function () {
this.set('isShowingModal', true);
}),
closeModal: action(function () {
closeFeaturedModal: action(function () {
this.set('isShowingModal', false);
this.updateLastSeen.perform();
this.seen();
}),
fetchLatest: task(function* () {
try {
// we should already be logged in at this point so lets grab the user
// record and store it locally so that we don't have to deal with
// session.user being a promise and causing issues with CPs
let user = yield this.session.user;
this.set('_user', user);
if (!this.response) {
// we should already be logged in at this point so lets grab the user
// record and store it locally so that we don't have to deal with
// session.user being a promise and causing issues with CPs
let user = yield this.session.user;
this.set('_user', user);
let response = yield fetch('https://ghost.org/changelog.json');
if (!response.ok) {
// eslint-disable-next-line
return console.error('Failed to fetch changelog', {response});
this.response = yield fetch('https://ghost.org/changelog.json');
if (!this.response.ok) {
// eslint-disable-next-line
return console.error('Failed to fetch changelog', {response});
}
let result = yield this.response.json();
this.set('entries', result.posts || []);
this.set('changelogUrl', result.changelogUrl);
}
let result = yield response.json();
this.set('entries', result.posts || []);
this.set('changelogUrl', result.changelogUrl);
} catch (e) {
console.error(e); // eslint-disable-line
}

View File

@ -54,6 +54,7 @@
@import "layouts/content.css";
@import "layouts/editor.css";
@import "layouts/whatsnew.css";
@import "layouts/whatsnew-modal.css";
@import "layouts/tags.css";
@import "layouts/members.css";
@import "layouts/member-activity.css";
@ -1426,3 +1427,7 @@ Onboarding checklist: Share publication modal */
.gh-share-links li a:hover {
background: #394047;
}
.gh-sidebar-banner.gh-error-banner {
background: var(--lightgrey-d1);
}

View File

@ -56,6 +56,7 @@
@import "layouts/content.css";
@import "layouts/editor.css";
@import "layouts/whatsnew.css";
@import "layouts/whatsnew-modal.css";
@import "layouts/tags.css";
@import "layouts/members.css";
@import "layouts/posts.css";

View File

@ -445,6 +445,10 @@
z-index: 999;
}
.gh-nav-list-home svg {
margin-top: -2px;
}
.gh-nav-list svg.force-fill path {
fill: var(--midgrey);
}
@ -2137,12 +2141,12 @@ section.gh-ds h2 {
background: var(--red);
}
/* Ghost Referrals invite toast */
.gh-referral-toast {
/* Sidebar banners like ghost Referrals invite toast */
.gh-sidebar-banner {
position: relative;
display: block;
padding: 25px;
margin-bottom: 50px;
padding: 12px 16px 16px;
margin: 0 -7px 32px;
color: var(--black);
text-decoration: none;
background: var(--white);
@ -2152,38 +2156,38 @@ section.gh-ds h2 {
cursor: pointer;
}
.gh-referral-toast a {
.gh-sidebar-banner a {
color: var(--black);
}
.gh-referral-toast:hover {
.gh-sidebar-banner:hover {
transform: translateY(-2px) scale(1.01);
box-shadow: rgb(75 225 226 / 38%) -7px -4px 42px 10px, rgb(202 103 255 / 42%) 7px 8px 42px 10px;
transition: all 0.3s ease;
}
.gh-referral-toast-close {
.gh-sidebar-banner-close {
position: absolute;
top: 7px;
right: 10px;
top: 4px;
right: 8px;
padding: 5px;
color: #ddd;
font-size: 2.2rem;
line-height: 1;
}
.gh-referral-toast-close:hover {
.gh-sidebar-banner-close:hover {
color: #15171A;
}
.gh-referral-toast a > strong {
.gh-sidebar-banner a > strong {
display: block;
margin-bottom: 10px;
font-size: 1.7rem;
line-height: 1.2em;
}
.gh-referral-toast a > p {
.gh-sidebar-banner a > p {
margin: 0;
font-size: 1.5rem;
line-height: 1.35em;
@ -2192,22 +2196,115 @@ section.gh-ds h2 {
color: #666;
}
.gh-referral-toast a > p strong {
.gh-sidebar-banner a > p strong {
color: #ff247d;
}
.gh-referral-toast-button {
.gh-sidebar-banner-button {
margin-top: 20px;
border-radius: 5px;
width: 100%;
}
.gh-referral-toast-button span {
.gh-sidebar-banner-button span {
padding: 0 12px;
font-size: 1.4rem;
text-align-last: center;
height: 40px;
line-height: 40px;
height: 38px;
line-height: 38px;
}
.gh-sidebar-banner-container {
display: flex;
flex-direction: column;
text-align: left;
}
.gh-sidebar-banner-icon {
width: 16px;
height: 16px;
}
.gh-sidebar-banner-head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
margin-top: 2px;
}
.gh-sidebar-banner-subhead {
font-size: 13px;
color: var(--midgrey);
font-weight: 400;
}
.gh-sidebar-banner a .gh-sidebar-banner-msg {
line-height: 1.4em;
font-weight: 600;
margin-top: -2px;
margin-bottom: 0;
font-size: 1.4rem;
}
.gh-sidebar-banner-details {
line-height: 1.4em;
font-size: 1.35rem;
margin: 5px 0 0;
color: var(--darkgrey);
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.gh-sidebar-banner-msg + .gh-sidebar-banner-button {
margin-top: 14px;
}
.gh-referral-toast {
padding: 25px;
margin: 0 0 50px;
}
.gh-referral-toast .gh-sidebar-banner-close {
top: 7px;
right: 10px;
}
.gh-sidebar-banner.gh-error-banner {
box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 0.1), 0px 3px 8px 0px rgba(0, 0, 0, 0.06);
margin-bottom: 32px;
padding: 16px;
}
.gh-sidebar-banner.gh-error-banner .gh-sidebar-banner-container {
flex-direction: row;
gap: 10px;
}
.gh-sidebar-banner.gh-error-banner .gh-sidebar-banner-icon {
color: var(--red);
}
.gh-sidebar-banner.gh-error-banner .gh-sidebar-banner-subhead {
color: var(--red);
font-size: 1.4rem;
font-weight: 600;
display: block;
margin-top: -2px;
}
.gh-sidebar-banner.gh-error-banner .gh-sidebar-banner-msg {
font-weight: 400;
font-size: 1.3rem;
margin-top: 4px;
color: var(--darkgrey);
}
.gh-sidebar-banner.gh-error-banner .gh-sidebar-banner-button {
color: var(--white);
background-color: var(--red);
}
.admin-x-container-error {

View File

@ -0,0 +1,150 @@
/* What's new modal */
/* ---------------------------------------------------------- */
.fullscreen-modal-whatsnew,
.fullscreen-modal-whatsnew-featured {
max-height: calc(100vh - 12vw);
overflow-y: auto;
border-radius: 8px !important;
}
@media (max-height: 960px) {
.fullscreen-modal-whatsnew,
.fullscreen-modal-whatsnew-featured {
max-height: calc(100vh - 80px);
}
}
.fullscreen-modal-whatsnew {
max-width: 640px !important;
}
.fullscreen-modal-whatsnew a:focus {
outline: none;
}
.gh-whatsnew-modal-entries {
display: flex;
flex-direction: column;
gap: 24px;
margin-bottom: 32px;
}
.fullscreen-modal-whatsnew .modal-content,
.fullscreen-modal-whatsnew-featured .modal-content {
padding: 0;
}
.gh-whatsnew-featured-container,
.gh-whatsnew-modal-entries {
padding: 0 32px;
}
.gh-whatsnew-featured-subhead {
margin-bottom: 12px;
font-size: 1.3rem;
font-weight: 600;
}
.gh-whatsnew-featured-date {
font-weight: 500;
color: var(--midgrey);
}
.gh-whasnew-featured-title {
font-size: 2.8rem;
}
.gh-whatsnew-featured-excerpt {
margin: 0 0 24px;
font-size: 1.6rem;
line-height: 1.35em;
}
.gh-whatsnew-modal-footer {
z-index: 999;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
bottom: 0px;
background: #fff;
padding: 32px;
margin-bottom: -32px;
box-shadow: 0px -7px 15px -10px rgba(0, 0, 0, 0.15);
}
@media (max-width: 900px) {
.gh-whatsnew-modal-footer {
bottom: -10px;
}
}
.gh-whatsnew-modal {
display: flex;
flex-direction: column;
gap: 24px;
}
.gh-whasnew-modal-title {
font-size: 2.5rem;
padding: 32px 32px 28px;
margin: 0;
}
.gh-whatsnew-modal-entry {
display: flex;
align-items: flex-start;
gap: 24px;
color: var(--black);
margin: -8px;
padding: 8px;
}
.gh-whatsnew-modal-entry:hover {
background: var(--whitegrey-l2);
border-radius: 6px;
}
.gh-whatsnew-modal-entry-featureimage {
max-width: 160px;
height: 110px;
object-fit: cover;
border-radius: 4px;
}
.gh-whatsnew-modal-entrycontent {
display: flex;
flex-direction: column;
gap: 8px;
font-size: 1.4rem;
}
.gh-whatsnew-modal-entrycontent h2 {
font-size: 1.7rem;
margin: 6px 0 0;
}
.gh-whatsnew-modal-entrycontent p {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.45em;
margin: 0;
}
.gh-whatsnew-modal-entrycontent span {
color: var(--midgrey);
}
.gh-whatsnew-modal-entrycontent img {
height: auto;
}
.gh-whatsnew-banner-icon {
color: var(--yellow);
}
.dropdown-item .gh-whatsnew-banner-icon {
margin-right: 0 !important;
}

View File

@ -1925,4 +1925,4 @@
.gh-whats-new .kg-header-card.kg-style-accent a.kg-header-card-button {
background: #fff;
color: #151515;
}
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" id="Reward-Stars-3--Streamline-Ultimate" height="24" width="24"><desc>Reward Stars 3 Streamline Icon: https://streamlinehq.com</desc><path fill="currentColor" fill-rule="evenodd" d="M17.66 1.143a0.75 0.75 0 0 1 1.451 0c0.33 1.262 0.693 2.037 1.235 2.61 0.546 0.577 1.344 1.027 2.705 1.484a0.75 0.75 0 0 1 0 1.422c-1.36 0.457 -2.16 0.907 -2.705 1.484 -0.542 0.574 -0.904 1.348 -1.235 2.61a0.75 0.75 0 0 1 -1.45 0c-0.331 -1.262 -0.694 -2.036 -1.236 -2.61 -0.545 -0.577 -1.344 -1.027 -2.705 -1.484a0.75 0.75 0 0 1 0 -1.422c1.361 -0.457 2.16 -0.907 2.705 -1.483 0.542 -0.574 0.905 -1.35 1.235 -2.61ZM8.052 5.322a0.75 0.75 0 0 1 1.451 0c0.595 2.283 1.27 3.774 2.33 4.9 1.062 1.129 2.584 1.967 5.031 2.792a0.75 0.75 0 0 1 0 1.422c-2.447 0.824 -3.969 1.663 -5.031 2.792 -1.06 1.126 -1.735 2.617 -2.33 4.9a0.75 0.75 0 0 1 -1.451 0c-0.596 -2.283 -1.27 -3.774 -2.33 -4.9C4.659 16.099 3.138 15.26 0.69 14.436a0.75 0.75 0 0 1 0 -1.422c2.448 -0.825 3.97 -1.663 5.032 -2.792 1.06 -1.126 1.734 -2.617 2.33 -4.9Zm11.85 10.147a0.75 0.75 0 0 0 -0.725 0.556c-0.24 0.9 -0.497 1.421 -0.863 1.8 -0.37 0.384 -0.926 0.698 -1.923 1.026a0.75 0.75 0 0 0 0 1.425c0.997 0.327 1.553 0.641 1.923 1.025 0.366 0.38 0.623 0.9 0.863 1.8a0.75 0.75 0 0 0 1.45 0c0.24 -0.9 0.496 -1.42 0.862 -1.8 0.37 -0.384 0.926 -0.698 1.923 -1.025a0.75 0.75 0 0 0 0 -1.425c-0.997 -0.328 -1.553 -0.642 -1.923 -1.026 -0.366 -0.379 -0.622 -0.9 -0.863 -1.8a0.75 0.75 0 0 0 -0.724 -0.556Z" clip-rule="evenodd" stroke-width="1"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="Road-Sign-Warning--Streamline-Ultimate" height="24" width="24"><desc>Road Sign Warning Streamline Icon: https://streamlinehq.com</desc><path d="M23.25 23.23a0.75 0.75 0 0 0 0.66 -1.1l-11.25 -21a0.78 0.78 0 0 0 -1.32 0l-11.25 21a0.73 0.73 0 0 0 0 0.74 0.73 0.73 0 0 0 0.64 0.36ZM12 20.48A1.5 1.5 0 1 1 13.5 19a1.5 1.5 0 0 1 -1.5 1.48Zm0 -12.25a1 1 0 0 1 1 1v5.47a1 1 0 0 1 -2 0V9.23a1 1 0 0 1 1 -1Z" fill="currentColor" stroke-width="1"></path></svg>

After

Width:  |  Height:  |  Size: 513 B