~cytrogen/masto-fe

960614ec6fca5d0e2f7aca5b0ca2f8fba94ed905 — Laura Hausmann 2 years ago d235afc
Merge patchset from akko branch
M README.md => README.md +26 -9
@@ 1,14 1,31 @@
# Mastodon Glitch Edition
# Mastodon Glitch Edition (standalone frontend)

> Now with automated deploys!
This is a very hacky fork of akkoma-masto-fe that adds standalone support (meaning your browser can OAuth against an arbitrary instance). It's currently tested to "work" (login doesn't break, basic functionality works) with Iceshrimp and GoToSocial (and it obviously works with Mastodon).

[![Build Status](https://img.shields.io/circleci/project/github/glitch-soc/mastodon.svg)][circleci]
[![Code Climate](https://img.shields.io/codeclimate/maintainability/glitch-soc/mastodon.svg)][code_climate]
To try this out, go to [masto-fe.iceshrimp.dev](https://masto-fe.iceshrimp.dev), type in your instance domain name (for split domain setups, use the web domain) & press the button.

[circleci]: https://circleci.com/gh/glitch-soc/mastodon
[code_climate]: https://codeclimate.com/github/glitch-soc/mastodon
To set this up yourself, clone the repo into e.g. `/home/user/masto-fe-standalone` and run `yarn && yarn build:production` (you might have to use `NODE_OPTIONS=--openssl-legacy-provider` until we've rebased this onto upstream glitch).

So here's the deal: we all work on this code, and anyone who uses that does so absolutely at their own risk. can you dig it?
Then configure nginx for a subdomain like this:

- You can view documentation for this project at [glitch-soc.github.io/docs/](https://glitch-soc.github.io/docs/).
- And contributing guidelines are available [here](CONTRIBUTING.md) and [here](https://glitch-soc.github.io/docs/contributing/).
```
map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
}

server {
        include sites/example.com/inc/ssl.conf;
        server_name masto.example.com;

        location / {
                root /home/user/masto-fe-standalone/public/;
                index index.html;
                try_files $uri /index.html;
        }
}
```

And open `https://masto.example.com` in your browser, type in your instance domain, press the button & follow the OAuth flow.

Should anything break, open `https://masto.example.com/logout.html` or clear local storage manually.

M app/javascript/flavours/glitch/api.js => app/javascript/flavours/glitch/api.js +11 -0
@@ 53,6 53,15 @@ const authorizationHeaderFromState = getState => {

/**
 * @param {() => import('immutable').Map<string,any>} getState
 * @returns string
 */
const baseUrlFromState = getState => {
  const baseUrl = getState && getState().getIn(['meta', 'base_url'], '');
  return `${baseUrl}`;
};

/**
 * @param {() => import('immutable').Map} getState
 * @returns {import('axios').AxiosInstance}
 */
export default function api(getState) {


@@ 62,6 71,8 @@ export default function api(getState) {
      ...authorizationHeaderFromState(getState),
    },

    baseURL: baseUrlFromState(getState),

    transformResponse: [
      function (data) {
        try {

M app/javascript/flavours/glitch/features/compose/components/action_bar.jsx => app/javascript/flavours/glitch/features/compose/components/action_bar.jsx +0 -2
@@ 42,8 42,6 @@ class ActionBar extends PureComponent {

    let menu = [];

    menu.push({ text: intl.formatMessage(messages.edit_profile), href: profileLink });
    menu.push({ text: intl.formatMessage(messages.preferences), href: preferencesLink });
    menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
    menu.push(null);
    menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });

M app/javascript/flavours/glitch/features/compose/components/navigation_bar.jsx => app/javascript/flavours/glitch/features/compose/components/navigation_bar.jsx +1 -7
@@ 27,16 27,10 @@ export default class NavigationBar extends ImmutablePureComponent {
        </Permalink>

        <div className='navigation-bar__profile'>
          <div>{this.props.account.get('display_name')}</div>
          <Permalink className='acct' href={this.props.account.get('url')} to={`/@${this.props.account.get('acct')}`}>
            <strong>@{this.props.account.get('acct')}</strong>
          </Permalink>

          { profileLink !== undefined && (
            <a
              className='edit'
              href={profileLink}
            ><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
          )}
        </div>

        <div className='navigation-bar__actions'>

M app/javascript/flavours/glitch/features/compose/containers/warning_container.jsx => app/javascript/flavours/glitch/features/compose/containers/warning_container.jsx +1 -1
@@ 19,7 19,7 @@ const mapStateToProps = state => ({

const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning }) => {
  if (needsLockWarning) {
    return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <a href={profileLink}><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></a> }} />} />;
    return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <span><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></span> }} />} />;
  }

  if (hashtagWarning) {

M app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx => app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx +1 -8
@@ 73,15 73,8 @@ class LocalSettingsNavigation extends PureComponent {
        />
        <LocalSettingsNavigationItem
          active={index === 5}
          href={preferencesLink}
          index={5}
          icon='cog'
          title={intl.formatMessage(messages.preferences)}
        />
        <LocalSettingsNavigationItem
          active={index === 6}
          className='close'
          index={6}
          index={5}
          onNavigate={onClose}
          icon='times'
          title={intl.formatMessage(messages.close)}

M app/javascript/flavours/glitch/features/local_settings/page/index.jsx => app/javascript/flavours/glitch/features/local_settings/page/index.jsx +2 -2
@@ 288,12 288,12 @@ class LocalSettingsPage extends PureComponent {
                defaultMessage="This setting is now controlled from Mastodon's {settings_page_link}"
                values={{
                  settings_page_link: (
                    <a href={preferenceLink('user_setting_expand_spoilers')}>
                    <span>
                      <FormattedMessage
                        id='settings.shared_settings_link'
                        defaultMessage='user preferences'
                      />
                    </a>
                    </span>
                  ),
                }}
              />

M app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx => app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx +1 -1
@@ 68,7 68,7 @@ class DeprecatedSettingsModal extends PureComponent {
            <ul>
              { settings.map((setting_name) => (
                <li key={setting_name}>
                  <a href={preferenceLink(setting_name)}><FormattedMessage {...messages[setting_name]} /></a>
                  <span><FormattedMessage {...messages[setting_name]} /></span>
                </li>
              )) }
            </ul>

M app/javascript/flavours/glitch/features/ui/components/link_footer.jsx => app/javascript/flavours/glitch/features/ui/components/link_footer.jsx +2 -32
@@ 64,42 64,12 @@ class LinkFooter extends PureComponent {
    return (
      <div className='link-footer'>
        <p>
          <strong>{domain}</strong>:
          {' '}
          <Link to='/about' target={multiColumn ? '_blank' : undefined}><FormattedMessage id='footer.about' defaultMessage='About' /></Link>
          {statusPageUrl && (
            <>
              {DividingCircle}
              <a href={statusPageUrl} target='_blank' rel='noopener'><FormattedMessage id='footer.status' defaultMessage='Status' /></a>
            </>
          )}
          {canInvite && (
            <>
              {DividingCircle}
              <a href='/invites' target='_blank'><FormattedMessage id='footer.invite' defaultMessage='Invite people' /></a>
            </>
          )}
          {canProfileDirectory && (
            <>
              {DividingCircle}
              <Link to='/directory'><FormattedMessage id='footer.directory' defaultMessage='Profiles directory' /></Link>
            </>
          )}
          <strong>Masto-FE-standalone</strong>
          {DividingCircle}
          <Link to='/privacy-policy' target={multiColumn ? '_blank' : undefined}><FormattedMessage id='footer.privacy_policy' defaultMessage='Privacy policy' /></Link>
        </p>

        <p>
          <strong>Mastodon</strong>:
          {' '}
          <a href='https://joinmastodon.org' target='_blank'><FormattedMessage id='footer.about' defaultMessage='About' /></a>
          {DividingCircle}
          <a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='footer.get_app' defaultMessage='Get the app' /></a>
          <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='footer.source_code' defaultMessage='Source code' /></a>
          {DividingCircle}
          <Link to='/keyboard-shortcuts'><FormattedMessage id='footer.keyboard_shortcuts' defaultMessage='Keyboard shortcuts' /></Link>
          {DividingCircle}
          <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='footer.source_code' defaultMessage='View source code' /></a>
          {DividingCircle}
          v{version}
        </p>
      </div>

M app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx => app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx +0 -1
@@ 104,7 104,6 @@ class NavigationPanel extends Component {

            <hr />

            {!!preferencesLink && <ColumnLink transparent href={preferencesLink} icon='cog' text={intl.formatMessage(messages.preferences)} />}
            <ColumnLink transparent onClick={onOpenSettings} icon='cogs' text={intl.formatMessage(messages.app_settings)} />
          </>
        )}

M app/javascript/flavours/glitch/stream.js => app/javascript/flavours/glitch/stream.js +2 -1
@@ 235,8 235,9 @@ const createConnection = (streamingAPIBaseURL, accessToken, channelName, { conne
  channelName = params.shift();

  if (streamingAPIBaseURL.startsWith('ws')) {
    params.push(`access_token=${accessToken}`);
    // @ts-expect-error
    const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken);
    const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming?${params.join('&')}`, accessToken);

    // @ts-expect-error
    ws.onopen      = connected;

M app/javascript/flavours/glitch/utils/backend_links.js => app/javascript/flavours/glitch/utils/backend_links.js +3 -3
@@ 1,6 1,6 @@
export const preferencesLink = '/settings/preferences';
export const profileLink = '/settings/profile';
export const signOutLink = '/auth/sign_out';
export const preferencesLink = undefined;
export const profileLink = undefined;
export const signOutLink = '/logout.html';
export const privacyPolicyLink = '/privacy-policy';
export const accountAdminLink = (id) => `/admin/accounts/${id}`;
export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses/${status_id}`;

M app/javascript/flavours/glitch/utils/log_out.js => app/javascript/flavours/glitch/utils/log_out.js +1 -1
@@ 26,7 26,7 @@ export const logOut = () => {
  submitButton.setAttribute('type', 'submit');
  form.appendChild(submitButton);

  form.method = 'post';
  form.method = 'get';
  form.action = signOutLink;
  form.style.display = 'none';


M app/javascript/mastodon/locales/en.json => app/javascript/mastodon/locales/en.json +1 -1
@@ 283,7 283,7 @@
  "footer.invite": "Invite people",
  "footer.keyboard_shortcuts": "Keyboard shortcuts",
  "footer.privacy_policy": "Privacy policy",
  "footer.source_code": "View source code",
  "footer.source_code": "Source code",
  "footer.status": "Status",
  "generic.saved": "Saved",
  "getting_started.heading": "Getting started",

A public/auth.js => public/auth.js +101 -0
@@ 0,0 1,101 @@
document.addEventListener("DOMContentLoaded", async function() {
  await ready();
});

async function ready() {
  const domain = localStorage.getItem('domain');
  let accessToken = localStorage.getItem(`access_token`);

  if (domain) document.getElementById('instance').value = domain;

  const urlParams = new URLSearchParams(window.location.search);
  const code = urlParams.get('code');

  if (domain && code && !accessToken) await getToken(code, domain).then(res => accessToken = res);
  if (accessToken) {
    window.location.href = '/prepare.html';
  }
}

async function auth() {
  setMessage('Please wait');
  const instance = document.getElementById('instance').value;
  const domain = instance.match(/(?:https?:\/\/)?(.*)/)[1];
  if (!domain) {
    setMessage('Invalid instance', false);
    return;
  }

  localStorage.setItem('domain', domain);

  // We need to run this every time in cases like Iceshrimp, where the client id/secret aren't reusable (yet) because they contain use-once session information
  await registerApp(domain);

  authorize(domain);
}

async function registerApp(domain) {
  setMessage('Registering app');

  const appsUrl = `https://${domain}/api/v1/apps`;
  const formData = new FormData();
  formData.append('client_name', 'Masto-FE standalone');
  formData.append('redirect_uris', document.location.origin + document.location.pathname);
  formData.append('scopes', 'read write follow push');

  // eslint-disable-next-line promise/catch-or-return
  await fetch(appsUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams(formData),
  })
    .then(async res => {
      const app = await res.json();
      localStorage.setItem(`client_id`, app.client_id);
      localStorage.setItem(`client_secret`, app.client_secret);
    });
}

function authorize(domain) {
  setMessage('Authorizing');
  const clientId = localStorage.getItem(`client_id`);
  document.location.href = `https://${domain}/oauth/authorize?response_type=code&client_id=${clientId}&redirect_uri=${document.location.origin + document.location.pathname}&scope=read+write+follow+push`;
}

async function getToken(code, domain) {
  setMessage('Getting token');

  const tokenUrl = `https://${domain}/oauth/token`;
  const clientId = localStorage.getItem(`client_id`);
  const clientSecret = localStorage.getItem(`client_secret`);

  const formData = new FormData();
  formData.append('grant_type', 'authorization_code');
  formData.append('code', code);
  formData.append('client_id', clientId);
  formData.append('client_secret', clientSecret);
  formData.append('scope', 'read write follow push');
  formData.append('redirect_uri', document.location.origin + document.location.pathname);


  // eslint-disable-next-line promise/catch-or-return
  return fetch(tokenUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams(formData),
  })
    .then(async res => {
      const app = await res.json();
      if (app.access_token) localStorage.setItem(`access_token`, app.access_token);
      return app.access_token;
    });
}

function setMessage(message, disabled = true) {
  document.getElementById('message').textContent = message;
  document.getElementById('btn').disabled = disabled;
}
\ No newline at end of file

A public/images/mascot.svg => public/images/mascot.svg +11 -0
@@ 0,0 1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="339.97784" height="263.62009" stroke-miterlimit="1.5" version="1.1" viewBox="0 0 339.97785 263.62008" xml:space="preserve" id="svg895" sodipodi:docname="mastodon-trans.svg" inkscape:version="0.92.4 5da689c313, 2019-01-14" style="clip-rule:evenodd;fill-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5"><metadata id="metadata901"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title/></cc:Work></rdf:RDF></metadata><defs id="defs899">






<filter style="color-interpolation-filters:sRGB" id="filter4796" inkscape:label="filter0"><feTurbulence id="feTurbulence5493" type="fractalNoise" baseFrequency="0.0029999999999999931" result="result1"/><feDisplacementMap in2="result1" id="feDisplacementMap5495" scale="100" yChannelSelector="R" xChannelSelector="B"/></filter></defs><sodipodi:namedview pagecolor="#c1c1c1" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="2556" inkscape:window-height="1414" id="namedview897" showgrid="false" inkscape:zoom="8" inkscape:cx="258.47919" inkscape:cy="208.2575" inkscape:window-x="0" inkscape:window-y="22" inkscape:window-maximized="0" inkscape:current-layer="svg895" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0"/><g id="mastodon" transform="translate(1.548497,28.510079)"><path d="M -0.05,235.11 0.2,98.14 C 3.862,92.556 10.515,83.5 22.717,77.42 12.803,69.461 16.496,60.603 23.013,52.704 33.475,40.025 48.48,34.376 64.985,36.522 61.266,32.27 60.64,30.793 58.717,27.087 c 11.337,-7.311 27.917,1.7 28.167,13.5 -0.136,-6.404 -2.855,-8.783 -1.667,-12.167 0.289,-0.011 0.577,-0.052 0.858,-0.018 17.264,2.078 26.303,18.554 17.142,28.351 33.605,3.604 59.756,52.679 95.675,52.586 37.871,-0.099 62.192,-36.921 35.992,-66.752 6.067,-7.321 13.278,-0.917 17.667,1.833 2.75,-2.482 4.25,-4.389 10.666,-6.333 5.917,6.222 -2.963,13.342 -0.833,18.833 6.393,16.481 6.361,50.928 -16.368,73.137 -14.105,13.782 -37.142,23.22 -73.465,17.696 -16.123,-2.452 -22.891,11.917 -36.192,29.058 -15.251,19.656 -17.901,41.707 -16.995,58.174 z" id="path815" inkscape:connector-curvature="0" style="fill:#56606b"/><g id="g823" style="fill:#3a434e"><path d="m 257.38,114.78 c 0.592,-1.223 -3.029,-0.751 -4,0.2 -10.167,9.967 -19.536,19.144 -34.2,22.4 -14.335,3.184 -33.1,2.867 -47.4,1.4 -9.93,-1.018 -24.681,4.919 -28.8,15 l 6.8,5.8 c 2.533,-4.849 8.314,-11.459 14.4,-11.8 11.933,-0.666 39.167,3.667 54.8,-1.8 15.568,-5.443 31.267,-16.466 38.4,-31.2 z" id="path817" inkscape:connector-curvature="0"/><path d="m 107.98,167.39 c 5.488,10.128 11.618,18.64 18.6,25 l 9.6,-16.8 z" id="path819" inkscape:connector-curvature="0"/><path d="m 22.975,76.978 c -5.43,-4.965 -11.864,-9.557 -2.6,-20 0,0 6.25,9.293 12.2,5.2 14.257,-9.806 18.61,-14.051 33.6,-15 10.62,-0.672 26.565,3.652 28.4,10.6 2.149,8.138 -8.933,11.667 -15.4,15.6 -6.131,3.73 -13.2,8.034 -19,10.4 -4.952,2.021 -14.8,5 -14.8,5 l -4,-2 -10,13.8 -17.8,-7.2 -5.4,-5.2 z" id="path821" inkscape:connector-curvature="0"/></g><path d="m 103.38,55.375 c 0.031,0.004 0.063,0.006 0.093,0.012 0.093,0.016 0.183,0.048 0.266,0.093 0.34,0.182 0.528,0.573 0.46,0.953 -0.023,0.123 -0.071,0.242 -0.142,0.346 -0.018,0.026 -0.038,0.05 -0.057,0.075 -0.155,0.183 1.259,1.058 1.102,1.239 -0.67,0.194 -0.923,-0.127 -2.229,0.338 -1.556,0.554 -3.048,2.689 -4.338,3.72 -1.363,1.088 -2.794,2.091 -4.283,2.997 -2.466,1.499 -5.101,2.746 -7.865,3.586 -2.049,0.622 -4.181,1.021 -6.326,1.043 C 79.5,69.783 78.94,69.762 78.382,69.712 78.244,69.7 78.107,69.686 77.969,69.67 c -0.128,-0.015 -0.26,-0.025 -0.383,-0.067 -0.301,-0.104 -0.529,-0.367 -0.59,-0.679 -0.018,-0.093 0.182,-0.559 0.572,-0.626 0.448,-0.078 4.628,-0.144 5.858,-0.381 1.554,-0.298 3.26,-0.735 4.733,-1.31 2.455,-0.958 4.761,-2.334 6.942,-3.811 2.353,-1.594 4.356,-3.653 6.3,-5.727 0.421,-0.449 0.828,-0.909 1.225,-1.378 l 0.065,-0.069 c 0.024,-0.02 0.046,-0.042 0.071,-0.061 0.1,-0.077 0.215,-0.133 0.337,-0.163 0.082,-0.02 0.104,-0.018 0.187,-0.025 0.031,0 0.063,0.001 0.094,0.002 z" id="path825" inkscape:connector-curvature="0" style="fill:#15181c;fill-rule:nonzero"/><g id="g837" style="fill:none;stroke:#000000;stroke-width:1.79999995px;stroke-opacity:0.69019997"><path d="m 66.884,69.752 c 2.98,2.263 -13.562,4.511 -26.667,4.5" id="path827" inkscape:connector-curvature="0"/><path d="m 149.72,94.586 5.834,-4.667" id="path829" inkscape:connector-curvature="0"/><path d="m 158.55,102.25 6.5,-5.833" id="path831" inkscape:connector-curvature="0"/><path d="m 176.88,112.59 c 1.34,-1.428 2.555,-4.052 3.667,-7.667" id="path833" inkscape:connector-curvature="0"/><path d="m 191.05,116.75 c 1.163,-1.499 2.33,-4.107 3.5,-7.333" id="path835" inkscape:connector-curvature="0"/></g><path d="m 99.384,96.919 c -1.799,1.324 0.46,5.835 3.756,11.95 3.064,5.687 6.811,12.002 8.577,10.717 2.85,-2.075 -0.356,-9.201 -3,-14.167 -2.482,-4.663 -7.318,-9.984 -9.333,-8.5 z" id="path839" inkscape:connector-curvature="0" style="fill:#3a434e;stroke:#05181e;stroke-width:2.70000005px"/><path d="m 99.568,133.34 c 7.003,-3.042 12.226,-1.139 12.855,0.449 1.041,2.632 -1.712,5.944 -9.068,9.081 -7.533,3.213 -11.034,1.908 -12.229,-0.658 -0.837,-1.796 2.482,-6.282 8.442,-8.872 z" id="path841" inkscape:connector-curvature="0" style="fill:#4f5862"/><path d="m -0.2,234.43 v -1.527 c 0.1,0.126 0.201,0.252 0.303,0.376 0.326,0.395 0.666,0.779 1.019,1.151 z M 106.38,55.95 c 1.339,0.253 -3.3,2.04 -3.3,2.04 -0.92,-0.093 -0.709,-1.267 -0.305,-2.441 0,0 0.705,-1.088 0.976,-1.51 0.536,-0.836 0.968,-1.738 1.276,-2.681 0.274,-0.836 0.449,-1.702 0.528,-2.578 0.052,-0.587 0.061,-1.177 0.029,-1.765 -0.023,-0.427 -0.068,-0.852 -0.132,-1.274 -0.488,-3.184 -2.092,-6.121 -4.222,-8.504 -0.374,-0.418 -0.766,-0.821 -1.173,-1.208 C 99.485,35.486 98.883,34.975 98.256,34.496 97.474,33.9 96.652,33.355 95.8,32.863 94.555,32.144 93.244,31.538 91.892,31.047 90.687,30.61 89.45,30.263 88.195,30.005 87.57,29.877 86.942,29.773 86.31,29.686 l -0.114,-0.015 c -0.122,1.02 0.171,2.034 0.455,3.003 0.159,0.533 0.323,1.064 0.478,1.598 0.272,0.951 0.511,1.91 0.68,2.885 0.185,1.072 0.006,3.929 -0.303,4.254 -0.345,0.363 -0.824,-1.27 -1.04,-2.014 -0.046,-0.156 -1.063,-2.533 -1.312,-3.07 -1.201,-2.584 -3.006,-4.466 -5.204,-6.252 -0.385,-0.313 -0.784,-0.612 -1.193,-0.895 -0.527,-0.363 -1.071,-0.701 -1.63,-1.012 -0.685,-0.38 -1.392,-0.721 -2.116,-1.019 -0.899,-0.369 -1.824,-0.674 -2.767,-0.907 -1.254,-0.311 -2.537,-0.49 -3.828,-0.536 -0.23,-0.007 -0.46,-0.01 -0.689,-0.009 -0.505,0.006 -1.009,0.03 -1.512,0.08 -0.96,0.094 -1.913,0.274 -2.841,0.543 -1.036,0.3 -2.034,0.713 -2.985,1.222 l -0.005,0.003 c 0.584,1.048 0.809,2.129 1.363,3.195 0.771,1.486 1.45,2.23 2.617,4.045 0.506,0.789 2.57,2.986 2.256,2.831 -2.438,-1.204 -4.796,-0.6 -7.533,-0.419 -3.657,0.241 -7.303,0.989 -10.886,1.76 -3.669,0.791 -7.245,2.021 -10.604,3.698 -2.608,1.303 -5.082,2.874 -7.365,4.688 -2.244,1.785 -4.301,3.803 -6.136,6.007 -0.583,0.7 -3.315,0.138 -2.816,-0.495 0.78,-0.991 1.595,-1.953 2.451,-2.878 1.356,-1.466 2.811,-2.841 4.352,-4.11 3.066,-2.526 6.474,-4.628 10.097,-6.254 3.043,-1.366 6.234,-2.397 9.494,-3.099 3.381,-0.729 6.835,-1.104 10.292,-1.154 1.694,-0.025 3.386,0.034 5.076,0.155 -0.222,-0.26 -0.441,-0.522 -0.661,-0.785 -0.914,-1.105 -1.803,-2.23 -2.61,-3.416 -0.761,-1.118 -1.442,-2.285 -2.072,-3.481 l -0.091,-0.174 c -0.117,-0.383 -0.208,-0.494 -0.099,-0.901 0.1,-0.375 0.252,-0.491 0.535,-0.731 0.041,-0.036 0.092,-0.059 0.138,-0.088 0.473,-0.299 0.96,-0.575 1.459,-0.827 1.594,-0.805 3.309,-1.36 5.069,-1.656 0.991,-0.166 1.993,-0.247 2.998,-0.259 h 0.315 c 0.84,0.011 1.678,0.068 2.511,0.179 1.862,0.248 3.69,0.746 5.428,1.461 2.235,0.92 4.328,2.198 6.149,3.79 0.533,0.466 1.039,0.96 1.522,1.478 l 0.034,0.036 c 0,-0.112 0.121,-1.168 0.289,-1.644 0.157,-0.443 0.573,-1.14 0.721,-1.211 0.667,-0.318 1.785,-0.243 3.413,0.152 2.003,0.486 4.018,0.956 5.919,1.753 0.987,0.415 1.95,0.886 2.881,1.415 0.794,0.451 1.564,0.943 2.306,1.476 0.53,0.38 1.045,0.782 1.543,1.203 3.133,2.648 5.645,6.142 6.76,10.12 0.18,0.643 0.323,1.297 0.424,1.957 0.153,1.003 0.211,2.021 0.163,3.035 -0.058,1.218 -0.269,2.428 -0.635,3.591 -0.427,1.357 -1.062,2.653 -1.875,3.82 0.269,0.044 0.536,0.093 0.804,0.142 z" id="path843" inkscape:connector-curvature="0"/><path d="m 0.2,98.139 c 3.662,-5.584 10.315,-14.64 22.517,-20.72 -9.914,-7.959 -6.221,-16.817 0.296,-24.716 1.86,-2.254 3.864,-4.286 5.993,-6.091" stroke-miterlimit="1.4142" id="path845" inkscape:connector-curvature="0" style="fill:none;stroke:#000000;stroke-width:2.5px;stroke-linecap:butt;stroke-miterlimit:1.41419995"/><path d="m 103.22,56.752 c 33.605,3.604 59.756,52.679 95.675,52.586 37.871,-0.099 62.192,-36.921 35.992,-66.752 6.067,-7.321 13.278,-0.917 17.667,1.833 2.75,-2.482 4.25,-4.389 10.666,-6.333 5.917,6.222 -2.963,13.342 -0.833,18.833 6.393,16.481 6.361,50.928 -16.368,73.137 -14.105,13.782 -37.142,23.22 -73.465,17.696 -16.123,-2.452 -22.891,11.917 -36.192,29.058 -15.251,19.656 -17.901,41.707 -16.995,58.174" stroke-miterlimit="1.4142" id="path847" inkscape:connector-curvature="0" style="fill:none;stroke:#000000;stroke-width:2.5px;stroke-linecap:butt;stroke-miterlimit:1.41419995"/></g><rect style="fill:#552200;fill-opacity:1;stroke:#000000;stroke-width:1.28518212;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:9.69999981;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" id="rect903" width="2.3921227" height="132.43498" x="261.48419" y="-98.017494" ry="3.6685588" rx="0.017737137" transform="matrix(0.93176634,0.36305851,-0.29026706,0.95694568,0,0)"/><g id="ear" transform="translate(1.548497,28.510079)"><path d="m -0.2,82.153 c 1.465,-0.675 2.995,-1.047 4.584,-1.067 14.728,-0.191 23.716,10.351 30.667,29.5 l -5.667,51.333 c -8.83,15.645 -19.32,22.601 -29.584,22.197 z" id="path852" inkscape:connector-curvature="0" style="fill:#56606b"/><path d="m 29.575,161.78 c -6.286,14.409 -16.353,21.623 -29.8,22.4 l -0.8,17.8 c 17.772,-2.913 32.907,-18.909 32.708,-34.4 -0.031,-2.45 0.409,-5.588 -2.108,-5.8 z" id="path854" inkscape:connector-curvature="0" style="fill:#3a434e"/><path d="m -0.322,181.64 0.437,1.238 c 0.17,0.004 0.17,0.004 0.339,0.006 0.386,0.001 0.771,-0.004 1.156,-0.022 0.854,-0.041 1.705,-0.137 2.547,-0.285 0.782,-0.137 1.557,-0.32 2.319,-0.544 0.548,-0.161 1.09,-0.344 1.624,-0.546 3.629,-1.374 6.866,-3.636 9.686,-6.276 0.463,-0.435 0.916,-0.88 1.36,-1.335 0.618,-0.634 1.216,-1.287 1.797,-1.955 0.769,-0.885 1.507,-1.798 2.216,-2.731 0.834,-1.099 5.784,-8.459 5.999,-8.498 0.143,-0.027 0.29,-0.028 0.434,-0.004 0.144,0.025 0.282,0.074 0.409,0.146 0.385,0.218 -5.943,11.794 -8.351,14.038 -0.5,0.467 -0.944,0.991 -1.433,1.47 -3.031,2.974 -6.524,5.542 -10.462,7.177 -0.507,0.211 -1.022,0.406 -1.542,0.584 -2.419,0.828 -4.964,1.277 -7.522,1.282 -0.338,0.001 -0.676,-0.006 -1.013,-0.021 z m 0,-100.8 C 0.004,80.702 0.336,80.577 0.673,80.465 1.088,80.327 1.511,80.21 1.938,80.116 2.427,80.009 2.923,79.931 3.422,79.885 3.674,79.862 3.925,79.849 4.178,79.84 4.42,79.835 4.662,79.833 4.904,79.833 c 0.818,0.006 1.634,0.039 2.448,0.111 1.45,0.127 2.891,0.368 4.3,0.734 1.417,0.369 2.801,0.864 4.129,1.482 0.881,0.41 1.738,0.873 2.564,1.384 0.556,0.344 1.099,0.709 1.627,1.094 3.727,2.714 6.691,6.349 9.114,10.241 0.488,0.783 0.955,1.578 1.404,2.384 1.606,2.881 4.443,13.486 4.447,13.632 0.02,0.667 -0.655,0.719 -0.843,0.495 -0.126,-0.149 -0.136,-0.201 -0.218,-0.376 -0.161,-0.441 -0.323,-0.881 -0.488,-1.321 C 32.262,106.715 31.03,103.775 29.6,100.928 28.572,98.881 27.445,96.882 26.175,94.975 23.889,91.54 21.108,88.361 17.649,86.067 17.08,85.689 16.495,85.337 15.894,85.013 14.957,84.507 13.984,84.069 12.983,83.706 11.394,83.129 9.739,82.743 8.062,82.533 7.119,82.415 6.17,82.351 5.221,82.337 4.798,82.33 4.373,82.326 3.95,82.352 3.603,82.373 3.257,82.413 2.914,82.472 1.78,82.667 0.69,83.068 -0.322,83.609 Z" id="path856" inkscape:connector-curvature="0"/><path d="m -0.283,99.086 c 5.874,-3.336 11.929,-1.378 18.167,6 -9.031,1.219 -5.404,8.43 2.5,9.166 -7.718,4.048 -8.567,8.798 3.5,12.534 -3.064,17.657 -11.848,25.767 -24.109,25.192" id="path858" inkscape:connector-curvature="0" style="fill:#3a434e"/><path d="m 22.884,126.59 c -12.067,-3.736 -10.218,-8.286 -2.5,-12.334 -7.904,-0.736 -11.531,-7.947 -2.5,-9.166 -6.238,-7.378 -12.293,-9.336 -18.167,-6" id="path860" inkscape:connector-curvature="0" style="fill:none;stroke:#000000;stroke-width:2.5px"/><path d="m 5.384,170.09 c -2.131,2.23 -4.079,3.994 -5.667,4.833" id="path862" inkscape:connector-curvature="0" style="fill:none;stroke:#000000;stroke-width:2px"/></g><g id="tooth" transform="translate(1.548497,28.510079)"><path d="m 185.42,51.423 c 1.041,-0.089 2.306,0.5 3.031,0.773 7.107,2.685 17.611,13.538 23.16,21.398 15.745,22.304 11.694,46.438 5.101,63.658 -22.38,58.462 -90.231,44.968 -110.5,28.5 8.201,-2.251 12.497,-7.919 16.625,-14.912 8.739,2.011 13.697,1.952 14.042,-3.754 18.919,11.165 42.946,4.876 56.167,-15.334 6.391,-9.77 17.973,-28.086 -1.834,-64 -4.763,-8.636 -6.735,-15.404 -5.792,-16.329 z" id="path865" inkscape:connector-curvature="0" style="fill:#b3bfcd"/><path d="m 120.02,155.24 -3.044,0.335 -4.4,3.4 c 0.926,3.558 -1.372,5.643 -4.653,7.046 27.684,17.335 60.967,17.571 83.211,3.419 16.456,-10.469 25.688,-28.819 31.042,-53.265 0,0 -4.899,-1.934 -7.8,1.4 -8.7,10 -18.813,29.711 -27.8,37.2 -6.466,5.389 -19.817,9.793 -30.388,10.144 -15.512,0.515 -31,-6.847 -36.168,-9.679 z" id="path867" inkscape:connector-curvature="0" style="fill:#93a1b5"/><path d="m 132.37,176.08 c 2.968,0.715 5.221,3.704 1.785,2.969 -3.093,-0.662 -6.128,-1.563 -9.164,-2.451 -6.083,-1.779 -15.916,-7.174 -18.053,-9.141 -0.255,-0.234 -1.133,-0.712 -1.305,-0.78 -0.157,-0.062 -2.171,0.065 -2.231,-0.204 -0.091,-0.411 1.157,-0.859 1.722,-1.002 1.442,-0.366 6.464,-2.566 6.378,-4.411 -0.071,-1.527 -2.286,-4.351 -1.997,-4.273 0.343,0.093 4.752,1.594 4.652,0.058 l 0.011,-0.005 c 0.021,-0.103 0.014,-0.212 0.003,-0.316 -0.021,-0.184 -0.064,-0.365 -0.115,-0.543 -0.172,-0.595 -0.44,-1.16 -0.722,-1.709 l -0.057,-0.109 c -0.03,-0.074 -0.068,-0.146 -0.091,-0.223 -0.046,-0.155 -0.061,-0.317 -0.045,-0.478 2.882,1.016 3.671,1.014 4.918,1.103 2.078,0.148 5.061,-3.258 2.755,-5.813 4.448,3.951 17.188,3.059 14.821,-1.653 l 0.02,-0.239 c 0.058,-0.203 0.059,-0.263 0.174,-0.444 0.261,-0.413 0.754,-0.639 1.238,-0.568 0.212,0.031 0.258,0.069 0.45,0.158 0.216,0.127 0.432,0.252 0.65,0.376 1.142,0.64 2.311,1.231 3.51,1.757 2.333,1.023 4.768,1.813 7.259,2.346 3.736,0.8 4.381,3.379 0.462,2.643 -2.577,-0.484 -5.104,-1.224 -7.536,-2.2 -1.286,-0.516 -2.541,-1.101 -3.769,-1.741 l -0.102,-0.053 c -2.731,3.7 -7.577,4.022 -14.814,2.388 l -0.008,-0.004 c -0.971,2.432 -1.92,4.774 -6.447,4.417 0.391,1.834 -2.308,3.855 -4.2,2.849 0.639,0.769 0.902,2.042 0.545,3.501 -0.41,1.675 -4.052,3.667 -4.522,3.645 0.197,0.139 0.762,0.562 0.961,0.698 0.56,0.375 1.125,0.741 1.7,1.093 2.71,1.659 5.576,3.059 8.509,4.276 4.098,1.7 8.343,3.045 12.655,4.083 z" id="path869" inkscape:connector-curvature="0" style="fill:#08080a"/><path d="m 143.44,150.2 c 17.632,6.463 37.877,-0.509 49.61,-18.445 6.391,-9.77 17.973,-28.086 -1.834,-64 -4.763,-8.636 -6.735,-15.404 -5.792,-16.329 1.041,-0.089 2.306,0.5 3.031,0.773 7.107,2.685 17.611,13.538 23.16,21.398 15.745,22.304 11.694,46.438 5.101,63.658 -18.433,48.151 -67.71,47.49 -95.844,36.558" stroke-miterlimit="1.4142" id="path871" inkscape:connector-curvature="0" style="fill:none;stroke:#000000;stroke-width:2.5px;stroke-linecap:butt;stroke-miterlimit:1.41419995"/></g><g id="snout" transform="matrix(1,0,0,1.0739962,3.5821848,26.426706)"><path d="m 234.97,42.396 c 2.764,-4.253 8.761,-4.06 18.176,0.832 3.047,9.604 -4.024,9.242 -9.28,13.888 z" id="path874" inkscape:connector-curvature="0" style="fill:#56606b"/><path d="m 242.5,57.519 c -1.207,-3.352 -2.893,-6.521 -4.913,-9.453 -1.119,-1.624 -2.342,-3.173 -3.64,-4.658 l -0.227,-0.376 c -0.027,-0.144 -0.08,-0.286 -0.082,-0.432 -0.002,-0.147 0.023,-0.296 0.072,-0.434 0.079,-0.221 0.166,-0.321 0.293,-0.471 0.152,-0.181 0.309,-0.358 0.47,-0.531 0.816,-0.877 1.754,-1.65 2.814,-2.214 0.582,-0.31 1.197,-0.555 1.834,-0.723 0.433,-0.115 0.875,-0.195 1.32,-0.239 0.319,-0.032 0.639,-0.046 0.96,-0.042 2.04,0.019 4.007,0.721 5.805,1.642 0.293,0.15 0.583,0.307 0.871,0.469 0.376,0.212 0.747,0.432 1.114,0.659 0.442,0.273 0.879,0.556 1.313,0.842 0.631,0.418 1.257,0.845 1.887,1.265 l 0.032,0.021 c 0.951,0.603 6.117,1.629 1.849,2.576 0,0 -3.661,1.412 -3.216,0.98 1.101,-1.068 -0.522,-1.829 -1.931,-2.756 -0.413,-0.272 -0.827,-0.541 -1.248,-0.801 -0.381,-0.235 -0.766,-0.464 -1.158,-0.681 -0.301,-0.168 -0.606,-0.329 -0.916,-0.481 -0.217,-0.108 -0.437,-0.211 -0.659,-0.308 -1.485,-0.655 -3.134,-1.107 -4.765,-0.858 -0.251,0.038 -0.498,0.092 -0.741,0.162 -0.408,0.118 -0.802,0.28 -1.176,0.479 -0.68,0.362 -1.287,0.843 -1.837,1.379 l -0.064,0.064 -0.006,0.007 c 0.215,0.255 0.427,0.512 0.638,0.771 1.227,1.516 2.378,3.093 3.426,4.739 1.874,2.945 3.424,6.102 4.531,9.414 l 0.026,0.079 c 0.479,2.186 0.175,12.285 -2.676,-0.09 z" id="path876" inkscape:connector-curvature="0"/></g><text x="171.46365" y="59.472778" font-size="40px" letter-spacing="0px" word-spacing="0px" style="font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:0" xml:space="preserve" id="text881"><tspan x="171.46365" y="94.863403" id="tspan879"/></text>

<path style="fill:#56606b;stroke-width:0;stroke-miterlimit:1.5" inkscape:connector-curvature="0" id="path891" stroke-miterlimit="1.5" d="m 254.51199,79.991394 -5.5061,-1.0298 -1.948,4.2665 c -0.6162,0.11756 1.3146,6.8002 2.4868,9.5628 z"/><g id="g5622" transform="matrix(1.1923945,0.05802226,-0.06851471,1.408021,-53.112954,-15.400916)"><path sodipodi:nodetypes="cscc" inkscape:connector-curvature="0" id="path5523" d="m 274.32232,2.0051234 c 11.13163,2.6582635 16.85979,-6.0144134 29.86742,3.6678054 7.08036,5.2702622 13.83993,0.074078 19.27872,4.1229183 l -8.01892,26.3612099" style="fill:none;stroke:#000000;stroke-width:1.00157475;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:1.5;stroke-dasharray:none;stroke-opacity:1"/><path sodipodi:nodetypes="cscc" inkscape:connector-curvature="0" id="path5523-1" d="m 265.8434,28.157243 c 11.13163,2.658263 17.69615,-5.665929 30.70378,4.01629 7.08036,5.270262 13.77898,-0.272306 19.21777,3.776535 v 0" style="clip-rule:evenodd;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:1.5;stroke-opacity:1"/><path sodipodi:nodetypes="cscc" inkscape:connector-curvature="0" id="path5523-1-2" d="m 267.67495,22.317022 c 11.13163,2.658263 17.69615,-5.665929 30.70378,4.01629 7.08036,5.270262 13.77898,-0.272306 19.21777,3.776535 v 0" style="clip-rule:evenodd;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:1.5;stroke-opacity:1"/><path sodipodi:nodetypes="cscc" inkscape:connector-curvature="0" id="path5523-1-2-9" d="m 269.23616,16.713392 c 11.13163,2.658263 17.69615,-5.665929 30.70378,4.01629 7.08036,5.270262 13.77898,-0.272306 19.21777,3.776535 v 0" style="clip-rule:evenodd;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:1.5;stroke-opacity:1"/><path sodipodi:nodetypes="cscc" inkscape:connector-curvature="0" id="path5523-1-2-3" d="m 272.40419,6.8164339 c 11.13163,2.658263 17.69615,-5.665929 30.70378,4.0162901 7.08036,5.270262 13.77898,-0.272306 19.21777,3.776535 v 0" style="clip-rule:evenodd;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:1.5;stroke-opacity:1"/><path sodipodi:nodetypes="cscc" inkscape:connector-curvature="0" id="path5523-1-2-94" d="m 270.57434,12.28067 c 11.13163,2.658263 17.69615,-5.6659289 30.70378,4.01629 7.08036,5.270262 13.77898,-0.272306 19.21777,3.776535 v 0" style="clip-rule:evenodd;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:1.5;stroke-opacity:1"/><path inkscape:connector-curvature="0" id="path5601" d="m 321.38216,13.399666 c -1.40538,-0.740849 -3.63687,-1.056585 -6.22938,-0.881405 -3.5006,0.236541 -4.31818,0.262389 -5.41364,0.171156 -2.61595,-0.217865 -4.28377,-0.845588 -6.5326,-2.458707 -5.56348,-3.9907604 -10.17202,-5.2819754 -16.28411,-4.562457 -0.41148,0.04844 -1.86589,0.2625228 -3.23201,0.47574 -1.36613,0.2132174 -2.98065,0.4463678 -3.58782,0.518112 -1.46561,0.1731803 -4.41604,0.1774676 -5.54566,0.00806 -0.44796,-0.067181 -0.82912,-0.1367888 -0.84701,-0.1546836 -0.0179,-0.017895 0.22415,-0.8920469 0.53788,-1.9425604 0.52023,-1.7419792 0.58434,-1.9066259 0.72873,-1.8713949 0.91061,0.2221871 2.11884,0.3197397 3.94327,0.3183775 2.28223,-0.0017 2.73805,-0.053293 6.59023,-0.7458644 1.41281,-0.2540033 2.99184,-0.517531 3.50896,-0.5856169 3.91811,-0.5158676 7.09526,0.013776 10.58929,1.7652781 1.43231,0.7179952 2.2234,1.2144929 4.66277,2.9263833 1.45359,1.020097 3.31503,1.7920102 5.17034,2.1440626 0.91293,0.1732335 1.06209,0.1778592 5.04112,0.1563336 4.37958,-0.023692 5.00741,0.011744 6.18997,0.3493816 0.78815,0.2250293 2.12903,0.864197 2.12903,1.0148655 0,0.05772 -0.13891,0.560014 -0.30868,1.116207 -0.16977,0.556193 -0.40257,1.321744 -0.51732,1.701225 -0.11475,0.37948 -0.2331,0.688395 -0.263,0.686478 -0.0299,-0.0019 -0.17856,-0.06895 -0.33036,-0.148971 z" style="fill:#5bcefa;fill-opacity:1;stroke:#000000;stroke-width:0.02980272;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:9.69999981;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"/><path inkscape:connector-curvature="0" id="path5603" d="m 314.71984,34.709345 c -1.57855,-0.820776 -3.43122,-1.019032 -7.24373,-0.775162 -2.4834,0.158853 -4.662,0.132827 -5.74876,-0.06868 -1.65951,-0.307699 -3.20238,-0.974578 -4.71077,-2.036146 -3.65385,-2.571483 -5.88952,-3.699962 -8.79161,-4.437664 -1.69735,-0.431461 -2.68039,-0.544932 -4.73169,-0.546173 -1.97917,-0.0012 -2.7881,0.0756 -5.80079,0.55074 -4.35037,0.686104 -4.40796,0.691904 -6.93397,0.698327 -1.57002,0.004 -2.51733,-0.02719 -2.91425,-0.09591 -0.32345,-0.056 -0.60143,-0.115169 -0.61774,-0.131475 -0.033,-0.03298 1.36353,-4.681873 1.42483,-4.743176 0.0213,-0.02127 0.45974,0.0233 0.97436,0.09905 1.14636,0.16875 4.28145,0.192795 5.58799,0.04286 0.47707,-0.05475 2.12707,-0.293636 3.66667,-0.530866 3.57943,-0.551537 4.67642,-0.664942 6.4422,-0.665981 4.47005,-0.0026 7.98434,1.289199 12.71909,4.675446 2.9121,2.08271 5.73158,2.791505 9.97038,2.506479 4.23069,-0.284481 5.22758,-0.271567 6.69211,0.08669 0.66745,0.163273 1.97992,0.745575 1.974,0.875803 -0.0107,0.23512 -1.42314,4.706003 -1.48596,4.703536 -0.0434,-0.0017 -0.25593,-0.09517 -0.47236,-0.207703 z" style="fill:#5bcefa;fill-opacity:1;stroke:#000000;stroke-width:0.02980272;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:9.69999981;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"/><path inkscape:connector-curvature="0" id="path5605" d="m 316.39959,28.781942 c -0.26236,-0.138438 -0.90923,-0.371666 -1.4375,-0.518285 -0.87045,-0.241593 -1.20067,-0.262011 -3.52298,-0.217829 -1.40937,0.02681 -3.74375,0.04685 -5.1875,0.04452 -2.48987,-0.004 -2.68291,-0.02171 -3.75,-0.34377 -1.44659,-0.436603 -2.58258,-1.015068 -4.0625,-2.068703 -4.27595,-3.044287 -7.92884,-4.460616 -12.12717,-4.702054 -1.7868,-0.102756 -3.48591,0.05465 -7.74783,0.71778 -2.99394,0.465838 -3.5469,0.516517 -5.75,0.526997 -1.34062,0.0064 -2.75304,-0.03365 -3.1387,-0.08896 l -0.70121,-0.100553 0.67436,-2.205938 c 0.3709,-1.213266 0.6926,-2.224166 0.71487,-2.246444 0.0223,-0.02228 0.59615,0.02502 1.27527,0.105117 1.73354,0.204448 4.90759,0.06997 7.42541,-0.31459 1.13438,-0.173261 2.62082,-0.400237 3.3032,-0.504392 4.0572,-0.619269 6.83914,-0.511264 9.8007,0.380501 2.20983,0.665408 4.77444,2.012701 7.33266,3.852137 1.31232,0.9436 2.49572,1.558843 3.83564,1.994125 1.66655,0.541389 2.71189,0.628017 6.1028,0.505742 3.90398,-0.140775 5.49961,-0.124442 6.4375,0.0659 0.83663,0.169785 2.4375,0.852281 2.4375,1.039172 0,0.238495 -1.28935,4.342157 -1.36258,4.336756 -0.0412,-0.003 -0.28958,-0.118792 -0.55194,-0.257229 z" style="fill:#f5a9b8;fill-opacity:1;stroke:#000000;stroke-width:0.04724409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:9.69999981;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"/><path inkscape:connector-curvature="0" id="path5607" d="m 319.24912,18.725955 c -1.60483,-0.758811 -3.37094,-0.887531 -8.68501,-0.632993 -4.54944,0.217913 -5.9557,-0.211861 -10.24873,-3.132153 -2.70809,-1.84216 -5.50161,-3.085465 -8.2953,-3.691972 -1.08678,-0.235941 -1.64445,-0.274779 -3.89347,-0.271161 -2.21305,0.0036 -2.94867,0.05576 -4.6875,0.332606 -4.08477,0.650362 -4.58875,0.718448 -6.37989,0.8619 -1.3669,0.109474 -2.19975,0.110398 -3.35989,0.0037 -0.84837,-0.078 -1.56765,-0.16698 -1.59839,-0.197727 -0.0307,-0.03075 0.23811,-1.032619 0.59746,-2.2263819 l 0.65337,-2.1704787 1.26242,0.1690202 c 1.76721,0.2366042 4.834,0.1188409 7.51242,-0.2884729 4.58886,-0.6978395 5.46271,-0.8095524 6.95416,-0.889019 4.63956,-0.2472032 8.72258,1.1325697 13.54584,4.5775323 1.56737,1.119473 2.93391,1.788188 4.51603,2.209909 1.1359,0.302779 1.43764,0.32948 3.79647,0.335943 1.40938,0.0039 3.6875,-0.01909 5.0625,-0.05101 2.73034,-0.06338 3.24951,0.0073 4.64139,0.631568 l 0.7664,0.343748 -0.65781,2.16588 c -0.36179,1.191234 -0.70666,2.17721 -0.76639,2.191056 -0.0597,0.01385 -0.39096,-0.108339 -0.73608,-0.271523 z" style="fill:#f5a9b8;fill-opacity:1;stroke:#000000;stroke-width:0.04724409;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:9.69999981;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"/><path inkscape:connector-curvature="0" id="path5609" d="m 307.02,22.652839 c -2.11273,-0.09543 -3.83584,-0.560643 -5.46173,-1.474583 -0.47468,-0.266828 -0.82516,-0.498086 -1.46796,-0.968615 -2.50032,-1.830244 -4.78059,-3.060929 -7.12312,-3.844432 -1.68295,-0.562895 -3.30394,-0.863691 -5.14173,-0.954117 -0.4756,-0.0234 -1.84777,-0.0096 -2.34452,0.02365 -1.4506,0.09698 -2.34825,0.212534 -5.10938,0.657741 -2.55972,0.412732 -3.58602,0.539618 -5.14062,0.635554 -0.42598,0.02629 -2.00282,0.02648 -2.39844,2.92e-4 -0.65377,-0.04327 -1.17714,-0.09558 -1.68577,-0.168485 -0.29013,-0.04159 -0.52764,-0.08096 -0.53256,-0.08829 -0.003,-0.0043 1.03878,-3.438807 1.04495,-3.445273 0.002,-0.0019 0.0838,0.01034 0.18212,0.02731 0.61626,0.106337 1.32635,0.188779 2.09282,0.242978 0.47869,0.03385 1.80665,0.04718 2.36868,0.02378 1.55654,-0.06481 2.82047,-0.215235 5.64695,-0.672062 2.18209,-0.35268 3.04252,-0.471749 4.125,-0.570832 3.67864,-0.336717 6.65813,0.142399 9.85937,1.585435 0.39489,0.178006 1.27805,0.62536 1.69531,0.858746 1.06862,0.597695 2.00199,1.203585 3.22657,2.094485 0.27929,0.203194 0.59218,0.425585 0.69531,0.494202 1.1481,0.763917 2.36409,1.320341 3.62735,1.659832 0.8602,0.231175 1.64388,0.357829 2.73984,0.4428 0.3482,0.027 2.137,0.02708 2.59375,1.25e-4 0.51865,-0.03061 1.01974,-0.06343 1.60937,-0.105421 0.56563,-0.04028 1.37949,-0.09193 1.73047,-0.109831 l 0.20703,-0.01056 v 1.702759 1.70276 l -0.77734,0.009 c -0.42754,0.005 -0.89688,0.01638 -1.04297,0.02535 -0.44289,0.02718 -0.97716,0.06278 -1.42188,0.09474 -1.5211,0.109314 -1.88646,0.126332 -2.82812,0.13173 -0.46406,0.0027 -0.9,0.0023 -0.96875,-8.09e-4 z" style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.01564961;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.5;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"/></g><path style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.04424743;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.5;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 311.9789,34.31339 c -1.99476,-0.119488 -3.62873,-0.54612 -5.26615,-1.374998 -1.0951,-0.554348 -2.02026,-1.224513 -3.17063,-2.296742 -2.14733,-2.001488 -3.38922,-3.018817 -4.99438,-4.091338 -2.6991,-1.803451 -5.39714,-2.923258 -8.17873,-3.394517 -1.24653,-0.211189 -1.88699,-0.258006 -3.59033,-0.262451 -2.06592,-0.0054 -2.57483,0.0395 -6.46966,0.570747 -3.65653,0.49874 -4.81495,0.606937 -6.51383,0.608398 -1.30706,0.0011 -2.42346,-0.07015 -3.37837,-0.21569 -0.65545,-0.0999 -1.88853,-0.345832 -1.927,-0.384335 -0.0163,-0.01627 -0.008,-0.04481 0.76244,-2.527814 0.73932,-2.38379 0.67296,-2.199432 0.78315,-2.175568 2.35235,0.509423 3.42742,0.628802 5.63664,0.625911 1.82781,-0.0024 2.95737,-0.105219 6.50773,-0.592417 2.18362,-0.299648 3.54825,-0.464102 4.39488,-0.529635 5.41577,-0.419207 9.53985,0.756496 14.08853,4.016389 1.09867,0.787384 2.23611,1.747108 3.78411,3.192878 0.43939,0.410375 1.02564,0.928169 1.30276,1.150653 1.70826,1.37142 3.58862,2.260554 5.71494,2.702313 1.00384,0.208555 1.56732,0.263183 3.64333,0.353206 0.18217,0.0079 1.35099,-0.01422 2.59739,-0.04916 0.63984,-0.01794 3.18272,-0.302018 5.32471,0.172072 2.0306,0.449437 3.68191,1.6578 3.68556,1.661952 0.004,0.005 -0.0981,0.575028 -0.28628,1.19005 -0.13238,0.432741 -0.3625,0.816748 -0.3883,1.35008 l -0.72239,2.245772 -1.67224,-1.015884 -2.62459,-0.782389 -2.85158,-0.226571 c -0.86348,-0.0031 -2.44292,0.0705 -3.54806,0.08699 -1.10515,0.01653 -2.29479,0.01295 -2.64365,-0.0079 z" id="path5638" inkscape:connector-curvature="0" sodipodi:nodetypes="ccccscsccccccsscccscsscscccccccc"/></svg>
\ No newline at end of file

A public/index.html => public/index.html +33 -0
@@ 0,0 1,33 @@
<!DOCTYPE html>
<html lang='en'>
<head>
    <meta charset='utf-8'>
    <meta content='width=device-width, initial-scale=1' name='viewport'>
    <title>Masto-FE standalone</title>
    <link rel="manifest" type="applicaton/manifest+json" href="/manifest.json" />

    <meta name="theme-color" content="#282c37" />

    <script crossorigin='anonymous' src="/packs/js/locales.js"></script>
    <script crossorigin='anonymous' src="/packs/js/locales/glitch/en.js"></script>

    <link rel='preload' as='script' href='/packs/js/flavours/glitch/async/getting_started.js'>
    <link rel='preload' as='script' href='/packs/js/flavours/glitch/async/compose.js'>
    <link rel='preload' as='script' href='/packs/js/flavours/glitch/async/home_timeline.js'>
    <link rel='preload' as='script' href='/packs/js/flavours/glitch/async/notifications.js'>
    <script id='initial-state' type='application/json'>{}</script>

    <script src="/verify-state.js"></script>
    <script src="/packs/js/core/common.js"></script>
    <link rel="stylesheet" media="all" href="/packs/css/core/common.css" />

    <script src="/packs/js/flavours/glitch/common.js"></script>
    <link rel="stylesheet" media="all" href="/packs/css/flavours/glitch/common.css" />

    <script src="/packs/js/flavours/glitch/home.js"></script>
</head>
<body class='app-body no-reduce-motion system-font'>
<div class='app-holder' data-props='{&quot;locale&quot;:&quot;en&quot;}' id='mastodon'>
</div>
</body>
</html>

A public/login.html => public/login.html +13 -0
@@ 0,0 1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login | Masto-FE standalone</title>
    <script src="/auth.js"></script>
</head>
<body>
<input type="text" id="instance" placeholder="yourinstance.tld">
<button onclick="auth()" id="btn">Log in</button>
<span id="message"></span>
</body>
</html>
\ No newline at end of file

A public/logout.html => public/logout.html +14 -0
@@ 0,0 1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Logout | Masto-FE standalone</title>
    <script>
        localStorage.clear();
        window.location.href = "/login.html";
    </script>
</head>
<body>
Clearing local storage and redirecting back to <a href="/login.html">login</a>...
</body>
</html>
\ No newline at end of file

A public/manifest.json => public/manifest.json +12 -0
@@ 0,0 1,12 @@
{
  "background_color": "#191b22",
  "categories": ["social"],
  "description": "Masto-FE standalone",
  "display": "standalone",
  "name": "Masto-FE standalone",
  "serviceworker": {
    "src": "/sw.js"
  },
  "start_url": "/getting-started",
  "theme_color": "#282c37"
}

A public/prepare.html => public/prepare.html +11 -0
@@ 0,0 1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login | Masto-FE standalone</title>
    <script src="/verify-state.js"></script>
</head>
<body>
<p>Preparing state object...</p>
</body>
</html>
\ No newline at end of file

A public/verify-state.js => public/verify-state.js +103 -0
@@ 0,0 1,103 @@
loadState().then(_ => null);

async function loadState() {
  const domain = localStorage.getItem('domain');
  const access_token = localStorage.getItem('access_token');
  const storedState = localStorage.getItem('initial_state');

  if (!domain || !access_token) {
    window.location.href = '/login.html';
    return;
  }

  if (storedState && window.location.pathname !== '/prepare.html') {
    document.getElementById('initial-state').textContent = storedState;
  }

  const apiUrl = `https://${domain}/api`;
  const instance = await fetch(`${apiUrl}/v1/instance`).then(async p => await p.json());
  const options = {headers: {Authorization: `Bearer ${access_token}`}};
  const credentials = await fetch(`${apiUrl}/v1/accounts/verify_credentials`, options).then(async p => await p.json());
  const state = {
    "accounts": {
      "plc":{
        "accepts_direct_messages_from":"everybody",
        "acct": credentials.acct,
        "avatar": credentials.avatar,
        "avatar_static": credentials.avatar_static,
        "bot": credentials.bot,
        "created_at": credentials.created_at,
        "display_name": credentials.display_name,
        "emojis":[],
        "fields":[],
        "follow_requests_count":0,
        "followers_count": credentials.followers_count,
        "following_count": credentials.following_count,
        "fqn":`${credentials.acct}@${domain}`,
        "header": credentials.header,
        "header_static": credentials.header_static,
        "id": credentials.id,
        "last_status_at": credentials.created_at,
        "locked": credentials.locked,
        "note":"",
        "source": credentials.source,
        "statuses_count": credentials.statuses_count,
        "url": credentials.url,
        "username": credentials.acct
      }
    },
    "char_limit": instance.configuration.statuses.max_characters,
    "compose": {
      "allow_content_types": [
        "text/x.misskeymarkdown"
      ],
      "default_privacy": credentials.source.privacy,
      "default_sensitive": credentials.source.sensitive,
      "me": credentials.id
    },
    "media_attachments": {
      "accept_content_types": instance.configuration.media_attachments.supported_mime_types
    },
    "meta": {
      "access_token": access_token,
      "admin": "0",
      "advanced_layout": true,
      "auto_play_gif": false,
      "boost_modal": false,
      "compact_reaction": false,
      "delete_modal": true,
      "display_sensitive_media": false,
      "domain": domain,
      "enable_reaction": true,
      "locale": "en",
      "mascot": "/images/mascot.svg",
      "max_toot_chars": instance.configuration.statuses.max_characters,
      "me": credentials.id,
      "reduce_motion": false,
      "show_quote_button": true,
      "base_url": `https://${domain}`,
      "streaming_api_base_url": `wss://${domain}`,
      "title": `${instance.title}`,
      "unfollow_modal": true,
      "source_url": 'https://iceshrimp.dev/iceshrimp/masto-fe-standalone',
      "version": instance.version
    },
    "poll_limits": {
      "max_expiration": instance.configuration.polls.max_expiration,
      "max_option_chars": instance.configuration.polls.max_characters_per_option,
      "max_options": instance.configuration.polls.max_options,
      "min_expiration": instance.configuration.polls.min_expiration
    },
    "push_subscription": null,
    "rights": {
      "admin": false,
      "delete_others_notice": false
    },
    "settings": {}
  };

  const json = JSON.stringify(state);
  if (window.location.pathname !== '/prepare.html') document.getElementById('initial-state').textContent = json;
  localStorage.setItem("initial_state", json);
  if (window.location.pathname === '/prepare.html') window.location.href = '/';
}
\ No newline at end of file