Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f82c7eb444 | ||
|
|
05db376b89 | ||
|
|
77e9c25353 | ||
|
|
a4581ab199 | ||
|
|
d8e6cca66b |
@@ -3,8 +3,9 @@
|
||||
The backend seam for the admin panel: the registration gate — the first account
|
||||
on a fresh install becomes the administrator and closes signup behind itself,
|
||||
and this is how that gets reopened — the default quota, and managing users
|
||||
(search, per-user quota, block, delete). An administrator cannot block or delete
|
||||
their own account, so the instance can never lock out its last admin that way.
|
||||
(search, per-user quota, block, delete). An administrator can change their own
|
||||
quota but cannot block or delete their own account, so the instance can never
|
||||
lock out its last admin that way.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -280,12 +281,15 @@ def _user_out(user: User, used_bytes: int, accounts: list[str]) -> AdminUserOut:
|
||||
)
|
||||
|
||||
|
||||
async def _target(db: AsyncSession, admin: User, user_id: uuid.UUID) -> User:
|
||||
"""The user an admin action is aimed at; never the admin themselves."""
|
||||
async def _target(
|
||||
db: AsyncSession, admin: User, user_id: uuid.UUID, *, allow_self: bool = False
|
||||
) -> User:
|
||||
"""The user an admin action is aimed at. Blocking and deleting are never
|
||||
allowed on the admin themselves; a quota change is (``allow_self``)."""
|
||||
user = await db.scalar(select(User).where(User.id == user_id))
|
||||
if user is None:
|
||||
raise_api_error(404, "not_found", "user not found")
|
||||
if user.id == admin.id:
|
||||
if user.id == admin.id and not allow_self:
|
||||
raise_api_error(409, "cannot_modify_self", "you cannot do this to your own account")
|
||||
return user
|
||||
|
||||
@@ -303,7 +307,8 @@ async def patch_user(
|
||||
user_id: uuid.UUID, body: AdminUserPatch, admin: CurrentAdmin, db: DbSession
|
||||
) -> AdminUserOut:
|
||||
"""Change a user's quota and/or block or unblock them."""
|
||||
user = await _target(db, admin, user_id)
|
||||
# Your own quota is fine to change; blocking yourself is not.
|
||||
user = await _target(db, admin, user_id, allow_self=body.blocked is None)
|
||||
if body.quota_bytes is not None:
|
||||
user.quota_bytes = body.quota_bytes
|
||||
if body.blocked is not None:
|
||||
|
||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.5 KiB |
@@ -168,6 +168,20 @@ async def test_admin_cannot_block_or_delete_themselves(world: World) -> None:
|
||||
assert (await c.delete(f"/api/admin/users/{admin.id}")).status_code == 409
|
||||
|
||||
|
||||
async def test_admin_can_change_their_own_quota(world: World) -> None:
|
||||
admin = await world.user(admin=True)
|
||||
async with await world.client(admin) as c:
|
||||
resp = await c.patch(f"/api/admin/users/{admin.id}", json={"quota_bytes": 5 * 2**30})
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["quota_bytes"] == 5 * 2**30
|
||||
# Quota together with a block is still refused: the block is the problem.
|
||||
assert (
|
||||
await c.patch(
|
||||
f"/api/admin/users/{admin.id}", json={"quota_bytes": 2**30, "blocked": True}
|
||||
)
|
||||
).status_code == 409
|
||||
|
||||
|
||||
async def test_delete_removes_the_user_and_their_data(world: World, bus: _Bus) -> None:
|
||||
admin = await world.user(admin=True)
|
||||
target = await world.user()
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="2.5488772"
|
||||
inkscape:cx="89.451151"
|
||||
inkscape:cy="-10.20057"
|
||||
inkscape:cy="-10.004405"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1403"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-height="1376"
|
||||
inkscape:window-x="2560"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" /><defs
|
||||
id="defs1"><linearGradient
|
||||
@@ -84,61 +84,61 @@
|
||||
style="fill:url(#linearGradient1789);fill-opacity:1;stroke-width:0.2;stroke-linecap:round"
|
||||
d="m 18.20332,107.02297 c 0,0 1.930731,1.59986 4.989281,4.12846 1.725315,1.42637 3.809507,3.14827 6.108453,5.04523 5.657506,4.74616 33.20207,27.48506 33.558689,27.18811 0.241178,-0.20083 -16.275384,9.86387 -16.275384,9.86387 L 17.979141,140.69463 Z"
|
||||
id="path1776-1"
|
||||
sodipodi:nodetypes="cccsccc" /></mask><linearGradient
|
||||
sodipodi:nodetypes="cccsccc" /></mask><radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient11"
|
||||
id="linearGradient13-6"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.75684298,0,0,0.80151105,-18.692802,-93.278335)"
|
||||
x1="54.340946"
|
||||
y1="122.84999"
|
||||
x2="54.340946"
|
||||
y2="162.66415" /><radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient8"
|
||||
id="radialGradient816"
|
||||
xlink:href="#linearGradient8-5"
|
||||
id="radialGradient816-3"
|
||||
cx="8.0343275"
|
||||
cy="13.452971"
|
||||
fx="8.0343275"
|
||||
fy="13.452971"
|
||||
r="16.678839"
|
||||
gradientTransform="matrix(-0.29271702,0.00410386,-0.01334442,-0.95181978,11.840139,23.950941)"
|
||||
gradientTransform="matrix(-0.29273442,-0.00257918,0.01127876,-1.2801289,10.212159,-15.400302)"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
id="linearGradient8"
|
||||
id="linearGradient8-5"
|
||||
inkscape:collect="always"><stop
|
||||
style="stop-color:#fa7367;stop-opacity:1;"
|
||||
style="stop-color:#fa4435;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop8" /><stop
|
||||
id="stop8-6" /><stop
|
||||
style="stop-color:#fa7367;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop9" /></linearGradient><radialGradient
|
||||
id="stop9-2" /></linearGradient><radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient6"
|
||||
id="radialGradient815"
|
||||
cx="35.709007"
|
||||
cy="12.042645"
|
||||
fx="35.709007"
|
||||
fy="12.042645"
|
||||
xlink:href="#linearGradient6-1"
|
||||
id="radialGradient815-9"
|
||||
cx="34.523952"
|
||||
cy="11.253304"
|
||||
fx="34.523952"
|
||||
fy="11.253304"
|
||||
r="16.740065"
|
||||
gradientTransform="matrix(0.25712152,0.00260463,-0.00969379,0.95694375,26.644192,0.97301292)"
|
||||
gradientTransform="matrix(0.27572675,-0.00557307,0.0193391,0.95679742,24.457243,-42.568342)"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
id="linearGradient6"
|
||||
id="linearGradient6-1"
|
||||
inkscape:collect="always"><stop
|
||||
style="stop-color:#fab23b;stop-opacity:1;"
|
||||
style="stop-color:#fadc56;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop6" /><stop
|
||||
style="stop-color:#fab23a;stop-opacity:0;"
|
||||
id="stop6-2" /><stop
|
||||
style="stop-color:#faae3a;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop7" /></linearGradient></defs><g
|
||||
id="g816"
|
||||
transform="translate(0.128676,-0.079965)"><path
|
||||
id="path1-2-1-1"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#linearGradient13-6);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 37.175189,4.174075 c -2.15234,-7.4e-4 -4.0678,1.84724 -4.0671,4.1266 v 2.85212 c -0.073,-0.005 -0.14656,-0.007 -0.2202,-0.007 h -10.50012 -10.49855 c -0.0418,-7e-5 -0.0835,5.9e-4 -0.12516,0.002 v -2.78918 c -1.6e-4,-2.27807 -1.9592601,-4.12477 -4.1103801,-4.12494 -2.15111,1.8e-4 -4.20874,1.84687 -4.2089,4.12494 v 15.55127 c -4e-5,0.005 -0.0295,3.84339 2.173,7.60498 2.20396,3.76402 7.0382001,7.38257 14.4526701,7.38257 h 4.58067 c 7.4177,0 12.26918,-3.66617 14.50586,-7.44221 2.23668,-3.77604 2.27039,-7.63481 2.27039,-7.63481 l 0.002,-0.0344 V 8.300605 c 7e-4,-2.27872 -2.10202,-4.12642 -4.25374,-4.1266 z m -2.00792,7.9252 v 11.59858 0.0286 c 0,0 -0.0664,2.0824 -1.31139,4.18416 -0.57202,0.96572 -1.44541,2.15762 -2.91767,2.99793 -1.47227,0.8403 -3.46066,1.33079 -6.27223,1.33079 h -4.58106 c -2.82381,0 -4.79864,-0.47938 -6.25697,-1.30429 -1.45833,-0.8249 -2.31357,-2.00343 -2.85901,-2.93496 -1.1966401,-2.04365 -1.2300401,-4.07193 -1.2300401,-4.07193 l -0.001,-0.0141 v -0.0157 -11.69637 l 1.96e-4,-0.0142 0.0035,-0.008 c 0.05128,0.0487 0.18806,0.16304 0.24192,0.20765 l 5.4247011,4.54513 5.51504,4.57144 c 0.65194,0.53945 2.27903,0.53945 2.93097,0 l 5.51504,-4.57144 5.487378,-4.57351 c 0.0199,-0.0164 0.04083,-0.0331 0.06003,-0.048 l 0.02765,-0.0215 z"
|
||||
id="stop7-7" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient11"
|
||||
id="linearGradient1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.75684298,0,0,0.80151105,-19.989667,-137.1)"
|
||||
x1="54.340946"
|
||||
y1="122.84999"
|
||||
x2="54.340946"
|
||||
y2="162.66415" /></defs><g
|
||||
id="g1"
|
||||
transform="translate(1.425541,43.741704)"><path
|
||||
id="path1-2-1"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#linearGradient1);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 35.878324,-39.647594 c -2.15234,-7.4e-4 -4.0678,1.84724 -4.0671,4.1266 v 2.85212 c -0.073,-0.005 -0.14656,-0.007 -0.2202,-0.007 h -10.50012 -10.49855 c -0.0418,-7e-5 -0.0835,5.9e-4 -0.12516,0.002 v -2.78918 c -1.6e-4,-2.27807 -1.9592603,-4.12477 -4.1103803,-4.12494 -2.15111,1.8e-4 -4.20874,1.84687 -4.2089,4.12494 v 15.55127 c -4e-5,0.005 -0.0295,3.84339 2.173,7.60498 2.20396,3.7640196 7.0382003,7.3825696 14.4526703,7.3825696 h 4.58067 c 7.4177,0 12.26918,-3.66617 14.50586,-7.4422096 2.23668,-3.77604 2.27039,-7.63481 2.27039,-7.63481 l 0.002,-0.0344 v -15.48541 c 7e-4,-2.27872 -2.10202,-4.12642 -4.25374,-4.1266 z m -2.00792,7.9252 v 11.59858 0.0286 c 0,0 -0.0664,2.0824 -1.31139,4.18416 -0.57202,0.96572 -1.44541,2.15762 -2.91767,2.99793 -1.47227,0.8403 -3.46066,1.33079 -6.27223,1.33079 h -4.58106 c -2.82381,0 -4.79864,-0.47938 -6.25697,-1.30429 -1.45833,-0.8249 -2.31357,-2.00343 -2.8590102,-2.93496 -1.1966401,-2.04365 -1.2300401,-4.07193 -1.2300401,-4.07193 l -10e-4,-0.0141 v -0.0157 -11.69637 l 1.96e-4,-0.0142 0.0035,-0.008 c 0.05128,0.0487 0.18806,0.16304 0.24192,0.20765 l 5.4247013,4.54513 5.51504,4.57144 c 0.65194,0.53945 2.27903,0.53945 2.93097,0 l 5.51504,-4.57144 5.487378,-4.57351 c 0.0199,-0.0164 0.04083,-0.0331 0.06003,-0.048 l 0.02765,-0.0215 z"
|
||||
sodipodi:nodetypes="cccccccccccssssccccccccssssssccccccccccccscc" /><path
|
||||
id="path1-2-1-9"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient816);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="M 3.6452555,7.14375 C 3.5159759,7.5278714 3.4447807,7.9357538 3.444751,8.3586629 V 23.909652 c -4e-5,0.005 -0.029506,3.84363 2.172994,7.60522 2.20396,3.76402 7.038395,7.382495 14.452865,7.382495 h 4.580598 c 5.433936,0 9.490679,-1.96731 12.151197,-4.504117 -1.517177,-1.218257 -3.218664,-2.595334 -5.013648,-4.05505 -0.258984,0.201994 -0.541601,0.394146 -0.850594,0.570508 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -1.45833,-0.8249 -2.313817,-2.003692 -2.859257,-2.935222 -1.1966406,-2.04365 -1.2298996,-4.071586 -1.2298996,-4.071586 l -0.00103,-0.01395 v -0.01602 -11.696444 -0.01395 l 0.00362,-0.0083 c 0.05128,0.0487 0.1879857,0.16313 0.2418457,0.20774 l 5.4249919,4.545459 5.514909,4.571297 c 0.0844,0.06984 0.185312,0.130661 0.298173,0.182418 C 16.15064,17.512631 11.679239,13.804108 9.750826,12.186336 7.4531167,10.290397 5.3700283,8.5696704 3.6452555,7.14375 Z" /><path
|
||||
id="path1-2-1-7"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient815);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 41.226941,7.09104 c -1.718284,1.4205409 -3.7895,3.131915 -6.072498,5.015715 -1.93499,1.62329 -6.430112,5.351263 -11.522294,9.54257 0.08232,-0.04302 0.156783,-0.0915 0.221692,-0.145211 l 5.514909,-4.571297 5.48752,-4.573881 c 0.0199,-0.0164 0.04075,-0.03316 0.05994,-0.04806 l 0.0279,-0.02119 0.222726,-0.190169 v 11.598258 0.02894 c 0,0 -0.06656,2.081964 -1.311548,4.183724 -0.57202,0.96572 -1.445392,2.157957 -2.917652,2.998267 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -0.308682,-0.174605 -0.590588,-0.364744 -0.848011,-0.565341 -1.805553,1.467965 -3.513225,2.849693 -5.0307002,4.067452 2.6425302,2.518552 6.6894502,4.460193 12.1212242,4.460193 h 4.580599 c 7.4177,0 12.269411,-3.665883 14.506091,-7.441923 2.23668,-3.77604 2.270146,-7.635193 2.270146,-7.635193 l 0.0021,-0.03411 V 8.3007853 C 41.429125,7.8798785 41.357396,7.4736084 41.226941,7.09104 Z" /></g></svg>
|
||||
id="path1-2-1-9-9"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient816-3);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 2.3483903,-36.677919 c -0.1292796,0.384121 -0.2004748,0.792004 -0.2005045,1.214913 v 15.550989 c -4e-5,0.005 -0.029506,3.84363 2.172994,7.60522 2.20396,3.7640196 7.0383952,7.3824946 14.4528652,7.3824946 h 4.580598 c 5.433936,0 9.490679,-1.96731 12.151197,-4.504117 -1.517177,-1.2182566 -3.218664,-2.5953336 -5.013648,-4.0550496 -0.258984,0.201994 -0.541601,0.394146 -0.850594,0.570508 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -1.45833,-0.8249 -2.313817,-2.003692 -2.8592572,-2.935222 -1.1966406,-2.04365 -1.2298996,-4.071586 -1.2298996,-4.071586 l -0.00103,-0.01395 v -0.01602 -11.696444 -0.01395 l 0.00362,-0.0083 c 0.05128,0.0487 0.1879857,0.16313 0.2418457,0.20774 l 5.4249921,4.545459 5.514909,4.571297 c 0.0844,0.06984 0.185312,0.130661 0.298173,0.182418 -5.07081,-4.173874 -9.542211,-7.882397 -11.4706242,-9.500169 -2.2977093,-1.895939 -4.3807977,-3.616666 -6.1055705,-5.042586 z" /><path
|
||||
id="path1-2-1-7-3"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient815-9);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 39.930076,-36.730629 c -1.718284,1.420541 -3.7895,3.131915 -6.072498,5.015715 -1.93499,1.62329 -6.430112,5.351263 -11.522294,9.54257 0.08232,-0.04302 0.156783,-0.0915 0.221692,-0.145211 l 5.514909,-4.571297 5.48752,-4.573881 c 0.0199,-0.0164 0.04075,-0.03316 0.05994,-0.04806 l 0.0279,-0.02119 0.222726,-0.190169 v 11.598258 0.02894 c 0,0 -0.06656,2.081964 -1.311548,4.183724 -0.57202,0.96572 -1.445392,2.157957 -2.917652,2.998267 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -0.308682,-0.174605 -0.590588,-0.364744 -0.848011,-0.565341 -1.8055532,1.467965 -3.5132252,2.849693 -5.0307004,4.0674516 2.6425302,2.518552 6.6894504,4.460193 12.1212244,4.460193 h 4.580599 c 7.4177,0 12.269411,-3.665883 14.506091,-7.4419226 2.23668,-3.77604 2.270146,-7.635193 2.270146,-7.635193 l 0.0021,-0.03411 v -15.485354 c 1.06e-4,-0.420906 -0.07162,-0.827177 -0.202078,-1.209745 z" /></g></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.1 KiB |
@@ -24,19 +24,19 @@
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="1.9327415"
|
||||
inkscape:cx="187.29872"
|
||||
inkscape:cy="135.81744"
|
||||
inkscape:zoom="0.96637075"
|
||||
inkscape:cx="84.853562"
|
||||
inkscape:cy="115.89755"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1376"
|
||||
inkscape:window-x="2560"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-height="1403"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" /><defs
|
||||
id="defs1"><linearGradient
|
||||
id="linearGradient8"
|
||||
inkscape:collect="always"><stop
|
||||
style="stop-color:#fa7367;stop-opacity:1;"
|
||||
style="stop-color:#fa4435;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop8" /><stop
|
||||
style="stop-color:#fa7367;stop-opacity:0;"
|
||||
@@ -44,10 +44,10 @@
|
||||
id="stop9" /></linearGradient><linearGradient
|
||||
id="linearGradient6"
|
||||
inkscape:collect="always"><stop
|
||||
style="stop-color:#fab23b;stop-opacity:1;"
|
||||
style="stop-color:#fadc56;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop6" /><stop
|
||||
style="stop-color:#fab23a;stop-opacity:0;"
|
||||
style="stop-color:#faae3a;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop7" /></linearGradient><linearGradient
|
||||
id="linearGradient11"><stop
|
||||
@@ -105,12 +105,12 @@
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient6"
|
||||
id="radialGradient815"
|
||||
cx="35.709007"
|
||||
cy="12.042645"
|
||||
fx="35.709007"
|
||||
fy="12.042645"
|
||||
cx="34.523952"
|
||||
cy="11.253304"
|
||||
fx="34.523952"
|
||||
fy="11.253304"
|
||||
r="16.740065"
|
||||
gradientTransform="matrix(0.25712152,0.00260463,-0.00969379,0.95694375,26.644192,0.97301292)"
|
||||
gradientTransform="matrix(0.27572675,-0.00557307,0.0193391,0.95679742,25.754108,1.2533273)"
|
||||
gradientUnits="userSpaceOnUse" /><radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient8"
|
||||
@@ -120,19 +120,18 @@
|
||||
fx="8.0343275"
|
||||
fy="13.452971"
|
||||
r="16.678839"
|
||||
gradientTransform="matrix(-0.29271702,0.00410386,-0.01334442,-0.95181978,11.840139,23.950941)"
|
||||
gradientUnits="userSpaceOnUse" /></defs><g
|
||||
id="g816"><path
|
||||
id="path1-2-1"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#linearGradient13);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 37.175189,4.174075 c -2.15234,-7.4e-4 -4.0678,1.84724 -4.0671,4.1266 v 2.85212 c -0.073,-0.005 -0.14656,-0.007 -0.2202,-0.007 h -10.50012 -10.49855 c -0.0418,-7e-5 -0.0835,5.9e-4 -0.12516,0.002 v -2.78918 c -1.6e-4,-2.27807 -1.9592601,-4.12477 -4.1103801,-4.12494 -2.15111,1.8e-4 -4.20874,1.84687 -4.2089,4.12494 v 15.55127 c -4e-5,0.005 -0.0295,3.84339 2.173,7.60498 2.20396,3.76402 7.0382001,7.38257 14.4526701,7.38257 h 4.58067 c 7.4177,0 12.26918,-3.66617 14.50586,-7.44221 2.23668,-3.77604 2.27039,-7.63481 2.27039,-7.63481 l 0.002,-0.0344 V 8.300605 c 7e-4,-2.27872 -2.10202,-4.12642 -4.25374,-4.1266 z m -2.00792,7.9252 v 11.59858 0.0286 c 0,0 -0.0664,2.0824 -1.31139,4.18416 -0.57202,0.96572 -1.44541,2.15762 -2.91767,2.99793 -1.47227,0.8403 -3.46066,1.33079 -6.27223,1.33079 h -4.58106 c -2.82381,0 -4.79864,-0.47938 -6.25697,-1.30429 -1.45833,-0.8249 -2.31357,-2.00343 -2.85901,-2.93496 -1.1966401,-2.04365 -1.2300401,-4.07193 -1.2300401,-4.07193 l -0.001,-0.0141 v -0.0157 -11.69637 l 1.96e-4,-0.0142 0.0035,-0.008 c 0.05128,0.0487 0.18806,0.16304 0.24192,0.20765 l 5.4247011,4.54513 5.51504,4.57144 c 0.65194,0.53945 2.27903,0.53945 2.93097,0 l 5.51504,-4.57144 5.487378,-4.57351 c 0.0199,-0.0164 0.04083,-0.0331 0.06003,-0.048 l 0.02765,-0.0215 z"
|
||||
sodipodi:nodetypes="cccccccccccssssccccccccssssssccccccccccccscc" /><path
|
||||
id="path1-2-1-9"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient816);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="M 3.6452555 7.14375 C 3.5159759 7.5278714 3.4447807 7.9357538 3.444751 8.3586629 L 3.444751 23.909652 C 3.444711 23.914652 3.4152451 27.753282 5.617745 31.514872 C 7.821705 35.278892 12.65614 38.897367 20.07061 38.897367 L 24.651208 38.897367 C 30.085144 38.897367 34.141887 36.930057 36.802405 34.39325 C 35.285228 33.174993 33.583741 31.797916 31.788757 30.3382 C 31.529773 30.540194 31.247156 30.732346 30.938163 30.908708 C 29.465893 31.749008 27.477765 32.239376 24.666195 32.239376 L 20.085079 32.239376 C 17.261269 32.239376 15.286427 31.759973 13.828097 30.935063 C 12.369767 30.110163 11.51428 28.931371 10.96884 27.999841 C 9.7721994 25.956191 9.7389404 23.928255 9.7389404 23.928255 L 9.7379069 23.914303 L 9.7379069 23.898283 L 9.7379069 12.201839 L 9.7379069 12.187887 L 9.7415243 12.179618 C 9.7928043 12.228318 9.92951 12.342748 9.98337 12.387358 L 15.408362 16.932817 L 20.923271 21.504114 C 21.007674 21.573954 21.108583 21.634775 21.221444 21.686532 C 16.15064 17.512631 11.679239 13.804108 9.750826 12.186336 C 7.4531167 10.290397 5.3700283 8.5696704 3.6452555 7.14375 z " /><path
|
||||
id="path1-2-1-7"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient815);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="M 41.226941 7.09104 C 39.508657 8.5115809 37.437441 10.222955 35.154443 12.106755 C 33.219453 13.730045 28.724331 17.458018 23.632149 21.649325 C 23.714466 21.60631 23.788932 21.557823 23.853841 21.504114 L 29.36875 16.932817 L 34.85627 12.358936 C 34.87617 12.342536 34.897015 12.325776 34.916215 12.310876 L 34.94412 12.289689 L 35.166846 12.09952 L 35.166846 23.697778 L 35.166846 23.726717 C 35.166846 23.726717 35.100288 25.808681 33.855298 27.910441 C 33.283278 28.876161 32.409906 30.068398 30.937646 30.908708 C 29.465376 31.749008 27.477248 32.239376 24.665678 32.239376 L 20.084562 32.239376 C 17.260752 32.239376 15.28591 31.759973 13.82758 30.935063 C 13.518898 30.760458 13.236992 30.570319 12.979569 30.369722 C 11.174016 31.837687 9.466344 33.219415 7.9488688 34.437174 C 10.591399 36.955726 14.638319 38.897367 20.070093 38.897367 L 24.650692 38.897367 C 32.068392 38.897367 36.920103 35.231484 39.156783 31.455444 C 41.393463 27.679404 41.426929 23.820251 41.426929 23.820251 L 41.428996 23.786145 L 41.428996 8.3007853 C 41.429125 7.8798785 41.357396 7.4736084 41.226941 7.09104 z " /></g><text
|
||||
gradientTransform="matrix(-0.29273442,-0.00257918,0.01127876,-1.2801289,11.509024,28.421367)"
|
||||
gradientUnits="userSpaceOnUse" /></defs><path
|
||||
id="path1-2-1"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#linearGradient13);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 37.175189,4.174075 c -2.15234,-7.4e-4 -4.0678,1.84724 -4.0671,4.1266 v 2.85212 c -0.073,-0.005 -0.14656,-0.007 -0.2202,-0.007 h -10.50012 -10.49855 c -0.0418,-7e-5 -0.0835,5.9e-4 -0.12516,0.002 v -2.78918 c -1.6e-4,-2.27807 -1.9592601,-4.12477 -4.1103801,-4.12494 -2.15111,1.8e-4 -4.20874,1.84687 -4.2089,4.12494 v 15.55127 c -4e-5,0.005 -0.0295,3.84339 2.173,7.60498 2.20396,3.76402 7.0382001,7.38257 14.4526701,7.38257 h 4.58067 c 7.4177,0 12.26918,-3.66617 14.50586,-7.44221 2.23668,-3.77604 2.27039,-7.63481 2.27039,-7.63481 l 0.002,-0.0344 V 8.300605 c 7e-4,-2.27872 -2.10202,-4.12642 -4.25374,-4.1266 z m -2.00792,7.9252 v 11.59858 0.0286 c 0,0 -0.0664,2.0824 -1.31139,4.18416 -0.57202,0.96572 -1.44541,2.15762 -2.91767,2.99793 -1.47227,0.8403 -3.46066,1.33079 -6.27223,1.33079 h -4.58106 c -2.82381,0 -4.79864,-0.47938 -6.25697,-1.30429 -1.45833,-0.8249 -2.31357,-2.00343 -2.85901,-2.93496 -1.1966401,-2.04365 -1.2300401,-4.07193 -1.2300401,-4.07193 l -0.001,-0.0141 v -0.0157 -11.69637 l 1.96e-4,-0.0142 0.0035,-0.008 c 0.05128,0.0487 0.18806,0.16304 0.24192,0.20765 l 5.4247011,4.54513 5.51504,4.57144 c 0.65194,0.53945 2.27903,0.53945 2.93097,0 l 5.51504,-4.57144 5.487378,-4.57351 c 0.0199,-0.0164 0.04083,-0.0331 0.06003,-0.048 l 0.02765,-0.0215 z"
|
||||
sodipodi:nodetypes="cccccccccccssssccccccccssssssccccccccccccscc" /><path
|
||||
id="path1-2-1-9"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient816);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="M 3.6452555,7.14375 C 3.5159759,7.5278714 3.4447807,7.9357538 3.444751,8.3586629 V 23.909652 c -4e-5,0.005 -0.029506,3.84363 2.172994,7.60522 2.20396,3.76402 7.038395,7.382495 14.452865,7.382495 h 4.580598 c 5.433936,0 9.490679,-1.96731 12.151197,-4.504117 -1.517177,-1.218257 -3.218664,-2.595334 -5.013648,-4.05505 -0.258984,0.201994 -0.541601,0.394146 -0.850594,0.570508 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -1.45833,-0.8249 -2.313817,-2.003692 -2.859257,-2.935222 -1.1966406,-2.04365 -1.2298996,-4.071586 -1.2298996,-4.071586 l -0.00103,-0.01395 v -0.01602 -11.696444 -0.01395 l 0.00362,-0.0083 c 0.05128,0.0487 0.1879857,0.16313 0.2418457,0.20774 l 5.4249919,4.545459 5.514909,4.571297 c 0.0844,0.06984 0.185312,0.130661 0.298173,0.182418 C 16.15064,17.512631 11.679239,13.804108 9.750826,12.186336 7.4531167,10.290397 5.3700283,8.5696704 3.6452555,7.14375 Z" /><path
|
||||
id="path1-2-1-7"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient815);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 41.226941,7.09104 c -1.718284,1.4205409 -3.7895,3.131915 -6.072498,5.015715 -1.93499,1.62329 -6.430112,5.351263 -11.522294,9.54257 0.08232,-0.04302 0.156783,-0.0915 0.221692,-0.145211 l 5.514909,-4.571297 5.48752,-4.573881 c 0.0199,-0.0164 0.04075,-0.03316 0.05994,-0.04806 l 0.0279,-0.02119 0.222726,-0.190169 v 11.598258 0.02894 c 0,0 -0.06656,2.081964 -1.311548,4.183724 -0.57202,0.96572 -1.445392,2.157957 -2.917652,2.998267 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -0.308682,-0.174605 -0.590588,-0.364744 -0.848011,-0.565341 -1.805553,1.467965 -3.513225,2.849693 -5.0307002,4.067452 2.6425302,2.518552 6.6894502,4.460193 12.1212242,4.460193 h 4.580599 c 7.4177,0 12.269411,-3.665883 14.506091,-7.441923 2.23668,-3.77604 2.270146,-7.635193 2.270146,-7.635193 l 0.0021,-0.03411 V 8.3007853 C 41.429125,7.8798785 41.357396,7.4736084 41.226941,7.09104 Z" /><text
|
||||
xml:space="preserve"
|
||||
style="font-size:44.7887px;line-height:1.5;font-family:Roboto;-inkscape-font-specification:Roboto;letter-spacing:0.223944px;writing-mode:lr-tb;direction:ltr;fill:#f74e6a;fill-opacity:1;stroke:#ff0000;stroke-width:56.4265;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none"
|
||||
x="43.547977"
|
||||
|
||||
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.5 KiB |
@@ -0,0 +1,237 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Ultimo Splash</title>
|
||||
<style>
|
||||
/* ---- Splash styles (copy into your site) ---------------------------- */
|
||||
:root { --splash-bg: #0f0f12; }
|
||||
#ultimo-splash {
|
||||
position: fixed; inset: 0; z-index: 9999;
|
||||
display: grid; place-items: center;
|
||||
background: var(--splash-bg);
|
||||
transition: opacity .35s ease, visibility 0s linear .35s;
|
||||
cursor: pointer;
|
||||
}
|
||||
#ultimo-splash.is-done { opacity: 0; visibility: hidden; pointer-events: none; }
|
||||
#ultimo-splash svg { width: min(46vmin, 340px); height: auto; overflow: visible; }
|
||||
|
||||
/* ---- Demo page only (remove on your site) ---------------------------- */
|
||||
html, body { margin: 0; height: 100%; }
|
||||
body { font-family: system-ui, sans-serif; background: #f6f6f7; color: #1b1b1f;
|
||||
display: grid; place-items: center; }
|
||||
.demo { text-align: center; }
|
||||
.demo button { font: inherit; padding: 12px 20px; border-radius: 10px; border: 1px solid #c9c9cf;
|
||||
background: #fff; cursor: pointer; min-height: 44px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ======================= SPLASH MARKUP (copy) ======================= -->
|
||||
<div id="ultimo-splash" aria-hidden="true">
|
||||
<svg viewBox="-10 -6 65 56" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="ul-g1" x1="54.340946" y1="122.84999" x2="54.340946" y2="162.66415" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.75684298,0,0,0.80151105,-19.989667,-137.1)">
|
||||
<stop offset="0.20198774" stop-color="#f89629"/><stop offset="1" stop-color="#f74176"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="ul-r1" cx="8.0343275" cy="13.452971" fx="8.0343275" fy="13.452971" r="16.678839" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-0.29273442,-0.00257918,0.01127876,-1.2801289,10.212159,-15.400302)">
|
||||
<stop offset="0" stop-color="#fa4435" stop-opacity="1"/><stop offset="1" stop-color="#fa7367" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="ul-r2" cx="34.523952" cy="11.253304" fx="34.523952" fy="11.253304" r="16.740065" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.27572675,-0.00557307,0.0193391,0.95679742,24.457243,-42.568342)">
|
||||
<stop offset="0" stop-color="#fadc56" stop-opacity="1"/><stop offset="1" stop-color="#faae3a" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="ul-gs" x1="0" y1="5.11" x2="0" y2="37.01" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.202" stop-color="#f89629"/><stop offset="1" stop-color="#f74176"/>
|
||||
</linearGradient>
|
||||
<filter id="ul-sh" x="-30%" y="-30%" width="160%" height="160%">
|
||||
<feDropShadow dx="0" dy="0.35" stdDeviation="0.5" flood-color="#000" flood-opacity="0.42"/>
|
||||
</filter>
|
||||
<filter id="ul-blur" x="-40%" y="-40%" width="180%" height="180%">
|
||||
<feGaussianBlur stdDeviation="1.1"/>
|
||||
</filter>
|
||||
<g id="ul-logo" transform="translate(1.425541,43.741704)">
|
||||
<path id="ul-main" fill="url(#ul-g1)" d="m 35.878324,-39.647594 c -2.15234,-7.4e-4 -4.0678,1.84724 -4.0671,4.1266 v 2.85212 c -0.073,-0.005 -0.14656,-0.007 -0.2202,-0.007 h -10.50012 -10.49855 c -0.0418,-7e-5 -0.0835,5.9e-4 -0.12516,0.002 v -2.78918 c -1.6e-4,-2.27807 -1.9592603,-4.12477 -4.1103803,-4.12494 -2.15111,1.8e-4 -4.20874,1.84687 -4.2089,4.12494 v 15.55127 c -4e-5,0.005 -0.0295,3.84339 2.173,7.60498 2.20396,3.7640196 7.0382003,7.3825696 14.4526703,7.3825696 h 4.58067 c 7.4177,0 12.26918,-3.66617 14.50586,-7.4422096 2.23668,-3.77604 2.27039,-7.63481 2.27039,-7.63481 l 0.002,-0.0344 v -15.48541 c 7e-4,-2.27872 -2.10202,-4.12642 -4.25374,-4.1266 z m -2.00792,7.9252 v 11.59858 0.0286 c 0,0 -0.0664,2.0824 -1.31139,4.18416 -0.57202,0.96572 -1.44541,2.15762 -2.91767,2.99793 -1.47227,0.8403 -3.46066,1.33079 -6.27223,1.33079 h -4.58106 c -2.82381,0 -4.79864,-0.47938 -6.25697,-1.30429 -1.45833,-0.8249 -2.31357,-2.00343 -2.8590102,-2.93496 -1.1966401,-2.04365 -1.2300401,-4.07193 -1.2300401,-4.07193 l -10e-4,-0.0141 v -0.0157 -11.69637 l 1.96e-4,-0.0142 0.0035,-0.008 c 0.05128,0.0487 0.18806,0.16304 0.24192,0.20765 l 5.4247013,4.54513 5.51504,4.57144 c 0.65194,0.53945 2.27903,0.53945 2.93097,0 l 5.51504,-4.57144 5.487378,-4.57351 c 0.0199,-0.0164 0.04083,-0.0331 0.06003,-0.048 l 0.02765,-0.0215 z"/>
|
||||
<path fill="url(#ul-r1)" d="m 2.3483903,-36.677919 c -0.1292796,0.384121 -0.2004748,0.792004 -0.2005045,1.214913 v 15.550989 c -4e-5,0.005 -0.029506,3.84363 2.172994,7.60522 2.20396,3.7640196 7.0383952,7.3824946 14.4528652,7.3824946 h 4.580598 c 5.433936,0 9.490679,-1.96731 12.151197,-4.504117 -1.517177,-1.2182566 -3.218664,-2.5953336 -5.013648,-4.0550496 -0.258984,0.201994 -0.541601,0.394146 -0.850594,0.570508 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -1.45833,-0.8249 -2.313817,-2.003692 -2.8592572,-2.935222 -1.1966406,-2.04365 -1.2298996,-4.071586 -1.2298996,-4.071586 l -0.00103,-0.01395 v -0.01602 -11.696444 -0.01395 l 0.00362,-0.0083 c 0.05128,0.0487 0.1879857,0.16313 0.2418457,0.20774 l 5.4249921,4.545459 5.514909,4.571297 c 0.0844,0.06984 0.185312,0.130661 0.298173,0.182418 -5.07081,-4.173874 -9.542211,-7.882397 -11.4706242,-9.500169 -2.2977093,-1.895939 -4.3807977,-3.616666 -6.1055705,-5.042586 z"/>
|
||||
<path fill="url(#ul-r2)" d="m 39.930076,-36.730629 c -1.718284,1.420541 -3.7895,3.131915 -6.072498,5.015715 -1.93499,1.62329 -6.430112,5.351263 -11.522294,9.54257 0.08232,-0.04302 0.156783,-0.0915 0.221692,-0.145211 l 5.514909,-4.571297 5.48752,-4.573881 c 0.0199,-0.0164 0.04075,-0.03316 0.05994,-0.04806 l 0.0279,-0.02119 0.222726,-0.190169 v 11.598258 0.02894 c 0,0 -0.06656,2.081964 -1.311548,4.183724 -0.57202,0.96572 -1.445392,2.157957 -2.917652,2.998267 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -0.308682,-0.174605 -0.590588,-0.364744 -0.848011,-0.565341 -1.8055532,1.467965 -3.5132252,2.849693 -5.0307004,4.0674516 2.6425302,2.518552 6.6894504,4.460193 12.1212244,4.460193 h 4.580599 c 7.4177,0 12.269411,-3.665883 14.506091,-7.4419226 2.23668,-3.77604 2.270146,-7.635193 2.270146,-7.635193 l 0.0021,-0.03411 v -15.485354 c 1.06e-4,-0.420906 -0.07162,-0.827177 -0.202078,-1.209745 z"/>
|
||||
</g>
|
||||
<!-- Mask that reveals the logo's U as the stroke draws -->
|
||||
<mask id="ul-mask-u" maskUnits="userSpaceOnUse" x="-20" y="-20" width="100" height="100">
|
||||
<path id="ul-stroke" d="M7.73,8.31 L7.73,23.8 C7.73,30.6 13.6,34.65 22.57,34.65 C31.5,34.65 37.4,30.6 37.4,23.8 L37.4,8.31"
|
||||
pathLength="1" fill="none" stroke="#fff" stroke-width="9.6" stroke-linecap="round"
|
||||
stroke-dasharray="1 2" stroke-dashoffset="1"/>
|
||||
</mask>
|
||||
<!-- Logo colours limited to the top-flap area -->
|
||||
<!-- Area between the U's arms: inside it the logo's own fill is exactly the flap -->
|
||||
<clipPath id="ul-clip-flap"><path d="M10.093,11.066 L35.296,11.066 L35.296,12.1 L22.69,23.6 L10.093,12.1 Z"/></clipPath>
|
||||
<!-- Darkens the coloured flap while it is mid-fold -->
|
||||
<filter id="ul-dark" x="0" y="0" width="1" height="1">
|
||||
<feComponentTransfer>
|
||||
<feFuncR id="ul-dr" type="linear" slope="1"/><feFuncG id="ul-dg" type="linear" slope="1"/><feFuncB id="ul-db" type="linear" slope="1"/>
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Envelope (white/grey paper) -->
|
||||
<g id="ul-env">
|
||||
<g id="ul-rest">
|
||||
<!-- Thin paper margin tucked under the U so no background shows along its inner edge -->
|
||||
<rect id="ul-under" x="9.8" y="12.1" width="25.8" height="20.4" fill="rgb(250,250,253)" opacity="0" mask="url(#ul-mask-u)"/>
|
||||
<rect x="10.093" y="11.066" width="25.203" height="21.064" fill="rgb(226,226,230)"/>
|
||||
<path id="ul-fl" filter="url(#ul-sh)"/>
|
||||
<path id="ul-fr" filter="url(#ul-sh)"/>
|
||||
<path id="ul-fb" filter="url(#ul-sh)"/>
|
||||
</g>
|
||||
<!-- Top flap: exact outline of the logo's V flap (shown folded); flipped about the top edge while folding -->
|
||||
<!-- Inside of the flap = white paper; outside = the logo's colours (clipped to the flap) -->
|
||||
<g id="ul-ftg" filter="url(#ul-sh)">
|
||||
<path id="ul-ft"
|
||||
d="M10.093,11.066 L35.296,11.066 L35.296,12.019 L35.208,12.089 L24.206,21.234 C23.554,21.773 21.927,21.773 21.275,21.234 L10.335,12.117 L10.093,11.910 Z"/>
|
||||
<g id="ul-fto" opacity="0" filter="url(#ul-dark)">
|
||||
<use href="#ul-logo" clip-path="url(#ul-clip-flap)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- U drawn over the envelope: the real logo artwork (with its side gradients) revealed by a growing stroke -->
|
||||
<g id="ul-ug" opacity="0">
|
||||
<use href="#ul-logo" mask="url(#ul-mask-u)"/>
|
||||
</g>
|
||||
|
||||
<!-- Final logo + glow -->
|
||||
<use id="ul-glow" href="#ul-logo" filter="url(#ul-blur)" opacity="0"/>
|
||||
<use id="ul-final" href="#ul-logo" opacity="0"/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- ===================================================================== -->
|
||||
|
||||
<!-- Demo page content (remove on your site) -->
|
||||
<main class="demo">
|
||||
<p>Your website content</p>
|
||||
<button type="button" id="replay">Replay intro</button>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
/* ======================= SPLASH SCRIPT (copy) ======================== */
|
||||
(function () {
|
||||
var ONCE_PER_SESSION = false; // true = show only on the first page view per browser session
|
||||
var SPEED = 1; // 1 = ~2.6 s to final logo; 0.8 = faster, 1.2 = slower
|
||||
var HOLD_MS = 450; // how long the finished logo stays before the splash fades
|
||||
|
||||
var root = document.getElementById('ultimo-splash');
|
||||
if (!root) return;
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var env = $('ul-env'), rest = $('ul-rest'), fl = $('ul-fl'), fr = $('ul-fr'), fb = $('ul-fb'), ft = $('ul-ft');
|
||||
var stroke = $('ul-stroke'), glow = $('ul-glow'), fin = $('ul-final');
|
||||
var ug = $('ul-ug'), under = $('ul-under');
|
||||
var ftg = $('ul-ftg'), fto = $('ul-fto'), dr = $('ul-dr'), dg = $('ul-dg'), db = $('ul-db');
|
||||
|
||||
// Envelope body edges (logo coordinates) and the four flaps.
|
||||
var L = 10.093, R = 35.296, T = 11.066, B = 32.13, CX = 22.69, CY = 21.6;
|
||||
var FLAPS = {
|
||||
l: { axis: 'x', h: L, a: T, b: B, mid: CY, depth: 12.2, dir: -1, r: 3.2 },
|
||||
r: { axis: 'x', h: R, a: T, b: B, mid: CY, depth: 12.2, dir: 1, r: 3.2 },
|
||||
b: { axis: 'y', h: B, a: L, b: R, mid: CX, depth: 14.0, dir: 1, r: 3.6 }
|
||||
};
|
||||
var END = 2450; // ms (at SPEED 1) when the logo is fully formed
|
||||
|
||||
function clamp(x) { return x < 0 ? 0 : x > 1 ? 1 : x; }
|
||||
function seg(t, a, b) { return clamp((t - a) / (b - a)); }
|
||||
function ease(x) { return x < .5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; }
|
||||
function easeOut(x) { return 1 - Math.pow(1 - x, 3); }
|
||||
function n(v) { return Math.round(v * 1000) / 1000; }
|
||||
|
||||
// Flap outline: base fixed exactly on the body edge, apex swings over the hinge, tip rounded.
|
||||
function flapD(f, p) {
|
||||
var s = Math.cos(Math.PI * p), ax, ay, A, C;
|
||||
if (f.axis === 'x') { ax = f.h + f.dir * f.depth * s; ay = f.mid; A = [f.h, f.a]; C = [f.h, f.b]; }
|
||||
else { ax = f.mid; ay = f.h + f.dir * f.depth * s; A = [f.a, f.h]; C = [f.b, f.h]; }
|
||||
function toward(P) {
|
||||
var dx = P[0] - ax, dy = P[1] - ay, l = Math.hypot(dx, dy) || 1, r = Math.min(f.r, l * 0.45);
|
||||
return n(ax + dx * r / l) + ',' + n(ay + dy * r / l);
|
||||
}
|
||||
return 'M' + A[0] + ',' + A[1] + ' L' + toward(A) + ' Q' + n(ax) + ',' + n(ay) + ' ' + toward(C) +
|
||||
' L' + C[0] + ',' + C[1] + ' Z';
|
||||
}
|
||||
// Paper shading: inner face (grey) before the flip, outer face (white) after it.
|
||||
function faceC(p) {
|
||||
var lift = Math.sin(Math.PI * p);
|
||||
var c = Math.round(p < .5 ? 230 - 42 * lift : 250 - 36 * lift);
|
||||
return 'rgb(' + c + ',' + c + ',' + (c + 3) + ')';
|
||||
}
|
||||
function setFlap(el, f, p) {
|
||||
el.setAttribute('d', flapD(f, p));
|
||||
el.setAttribute('fill', faceC(p));
|
||||
}
|
||||
|
||||
function render(ms) {
|
||||
var intro = easeOut(seg(ms, 0, 220));
|
||||
setFlap(fl, FLAPS.l, ease(seg(ms, 180, 560)));
|
||||
setFlap(fr, FLAPS.r, ease(seg(ms, 240, 620)));
|
||||
setFlap(fb, FLAPS.b, ease(seg(ms, 560, 920)));
|
||||
var pt = ease(seg(ms, 880, 1260)), st = -Math.cos(Math.PI * pt);
|
||||
if (Math.abs(st) < 0.001) st = 0.001;
|
||||
// Top flap: white inside while open; once it flips past vertical its outside (logo colours) shows
|
||||
ftg.setAttribute('transform', 'translate(0 ' + T + ') scale(1 ' + n(st) + ') translate(0 ' + (-T) + ')');
|
||||
ft.setAttribute('fill', faceC(pt));
|
||||
ft.setAttribute('opacity', pt >= 0.5 ? 0 : 1); // white inside face only while it faces the viewer
|
||||
fto.setAttribute('opacity', pt >= 0.5 ? 1 : 0);
|
||||
var slope = n(1 - 0.35 * Math.sin(Math.PI * pt));
|
||||
dr.setAttribute('slope', slope); dg.setAttribute('slope', slope); db.setAttribute('slope', slope);
|
||||
|
||||
var draw = ease(seg(ms, 1180, 1800));
|
||||
var fade = ease(seg(ms, 2150, 2450)); // white envelope (all but the top flap) vanishes after the logo has formed
|
||||
var dis = ease(seg(ms, 1800, 2150)); // dissolve: U stroke + top flap -> final logo
|
||||
var sc = 0.9 + 0.1 * intro;
|
||||
env.setAttribute('transform', 'translate(22.5 22) scale(' + sc + ') translate(-22.5 -22)');
|
||||
env.setAttribute('opacity', intro);
|
||||
rest.setAttribute('opacity', 1 - fade);
|
||||
ftg.setAttribute('opacity', 1 - fade); // coloured flap sits under the logo until the envelope goes
|
||||
stroke.setAttribute('stroke-dashoffset', 1 - draw);
|
||||
// Stroke stays solid under the logo while it dissolves in, then is removed once fully covered
|
||||
ug.setAttribute('opacity', draw > 0 && dis < 1 ? 1 : 0);
|
||||
under.setAttribute('opacity', draw > 0 ? 1 : 0);
|
||||
fin.setAttribute('opacity', dis);
|
||||
glow.setAttribute('opacity', 0.35 * Math.sin(Math.PI * seg(ms, 1900, 2450)));
|
||||
}
|
||||
|
||||
var raf = 0, start = 0, hideTimer = 0;
|
||||
function finish() {
|
||||
cancelAnimationFrame(raf);
|
||||
render(99999);
|
||||
root.classList.add('is-done');
|
||||
document.dispatchEvent(new CustomEvent('ultimo-splash:done'));
|
||||
}
|
||||
function tick(now) {
|
||||
if (!start) start = now;
|
||||
var ms = (now - start) / SPEED;
|
||||
render(ms);
|
||||
if (ms < END) raf = requestAnimationFrame(tick);
|
||||
else hideTimer = setTimeout(finish, HOLD_MS);
|
||||
}
|
||||
function play() {
|
||||
cancelAnimationFrame(raf); clearTimeout(hideTimer);
|
||||
root.classList.remove('is-done');
|
||||
start = 0; render(0);
|
||||
if (window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||
render(99999); hideTimer = setTimeout(finish, 700); return;
|
||||
}
|
||||
raf = requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
try {
|
||||
if (ONCE_PER_SESSION && sessionStorage.getItem('ultimo-splash-seen')) { root.classList.add('is-done'); return; }
|
||||
sessionStorage.setItem('ultimo-splash-seen', '1');
|
||||
} catch (e) {}
|
||||
|
||||
root.addEventListener('click', finish); // click / tap skips the intro
|
||||
window.ultimoSplash = { play: play, skip: finish, seek: function (ms) { cancelAnimationFrame(raf); clearTimeout(hideTimer); root.classList.remove('is-done'); render(ms); } };
|
||||
play();
|
||||
})();
|
||||
/* ===================================================================== */
|
||||
|
||||
// Demo only
|
||||
document.getElementById('replay').addEventListener('click', function () { window.ultimoSplash.play(); });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -13,8 +13,99 @@
|
||||
<meta name="apple-mobile-web-app-title" content="Ultimo" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<title>Ultimo Email</title>
|
||||
<style>
|
||||
/* Loading splash (public/splash.js). Dark grey like the app's dark theme. */
|
||||
#ultimo-splash {
|
||||
position: fixed; inset: 0; z-index: 9999;
|
||||
display: grid; place-items: center;
|
||||
background: #1b1e20;
|
||||
transition: opacity .35s ease, visibility 0s linear .35s;
|
||||
cursor: pointer;
|
||||
}
|
||||
#ultimo-splash.is-done { opacity: 0; visibility: hidden; pointer-events: none; }
|
||||
#ultimo-splash svg { width: min(46vmin, 340px); height: auto; overflow: visible; }
|
||||
/* Never on a phone (same query as useIsMobile). */
|
||||
@media (max-width: 767px), (pointer: coarse) and (max-height: 500px) {
|
||||
#ultimo-splash { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="ultimo-splash" aria-hidden="true">
|
||||
<svg viewBox="-10 -6 65 56" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="ul-g1" x1="54.340946" y1="122.84999" x2="54.340946" y2="162.66415" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.75684298,0,0,0.80151105,-19.989667,-137.1)">
|
||||
<stop offset="0.20198774" stop-color="#f89629"/><stop offset="1" stop-color="#f74176"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="ul-r1" cx="8.0343275" cy="13.452971" fx="8.0343275" fy="13.452971" r="16.678839" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-0.29273442,-0.00257918,0.01127876,-1.2801289,10.212159,-15.400302)">
|
||||
<stop offset="0" stop-color="#fa4435" stop-opacity="1"/><stop offset="1" stop-color="#fa7367" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="ul-r2" cx="34.523952" cy="11.253304" fx="34.523952" fy="11.253304" r="16.740065" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.27572675,-0.00557307,0.0193391,0.95679742,24.457243,-42.568342)">
|
||||
<stop offset="0" stop-color="#fadc56" stop-opacity="1"/><stop offset="1" stop-color="#faae3a" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="ul-gs" x1="0" y1="5.11" x2="0" y2="37.01" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.202" stop-color="#f89629"/><stop offset="1" stop-color="#f74176"/>
|
||||
</linearGradient>
|
||||
<filter id="ul-sh" x="-30%" y="-30%" width="160%" height="160%">
|
||||
<feDropShadow dx="0" dy="0.35" stdDeviation="0.5" flood-color="#000" flood-opacity="0.42"/>
|
||||
</filter>
|
||||
<filter id="ul-blur" x="-40%" y="-40%" width="180%" height="180%">
|
||||
<feGaussianBlur stdDeviation="1.1"/>
|
||||
</filter>
|
||||
<g id="ul-logo" transform="translate(1.425541,43.741704)">
|
||||
<path id="ul-main" fill="url(#ul-g1)" d="m 35.878324,-39.647594 c -2.15234,-7.4e-4 -4.0678,1.84724 -4.0671,4.1266 v 2.85212 c -0.073,-0.005 -0.14656,-0.007 -0.2202,-0.007 h -10.50012 -10.49855 c -0.0418,-7e-5 -0.0835,5.9e-4 -0.12516,0.002 v -2.78918 c -1.6e-4,-2.27807 -1.9592603,-4.12477 -4.1103803,-4.12494 -2.15111,1.8e-4 -4.20874,1.84687 -4.2089,4.12494 v 15.55127 c -4e-5,0.005 -0.0295,3.84339 2.173,7.60498 2.20396,3.7640196 7.0382003,7.3825696 14.4526703,7.3825696 h 4.58067 c 7.4177,0 12.26918,-3.66617 14.50586,-7.4422096 2.23668,-3.77604 2.27039,-7.63481 2.27039,-7.63481 l 0.002,-0.0344 v -15.48541 c 7e-4,-2.27872 -2.10202,-4.12642 -4.25374,-4.1266 z m -2.00792,7.9252 v 11.59858 0.0286 c 0,0 -0.0664,2.0824 -1.31139,4.18416 -0.57202,0.96572 -1.44541,2.15762 -2.91767,2.99793 -1.47227,0.8403 -3.46066,1.33079 -6.27223,1.33079 h -4.58106 c -2.82381,0 -4.79864,-0.47938 -6.25697,-1.30429 -1.45833,-0.8249 -2.31357,-2.00343 -2.8590102,-2.93496 -1.1966401,-2.04365 -1.2300401,-4.07193 -1.2300401,-4.07193 l -10e-4,-0.0141 v -0.0157 -11.69637 l 1.96e-4,-0.0142 0.0035,-0.008 c 0.05128,0.0487 0.18806,0.16304 0.24192,0.20765 l 5.4247013,4.54513 5.51504,4.57144 c 0.65194,0.53945 2.27903,0.53945 2.93097,0 l 5.51504,-4.57144 5.487378,-4.57351 c 0.0199,-0.0164 0.04083,-0.0331 0.06003,-0.048 l 0.02765,-0.0215 z"/>
|
||||
<path fill="url(#ul-r1)" d="m 2.3483903,-36.677919 c -0.1292796,0.384121 -0.2004748,0.792004 -0.2005045,1.214913 v 15.550989 c -4e-5,0.005 -0.029506,3.84363 2.172994,7.60522 2.20396,3.7640196 7.0383952,7.3824946 14.4528652,7.3824946 h 4.580598 c 5.433936,0 9.490679,-1.96731 12.151197,-4.504117 -1.517177,-1.2182566 -3.218664,-2.5953336 -5.013648,-4.0550496 -0.258984,0.201994 -0.541601,0.394146 -0.850594,0.570508 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -1.45833,-0.8249 -2.313817,-2.003692 -2.8592572,-2.935222 -1.1966406,-2.04365 -1.2298996,-4.071586 -1.2298996,-4.071586 l -0.00103,-0.01395 v -0.01602 -11.696444 -0.01395 l 0.00362,-0.0083 c 0.05128,0.0487 0.1879857,0.16313 0.2418457,0.20774 l 5.4249921,4.545459 5.514909,4.571297 c 0.0844,0.06984 0.185312,0.130661 0.298173,0.182418 -5.07081,-4.173874 -9.542211,-7.882397 -11.4706242,-9.500169 -2.2977093,-1.895939 -4.3807977,-3.616666 -6.1055705,-5.042586 z"/>
|
||||
<path fill="url(#ul-r2)" d="m 39.930076,-36.730629 c -1.718284,1.420541 -3.7895,3.131915 -6.072498,5.015715 -1.93499,1.62329 -6.430112,5.351263 -11.522294,9.54257 0.08232,-0.04302 0.156783,-0.0915 0.221692,-0.145211 l 5.514909,-4.571297 5.48752,-4.573881 c 0.0199,-0.0164 0.04075,-0.03316 0.05994,-0.04806 l 0.0279,-0.02119 0.222726,-0.190169 v 11.598258 0.02894 c 0,0 -0.06656,2.081964 -1.311548,4.183724 -0.57202,0.96572 -1.445392,2.157957 -2.917652,2.998267 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -0.308682,-0.174605 -0.590588,-0.364744 -0.848011,-0.565341 -1.8055532,1.467965 -3.5132252,2.849693 -5.0307004,4.0674516 2.6425302,2.518552 6.6894504,4.460193 12.1212244,4.460193 h 4.580599 c 7.4177,0 12.269411,-3.665883 14.506091,-7.4419226 2.23668,-3.77604 2.270146,-7.635193 2.270146,-7.635193 l 0.0021,-0.03411 v -15.485354 c 1.06e-4,-0.420906 -0.07162,-0.827177 -0.202078,-1.209745 z"/>
|
||||
</g>
|
||||
<!-- Mask that reveals the logo's U as the stroke draws -->
|
||||
<mask id="ul-mask-u" maskUnits="userSpaceOnUse" x="-20" y="-20" width="100" height="100">
|
||||
<path id="ul-stroke" d="M7.73,8.31 L7.73,23.8 C7.73,30.6 13.6,34.65 22.57,34.65 C31.5,34.65 37.4,30.6 37.4,23.8 L37.4,8.31"
|
||||
pathLength="1" fill="none" stroke="#fff" stroke-width="9.6" stroke-linecap="round"
|
||||
stroke-dasharray="1 2" stroke-dashoffset="1"/>
|
||||
</mask>
|
||||
<!-- Logo colours limited to the top-flap area -->
|
||||
<!-- Area between the U's arms: inside it the logo's own fill is exactly the flap -->
|
||||
<clipPath id="ul-clip-flap"><path d="M10.093,11.066 L35.296,11.066 L35.296,12.1 L22.69,23.6 L10.093,12.1 Z"/></clipPath>
|
||||
<!-- Darkens the coloured flap while it is mid-fold -->
|
||||
<filter id="ul-dark" x="0" y="0" width="1" height="1">
|
||||
<feComponentTransfer>
|
||||
<feFuncR id="ul-dr" type="linear" slope="1"/><feFuncG id="ul-dg" type="linear" slope="1"/><feFuncB id="ul-db" type="linear" slope="1"/>
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Envelope (white/grey paper) -->
|
||||
<g id="ul-env">
|
||||
<g id="ul-rest">
|
||||
<!-- Thin paper margin tucked under the U so no background shows along its inner edge -->
|
||||
<rect id="ul-under" x="9.8" y="12.1" width="25.8" height="20.4" fill="rgb(250,250,253)" opacity="0" mask="url(#ul-mask-u)"/>
|
||||
<rect x="10.093" y="11.066" width="25.203" height="21.064" fill="rgb(226,226,230)"/>
|
||||
<path id="ul-fl" filter="url(#ul-sh)"/>
|
||||
<path id="ul-fr" filter="url(#ul-sh)"/>
|
||||
<path id="ul-fb" filter="url(#ul-sh)"/>
|
||||
</g>
|
||||
<!-- Top flap: exact outline of the logo's V flap (shown folded); flipped about the top edge while folding -->
|
||||
<!-- Inside of the flap = white paper; outside = the logo's colours (clipped to the flap) -->
|
||||
<g id="ul-ftg" filter="url(#ul-sh)">
|
||||
<path id="ul-ft"
|
||||
d="M10.093,11.066 L35.296,11.066 L35.296,12.019 L35.208,12.089 L24.206,21.234 C23.554,21.773 21.927,21.773 21.275,21.234 L10.335,12.117 L10.093,11.910 Z"/>
|
||||
<g id="ul-fto" opacity="0" filter="url(#ul-dark)">
|
||||
<use href="#ul-logo" clip-path="url(#ul-clip-flap)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- U drawn over the envelope: the real logo artwork (with its side gradients) revealed by a growing stroke -->
|
||||
<g id="ul-ug" opacity="0">
|
||||
<use href="#ul-logo" mask="url(#ul-mask-u)"/>
|
||||
</g>
|
||||
|
||||
<!-- Final logo + glow -->
|
||||
<use id="ul-glow" href="#ul-logo" filter="url(#ul-blur)" opacity="0"/>
|
||||
<use id="ul-final" href="#ul-logo" opacity="0"/>
|
||||
</svg>
|
||||
</div>
|
||||
<script src="/splash.js"></script>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny-ps" viewBox="0 0 512 512">
|
||||
<title>Ultimo Email</title>
|
||||
<desc>Ultimo Email logo, for BIMI (SVG Tiny Portable/Secure)</desc>
|
||||
<defs><linearGradient id="g0" gradientUnits="userSpaceOnUse" x1="22.4348" y1="5.1873" x2="22.4348" y2="37.0988"><stop offset="0.2020" stop-color="#f89629" stop-opacity="1.0000"/><stop offset="1.0000" stop-color="#f74176" stop-opacity="1.0000"/></linearGradient><radialGradient id="g1" gradientUnits="userSpaceOnUse" cx="8.0343" cy="13.4530" r="16.6788"><stop offset="0.0000" stop-color="#fa7367" stop-opacity="1.0000"/><stop offset="1.0000" stop-color="#fa7367" stop-opacity="0.0000"/></radialGradient><radialGradient id="g2" gradientUnits="userSpaceOnUse" cx="35.7090" cy="12.0426" r="16.7401"><stop offset="0.0000" stop-color="#fab23b" stop-opacity="1.0000"/><stop offset="1.0000" stop-color="#fab23a" stop-opacity="0.0000"/></radialGradient></defs>
|
||||
<defs><linearGradient id="g0" gradientUnits="userSpaceOnUse" x1="54.3409" y1="122.8500" x2="54.3409" y2="162.6642"><stop offset="0.2020" stop-color="#f89629" stop-opacity="1.0000"/><stop offset="1.0000" stop-color="#f74176" stop-opacity="1.0000"/></linearGradient><radialGradient id="g1" gradientUnits="userSpaceOnUse" cx="8.0343" cy="13.4530" r="16.6788"><stop offset="0.0000" stop-color="#fa4435" stop-opacity="1.0000"/><stop offset="1.0000" stop-color="#fa7367" stop-opacity="0.0000"/></radialGradient><radialGradient id="g2" gradientUnits="userSpaceOnUse" cx="34.5240" cy="11.2533" r="16.7401"><stop offset="0.0000" stop-color="#fadc56" stop-opacity="1.0000"/><stop offset="1.0000" stop-color="#faae3a" stop-opacity="0.0000"/></radialGradient></defs>
|
||||
<rect width="512" height="512" fill="#ffffff"/>
|
||||
<g transform="matrix(7.081810,0,0,7.081810,96.1936,104.0546)"><g transform="matrix(1,0,0,1,0.128676,-0.079965)"><path fill="url(#g0)" d="M37.1752,4.1741C35.0228,4.1733 33.1074,6.0213 33.1081,8.3007L33.1081,11.1528C33.0351,11.1478 32.9615,11.1458 32.8879,11.1458L22.3878,11.1458L11.8892,11.1458C11.8474,11.1457 11.8057,11.1464 11.7641,11.1478L11.7641,8.3586C11.7639,6.0805 9.8048,4.2338 7.6537,4.2337C5.5026,4.2339 3.4449,6.0805 3.4448,8.3586L3.4448,23.9099C3.4447,23.9149 3.4153,27.7533 5.6178,31.5149C7.8217,35.2789 12.6560,38.8974 20.0704,38.8974L24.6511,38.8974C32.0688,38.8974 36.9203,35.2313 39.1570,31.4552C41.3937,27.6792 41.4274,23.8204 41.4274,23.8204L41.4294,23.7860L41.4294,8.3006C41.4301,6.0219 39.3273,4.1742 37.1756,4.1740ZM35.1673,12.0993L35.1673,23.6979L35.1673,23.7265C35.1673,23.7265 35.1009,25.8089 33.8559,27.9106C33.2839,28.8763 32.4105,30.0682 30.9382,30.9085C29.4659,31.7488 27.4775,32.2393 24.6660,32.2393L20.0849,32.2393C17.2611,32.2393 15.2863,31.7600 13.8279,30.9350C12.3696,30.1101 11.5144,28.9316 10.9689,28.0001C9.7723,25.9564 9.7389,23.9282 9.7389,23.9282L9.7379,23.9141L9.7379,23.8984L9.7379,12.2020L9.7381,12.1878L9.7416,12.1798C9.7929,12.2285 9.9297,12.3428 9.9835,12.3874L15.4082,16.9326L20.9233,21.5040C21.5752,22.0435 23.2023,22.0435 23.8542,21.5040L29.3693,16.9326L34.8566,12.3591C34.8765,12.3427 34.8975,12.3260 34.9167,12.3111L34.9443,12.2896Z"/><g transform="matrix(-0.29271702,0.00410386,-0.01334442,-0.95181978,11.840139,23.950941)"><path fill="url(#g1)" d="M27.1856,17.7752C27.6455,17.3736 27.9083,16.9462 27.9286,16.5020L28.6733,0.1670C28.6737,0.1618 28.9581,-3.8700 21.6154,-7.8536C14.2678,-11.8398 -2.0714,-15.7119 -27.3962,-15.8211L-43.0417,-15.8886C-61.6019,-15.9686 -75.5523,-13.9619 -84.7610,-11.3363C-79.6373,-10.0343 -73.8916,-8.5628 -67.8306,-7.0030C-66.9363,-7.2114 -65.9618,-7.4091 -64.8980,-7.5898C-59.8291,-8.4508 -53.0149,-8.9366 -43.4117,-8.8952L-27.7645,-8.8277C-18.1195,-8.7861 -11.3972,-8.2535 -6.4556,-7.3655C-1.5141,-6.4775 1.3515,-5.2267 3.1699,-4.2402C7.1593,-2.0759 7.1758,0.0548 7.1758,0.0548L7.1786,0.0694L7.1778,0.0863L6.6177,12.3724L6.6171,12.3870L6.6043,12.3957C6.4315,12.3438 5.9700,12.2216 5.7882,12.1739L-12.5237,7.3194L-31.1415,2.4364C-31.4264,2.3618 -31.7682,2.2965 -32.1512,2.2404C-15.0312,6.6994 0.0637,10.6607 6.5729,12.3885C14.3301,14.4138 21.3627,16.2520 27.1856,17.7752Z"/></g><g transform="matrix(0.25712152,0.00260463,-0.00969379,0.95694375,26.644192,0.973013)"><path fill="url(#g2)" d="M56.9506,6.2383C50.3245,7.7408 42.3373,9.5509 33.5334,11.5434C26.0725,13.2601 8.7387,17.2029 -10.8988,21.6363C-10.5803,21.5905 -10.2927,21.5390 -10.0424,21.4822L11.2240,16.6473L32.3838,11.8101C32.4605,11.7927 32.5409,11.7750 32.6150,11.7592L32.7226,11.7368L33.5813,11.5357L34.0382,23.6546L34.0393,23.6848C34.0393,23.6848 33.8625,25.8609 29.1038,28.0702C26.9173,29.0853 23.5679,30.3403 17.8757,31.2340C12.1834,32.1276 4.4713,32.6610 -6.4624,32.6907L-24.2775,32.7392C-35.2588,32.7691 -42.9575,32.2891 -48.6611,31.4426C-49.8684,31.2634 -50.9722,31.0677 -51.9812,30.8609C-58.9448,32.4138 -65.5312,33.8756 -71.3844,35.1641C-61.0089,37.7678 -45.1947,39.7537 -24.0715,39.6962L-6.2584,39.6477C22.5876,39.5692 41.3106,35.6874 49.8599,31.7182C58.4092,27.7490 58.3873,23.7163 58.3873,23.7163L58.3941,23.6806L57.7841,7.5002C57.7679,7.0604 57.4730,6.6366 56.9506,6.2383Z"/></g></g></g>
|
||||
<g transform="matrix(5.359577,0.000000,0.000000,5.675894,-34.596697,-557.282863)"><path fill="url(#g0)" d="M73.8171,121.5859C70.9733,121.5849 68.4425,123.8906 68.4434,126.7344L68.4434,130.2928C68.3469,130.2866 68.2497,130.2841 68.1524,130.2841L54.2789,130.2841L40.4074,130.2841C40.3521,130.2840 40.2970,130.2848 40.2420,130.2866L40.2420,126.8067C40.2418,123.9644 37.6533,121.6604 34.8110,121.6602C31.9688,121.6604 29.2501,123.9644 29.2499,126.8067L29.2499,146.2091C29.2498,146.2153 29.2109,151.0043 32.1210,155.6974C35.0331,160.3936 41.4205,164.9082 51.2170,164.9082L57.2694,164.9082C67.0702,164.9082 73.4804,160.3342 76.4356,155.6230C79.3909,150.9119 79.4355,146.0975 79.4355,146.0975L79.4381,146.0546L79.4381,126.7343C79.4390,123.8913 76.6608,121.5860 73.8177,121.5858ZM71.1641,131.4737L71.1641,145.9446L71.1641,145.9803C71.1641,145.9803 71.0764,148.5783 69.4314,151.2006C68.6756,152.4055 67.5216,153.8925 65.5764,154.9409C63.6311,155.9893 61.0039,156.6013 57.2890,156.6013L51.2362,156.6013C47.5051,156.6013 44.8958,156.0032 42.9690,154.9740C41.0421,153.9448 39.9121,152.4744 39.1914,151.3122C37.6103,148.7625 37.5662,146.2319 37.5662,146.2319L37.5649,146.2143L37.5649,146.1947L37.5649,131.6018L37.5651,131.5841L37.5697,131.5741C37.6375,131.6349 37.8182,131.7775 37.8894,131.8332L45.0569,137.5039L52.3438,143.2074C53.2052,143.8805 55.3551,143.8805 56.2165,143.2074L63.5034,137.5039L70.7537,131.7978C70.7800,131.7773 70.8077,131.7565 70.8330,131.7379L70.8696,131.7111Z"/></g><g transform="matrix(-2.072996,-0.018264,0.079870,-9.065222,179.277271,304.532491)"><path fill="url(#g1)" d="M27.5014,16.5661C27.9315,16.2651 28.1624,15.9460 28.1497,15.6157L27.6817,3.4686C27.6817,3.4647 27.6668,0.4661 20.0303,-2.4569C12.3888,-5.3819 -4.2336,-8.1750 -29.5599,-8.1240L-45.2064,-8.0925C-63.7676,-8.0551 -77.5654,-6.4905 -86.5769,-4.4906C-81.3578,-3.5495 -75.5044,-2.4855 -69.3292,-1.3577C-68.4507,-1.5173 -67.4911,-1.6693 -66.4409,-1.8092C-61.4372,-2.4757 -54.6609,-2.8724 -45.0572,-2.8917L-29.4090,-2.9233C-19.7634,-2.9427 -13.0033,-2.5818 -7.9971,-1.9475C-2.9910,-1.3132 -0.0333,-0.3983 1.8578,0.3255C6.0068,1.9136 6.1815,3.4974 6.1815,3.4974L6.1854,3.5083L6.1859,3.5208L6.5379,12.6571L6.5383,12.6680L6.5262,12.6745C6.3496,12.6368 5.8792,12.5483 5.6939,12.5139L-12.9736,9.0007L-31.9490,5.4680C-32.2394,5.4140 -32.5860,5.3672 -32.9730,5.3275C-15.5265,8.5529 -0.1415,11.4189 6.4943,12.6693C14.3998,14.1344 21.5670,15.4641 27.5014,16.5661Z"/></g><g transform="matrix(1.952557,-0.039466,0.136950,6.775553,280.153710,112.142252)"><path fill="url(#g2)" d="M55.6659,6.4255C49.3325,7.8733 41.6983,9.6175 33.2837,11.5374C26.1499,13.1924 9.5805,16.9922 -9.1873,21.2634C-8.8857,21.2202 -8.6122,21.1712 -8.3729,21.1164L11.9552,16.4571L32.1843,11.7945C32.2576,11.7778 32.3344,11.7607 32.4051,11.7456L32.5078,11.7240L33.3292,11.5301L32.4793,23.6471L32.4772,23.6773C32.4772,23.6773 32.0833,25.8510 27.4159,28.0205C25.2714,29.0173 22.0178,30.2444 16.6188,31.0912C11.2198,31.9380 3.9763,32.4083 -6.2164,32.3490L-22.8244,32.2522C-33.0615,32.1926 -40.1858,31.6500 -45.4122,30.7574C-46.5185,30.5685 -47.5265,30.3639 -48.4451,30.1489C-55.0983,31.6444 -61.3904,33.0519 -66.9809,34.2921C-57.5855,36.9791 -43.0565,39.0930 -23.3647,39.2077L-6.7586,39.3044C20.1327,39.4611 37.9902,35.7337 46.3756,31.8360C54.7609,27.9383 55.1650,23.9072 55.1650,23.9072L55.1751,23.8716L56.3098,7.6937C56.3410,7.2539 56.1108,6.8280 55.6659,6.4255Z"/></g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,120 @@
|
||||
// Loading splash: the animated Ultimo logo over a dark overlay while the app
|
||||
// starts (markup and styles inline in index.html, so it paints before any script
|
||||
// runs; this file is separate because the CSP allows no inline scripts). About
|
||||
// 3.25 s in all: ~2.45 s to the finished logo, a short hold, then a .35 s fade.
|
||||
// Desktop and tablet only; a click skips it. Source: frontend/icons/ultimo-splash.html.
|
||||
(function () {
|
||||
var SPEED = 1; // 1 = ~2.6 s to final logo; 0.8 = faster, 1.2 = slower
|
||||
var HOLD_MS = 450; // how long the finished logo stays before the splash fades
|
||||
|
||||
var root = document.getElementById('ultimo-splash');
|
||||
if (!root) return;
|
||||
// Not on a phone (same query as useIsMobile), and not in a pop-out compose window.
|
||||
var PHONE = '(max-width: 767px), (pointer: coarse) and (max-height: 500px)';
|
||||
if ((window.matchMedia && matchMedia(PHONE).matches) || location.pathname.indexOf('/compose/') === 0) {
|
||||
root.parentNode.removeChild(root);
|
||||
return;
|
||||
}
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var env = $('ul-env'), rest = $('ul-rest'), fl = $('ul-fl'), fr = $('ul-fr'), fb = $('ul-fb'), ft = $('ul-ft');
|
||||
var stroke = $('ul-stroke'), glow = $('ul-glow'), fin = $('ul-final');
|
||||
var ug = $('ul-ug'), under = $('ul-under');
|
||||
var ftg = $('ul-ftg'), fto = $('ul-fto'), dr = $('ul-dr'), dg = $('ul-dg'), db = $('ul-db');
|
||||
|
||||
// Envelope body edges (logo coordinates) and the four flaps.
|
||||
var L = 10.093, R = 35.296, T = 11.066, B = 32.13, CX = 22.69, CY = 21.6;
|
||||
var FLAPS = {
|
||||
l: { axis: 'x', h: L, a: T, b: B, mid: CY, depth: 12.2, dir: -1, r: 3.2 },
|
||||
r: { axis: 'x', h: R, a: T, b: B, mid: CY, depth: 12.2, dir: 1, r: 3.2 },
|
||||
b: { axis: 'y', h: B, a: L, b: R, mid: CX, depth: 14.0, dir: 1, r: 3.6 }
|
||||
};
|
||||
var END = 2450; // ms (at SPEED 1) when the logo is fully formed
|
||||
|
||||
function clamp(x) { return x < 0 ? 0 : x > 1 ? 1 : x; }
|
||||
function seg(t, a, b) { return clamp((t - a) / (b - a)); }
|
||||
function ease(x) { return x < .5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; }
|
||||
function easeOut(x) { return 1 - Math.pow(1 - x, 3); }
|
||||
function n(v) { return Math.round(v * 1000) / 1000; }
|
||||
|
||||
// Flap outline: base fixed exactly on the body edge, apex swings over the hinge, tip rounded.
|
||||
function flapD(f, p) {
|
||||
var s = Math.cos(Math.PI * p), ax, ay, A, C;
|
||||
if (f.axis === 'x') { ax = f.h + f.dir * f.depth * s; ay = f.mid; A = [f.h, f.a]; C = [f.h, f.b]; }
|
||||
else { ax = f.mid; ay = f.h + f.dir * f.depth * s; A = [f.a, f.h]; C = [f.b, f.h]; }
|
||||
function toward(P) {
|
||||
var dx = P[0] - ax, dy = P[1] - ay, l = Math.hypot(dx, dy) || 1, r = Math.min(f.r, l * 0.45);
|
||||
return n(ax + dx * r / l) + ',' + n(ay + dy * r / l);
|
||||
}
|
||||
return 'M' + A[0] + ',' + A[1] + ' L' + toward(A) + ' Q' + n(ax) + ',' + n(ay) + ' ' + toward(C) +
|
||||
' L' + C[0] + ',' + C[1] + ' Z';
|
||||
}
|
||||
// Paper shading: inner face (grey) before the flip, outer face (white) after it.
|
||||
function faceC(p) {
|
||||
var lift = Math.sin(Math.PI * p);
|
||||
var c = Math.round(p < .5 ? 230 - 42 * lift : 250 - 36 * lift);
|
||||
return 'rgb(' + c + ',' + c + ',' + (c + 3) + ')';
|
||||
}
|
||||
function setFlap(el, f, p) {
|
||||
el.setAttribute('d', flapD(f, p));
|
||||
el.setAttribute('fill', faceC(p));
|
||||
}
|
||||
|
||||
function render(ms) {
|
||||
var intro = easeOut(seg(ms, 0, 220));
|
||||
setFlap(fl, FLAPS.l, ease(seg(ms, 180, 560)));
|
||||
setFlap(fr, FLAPS.r, ease(seg(ms, 240, 620)));
|
||||
setFlap(fb, FLAPS.b, ease(seg(ms, 560, 920)));
|
||||
var pt = ease(seg(ms, 880, 1260)), st = -Math.cos(Math.PI * pt);
|
||||
if (Math.abs(st) < 0.001) st = 0.001;
|
||||
// Top flap: white inside while open; once it flips past vertical its outside (logo colours) shows
|
||||
ftg.setAttribute('transform', 'translate(0 ' + T + ') scale(1 ' + n(st) + ') translate(0 ' + (-T) + ')');
|
||||
ft.setAttribute('fill', faceC(pt));
|
||||
ft.setAttribute('opacity', pt >= 0.5 ? 0 : 1); // white inside face only while it faces the viewer
|
||||
fto.setAttribute('opacity', pt >= 0.5 ? 1 : 0);
|
||||
var slope = n(1 - 0.35 * Math.sin(Math.PI * pt));
|
||||
dr.setAttribute('slope', slope); dg.setAttribute('slope', slope); db.setAttribute('slope', slope);
|
||||
|
||||
var draw = ease(seg(ms, 1180, 1800));
|
||||
var fade = ease(seg(ms, 2150, 2450)); // white envelope (all but the top flap) vanishes after the logo has formed
|
||||
var dis = ease(seg(ms, 1800, 2150)); // dissolve: U stroke + top flap -> final logo
|
||||
var sc = 0.9 + 0.1 * intro;
|
||||
env.setAttribute('transform', 'translate(22.5 22) scale(' + sc + ') translate(-22.5 -22)');
|
||||
env.setAttribute('opacity', intro);
|
||||
rest.setAttribute('opacity', 1 - fade);
|
||||
ftg.setAttribute('opacity', 1 - fade); // coloured flap sits under the logo until the envelope goes
|
||||
stroke.setAttribute('stroke-dashoffset', 1 - draw);
|
||||
// Stroke stays solid under the logo while it dissolves in, then is removed once fully covered
|
||||
ug.setAttribute('opacity', draw > 0 && dis < 1 ? 1 : 0);
|
||||
under.setAttribute('opacity', draw > 0 ? 1 : 0);
|
||||
fin.setAttribute('opacity', dis);
|
||||
glow.setAttribute('opacity', 0.35 * Math.sin(Math.PI * seg(ms, 1900, 2450)));
|
||||
}
|
||||
|
||||
var raf = 0, start = 0, hideTimer = 0;
|
||||
function finish() {
|
||||
cancelAnimationFrame(raf);
|
||||
render(99999);
|
||||
root.classList.add('is-done');
|
||||
// Gone from the page once faded out (the .35 s opacity transition).
|
||||
setTimeout(function () { if (root.parentNode) root.parentNode.removeChild(root); }, 400);
|
||||
}
|
||||
function tick(now) {
|
||||
if (!start) start = now;
|
||||
var ms = (now - start) / SPEED;
|
||||
render(ms);
|
||||
if (ms < END) raf = requestAnimationFrame(tick);
|
||||
else hideTimer = setTimeout(finish, HOLD_MS);
|
||||
}
|
||||
function play() {
|
||||
cancelAnimationFrame(raf); clearTimeout(hideTimer);
|
||||
root.classList.remove('is-done');
|
||||
start = 0; render(0);
|
||||
if (window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||
render(99999); hideTimer = setTimeout(finish, 700); return;
|
||||
}
|
||||
raf = requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
root.addEventListener('click', finish); // a click skips the intro
|
||||
play();
|
||||
})();
|
||||
@@ -58,55 +58,55 @@
|
||||
id="mask1776-6"><path
|
||||
style="fill:url(#linearGradient1789);fill-opacity:1;stroke-width:0.2;stroke-linecap:round"
|
||||
d="m 18.20332,107.02297 c 0,0 1.930731,1.59986 4.989281,4.12846 1.725315,1.42637 3.809507,3.14827 6.108453,5.04523 5.657506,4.74616 33.20207,27.48506 33.558689,27.18811 0.241178,-0.20083 -16.275384,9.86387 -16.275384,9.86387 L 17.979141,140.69463 Z"
|
||||
id="path1776-1" /></mask><linearGradient
|
||||
xlink:href="#linearGradient11"
|
||||
id="linearGradient13-6"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.75684298,0,0,0.80151105,-18.692802,-93.278335)"
|
||||
x1="54.340946"
|
||||
y1="122.84999"
|
||||
x2="54.340946"
|
||||
y2="162.66415" /><radialGradient
|
||||
xlink:href="#linearGradient8"
|
||||
id="radialGradient816"
|
||||
id="path1776-1" /></mask><radialGradient
|
||||
xlink:href="#linearGradient8-5"
|
||||
id="radialGradient816-3"
|
||||
cx="8.0343275"
|
||||
cy="13.452971"
|
||||
fx="8.0343275"
|
||||
fy="13.452971"
|
||||
r="16.678839"
|
||||
gradientTransform="matrix(-0.29271702,0.00410386,-0.01334442,-0.95181978,11.840139,23.950941)"
|
||||
gradientTransform="matrix(-0.29273442,-0.00257918,0.01127876,-1.2801289,10.212159,-15.400302)"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
id="linearGradient8"><stop
|
||||
style="stop-color:#fa7367;stop-opacity:1;"
|
||||
id="linearGradient8-5"><stop
|
||||
style="stop-color:#fa4435;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop8" /><stop
|
||||
id="stop8-6" /><stop
|
||||
style="stop-color:#fa7367;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop9" /></linearGradient><radialGradient
|
||||
xlink:href="#linearGradient6"
|
||||
id="radialGradient815"
|
||||
cx="35.709007"
|
||||
cy="12.042645"
|
||||
fx="35.709007"
|
||||
fy="12.042645"
|
||||
id="stop9-2" /></linearGradient><radialGradient
|
||||
xlink:href="#linearGradient6-1"
|
||||
id="radialGradient815-9"
|
||||
cx="34.523952"
|
||||
cy="11.253304"
|
||||
fx="34.523952"
|
||||
fy="11.253304"
|
||||
r="16.740065"
|
||||
gradientTransform="matrix(0.25712152,0.00260463,-0.00969379,0.95694375,26.644192,0.97301292)"
|
||||
gradientTransform="matrix(0.27572675,-0.00557307,0.0193391,0.95679742,24.457243,-42.568342)"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
id="linearGradient6"><stop
|
||||
style="stop-color:#fab23b;stop-opacity:1;"
|
||||
id="linearGradient6-1"><stop
|
||||
style="stop-color:#fadc56;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop6" /><stop
|
||||
style="stop-color:#fab23a;stop-opacity:0;"
|
||||
id="stop6-2" /><stop
|
||||
style="stop-color:#faae3a;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop7" /></linearGradient></defs><g
|
||||
id="g816"
|
||||
transform="translate(0.128676,-0.079965)"><path
|
||||
id="path1-2-1-1"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#linearGradient13-6);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 37.175189,4.174075 c -2.15234,-7.4e-4 -4.0678,1.84724 -4.0671,4.1266 v 2.85212 c -0.073,-0.005 -0.14656,-0.007 -0.2202,-0.007 h -10.50012 -10.49855 c -0.0418,-7e-5 -0.0835,5.9e-4 -0.12516,0.002 v -2.78918 c -1.6e-4,-2.27807 -1.9592601,-4.12477 -4.1103801,-4.12494 -2.15111,1.8e-4 -4.20874,1.84687 -4.2089,4.12494 v 15.55127 c -4e-5,0.005 -0.0295,3.84339 2.173,7.60498 2.20396,3.76402 7.0382001,7.38257 14.4526701,7.38257 h 4.58067 c 7.4177,0 12.26918,-3.66617 14.50586,-7.44221 2.23668,-3.77604 2.27039,-7.63481 2.27039,-7.63481 l 0.002,-0.0344 V 8.300605 c 7e-4,-2.27872 -2.10202,-4.12642 -4.25374,-4.1266 z m -2.00792,7.9252 v 11.59858 0.0286 c 0,0 -0.0664,2.0824 -1.31139,4.18416 -0.57202,0.96572 -1.44541,2.15762 -2.91767,2.99793 -1.47227,0.8403 -3.46066,1.33079 -6.27223,1.33079 h -4.58106 c -2.82381,0 -4.79864,-0.47938 -6.25697,-1.30429 -1.45833,-0.8249 -2.31357,-2.00343 -2.85901,-2.93496 -1.1966401,-2.04365 -1.2300401,-4.07193 -1.2300401,-4.07193 l -0.001,-0.0141 v -0.0157 -11.69637 l 1.96e-4,-0.0142 0.0035,-0.008 c 0.05128,0.0487 0.18806,0.16304 0.24192,0.20765 l 5.4247011,4.54513 5.51504,4.57144 c 0.65194,0.53945 2.27903,0.53945 2.93097,0 l 5.51504,-4.57144 5.487378,-4.57351 c 0.0199,-0.0164 0.04083,-0.0331 0.06003,-0.048 l 0.02765,-0.0215 z" /><path
|
||||
id="path1-2-1-9"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient816);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="M 3.6452555,7.14375 C 3.5159759,7.5278714 3.4447807,7.9357538 3.444751,8.3586629 V 23.909652 c -4e-5,0.005 -0.029506,3.84363 2.172994,7.60522 2.20396,3.76402 7.038395,7.382495 14.452865,7.382495 h 4.580598 c 5.433936,0 9.490679,-1.96731 12.151197,-4.504117 -1.517177,-1.218257 -3.218664,-2.595334 -5.013648,-4.05505 -0.258984,0.201994 -0.541601,0.394146 -0.850594,0.570508 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -1.45833,-0.8249 -2.313817,-2.003692 -2.859257,-2.935222 -1.1966406,-2.04365 -1.2298996,-4.071586 -1.2298996,-4.071586 l -0.00103,-0.01395 v -0.01602 -11.696444 -0.01395 l 0.00362,-0.0083 c 0.05128,0.0487 0.1879857,0.16313 0.2418457,0.20774 l 5.4249919,4.545459 5.514909,4.571297 c 0.0844,0.06984 0.185312,0.130661 0.298173,0.182418 C 16.15064,17.512631 11.679239,13.804108 9.750826,12.186336 7.4531167,10.290397 5.3700283,8.5696704 3.6452555,7.14375 Z" /><path
|
||||
id="path1-2-1-7"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient815);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 41.226941,7.09104 c -1.718284,1.4205409 -3.7895,3.131915 -6.072498,5.015715 -1.93499,1.62329 -6.430112,5.351263 -11.522294,9.54257 0.08232,-0.04302 0.156783,-0.0915 0.221692,-0.145211 l 5.514909,-4.571297 5.48752,-4.573881 c 0.0199,-0.0164 0.04075,-0.03316 0.05994,-0.04806 l 0.0279,-0.02119 0.222726,-0.190169 v 11.598258 0.02894 c 0,0 -0.06656,2.081964 -1.311548,4.183724 -0.57202,0.96572 -1.445392,2.157957 -2.917652,2.998267 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -0.308682,-0.174605 -0.590588,-0.364744 -0.848011,-0.565341 -1.805553,1.467965 -3.513225,2.849693 -5.0307002,4.067452 2.6425302,2.518552 6.6894502,4.460193 12.1212242,4.460193 h 4.580599 c 7.4177,0 12.269411,-3.665883 14.506091,-7.441923 2.23668,-3.77604 2.270146,-7.635193 2.270146,-7.635193 l 0.0021,-0.03411 V 8.3007853 C 41.429125,7.8798785 41.357396,7.4736084 41.226941,7.09104 Z" /></g></svg>
|
||||
id="stop7-7" /></linearGradient><linearGradient
|
||||
xlink:href="#linearGradient11"
|
||||
id="linearGradient1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.75684298,0,0,0.80151105,-19.989667,-137.1)"
|
||||
x1="54.340946"
|
||||
y1="122.84999"
|
||||
x2="54.340946"
|
||||
y2="162.66415" /></defs><g
|
||||
id="g1"
|
||||
transform="translate(1.425541,43.741704)"><path
|
||||
id="path1-2-1"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#linearGradient1);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 35.878324,-39.647594 c -2.15234,-7.4e-4 -4.0678,1.84724 -4.0671,4.1266 v 2.85212 c -0.073,-0.005 -0.14656,-0.007 -0.2202,-0.007 h -10.50012 -10.49855 c -0.0418,-7e-5 -0.0835,5.9e-4 -0.12516,0.002 v -2.78918 c -1.6e-4,-2.27807 -1.9592603,-4.12477 -4.1103803,-4.12494 -2.15111,1.8e-4 -4.20874,1.84687 -4.2089,4.12494 v 15.55127 c -4e-5,0.005 -0.0295,3.84339 2.173,7.60498 2.20396,3.7640196 7.0382003,7.3825696 14.4526703,7.3825696 h 4.58067 c 7.4177,0 12.26918,-3.66617 14.50586,-7.4422096 2.23668,-3.77604 2.27039,-7.63481 2.27039,-7.63481 l 0.002,-0.0344 v -15.48541 c 7e-4,-2.27872 -2.10202,-4.12642 -4.25374,-4.1266 z m -2.00792,7.9252 v 11.59858 0.0286 c 0,0 -0.0664,2.0824 -1.31139,4.18416 -0.57202,0.96572 -1.44541,2.15762 -2.91767,2.99793 -1.47227,0.8403 -3.46066,1.33079 -6.27223,1.33079 h -4.58106 c -2.82381,0 -4.79864,-0.47938 -6.25697,-1.30429 -1.45833,-0.8249 -2.31357,-2.00343 -2.8590102,-2.93496 -1.1966401,-2.04365 -1.2300401,-4.07193 -1.2300401,-4.07193 l -10e-4,-0.0141 v -0.0157 -11.69637 l 1.96e-4,-0.0142 0.0035,-0.008 c 0.05128,0.0487 0.18806,0.16304 0.24192,0.20765 l 5.4247013,4.54513 5.51504,4.57144 c 0.65194,0.53945 2.27903,0.53945 2.93097,0 l 5.51504,-4.57144 5.487378,-4.57351 c 0.0199,-0.0164 0.04083,-0.0331 0.06003,-0.048 l 0.02765,-0.0215 z" /><path
|
||||
id="path1-2-1-9-9"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient816-3);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 2.3483903,-36.677919 c -0.1292796,0.384121 -0.2004748,0.792004 -0.2005045,1.214913 v 15.550989 c -4e-5,0.005 -0.029506,3.84363 2.172994,7.60522 2.20396,3.7640196 7.0383952,7.3824946 14.4528652,7.3824946 h 4.580598 c 5.433936,0 9.490679,-1.96731 12.151197,-4.504117 -1.517177,-1.2182566 -3.218664,-2.5953336 -5.013648,-4.0550496 -0.258984,0.201994 -0.541601,0.394146 -0.850594,0.570508 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -1.45833,-0.8249 -2.313817,-2.003692 -2.8592572,-2.935222 -1.1966406,-2.04365 -1.2298996,-4.071586 -1.2298996,-4.071586 l -0.00103,-0.01395 v -0.01602 -11.696444 -0.01395 l 0.00362,-0.0083 c 0.05128,0.0487 0.1879857,0.16313 0.2418457,0.20774 l 5.4249921,4.545459 5.514909,4.571297 c 0.0844,0.06984 0.185312,0.130661 0.298173,0.182418 -5.07081,-4.173874 -9.542211,-7.882397 -11.4706242,-9.500169 -2.2977093,-1.895939 -4.3807977,-3.616666 -6.1055705,-5.042586 z" /><path
|
||||
id="path1-2-1-7-3"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient815-9);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 39.930076,-36.730629 c -1.718284,1.420541 -3.7895,3.131915 -6.072498,5.015715 -1.93499,1.62329 -6.430112,5.351263 -11.522294,9.54257 0.08232,-0.04302 0.156783,-0.0915 0.221692,-0.145211 l 5.514909,-4.571297 5.48752,-4.573881 c 0.0199,-0.0164 0.04075,-0.03316 0.05994,-0.04806 l 0.0279,-0.02119 0.222726,-0.190169 v 11.598258 0.02894 c 0,0 -0.06656,2.081964 -1.311548,4.183724 -0.57202,0.96572 -1.445392,2.157957 -2.917652,2.998267 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -0.308682,-0.174605 -0.590588,-0.364744 -0.848011,-0.565341 -1.8055532,1.467965 -3.5132252,2.849693 -5.0307004,4.0674516 2.6425302,2.518552 6.6894504,4.460193 12.1212244,4.460193 h 4.580599 c 7.4177,0 12.269411,-3.665883 14.506091,-7.4419226 2.23668,-3.77604 2.270146,-7.635193 2.270146,-7.635193 l 0.0021,-0.03411 v -15.485354 c 1.06e-4,-0.420906 -0.07162,-0.827177 -0.202078,-1.209745 z" /></g></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
@@ -1,3 +1,5 @@
|
||||
import { uiZoom } from "../lib/uiScale";
|
||||
|
||||
// A drag handle between two panels. Vertical (the default) sits between panels
|
||||
// side by side and reports horizontal movement; horizontal sits between panels
|
||||
// stacked one above the other (the "reading pane at the bottom" layout) and
|
||||
@@ -17,7 +19,7 @@ export function Resizer({
|
||||
let last = vertical ? e.clientX : e.clientY;
|
||||
const onMove = (ev: MouseEvent) => {
|
||||
const now = vertical ? ev.clientX : ev.clientY;
|
||||
onDelta(now - last);
|
||||
onDelta((now - last) / uiZoom()); // screen px → CSS px under the interface zoom
|
||||
last = now;
|
||||
};
|
||||
const onUp = () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { useT } from "../../i18n";
|
||||
import { uiZoom } from "../../lib/uiScale";
|
||||
import { Composer } from "./Composer";
|
||||
import { CHIP_W, type ComposeWin, DOCK_INSET, layoutDock } from "./windows";
|
||||
|
||||
@@ -8,7 +9,7 @@ function useViewportSize() {
|
||||
const read = () =>
|
||||
typeof window === "undefined"
|
||||
? { vw: 1280, vh: 800 }
|
||||
: { vw: window.innerWidth, vh: window.innerHeight };
|
||||
: { vw: window.innerWidth / uiZoom(), vh: window.innerHeight / uiZoom() };
|
||||
const [size, setSize] = useState(read);
|
||||
useEffect(() => {
|
||||
const onResize = () => setSize(read());
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createPortal } from "react-dom";
|
||||
|
||||
import { useT } from "../../i18n";
|
||||
import { useTheme } from "../../lib/theme";
|
||||
import { uiZoom } from "../../lib/uiScale";
|
||||
|
||||
// emoji-mart, loaded on first use: its emoji data is a few hundred KB that the
|
||||
// rest of the app does without. The data is bundled — nothing is fetched from
|
||||
@@ -82,13 +83,18 @@ export function EmojiButton({
|
||||
const [pos, setPos] = useState<{ left: number; top: number } | null>(null);
|
||||
|
||||
const open = () => {
|
||||
const r = btnRef.current?.getBoundingClientRect();
|
||||
if (!r) return;
|
||||
const sr = btnRef.current?.getBoundingClientRect();
|
||||
if (!sr) return;
|
||||
onOpen();
|
||||
// Screen px → CSS px under the interface zoom, so the picker lands on the button.
|
||||
const z = uiZoom();
|
||||
const r = { left: sr.left / z, top: sr.top / z, bottom: sr.bottom / z };
|
||||
const vw = window.innerWidth / z;
|
||||
const vh = window.innerHeight / z;
|
||||
// Below the button, or above it when the window ends first; inside the viewport.
|
||||
const below = r.bottom + 6 + PICKER_H <= window.innerHeight;
|
||||
const below = r.bottom + 6 + PICKER_H <= vh;
|
||||
setPos({
|
||||
left: Math.max(8, Math.min(r.left, window.innerWidth - PICKER_W - 8)),
|
||||
left: Math.max(8, Math.min(r.left, vw - PICKER_W - 8)),
|
||||
top: below ? r.bottom + 6 : Math.max(8, r.top - 6 - PICKER_H),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
|
||||
import { useT } from "../../i18n";
|
||||
import { api, ApiError } from "../../lib/api";
|
||||
import { uiZoom } from "../../lib/uiScale";
|
||||
import { notify } from "../AppDialog";
|
||||
import { EmojiButton } from "./EmojiButton";
|
||||
import {
|
||||
@@ -581,7 +582,8 @@ export const RichTextEditor = forwardRef<
|
||||
if (!img || !ed) return null;
|
||||
const ir = img.getBoundingClientRect();
|
||||
const er = ed.getBoundingClientRect();
|
||||
return { left: ir.left - er.left, top: ir.top - er.top, width: ir.width, height: ir.height };
|
||||
const z = uiZoom(); // rects are screen px; the handles are placed in CSS px
|
||||
return { left: (ir.left - er.left) / z, top: (ir.top - er.top) / z, width: ir.width / z, height: ir.height / z };
|
||||
}, [selImg]);
|
||||
|
||||
useEffect(() => setBox(computeBox()), [selImg, computeBox]);
|
||||
@@ -610,10 +612,11 @@ export const RichTextEditor = forwardRef<
|
||||
const ed = ref.current;
|
||||
if (!img || !ed) return;
|
||||
const startX = e.clientX;
|
||||
const startW = img.getBoundingClientRect().width;
|
||||
const z = uiZoom();
|
||||
const startW = img.getBoundingClientRect().width / z;
|
||||
const maxW = ed.clientWidth - 24; // minus px-3 padding
|
||||
const onMove = (ev: MouseEvent) => {
|
||||
const dx = ev.clientX - startX;
|
||||
const dx = (ev.clientX - startX) / z;
|
||||
const grow = corner === "se" || corner === "ne" ? dx : -dx;
|
||||
img.style.width = `${Math.round(Math.max(24, Math.min(startW + grow, maxW)))}px`;
|
||||
img.style.height = "auto";
|
||||
@@ -663,8 +666,9 @@ export const RichTextEditor = forwardRef<
|
||||
if (!ed || !spellIssue || !stillValid(spellIssue)) return null;
|
||||
const r = spellIssue.range.getBoundingClientRect();
|
||||
const er = ed.getBoundingClientRect();
|
||||
const z = uiZoom();
|
||||
return {
|
||||
left: Math.max(8, Math.min(r.left - er.left, er.width - 300)),
|
||||
left: Math.max(8, Math.min((r.left - er.left) / z, er.width / z - 300)),
|
||||
top: popupTop(r, spellPopupRef.current?.offsetHeight || 160),
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -714,14 +718,17 @@ export const RichTextEditor = forwardRef<
|
||||
// A popup's top edge, relative to the editor: just under its anchor, or just
|
||||
// above it when there is no room left below — inside the editor's visible
|
||||
// box and the window alike — so it is never cut off at the bottom.
|
||||
const popupTop = (anchor: DOMRect, popupH: number) => {
|
||||
const popupTop = (anchor: DOMRect, cssPopupH: number) => {
|
||||
// Worked out in screen px (the rects'), returned in CSS px for the style.
|
||||
const z = uiZoom();
|
||||
const popupH = cssPopupH * z;
|
||||
const er = ref.current!.getBoundingClientRect();
|
||||
const bottom = Math.min(er.bottom, window.innerHeight) - 4;
|
||||
const top = Math.max(er.top, 0) + 4;
|
||||
const below = anchor.bottom + 4;
|
||||
const above = anchor.top - 4 - popupH;
|
||||
const fitsBelow = below + popupH <= bottom;
|
||||
return (fitsBelow || above < top ? below : above) - er.top;
|
||||
return ((fitsBelow || above < top ? below : above) - er.top) / z;
|
||||
};
|
||||
|
||||
// Where the link popup goes: just under the link (or the text becoming one),
|
||||
@@ -738,7 +745,8 @@ export const RichTextEditor = forwardRef<
|
||||
}
|
||||
const er = ed.getBoundingClientRect();
|
||||
if (!ar || (!ar.width && !ar.height && !ar.top)) return newLinkRange ? { left: 12, top: 12 } : null;
|
||||
const left = Math.max(8, Math.min(ar.left - er.left, er.width - 320));
|
||||
const z = uiZoom();
|
||||
const left = Math.max(8, Math.min((ar.left - er.left) / z, er.width / z - 320));
|
||||
return { left, top: popupTop(ar, linkPopupRef.current?.offsetHeight || 44) };
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selLink, newLinkRange]);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { avatarColor } from "../../lib/avatarColor";
|
||||
import { useSavedThreads } from "../../lib/offline";
|
||||
import { formatClock, getDateLocale } from "../../lib/timeFormat";
|
||||
import type { Address, ThreadSummary } from "../../lib/types";
|
||||
import { uiZoom } from "../../lib/uiScale";
|
||||
import { Icon } from "../Icon";
|
||||
import { Clock } from "../Offline";
|
||||
import { AvatarImage } from "../SenderAvatar";
|
||||
@@ -554,8 +555,8 @@ function useSwipeToDelete(onDelete?: (id: string) => void) {
|
||||
onPointerMove: (e: React.PointerEvent<HTMLDivElement>) => {
|
||||
const s = start.current;
|
||||
if (!s || s.id !== id) return;
|
||||
const dx = e.clientX - s.x;
|
||||
const dy = e.clientY - s.y;
|
||||
const dx = (e.clientX - s.x) / uiZoom(); // CSS px, like the row's offsetWidth
|
||||
const dy = (e.clientY - s.y) / uiZoom();
|
||||
if (!s.active) {
|
||||
if (Math.abs(dy) > SWIPE_START_PX && Math.abs(dy) > Math.abs(dx)) {
|
||||
start.current = null; // a scroll
|
||||
@@ -572,7 +573,7 @@ function useSwipeToDelete(onDelete?: (id: string) => void) {
|
||||
start.current = null;
|
||||
if (!s || !s.active || s.id !== id) return;
|
||||
swipedAt.current = Date.now();
|
||||
const dx = Math.max(0, e.clientX - s.x);
|
||||
const dx = Math.max(0, (e.clientX - s.x) / uiZoom());
|
||||
if (dx > s.width * SWIPE_DELETE_SHARE) {
|
||||
setState({ id, dx: s.width, dragging: false }); // off it goes
|
||||
window.setTimeout(() => {
|
||||
|
||||
@@ -3,11 +3,13 @@ import { useEffect, useRef } from "react";
|
||||
import { useT } from "../../i18n";
|
||||
import { ListDividersOption } from "./ListDividersOption";
|
||||
import { ReadingPanePicker } from "./ReadingPanePicker";
|
||||
import { UiScaleOption } from "./UiScaleOption";
|
||||
|
||||
/**
|
||||
* "Mailbox display": the reading-pane choice and the row dividers, opened from
|
||||
* "Mailbox display": the reading-pane choice, the row dividers and the interface
|
||||
* size, opened from
|
||||
* the list's ⋯ menu. Anchored below that menu. Picking a layout applies it and
|
||||
* closes the popover; the dividers switch applies at once and leaves it open;
|
||||
* closes the popover; the dividers switch and the size apply at once and leave it open;
|
||||
* ✕, Escape or a click elsewhere closes it.
|
||||
*/
|
||||
export function MailboxDisplay({
|
||||
@@ -59,12 +61,16 @@ export function MailboxDisplay({
|
||||
<>
|
||||
<p className="mb-2 text-[13px] font-medium text-app-fg">{t("display.readingPane")}</p>
|
||||
<ReadingPanePicker onPick={onClose} />
|
||||
<div className="mt-4 border-t border-app-border pt-3">
|
||||
<div className="mt-4 space-y-3 border-t border-app-border pt-3">
|
||||
<ListDividersOption />
|
||||
<UiScaleOption />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<ListDividersOption />
|
||||
<div className="space-y-3">
|
||||
<ListDividersOption />
|
||||
<UiScaleOption />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useT } from "../../i18n";
|
||||
import { setUiScale, UI_SCALES, useUiScale } from "../../lib/uiScale";
|
||||
|
||||
/** "Interface size" — Default / +10% / +20%, in the ⋯ menu's Mailbox display
|
||||
* and in Settings → Appearance. Applies at once. */
|
||||
export function UiScaleOption() {
|
||||
const t = useT();
|
||||
const scale = useUiScale();
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-3 text-[14px] text-app-fg">
|
||||
<span>{t("display.size")}</span>
|
||||
<div role="radiogroup" aria-label={t("display.size")} className="flex gap-1 rounded-lg border border-app-border p-0.5">
|
||||
{UI_SCALES.map((value) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={scale === value}
|
||||
onClick={() => setUiScale(value)}
|
||||
className={`rounded px-2.5 py-0.5 ${
|
||||
scale === value ? "bg-app-active font-medium text-app-fg" : "text-app-muted hover:bg-app-hover"
|
||||
}`}
|
||||
>
|
||||
{value === 1 ? t("display.sizeDefault") : `+${Math.round((value - 1) * 100)}%`}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -670,6 +670,8 @@ export const en = {
|
||||
"display.bottom": "Bottom",
|
||||
"display.off": "Off",
|
||||
"display.dividers": "Show a dividing line between rows",
|
||||
"display.size": "Interface size",
|
||||
"display.sizeDefault": "Default",
|
||||
"admin.mail": "Outgoing system mail",
|
||||
"admin.mailHelp":
|
||||
"The mail account the server sends password-reset links from. Without it, “Forgot password?” sends nothing.",
|
||||
@@ -876,6 +878,25 @@ export const en = {
|
||||
"mail.translatedFrom": "Translated from {language}",
|
||||
"mail.translatedFromMany": "Translated from {languages}",
|
||||
"mail.translateFailed": "Couldn't translate this message. The translation service may be busy or unavailable.",
|
||||
"antispam.looksLikeSpam": "Looks like spam!",
|
||||
"antispam.score": "score {score}",
|
||||
"antispam.moveToSpam": "Move to Spam",
|
||||
"antispam.notSpam": "Not spam",
|
||||
"antispam.trustSender": "Always trust sender",
|
||||
"antispam.why.bayes": "resembles mail you marked as spam",
|
||||
"antispam.why.fuzzy": "matches known spam",
|
||||
"antispam.why.reputation": "sender has a poor reputation",
|
||||
"antispam.why.blocklist": "sending server is on a spam blocklist",
|
||||
"antispam.why.badLinks": "links to known spam or phishing sites",
|
||||
"antispam.why.phishing": "link text hides its real destination",
|
||||
"antispam.why.authFail": "sender failed authenticity checks",
|
||||
"antispam.why.forged": "sender address looks forged",
|
||||
"antispam.why.replyTo": "replies would go to a different address",
|
||||
"antispam.why.headers": "message is missing standard headers",
|
||||
"antispam.why.hiddenText": "contains hidden text",
|
||||
"antispam.why.suspiciousServer": "sending server looks suspicious",
|
||||
"antispam.why.shouting": "subject written in capitals",
|
||||
"antispam.why.date": "message date is implausible",
|
||||
} as const;
|
||||
|
||||
export type MessageKey = keyof typeof en;
|
||||
|
||||
@@ -680,6 +680,8 @@ export const pl: Catalog = {
|
||||
"display.bottom": "Na dole",
|
||||
"display.off": "Wyłączone",
|
||||
"display.dividers": "Pokazuj linię między wierszami",
|
||||
"display.size": "Rozmiar interfejsu",
|
||||
"display.sizeDefault": "Domyślny",
|
||||
"admin.mail": "Wychodząca poczta systemowa",
|
||||
"admin.mailHelp":
|
||||
"Konto pocztowe, z którego serwer wysyła linki do resetowania hasła. Bez niego „Nie pamiętasz hasła?” niczego nie wyśle.",
|
||||
@@ -902,4 +904,23 @@ export const pl: Catalog = {
|
||||
"mail.translatedFrom": "Przetłumaczono z języka: {language}",
|
||||
"mail.translatedFromMany": "Przetłumaczono z języków: {languages}",
|
||||
"mail.translateFailed": "Nie udało się przetłumaczyć tej wiadomości. Usługa tłumaczenia może być zajęta lub niedostępna.",
|
||||
"antispam.looksLikeSpam": "Wygląda na spam!",
|
||||
"antispam.score": "wynik {score}",
|
||||
"antispam.moveToSpam": "Przenieś do spamu",
|
||||
"antispam.notSpam": "To nie spam",
|
||||
"antispam.trustSender": "Zawsze ufaj nadawcy",
|
||||
"antispam.why.bayes": "przypomina wiadomości oznaczone przez Ciebie jako spam",
|
||||
"antispam.why.fuzzy": "pasuje do znanego spamu",
|
||||
"antispam.why.reputation": "nadawca ma złą reputację",
|
||||
"antispam.why.blocklist": "serwer nadawcy jest na liście spamerów",
|
||||
"antispam.why.badLinks": "zawiera linki do stron ze spamem lub phishingiem",
|
||||
"antispam.why.phishing": "tekst linku ukrywa jego prawdziwy adres",
|
||||
"antispam.why.authFail": "nadawca nie przeszedł weryfikacji autentyczności",
|
||||
"antispam.why.forged": "adres nadawcy wygląda na podrobiony",
|
||||
"antispam.why.replyTo": "odpowiedzi trafiłyby na inny adres",
|
||||
"antispam.why.headers": "brakuje standardowych nagłówków",
|
||||
"antispam.why.hiddenText": "zawiera ukryty tekst",
|
||||
"antispam.why.suspiciousServer": "serwer nadawcy wygląda podejrzanie",
|
||||
"antispam.why.shouting": "temat napisany wielkimi literami",
|
||||
"antispam.why.date": "data wiadomości jest nieprawdopodobna",
|
||||
};
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Interface size (Mailbox display / Settings → Appearance): Default, +10% or
|
||||
// +20%. Per device, in localStorage, like the reading-pane layout.
|
||||
//
|
||||
// Applied as CSS `zoom` on <html>, which scales everything evenly — px and rem
|
||||
// sizes, inline styles, SVG icons and the message iframes alike. Two things zoom
|
||||
// does NOT hide, and the app compensates for:
|
||||
// - viewport units are zoomed too, so a 100vh box would overflow; `h-screen`
|
||||
// and the dvh heights divide by --ui-zoom (tailwind.config.js, index.css).
|
||||
// - mouse coordinates, getBoundingClientRect() and innerWidth/innerHeight are
|
||||
// in unzoomed screen px, while a style written from them is zoomed again;
|
||||
// divide by uiZoom() before turning them into a CSS length.
|
||||
|
||||
import { useSyncExternalStore } from "react";
|
||||
|
||||
export const UI_SCALES = [1, 1.1, 1.2] as const;
|
||||
export type UiScale = (typeof UI_SCALES)[number];
|
||||
|
||||
const KEY = "ui.scale";
|
||||
const EVENT = "webmail:ui-scale";
|
||||
|
||||
function read(): UiScale {
|
||||
try {
|
||||
const v = Number(localStorage.getItem(KEY));
|
||||
return (UI_SCALES as readonly number[]).includes(v) ? (v as UiScale) : 1;
|
||||
} catch {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** The current zoom factor, for converting screen px into CSS px. */
|
||||
export function uiZoom(): number {
|
||||
return read();
|
||||
}
|
||||
|
||||
let applied = 1;
|
||||
|
||||
function apply(): void {
|
||||
const z = read();
|
||||
if (z === applied) return; // the storage event fires for every key
|
||||
applied = z;
|
||||
const root = document.documentElement;
|
||||
root.style.zoom = z === 1 ? "" : String(z);
|
||||
root.style.setProperty("--ui-zoom", String(z));
|
||||
// The viewport in CSS px just changed size; let everything that measures it
|
||||
// on "resize" (panel clamps, the compose dock, popups) measure again.
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
}
|
||||
|
||||
function subscribe(onChange: () => void): () => void {
|
||||
// Our own writes (same tab) and other tabs' writes (the storage event).
|
||||
window.addEventListener(EVENT, onChange);
|
||||
window.addEventListener("storage", onChange);
|
||||
return () => {
|
||||
window.removeEventListener(EVENT, onChange);
|
||||
window.removeEventListener("storage", onChange);
|
||||
};
|
||||
}
|
||||
|
||||
export function useUiScale(): UiScale {
|
||||
return useSyncExternalStore(subscribe, read, () => 1);
|
||||
}
|
||||
|
||||
export function setUiScale(scale: UiScale): void {
|
||||
try {
|
||||
localStorage.setItem(KEY, String(scale));
|
||||
} catch {
|
||||
/* private mode: the choice simply does not stick */
|
||||
}
|
||||
window.dispatchEvent(new Event(EVENT));
|
||||
}
|
||||
|
||||
// Applied before the first render (imported from main.tsx), so the app never
|
||||
// paints at the wrong size, and kept in step with later changes from any tab.
|
||||
apply();
|
||||
subscribe(apply);
|
||||
@@ -8,6 +8,7 @@ import { I18nProvider } from "./i18n";
|
||||
import { isSessionGone, signedOut } from "./lib/auth";
|
||||
import "./lib/install"; // catches the install event, which can come before the app renders
|
||||
import { ThemeProvider } from "./lib/theme";
|
||||
import "./lib/uiScale"; // applies the chosen interface size before the first paint
|
||||
import "./index.css";
|
||||
import "./plugins/plugin-ui.css"; // .wm-* classes for plugin bundles (SPEC §11.3)
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ import type { AccountOut, QuotaOut, SavedSearch, ThreadSummary } from "../lib/ty
|
||||
import { useReadingPane } from "../lib/readingPane";
|
||||
import { useIsMobile } from "../lib/useIsMobile";
|
||||
import { useKeyboardShortcuts } from "../lib/useKeyboardShortcuts";
|
||||
import { uiZoom } from "../lib/uiScale";
|
||||
import { subscribeWs } from "../lib/ws";
|
||||
import { PluginSlot } from "../plugins/PluginSlot";
|
||||
import { usePluginPages } from "../plugins/manifest";
|
||||
@@ -213,14 +214,15 @@ export function MailLayout() {
|
||||
if (!el) return;
|
||||
const measure = () => {
|
||||
const r = el.getBoundingClientRect();
|
||||
const z = uiZoom(); // the rect is in screen px; the dock lays out in CSS px
|
||||
// `right` is the gap to the viewport edge, which is what the dock needs
|
||||
// to line an expanded window up with the pane — it is non-zero whenever
|
||||
// the right panel is open. `height` only under the list: everywhere else
|
||||
// the pane runs the full height of the screen.
|
||||
setPane({
|
||||
width: Math.round(r.width),
|
||||
right: Math.round(window.innerWidth - r.right),
|
||||
height: bottom ? Math.round(r.height) : undefined,
|
||||
width: Math.round(r.width / z),
|
||||
right: Math.round((window.innerWidth - r.right) / z),
|
||||
height: bottom ? Math.round(r.height / z) : undefined,
|
||||
});
|
||||
};
|
||||
measure();
|
||||
@@ -240,7 +242,7 @@ export function MailLayout() {
|
||||
// The list's height when the reading pane sits under it.
|
||||
// Until dragged, the list takes 45% of the height and the reading pane 55%.
|
||||
const [listH, setListH] = useState(
|
||||
() => Number(localStorage.getItem("panel.listH")) || Math.round(window.innerHeight * 0.45),
|
||||
() => Number(localStorage.getItem("panel.listH")) || Math.round((window.innerHeight / uiZoom()) * 0.45),
|
||||
);
|
||||
// Collapsed sidebar shows icons only (persisted). The mobile drawer is always full.
|
||||
const [collapsedPref, setCollapsed] = useState(
|
||||
@@ -253,13 +255,13 @@ export function MailLayout() {
|
||||
const [rightW, setRightW] = useState(() => Number(localStorage.getItem("panel.rightW")) || 340);
|
||||
const clampSidebar = (w: number) => Math.min(Math.max(w, 180), 420);
|
||||
// The list can be dragged up to 75% of the window width.
|
||||
const clampList = (w: number) => Math.min(Math.max(w, 260), window.innerWidth * 0.75);
|
||||
const clampList = (w: number) => Math.min(Math.max(w, 260), (window.innerWidth / uiZoom()) * 0.75);
|
||||
const clampRight = (w: number) => Math.min(Math.max(w, 280), 560);
|
||||
useEffect(() => localStorage.setItem("panel.sidebar", String(sidebarW)), [sidebarW]);
|
||||
useEffect(() => localStorage.setItem("panel.list", String(listW)), [listW]);
|
||||
useEffect(() => localStorage.setItem("panel.listH", String(listH)), [listH]);
|
||||
// Leave both the list and the reading pane at least a usable strip.
|
||||
const clampListH = (h: number) => Math.min(Math.max(h, 160), window.innerHeight - 220);
|
||||
const clampListH = (h: number) => Math.min(Math.max(h, 160), window.innerHeight / uiZoom() - 220);
|
||||
useEffect(
|
||||
() => localStorage.setItem("panel.collapsed", collapsedPref ? "1" : "0"),
|
||||
[collapsedPref],
|
||||
@@ -1166,7 +1168,7 @@ export function MailLayout() {
|
||||
const paneShown = Boolean(paneDraft || threadId || selectedSub);
|
||||
|
||||
return (
|
||||
<div className={`flex bg-app-bg text-app-fg ${mobile ? "h-[100dvh]" : "h-screen"}`}>
|
||||
<div className={`flex bg-app-bg text-app-fg ${mobile ? "h-[calc(100dvh/var(--ui-zoom,1))]" : "h-screen"}`}>
|
||||
<BackfillBanner />
|
||||
{mobile && (
|
||||
// Tapping outside the drawer closes it.
|
||||
|
||||
@@ -7,6 +7,7 @@ import { RichTextEditor } from "../../components/compose/RichTextEditor";
|
||||
import { TypedPasswordInput } from "../../components/TypedPasswordInput";
|
||||
import { ListDividersOption } from "../../components/mail/ListDividersOption";
|
||||
import { ReadingPanePicker } from "../../components/mail/ReadingPanePicker";
|
||||
import { UiScaleOption } from "../../components/mail/UiScaleOption";
|
||||
import { LANGUAGES, translate, useLocale, useT } from "../../i18n";
|
||||
import type { MessageKey } from "../../i18n/en";
|
||||
import { api, ApiError } from "../../lib/api";
|
||||
@@ -190,7 +191,7 @@ function MobileSettings({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative h-[100dvh] overflow-hidden bg-app-bg text-app-fg">
|
||||
<div className="relative h-[calc(100dvh/var(--ui-zoom,1))] overflow-hidden bg-app-bg text-app-fg">
|
||||
<div className="flex h-full flex-col pt-[env(safe-area-inset-top)]">
|
||||
<header className="flex h-14 shrink-0 items-center gap-2 border-b border-app-border px-2">
|
||||
<Link to={from} className="flex items-center rounded-lg px-2 py-2 text-app-accent" aria-label={t("nav.back")}>
|
||||
@@ -1444,6 +1445,9 @@ function AppearanceSection() {
|
||||
<div className="max-w-md pt-2">
|
||||
<ListDividersOption />
|
||||
</div>
|
||||
<div className="max-w-md pt-2">
|
||||
<UiScaleOption />
|
||||
</div>
|
||||
</div>
|
||||
<SpellcheckSetting />
|
||||
<CompletionSetting />
|
||||
|
||||
@@ -4,6 +4,10 @@ export default {
|
||||
content: ["./index.html", "./admin.html", "./src/**/*.{ts,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
// The interface-size setting zooms <html>, and zoom scales viewport units
|
||||
// too: divide them back so a full-height layout still fits (lib/uiScale.ts).
|
||||
height: { screen: "calc(100vh / var(--ui-zoom, 1))" },
|
||||
minHeight: { screen: "calc(100vh / var(--ui-zoom, 1))" },
|
||||
fontFamily: {
|
||||
sans: ['"Lato"', "system-ui", "-apple-system", '"Segoe UI"', "Roboto", "sans-serif"],
|
||||
},
|
||||
|
||||
@@ -6,6 +6,37 @@
|
||||
// mounts on every message, and a spam warning on ordinary mail is worse than no
|
||||
// warning at all.
|
||||
|
||||
// Engine symbols mean nothing to a reader, so the banner explains only the ones
|
||||
// that point AT spam, in plain words (app catalog keys antispam.why.*). Neutral
|
||||
// or reassuring symbols (ARC_NA, DKIM_TRACE, R_SPF_ALLOW…) are not reasons and
|
||||
// stay hidden. Ordered by how telling the reason is; first match wins per symbol.
|
||||
const REASONS = [
|
||||
[/^BAYES_SPAM$/, "bayes"],
|
||||
[/^FUZZY_(DENIED|PROB|BLACK)/, "fuzzy"],
|
||||
[/^PHISH(ING|ED)/, "phishing"],
|
||||
[/^(PH_SURBL|DBL_PHISH|URIBL_|SURBL_|DBL_|SEM_URIBL|RSPAMD_URIBL|MSBL_)/, "badLinks"],
|
||||
[/^(RBL_|RECEIVED_SPAMHAUS|RCVD_IN_(?!DNSWL)|RSPAMD_EMAILBL|MAILSPIKE_BAD)/, "blocklist"],
|
||||
[/^(BAD_REP_|RWL_.*_BAD|SENDER_REP_SPAM|IP_REPUTATION_SPAM)/, "reputation"],
|
||||
[/^(R_SPF_(FAIL|SOFTFAIL)|R_DKIM_REJECT|DMARC_POLICY_(REJECT|QUARANTINE|SOFTFAIL)|ARC_REJECT)$/, "authFail"],
|
||||
[/^(FORGED_|SPOOF_)/, "forged"],
|
||||
[/^(FREEMAIL_REPLYTO_NEQ_FROM|REPLYTO_DOM_NEQ_FROM_DOM|REPLYTO_ADDR_EQ_TO_ADDR)/, "replyTo"],
|
||||
[/^(ZERO_FONT|MANY_INVISIBLE_PARTS|R_WHITE_ON_WHITE|HTML_SHORT_LINK_IMG)/, "hiddenText"],
|
||||
[/^HFILTER_/, "suspiciousServer"],
|
||||
[/^MISSING_(DATE|MID|FROM|TO)$/, "headers"],
|
||||
[/^(DATE_IN_FUTURE|DATE_IN_PAST)$/, "date"],
|
||||
[/^SUBJ_ALL_CAPS$/, "shouting"],
|
||||
];
|
||||
|
||||
/** Distinct plain-language reasons for a verdict's symbols, most telling first. */
|
||||
function reasons(symbols) {
|
||||
const found = new Set();
|
||||
for (const s of symbols || []) {
|
||||
const hit = REASONS.find(([re]) => re.test(s));
|
||||
if (hit) found.add(hit[1]);
|
||||
}
|
||||
return REASONS.map(([, key]) => key).filter((key) => found.has(key));
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
||||
}
|
||||
@@ -14,6 +45,7 @@ export default function mount(el, ctx, props) {
|
||||
const messageId = props && props.messageId;
|
||||
if (!messageId) return;
|
||||
let disposed = false;
|
||||
const t = ctx.t;
|
||||
|
||||
async function act(path, body) {
|
||||
await ctx.api.pluginFetch(path, { method: "POST", body: JSON.stringify(body) });
|
||||
@@ -31,15 +63,18 @@ export default function mount(el, ctx, props) {
|
||||
}
|
||||
if (disposed || !v || v.action !== "banner") return;
|
||||
|
||||
const why = (v.symbols || []).slice(0, 4).join(", ");
|
||||
const why = reasons(v.symbols).slice(0, 3).map((key) => t(`antispam.why.${key}`));
|
||||
const meta = [t("antispam.score", { score: Number(v.score).toFixed(1) }), ...why].join(" · ");
|
||||
// Inset like the rest of the message body (px-4) with air below, so the card
|
||||
// does not run edge to edge into the remote-images notice under it.
|
||||
el.innerHTML = `
|
||||
<div class="wm-card" role="status">
|
||||
<strong>Looks like spam</strong>
|
||||
<span class="wm-muted">score ${Number(v.score).toFixed(1)}${why ? ` · ${esc(why)}` : ""}</span>
|
||||
<div class="wm-row">
|
||||
<button class="wm-btn" data-a="junk">Move to Spam</button>
|
||||
<button class="wm-btn-ghost" data-a="ham">Not spam</button>
|
||||
<button class="wm-btn-ghost" data-a="trust">Always trust sender</button>
|
||||
<div class="wm-card" role="status" style="margin: 0 16px 10px">
|
||||
<strong style="color: rgb(239 68 68)">${esc(t("antispam.looksLikeSpam"))}</strong>
|
||||
<span class="wm-muted">${esc(meta)}</span>
|
||||
<div class="wm-row" style="margin-top: 6px">
|
||||
<button class="wm-btn" data-a="junk">${esc(t("antispam.moveToSpam"))}</button>
|
||||
<button class="wm-btn-ghost" data-a="ham">${esc(t("antispam.notSpam"))}</button>
|
||||
<button class="wm-btn-ghost" data-a="trust">${esc(t("antispam.trustSender"))}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
||||
@@ -58,55 +58,55 @@
|
||||
id="mask1776-6"><path
|
||||
style="fill:url(#linearGradient1789);fill-opacity:1;stroke-width:0.2;stroke-linecap:round"
|
||||
d="m 18.20332,107.02297 c 0,0 1.930731,1.59986 4.989281,4.12846 1.725315,1.42637 3.809507,3.14827 6.108453,5.04523 5.657506,4.74616 33.20207,27.48506 33.558689,27.18811 0.241178,-0.20083 -16.275384,9.86387 -16.275384,9.86387 L 17.979141,140.69463 Z"
|
||||
id="path1776-1" /></mask><linearGradient
|
||||
xlink:href="#linearGradient11"
|
||||
id="linearGradient13-6"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.75684298,0,0,0.80151105,-18.692802,-93.278335)"
|
||||
x1="54.340946"
|
||||
y1="122.84999"
|
||||
x2="54.340946"
|
||||
y2="162.66415" /><radialGradient
|
||||
xlink:href="#linearGradient8"
|
||||
id="radialGradient816"
|
||||
id="path1776-1" /></mask><radialGradient
|
||||
xlink:href="#linearGradient8-5"
|
||||
id="radialGradient816-3"
|
||||
cx="8.0343275"
|
||||
cy="13.452971"
|
||||
fx="8.0343275"
|
||||
fy="13.452971"
|
||||
r="16.678839"
|
||||
gradientTransform="matrix(-0.29271702,0.00410386,-0.01334442,-0.95181978,11.840139,23.950941)"
|
||||
gradientTransform="matrix(-0.29273442,-0.00257918,0.01127876,-1.2801289,10.212159,-15.400302)"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
id="linearGradient8"><stop
|
||||
style="stop-color:#fa7367;stop-opacity:1;"
|
||||
id="linearGradient8-5"><stop
|
||||
style="stop-color:#fa4435;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop8" /><stop
|
||||
id="stop8-6" /><stop
|
||||
style="stop-color:#fa7367;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop9" /></linearGradient><radialGradient
|
||||
xlink:href="#linearGradient6"
|
||||
id="radialGradient815"
|
||||
cx="35.709007"
|
||||
cy="12.042645"
|
||||
fx="35.709007"
|
||||
fy="12.042645"
|
||||
id="stop9-2" /></linearGradient><radialGradient
|
||||
xlink:href="#linearGradient6-1"
|
||||
id="radialGradient815-9"
|
||||
cx="34.523952"
|
||||
cy="11.253304"
|
||||
fx="34.523952"
|
||||
fy="11.253304"
|
||||
r="16.740065"
|
||||
gradientTransform="matrix(0.25712152,0.00260463,-0.00969379,0.95694375,26.644192,0.97301292)"
|
||||
gradientTransform="matrix(0.27572675,-0.00557307,0.0193391,0.95679742,24.457243,-42.568342)"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
id="linearGradient6"><stop
|
||||
style="stop-color:#fab23b;stop-opacity:1;"
|
||||
id="linearGradient6-1"><stop
|
||||
style="stop-color:#fadc56;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop6" /><stop
|
||||
style="stop-color:#fab23a;stop-opacity:0;"
|
||||
id="stop6-2" /><stop
|
||||
style="stop-color:#faae3a;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop7" /></linearGradient></defs><g
|
||||
id="g816"
|
||||
transform="translate(0.128676,-0.079965)"><path
|
||||
id="path1-2-1-1"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#linearGradient13-6);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 37.175189,4.174075 c -2.15234,-7.4e-4 -4.0678,1.84724 -4.0671,4.1266 v 2.85212 c -0.073,-0.005 -0.14656,-0.007 -0.2202,-0.007 h -10.50012 -10.49855 c -0.0418,-7e-5 -0.0835,5.9e-4 -0.12516,0.002 v -2.78918 c -1.6e-4,-2.27807 -1.9592601,-4.12477 -4.1103801,-4.12494 -2.15111,1.8e-4 -4.20874,1.84687 -4.2089,4.12494 v 15.55127 c -4e-5,0.005 -0.0295,3.84339 2.173,7.60498 2.20396,3.76402 7.0382001,7.38257 14.4526701,7.38257 h 4.58067 c 7.4177,0 12.26918,-3.66617 14.50586,-7.44221 2.23668,-3.77604 2.27039,-7.63481 2.27039,-7.63481 l 0.002,-0.0344 V 8.300605 c 7e-4,-2.27872 -2.10202,-4.12642 -4.25374,-4.1266 z m -2.00792,7.9252 v 11.59858 0.0286 c 0,0 -0.0664,2.0824 -1.31139,4.18416 -0.57202,0.96572 -1.44541,2.15762 -2.91767,2.99793 -1.47227,0.8403 -3.46066,1.33079 -6.27223,1.33079 h -4.58106 c -2.82381,0 -4.79864,-0.47938 -6.25697,-1.30429 -1.45833,-0.8249 -2.31357,-2.00343 -2.85901,-2.93496 -1.1966401,-2.04365 -1.2300401,-4.07193 -1.2300401,-4.07193 l -0.001,-0.0141 v -0.0157 -11.69637 l 1.96e-4,-0.0142 0.0035,-0.008 c 0.05128,0.0487 0.18806,0.16304 0.24192,0.20765 l 5.4247011,4.54513 5.51504,4.57144 c 0.65194,0.53945 2.27903,0.53945 2.93097,0 l 5.51504,-4.57144 5.487378,-4.57351 c 0.0199,-0.0164 0.04083,-0.0331 0.06003,-0.048 l 0.02765,-0.0215 z" /><path
|
||||
id="path1-2-1-9"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient816);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="M 3.6452555,7.14375 C 3.5159759,7.5278714 3.4447807,7.9357538 3.444751,8.3586629 V 23.909652 c -4e-5,0.005 -0.029506,3.84363 2.172994,7.60522 2.20396,3.76402 7.038395,7.382495 14.452865,7.382495 h 4.580598 c 5.433936,0 9.490679,-1.96731 12.151197,-4.504117 -1.517177,-1.218257 -3.218664,-2.595334 -5.013648,-4.05505 -0.258984,0.201994 -0.541601,0.394146 -0.850594,0.570508 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -1.45833,-0.8249 -2.313817,-2.003692 -2.859257,-2.935222 -1.1966406,-2.04365 -1.2298996,-4.071586 -1.2298996,-4.071586 l -0.00103,-0.01395 v -0.01602 -11.696444 -0.01395 l 0.00362,-0.0083 c 0.05128,0.0487 0.1879857,0.16313 0.2418457,0.20774 l 5.4249919,4.545459 5.514909,4.571297 c 0.0844,0.06984 0.185312,0.130661 0.298173,0.182418 C 16.15064,17.512631 11.679239,13.804108 9.750826,12.186336 7.4531167,10.290397 5.3700283,8.5696704 3.6452555,7.14375 Z" /><path
|
||||
id="path1-2-1-7"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient815);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 41.226941,7.09104 c -1.718284,1.4205409 -3.7895,3.131915 -6.072498,5.015715 -1.93499,1.62329 -6.430112,5.351263 -11.522294,9.54257 0.08232,-0.04302 0.156783,-0.0915 0.221692,-0.145211 l 5.514909,-4.571297 5.48752,-4.573881 c 0.0199,-0.0164 0.04075,-0.03316 0.05994,-0.04806 l 0.0279,-0.02119 0.222726,-0.190169 v 11.598258 0.02894 c 0,0 -0.06656,2.081964 -1.311548,4.183724 -0.57202,0.96572 -1.445392,2.157957 -2.917652,2.998267 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -0.308682,-0.174605 -0.590588,-0.364744 -0.848011,-0.565341 -1.805553,1.467965 -3.513225,2.849693 -5.0307002,4.067452 2.6425302,2.518552 6.6894502,4.460193 12.1212242,4.460193 h 4.580599 c 7.4177,0 12.269411,-3.665883 14.506091,-7.441923 2.23668,-3.77604 2.270146,-7.635193 2.270146,-7.635193 l 0.0021,-0.03411 V 8.3007853 C 41.429125,7.8798785 41.357396,7.4736084 41.226941,7.09104 Z" /></g></svg>
|
||||
id="stop7-7" /></linearGradient><linearGradient
|
||||
xlink:href="#linearGradient11"
|
||||
id="linearGradient1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.75684298,0,0,0.80151105,-19.989667,-137.1)"
|
||||
x1="54.340946"
|
||||
y1="122.84999"
|
||||
x2="54.340946"
|
||||
y2="162.66415" /></defs><g
|
||||
id="g1"
|
||||
transform="translate(1.425541,43.741704)"><path
|
||||
id="path1-2-1"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#linearGradient1);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 35.878324,-39.647594 c -2.15234,-7.4e-4 -4.0678,1.84724 -4.0671,4.1266 v 2.85212 c -0.073,-0.005 -0.14656,-0.007 -0.2202,-0.007 h -10.50012 -10.49855 c -0.0418,-7e-5 -0.0835,5.9e-4 -0.12516,0.002 v -2.78918 c -1.6e-4,-2.27807 -1.9592603,-4.12477 -4.1103803,-4.12494 -2.15111,1.8e-4 -4.20874,1.84687 -4.2089,4.12494 v 15.55127 c -4e-5,0.005 -0.0295,3.84339 2.173,7.60498 2.20396,3.7640196 7.0382003,7.3825696 14.4526703,7.3825696 h 4.58067 c 7.4177,0 12.26918,-3.66617 14.50586,-7.4422096 2.23668,-3.77604 2.27039,-7.63481 2.27039,-7.63481 l 0.002,-0.0344 v -15.48541 c 7e-4,-2.27872 -2.10202,-4.12642 -4.25374,-4.1266 z m -2.00792,7.9252 v 11.59858 0.0286 c 0,0 -0.0664,2.0824 -1.31139,4.18416 -0.57202,0.96572 -1.44541,2.15762 -2.91767,2.99793 -1.47227,0.8403 -3.46066,1.33079 -6.27223,1.33079 h -4.58106 c -2.82381,0 -4.79864,-0.47938 -6.25697,-1.30429 -1.45833,-0.8249 -2.31357,-2.00343 -2.8590102,-2.93496 -1.1966401,-2.04365 -1.2300401,-4.07193 -1.2300401,-4.07193 l -10e-4,-0.0141 v -0.0157 -11.69637 l 1.96e-4,-0.0142 0.0035,-0.008 c 0.05128,0.0487 0.18806,0.16304 0.24192,0.20765 l 5.4247013,4.54513 5.51504,4.57144 c 0.65194,0.53945 2.27903,0.53945 2.93097,0 l 5.51504,-4.57144 5.487378,-4.57351 c 0.0199,-0.0164 0.04083,-0.0331 0.06003,-0.048 l 0.02765,-0.0215 z" /><path
|
||||
id="path1-2-1-9-9"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient816-3);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 2.3483903,-36.677919 c -0.1292796,0.384121 -0.2004748,0.792004 -0.2005045,1.214913 v 15.550989 c -4e-5,0.005 -0.029506,3.84363 2.172994,7.60522 2.20396,3.7640196 7.0383952,7.3824946 14.4528652,7.3824946 h 4.580598 c 5.433936,0 9.490679,-1.96731 12.151197,-4.504117 -1.517177,-1.2182566 -3.218664,-2.5953336 -5.013648,-4.0550496 -0.258984,0.201994 -0.541601,0.394146 -0.850594,0.570508 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -1.45833,-0.8249 -2.313817,-2.003692 -2.8592572,-2.935222 -1.1966406,-2.04365 -1.2298996,-4.071586 -1.2298996,-4.071586 l -0.00103,-0.01395 v -0.01602 -11.696444 -0.01395 l 0.00362,-0.0083 c 0.05128,0.0487 0.1879857,0.16313 0.2418457,0.20774 l 5.4249921,4.545459 5.514909,4.571297 c 0.0844,0.06984 0.185312,0.130661 0.298173,0.182418 -5.07081,-4.173874 -9.542211,-7.882397 -11.4706242,-9.500169 -2.2977093,-1.895939 -4.3807977,-3.616666 -6.1055705,-5.042586 z" /><path
|
||||
id="path1-2-1-7-3"
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#radialGradient815-9);fill-opacity:1;stroke-width:0.778856;stroke-linecap:round;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 39.930076,-36.730629 c -1.718284,1.420541 -3.7895,3.131915 -6.072498,5.015715 -1.93499,1.62329 -6.430112,5.351263 -11.522294,9.54257 0.08232,-0.04302 0.156783,-0.0915 0.221692,-0.145211 l 5.514909,-4.571297 5.48752,-4.573881 c 0.0199,-0.0164 0.04075,-0.03316 0.05994,-0.04806 l 0.0279,-0.02119 0.222726,-0.190169 v 11.598258 0.02894 c 0,0 -0.06656,2.081964 -1.311548,4.183724 -0.57202,0.96572 -1.445392,2.157957 -2.917652,2.998267 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -0.308682,-0.174605 -0.590588,-0.364744 -0.848011,-0.565341 -1.8055532,1.467965 -3.5132252,2.849693 -5.0307004,4.0674516 2.6425302,2.518552 6.6894504,4.460193 12.1212244,4.460193 h 4.580599 c 7.4177,0 12.269411,-3.665883 14.506091,-7.4419226 2.23668,-3.77604 2.270146,-7.635193 2.270146,-7.635193 l 0.0021,-0.03411 v -15.485354 c 1.06e-4,-0.420906 -0.07162,-0.827177 -0.202078,-1.209745 z" /></g></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |