diff --git a/doc/markdown.md b/doc/markdown.md
index eb96d0bb..23880877 100644
--- a/doc/markdown.md
+++ b/doc/markdown.md
@@ -13,6 +13,8 @@ such as [quick reference](/doc/quickReference.md).
 
 `[R]` means required; `[O]` means optional.
 
+**These syntaxes must not nest with general markdown syntaxes.
+
 ### Text coloring
 
 ```
diff --git a/pages/[lang]/thanks.tsx b/pages/[lang]/thanks.tsx
index 4bbba801..c2e644a0 100644
--- a/pages/[lang]/thanks.tsx
+++ b/pages/[lang]/thanks.tsx
@@ -17,24 +17,33 @@ const SpecialThanks = () => {
         </a>
       </h3>
       <ul>
+        <li>
+          桜井みゆき&nbsp;
+          <Badge variant="primary">{t((t) => t.donation.tierSSS)}</Badge>&nbsp;
+          <Badge variant="orange">{t((t) => t.misc.omGroup)}</Badge>
+        </li>
+        <li>
+          Yu&nbsp;
+          <Badge variant="primary">{t((t) => t.donation.tierSSS)}</Badge>
+        </li>
         <li>
           Andy&nbsp;
-          <Badge variant="info">{t((t) => t.donation.tierS2)}</Badge>&nbsp;
+          <Badge variant="secondary">{t((t) => t.donation.tierS2)}</Badge>&nbsp;
           <Badge variant="orange">{t((t) => t.misc.omMember)}</Badge>
         </li>
         <li>
           Ellie&nbsp;
-          <Badge variant="info">{t((t) => t.donation.tierS2)}</Badge>&nbsp;
+          <Badge variant="secondary">{t((t) => t.donation.tierS2)}</Badge>&nbsp;
           <Badge variant="orange">{t((t) => t.misc.omMember)}</Badge>
         </li>
         <li>
-          Piglet&nbsp;/&nbsp;ピグレット
-          <Badge variant="info">{t((t) => t.donation.tierS1)}</Badge>&nbsp;
-          <Badge variant="orange">{t((t) => t.misc.omGroup)}</Badge>
+          皮皮熊艹&nbsp;
+          <Badge variant="secondary">{t((t) => t.donation.tierS2)}</Badge>&nbsp;
+          <Badge variant="orange">{t((t) => t.misc.omMember)}</Badge>
         </li>
         <li>
-          皮皮熊艹&nbsp;
-          <Badge variant="info">{t((t) => t.donation.tierS2)}</Badge>&nbsp;
+          Piglet&nbsp;/&nbsp;ピグレット
+          <Badge variant="info">{t((t) => t.donation.tierS1)}</Badge>&nbsp;
           <Badge variant="orange">{t((t) => t.misc.omMember)}</Badge>
         </li>
         <li>
diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index f4a23893..00000000
Binary files a/public/favicon.ico and /dev/null differ
diff --git a/public/logo192.png b/public/logo192.png
index 2cfcb283..4ada50cd 100644
Binary files a/public/logo192.png and b/public/logo192.png differ
diff --git a/public/logo512.png b/public/logo512.png
index c645f47f..959d78bf 100644
Binary files a/public/logo512.png and b/public/logo512.png differ
diff --git a/public/manifest.json b/public/manifest.json
index 38a3e139..e3cbc77d 100644
--- a/public/manifest.json
+++ b/public/manifest.json
@@ -2,11 +2,6 @@
   "short_name": "龍絆攻略站 by OM",
   "name": "龍絆攻略站 by OM",
   "icons": [
-    {
-      "src": "favicon.ico",
-      "sizes": "256x256 128x128 64x64 32x32 24x24 16x16",
-      "type": "image/x-icon"
-    },
     {
       "src": "logo192.png",
       "type": "image/png",
diff --git a/src/api-def b/src/api-def
index 8923d4c4..ac49f64d 160000
--- a/src/api-def
+++ b/src/api-def
@@ -1 +1 @@
-Subproject commit 8923d4c4a6335c82a687e0c3dc22a2af5c9bf915
+Subproject commit ac49f64dc3a1e2231d2d3561c4acb01b794a9a95
diff --git a/src/components/elements/gameData/skillAtk/hooks/preset.test.ts b/src/components/elements/gameData/skillAtk/hooks/preset.test.ts
index 40a16431..977065d7 100644
--- a/src/components/elements/gameData/skillAtk/hooks/preset.test.ts
+++ b/src/components/elements/gameData/skillAtk/hooks/preset.test.ts
@@ -113,35 +113,4 @@ describe('Input preset hook', () => {
     await waitFor(() => expect(result.current.getPresetStatus.fetched).toBeTruthy());
     expect(gaEvent).toHaveBeenCalledTimes(1);
   });
-
-  it('does not have 2 preset IDs in the new link if created twice', async () => {
-    jest.spyOn(ApiRequestSender, 'getPresetAtkSkill').mockResolvedValue({
-      code: ApiResponseCode.SUCCESS,
-      success: true,
-      preset: {a: true},
-    });
-    jest.spyOn(ApiRequestSender, 'setPresetAtkSkill').mockResolvedValue({
-      code: ApiResponseCode.SUCCESS,
-      success: true,
-      presetId: 'preset2',
-    });
-    // @ts-ignore
-    delete window.location;
-    // @ts-ignore
-    window.location = {
-      href: `http://localhost/?${PRESET_QUERY_NAME}=preset`,
-    };
-
-    const {result} = renderReactHook(
-      () => useAtkSkillInput(fnOnNotLoggedIn),
-      {
-        hasSession: true,
-        routerOptions: {query: {[PRESET_QUERY_NAME]: 'preset'}},
-      },
-    );
-
-    result.current.makePreset();
-    await waitFor(() => expect(result.current.makePresetLink).not.toBeNull());
-    expect(new URL(result.current.makePresetLink || '').searchParams.getAll(PRESET_QUERY_NAME).length).toBe(1);
-  });
 });
diff --git a/src/components/elements/gameData/skillAtk/hooks/preset.ts b/src/components/elements/gameData/skillAtk/hooks/preset.ts
index 79cd1913..c4860eed 100644
--- a/src/components/elements/gameData/skillAtk/hooks/preset.ts
+++ b/src/components/elements/gameData/skillAtk/hooks/preset.ts
@@ -4,7 +4,7 @@ import {AppReactContext} from '../../../../../context/app/main';
 import {useNextRouter} from '../../../../../utils/router';
 import {ApiRequestSender} from '../../../../../utils/services/api/requestSender';
 import {GoogleAnalytics} from '../../../../../utils/services/ga';
-import {FetchStatus, FetchStatusSimple, isNotFetched} from '../../../common/fetch';
+import {FetchStatusSimple, isNotFetched} from '../../../common/fetch';
 import {InputPanelCommonProps} from '../../../input/types';
 import {InputData} from '../in/types';
 import {generateInputData, overwriteInputData} from '../in/utils/inputData';
@@ -14,14 +14,12 @@ export const PRESET_QUERY_NAME = 'preset';
 
 type UseAtkSkillInputReturn = InputPanelCommonProps<InputData> & {
   getPresetStatus: FetchStatusSimple,
-  makePresetLink: string | null,
-  makePreset: () => void,
 }
 
 // This input data is expect to change frequently.
 // Therefore, it should not be used in expensive component, such as ATK skill output,
 // because every change triggers a re-render.
-export const useAtkSkillInput = (onNotLoggedIn?: () => void): UseAtkSkillInputReturn => {
+export const useAtkSkillInput = (onNotLoggedIn: () => void): UseAtkSkillInputReturn => {
   const {query} = useNextRouter();
   const presetId = query[PRESET_QUERY_NAME];
 
@@ -32,38 +30,6 @@ export const useAtkSkillInput = (onNotLoggedIn?: () => void): UseAtkSkillInputRe
     fetched: !presetId,
     fetching: false,
   });
-  const [makePresetStatus, setMakePresetStatus] = React.useState<FetchStatus<string | null>>({
-    fetched: false,
-    fetching: false,
-    data: null,
-  });
-
-  const onNotLoggedInInternal = () => {
-    if (onNotLoggedIn) {
-      onNotLoggedIn();
-    } else {
-      console.error('User not logged in, action prohibited.');
-    }
-  };
-
-  const makePreset = () => {
-    setMakePresetStatus({...makePresetStatus, fetched: false, fetching: true});
-    if (context?.session) {
-      ApiRequestSender.setPresetAtkSkill(context.session.user.id.toString(), inputData)
-        .then((response) => {
-          const link = new URL(window.location.href);
-          link.searchParams.set(PRESET_QUERY_NAME, response.presetId);
-
-          setMakePresetStatus({fetched: true, fetching: false, data: link.href});
-        })
-        .catch(() => {
-          setMakePresetStatus({fetched: true, fetching: false, data: null});
-        });
-    } else {
-      onNotLoggedInInternal();
-      setMakePresetStatus({...makePresetStatus, fetched: true, fetching: false});
-    }
-  };
 
   if (isNotFetched(getPresetStatus)) {
     if (context?.session) {
@@ -79,15 +45,9 @@ export const useAtkSkillInput = (onNotLoggedIn?: () => void): UseAtkSkillInputRe
       GoogleAnalytics.presetLoaded('atkSkill');
     } else {
       setGetPresetStatus({...getPresetStatus, fetched: true, fetching: false});
-      onNotLoggedInInternal();
+      onNotLoggedIn();
     }
   }
 
-  return {
-    inputData,
-    setInputData,
-    getPresetStatus: getPresetStatus,
-    makePresetLink: makePresetStatus.data,
-    makePreset,
-  };
+  return {inputData, setInputData, getPresetStatus};
 };
diff --git a/src/components/elements/gameData/skillAtk/in/limit.tsx b/src/components/elements/gameData/skillAtk/in/display.tsx
similarity index 100%
rename from src/components/elements/gameData/skillAtk/in/limit.tsx
rename to src/components/elements/gameData/skillAtk/in/display.tsx
diff --git a/src/components/elements/gameData/skillAtk/in/main.tsx b/src/components/elements/gameData/skillAtk/in/main.tsx
index 1e40e539..633b1bc1 100644
--- a/src/components/elements/gameData/skillAtk/in/main.tsx
+++ b/src/components/elements/gameData/skillAtk/in/main.tsx
@@ -9,8 +9,8 @@ import {ResourceLoader} from '../../../../../utils/services/resources/loader';
 import {useFetchState} from '../../../common/fetch';
 import {CommonModal, ModalState} from '../../../common/modal';
 import {useAtkSkillInput} from '../hooks/preset';
+import {DisplayItemPicker} from './display';
 import {Filter} from './filter';
-import {DisplayItemPicker} from './limit';
 import {InputParameters} from './params';
 import {InputData} from './types';
 import {validateInputData} from './utils/inputData';
@@ -30,9 +30,13 @@ export const AttackingSkillInput = ({isAllFetched, onSearchRequested}: InputProp
     title: '',
     message: '',
   });
-  const {inputData, setInputData, getPresetStatus} = useAtkSkillInput();
-  const isSearchAllowed = isAllFetched && getPresetStatus.fetched;
-
+  const {inputData, setInputData, getPresetStatus} = useAtkSkillInput(() => {
+    setModalState({
+      title: 'Error',
+      show: true,
+      message: t((t) => t.game.skillAtk.error.presetMustLogin),
+    });
+  });
   const {fetchStatus: conditionEnums, fetchFunction: fetchConditionEnums} = useFetchState<CategorizedConditionEnums>(
     {afflictions: [], elements: []},
     ResourceLoader.getEnumCategorizedConditions,
@@ -44,6 +48,8 @@ export const AttackingSkillInput = ({isAllFetched, onSearchRequested}: InputProp
     'Failed to fetch the element enums.',
   );
 
+  const isSearchAllowed = isAllFetched && getPresetStatus.fetched;
+
   fetchConditionEnums();
   fetchElemEnums();
 
diff --git a/src/components/elements/gameData/skillAtk/in/utils/calculate.ts b/src/components/elements/gameData/skillAtk/in/utils/calculate.ts
index df77ba7f..a6c0e5e0 100644
--- a/src/components/elements/gameData/skillAtk/in/utils/calculate.ts
+++ b/src/components/elements/gameData/skillAtk/in/utils/calculate.ts
@@ -1,7 +1,7 @@
 import {ElementBonusData} from '../../../../../../api-def/resources';
 import {UseFetchEnumsReturn} from '../../hooks/enums';
 import {CalculatedSkillEntry} from '../../out/types';
-import {calculateEntries, filterSkillEntries} from '../../out/utils';
+import {calculateEntries, filterSkillEntries} from '../../out/utils/entries';
 import {InputData} from '../types';
 
 
diff --git a/src/components/elements/gameData/skillAtk/main.test.tsx b/src/components/elements/gameData/skillAtk/main.test.tsx
index e722aaaa..186049e7 100644
--- a/src/components/elements/gameData/skillAtk/main.test.tsx
+++ b/src/components/elements/gameData/skillAtk/main.test.tsx
@@ -319,10 +319,75 @@ describe('ATK skill lookup', () => {
     userEvent.click(searchButton);
 
     await waitForEntryProcessed();
-    screen.debug(undefined, 100000);
     // Actual damage from Wedding Aoi
     expect(await screen.findByText('417,497')).toBeInTheDocument();
   });
 
-  it.todo('reset preset status on input changed then re-search');
+  it('makes correct input preset', async () => {
+    const fnMakePreset = jest.spyOn(ApiRequestSender, 'setPresetAtkSkill').mockResolvedValue({
+      code: ApiResponseCode.SUCCESS,
+      success: true,
+      presetId: 'abc',
+    });
+
+    renderReact(
+      () => <AttackingSkillLookup/>,
+      {hasSession: true},
+    );
+
+    const displayActualDamageBtn = await screen.findByText(translationEN.game.skillAtk.display.options.actualDamage);
+    userEvent.click(displayActualDamageBtn);
+    await waitFor(() => expect(displayActualDamageBtn.parentNode).toHaveClass('active'));
+
+    const searchButton = await screen.findByText(
+      translationEN.misc.search,
+      {selector: 'button:enabled'},
+      {timeout: 2000},
+    );
+    userEvent.click(searchButton);
+
+    await waitForEntryProcessed();
+
+    const shareButton = screen.getByText('', {selector: 'i.bi-share-fill'});
+    userEvent.click(shareButton);
+
+    expect(fnMakePreset.mock.calls[0][1].display.actualDamage).toBe(true);
+  });
+
+  it('reset preset status on input changed then re-search', async () => {
+    jest.spyOn(ApiRequestSender, 'setPresetAtkSkill').mockResolvedValue({
+      code: ApiResponseCode.SUCCESS,
+      success: true,
+      presetId: 'abc',
+    });
+
+    renderReact(
+      () => <AttackingSkillLookup/>,
+      {hasSession: true},
+    );
+
+    const displayActualDamageBtn = await screen.findByText(translationEN.game.skillAtk.display.options.actualDamage);
+    userEvent.click(displayActualDamageBtn);
+    await waitFor(() => expect(displayActualDamageBtn.parentNode).toHaveClass('active'));
+
+    const searchButton = await screen.findByText(
+      translationEN.misc.search,
+      {selector: 'button:enabled'},
+      {timeout: 2000},
+    );
+    userEvent.click(searchButton);
+
+    await waitForEntryProcessed();
+
+    const shareButton = screen.getByText('', {selector: 'i.bi-share-fill'});
+    userEvent.click(shareButton);
+    expect(shareButton).not.toBeInTheDocument();
+
+    userEvent.click(displayActualDamageBtn);
+    await waitFor(() => expect(displayActualDamageBtn.parentNode).not.toHaveClass('active'));
+
+    userEvent.click(searchButton);
+
+    expect(screen.getByText('', {selector: 'i.bi-share-fill'})).toBeInTheDocument();
+  });
 });
diff --git a/src/components/elements/gameData/skillAtk/main.tsx b/src/components/elements/gameData/skillAtk/main.tsx
index 34cd3948..bc792277 100644
--- a/src/components/elements/gameData/skillAtk/main.tsx
+++ b/src/components/elements/gameData/skillAtk/main.tsx
@@ -6,7 +6,6 @@ import Row from 'react-bootstrap/Row';
 
 import {scrollRefToTop} from '../../../../utils/scroll';
 import {GoogleAnalytics} from '../../../../utils/services/ga';
-import {CommonModal, ModalState} from '../../common/modal';
 import {useFetchEnums} from './hooks/enums';
 import {AttackingSkillInput} from './in/main';
 import {InputData} from './in/types';
@@ -18,16 +17,17 @@ import {AttackingSkillPreset} from './preset/main';
 import {AttackingSkillSorter} from './sorter/main';
 
 
+type State = {
+  inputData: InputData,
+  calculatedEntries?: Array<CalculatedSkillEntry>,
+}
+
 export const AttackingSkillLookup = () => {
   // Having this reduces state updates when changing input.
   // Frequent update in this component is not ideal because rendering output is expensive.
-  const [inputDataForward, setInputDataForward] = React.useState(generateInputData());
-  const [modalState, setModalState] = React.useState<ModalState>({
-    show: false,
-    title: '',
-    message: '',
+  const [inputDataForward, setInputDataForward] = React.useState<State>({
+    inputData: generateInputData(),
   });
-  const [calculatedEntries, setCalculatedEntries] = React.useState<Array<CalculatedSkillEntry> | undefined>();
 
   const entryCol = React.useRef<HTMLDivElement>(null);
 
@@ -42,15 +42,14 @@ export const AttackingSkillLookup = () => {
   } = useFetchEnums();
 
   React.useEffect(() => {
-    if (!calculatedEntries) {
+    if (!inputDataForward.calculatedEntries) {
       return;
     }
     scrollRefToTop(entryCol);
-  }, [calculatedEntries]);
+  }, [inputDataForward.calculatedEntries]);
 
   return (
     <>
-      <CommonModal modalState={modalState} setModalState={setModalState}/>
       <Row>
         <Col lg={4} className="rounded bg-black-32 p-3 mb-3">
           <AttackingSkillInput
@@ -58,27 +57,36 @@ export const AttackingSkillLookup = () => {
             onSearchRequested={(inputData: InputData) => {
               GoogleAnalytics.damageCalc('search', inputData);
 
-              setCalculatedEntries(getCalculatedEntries(inputData, attackingSkillEntries, elementBonuses));
-              setInputDataForward(inputData);
+              setInputDataForward({
+                inputData,
+                calculatedEntries: getCalculatedEntries(inputData, attackingSkillEntries, elementBonuses),
+              });
             }}
           />
         </Col>
         <Col ref={entryCol} lg={8} className="px-0 px-lg-3">
           <Form.Row className="text-right mb-1">
             <Col>
-              <AttackingSkillPreset isEnabled={!!calculatedEntries?.length}/>
+              <AttackingSkillPreset
+                inputData={inputDataForward.inputData}
+                isEnabled={!!inputDataForward.calculatedEntries?.length}
+              />
             </Col>
             <Col xs="auto">
               <AttackingSkillSorter
+                inputData={inputDataForward.inputData}
                 onOrderPicked={(newInputData) => {
-                  setCalculatedEntries(getCalculatedEntries(newInputData, attackingSkillEntries, elementBonuses));
+                  setInputDataForward({
+                    inputData: newInputData,
+                    calculatedEntries: getCalculatedEntries(newInputData, attackingSkillEntries, elementBonuses),
+                  });
                 }}
               />
             </Col>
           </Form.Row>
           <AttackingSkillOutput
-            displayConfig={inputDataForward.display}
-            calculatedEntries={calculatedEntries || []}
+            displayConfig={inputDataForward.inputData.display}
+            calculatedEntries={inputDataForward.calculatedEntries || []}
             conditionEnumMap={conditionEnumMap}
             skillIdentifierInfo={skillIdentifierInfo}
             skillEnums={skillEnums}
diff --git a/src/components/elements/gameData/skillAtk/out/sections/animation.tsx b/src/components/elements/gameData/skillAtk/out/sections/animation.tsx
index 0f3a9fa1..8efcbb94 100644
--- a/src/components/elements/gameData/skillAtk/out/sections/animation.tsx
+++ b/src/components/elements/gameData/skillAtk/out/sections/animation.tsx
@@ -28,10 +28,12 @@ const HitTiming = ({atkSkillEntry}: SectionProps) => {
       <Collapse in={show}>
         <div className="mt-2">
           <table>
-            <tr>
-              <th>#</th>
-              <th>{t((t) => t.game.skillAtk.animation.hitTimingHeader)}</th>
-            </tr>
+            <thead>
+              <tr>
+                <th>#</th>
+                <th>{t((t) => t.game.skillAtk.animation.hitTimingHeader)}</th>
+              </tr>
+            </thead>
             <tbody>
               {
                 atkSkillEntry.skill.hitTimingSecMax.map((timing, idx) => (
@@ -76,14 +78,16 @@ const CancelAction = ({atkSkillEntry, skillEnums, conditionEnumMap}: SectionAnim
       <Collapse in={show}>
         <div className="mt-2">
           <table>
-            <tr>
-              <th>{t((t) => t.game.skillAtk.animation.cancelHeader.action)}</th>
-              <th>{t((t) => t.game.skillAtk.animation.cancelHeader.time)}</th>
-              {
-                isAnyCancelHasPreconditions &&
-                <th>{t((t) => t.game.skillAtk.animation.cancelHeader.preConditions)}</th>
-              }
-            </tr>
+            <thead>
+              <tr>
+                <th>{t((t) => t.game.skillAtk.animation.cancelHeader.action)}</th>
+                <th>{t((t) => t.game.skillAtk.animation.cancelHeader.time)}</th>
+                {
+                  isAnyCancelHasPreconditions &&
+                  <th>{t((t) => t.game.skillAtk.animation.cancelHeader.preConditions)}</th>
+                }
+              </tr>
+            </thead>
             <tbody>
               {
                 atkSkillEntry.skill.cancelActionsMax.map((cancelUnit, idx) => (
diff --git a/src/components/elements/gameData/skillAtk/out/sections/sp/efficiency.tsx b/src/components/elements/gameData/skillAtk/out/sections/sp/efficiency.tsx
index bdfb8ed8..92380921 100644
--- a/src/components/elements/gameData/skillAtk/out/sections/sp/efficiency.tsx
+++ b/src/components/elements/gameData/skillAtk/out/sections/sp/efficiency.tsx
@@ -73,15 +73,25 @@ export const SpEfficiencyTable = ({calculatedData, statusEnums}: SectionSpInfoPr
                   <td>{calculatedData.efficiency.modPctPer1KSsp.toFixed(2)}%</td>
                 </tr>
               }
+              {
+                calculatedData.skillEntry.skill.spGradualPctMax > 0 &&
+                <tr>
+                  <td>{t((t) => t.game.skillAtk.spInfo.spPctPerSec)}</td>
+                  <td>{calculatedData.skillEntry.skill.spGradualPctMax.toFixed(2)}%</td>
+                </tr>
+              }
               {
                 !!calculatedData.skillEntry.skill.afflictions.length &&
                 <>
-                  <tr>
-                    <td>{t((t) => t.game.skillAtk.spInfo.efficiency.secPer1KSp)}</td>
-                    <td>
-                      <AfflictionDataCell statusEnums={statusEnums} data={calculatedData.efficiency.secPer1KSp}/>
-                    </td>
-                  </tr>
+                  {
+                    !calculatedData.skillEntry.skill.spGradualPctMax &&
+                    <tr>
+                      <td>{t((t) => t.game.skillAtk.spInfo.efficiency.secPer1KSp)}</td>
+                      <td>
+                        <AfflictionDataCell statusEnums={statusEnums} data={calculatedData.efficiency.secPer1KSp}/>
+                      </td>
+                    </tr>
+                  }
                   {
                     calculatedData.skillEntry.skill.sharable &&
                     <tr>
diff --git a/src/components/elements/gameData/skillAtk/out/sections/sp/info.tsx b/src/components/elements/gameData/skillAtk/out/sections/sp/info.tsx
index 82541a1a..53b3e4fc 100644
--- a/src/components/elements/gameData/skillAtk/out/sections/sp/info.tsx
+++ b/src/components/elements/gameData/skillAtk/out/sections/sp/info.tsx
@@ -8,6 +8,8 @@ import {SectionSpInfoProps} from './main';
 export const SpInfoTable = ({calculatedData}: Pick<SectionSpInfoProps, 'calculatedData'>) => {
   const {t} = useI18n();
 
+  const sp = calculatedData.skillEntry.skill.spMax.toFixed(0);
+
   return (
     <div className="mt-2">
       <table>
@@ -18,7 +20,14 @@ export const SpInfoTable = ({calculatedData}: Pick<SectionSpInfoProps, 'calculat
             <td className={styles.ssCost}>{t((t) => t.game.skillAtk.spInfo.ssCost)}</td>
           </tr>
           <tr>
-            <td className={styles.sp}>{calculatedData.skillEntry.skill.spMax.toFixed(0)}</td>
+            <td className={styles.sp}>{
+              calculatedData.skillEntry.skill.spGradualPctMax ?
+                t(
+                  (t) => t.game.skillAtk.spInfo.spGradualFill,
+                  {secs: calculatedData.efficiency.spFullFillSec.toFixed(1), sp},
+                ) :
+                sp
+            }</td>
             <td className={styles.ssp}>{
               calculatedData.skillEntry.skill.sharable ?
                 calculatedData.skillEntry.skill.ssSp.toFixed(0) :
diff --git a/src/components/elements/gameData/skillAtk/out/sections/sp/main.test.tsx b/src/components/elements/gameData/skillAtk/out/sections/sp/main.test.tsx
index 90751b5c..a20a5422 100644
--- a/src/components/elements/gameData/skillAtk/out/sections/sp/main.test.tsx
+++ b/src/components/elements/gameData/skillAtk/out/sections/sp/main.test.tsx
@@ -193,4 +193,115 @@ describe('SP info section', () => {
     expect(screen.getByText(translationEN.game.skillAtk.spInfo.efficiency.secPer1KSp)).toBeInTheDocument();
     expect(screen.getByText(translationEN.game.skillAtk.spInfo.efficiency.secPer1KSsp)).toBeInTheDocument();
   });
+
+  it('hides affliction efficiency if the skill gradually fills', async () => {
+    const calculatedData: CalculatedSkillEntry = {
+      ...calculatedDataTemplate,
+      skillEntry: {
+        ...calculatedDataTemplate.skillEntry,
+        skill: {
+          ...calculatedDataTemplate.skillEntry.skill,
+          spGradualPctMax: 2.5,
+        },
+      },
+      efficiency: {
+        ...calculatedDataTemplate.efficiency,
+        spFullFillSec: 40,
+      },
+    };
+
+    renderReact(() => (
+      <SectionSpInfo
+        calculatedData={calculatedData}
+        statusEnums={statusEnums}
+      />
+    ));
+
+    const efficiencyButton = screen.getByText(
+      translationEN.game.skillAtk.spInfo.efficiencyIndexes,
+      {selector: 'button'},
+    );
+    userEvent.click(efficiencyButton);
+
+    await waitFor(() => expect(screen.getByText(
+      translationEN.game.skillAtk.spInfo.efficiency.modPctPer1KSp,
+      {selector: '.collapse.show *'},
+    )).toBeInTheDocument());
+    expect(screen.queryByText(translationEN.game.skillAtk.spInfo.efficiency.secPer1KSp)).not.toBeInTheDocument();
+  });
+
+  it('shows SP % per second if the skill gradually fills', async () => {
+    const calculatedData: CalculatedSkillEntry = {
+      ...calculatedDataTemplate,
+      skillEntry: {
+        ...calculatedDataTemplate.skillEntry,
+        skill: {
+          ...calculatedDataTemplate.skillEntry.skill,
+          spMax: 8000,
+          spGradualPctMax: 2.5,
+        },
+      },
+      efficiency: {
+        ...calculatedDataTemplate.efficiency,
+        spFullFillSec: 40,
+      },
+    };
+
+    renderReact(() => (
+      <SectionSpInfo
+        calculatedData={calculatedData}
+        statusEnums={statusEnums}
+      />
+    ));
+    expect(screen.getByText('40.0 secs (8000)')).toBeInTheDocument();
+
+    const efficiencyButton = screen.getByText(
+      translationEN.game.skillAtk.spInfo.efficiencyIndexes,
+      {selector: 'button'},
+    );
+    userEvent.click(efficiencyButton);
+
+    await waitFor(() => expect(screen.getByText(
+      translationEN.game.skillAtk.spInfo.efficiency.modPctPer1KSp,
+      {selector: '.collapse.show *'},
+    )).toBeInTheDocument());
+    expect(screen.getByText(translationEN.game.skillAtk.spInfo.spPctPerSec)).toBeInTheDocument();
+    expect(screen.getByText('2.50%')).toBeInTheDocument();
+  });
+
+  it('does not show SP % per second if the skill is SP-based', async () => {
+    const calculatedData: CalculatedSkillEntry = {
+      ...calculatedDataTemplate,
+      skillEntry: {
+        ...calculatedDataTemplate.skillEntry,
+        skill: {
+          ...calculatedDataTemplate.skillEntry.skill,
+          spMax: 8000,
+        },
+      },
+      efficiency: {
+        ...calculatedDataTemplate.efficiency,
+        spFullFillSec: 0,
+      },
+    };
+
+    renderReact(() => (
+      <SectionSpInfo
+        calculatedData={calculatedData}
+        statusEnums={statusEnums}
+      />
+    ));
+
+    const efficiencyButton = screen.getByText(
+      translationEN.game.skillAtk.spInfo.efficiencyIndexes,
+      {selector: 'button'},
+    );
+    userEvent.click(efficiencyButton);
+
+    await waitFor(() => expect(screen.getByText(
+      translationEN.game.skillAtk.spInfo.efficiency.modPctPer1KSp,
+      {selector: '.collapse.show *'},
+    )).toBeInTheDocument());
+    expect(screen.queryByText(translationEN.game.skillAtk.spInfo.spPctPerSec)).not.toBeInTheDocument();
+  });
 });
diff --git a/src/components/elements/gameData/skillAtk/out/types.ts b/src/components/elements/gameData/skillAtk/out/types.ts
index 6710d933..c94a7920 100644
--- a/src/components/elements/gameData/skillAtk/out/types.ts
+++ b/src/components/elements/gameData/skillAtk/out/types.ts
@@ -7,6 +7,7 @@ export type Efficiency = {
   modPctPer1KSsp: number,
   secPer1KSp: {[StatusCode in number]: number},
   secPer1KSsp: {[StatusCode in number]: number},
+  spFullFillSec: number,
 }
 
 export type CalculatedSkillEntry = {
diff --git a/src/components/elements/gameData/skillAtk/out/utils/calc.ts b/src/components/elements/gameData/skillAtk/out/utils/calc.ts
new file mode 100644
index 00000000..eb9f0e76
--- /dev/null
+++ b/src/components/elements/gameData/skillAtk/out/utils/calc.ts
@@ -0,0 +1,15 @@
+import {AttackingSkillData} from '../../../../../../api-def/resources';
+import {Efficiency} from '../types';
+
+
+export const calculateEfficiency = (totalSkillMods: number, entry: AttackingSkillData): Efficiency => ({
+  modPctPer1KSp: (totalSkillMods * 100) / (entry.skill.spMax / 1000),
+  modPctPer1KSsp: (totalSkillMods * 100) / (entry.skill.ssSp / 1000),
+  secPer1KSp: Object.fromEntries(entry.skill.afflictions.map((afflictionUnit) => (
+    [afflictionUnit.statusCode, afflictionUnit.duration / (entry.skill.spMax / 1000)]
+  ))),
+  secPer1KSsp: Object.fromEntries(entry.skill.afflictions.map((afflictionUnit) => (
+    [afflictionUnit.statusCode, afflictionUnit.duration / (entry.skill.ssSp / 1000)]
+  ))),
+  spFullFillSec: entry.skill.spGradualPctMax ? 100 / entry.skill.spGradualPctMax : 0,
+});
diff --git a/src/components/elements/gameData/skillAtk/out/utils.test.tsx b/src/components/elements/gameData/skillAtk/out/utils/entries.test.tsx
similarity index 86%
rename from src/components/elements/gameData/skillAtk/out/utils.test.tsx
rename to src/components/elements/gameData/skillAtk/out/utils/entries.test.tsx
index 23b3060e..5a21cacc 100644
--- a/src/components/elements/gameData/skillAtk/out/utils.test.tsx
+++ b/src/components/elements/gameData/skillAtk/out/utils/entries.test.tsx
@@ -1,9 +1,9 @@
-import {generateAttackingSkillEntry} from '../../../../../../test/data/mock/skill';
-import {AttackingSkillData, ElementBonusData} from '../../../../../api-def/resources';
-import {ResourceLoader} from '../../../../../utils/services/resources';
-import {InputData} from '../in/types';
-import {generateInputData} from '../in/utils/inputData';
-import {calculateEntries, filterSkillEntries} from './utils';
+import {generateAttackingSkillEntry} from '../../../../../../../test/data/mock/skill';
+import {AttackingSkillData, ElementBonusData} from '../../../../../../api-def/resources';
+import {ResourceLoader} from '../../../../../../utils/services/resources';
+import {InputData} from '../../in/types';
+import {generateInputData} from '../../in/utils/inputData';
+import {calculateEntries, filterSkillEntries} from './entries';
 
 
 const inputDataTemplate: InputData = generateInputData();
@@ -111,6 +111,10 @@ describe('Sort ATK skill entries', () => {
     const inputData: InputData = {
       ...inputDataTemplate,
       sortBy: 'damage',
+      display: {
+        ...inputDataTemplate.display,
+        actualDamage: true,
+      },
     };
 
     const entries = calculateEntries(data, inputData, elemBonusData)
@@ -375,4 +379,48 @@ describe('Entry calculation', () => {
     const entry = calculateEntries([dataModified], inputDataTemplate, elemBonusData)[0];
     expect(entry.efficiency.secPer1KSsp).toStrictEqual({4: 2, 5: 4});
   });
+
+  it('calculates SP full fill sec', async () => {
+    const dataModified: AttackingSkillData = {
+      ...data,
+      skill: {
+        ...data.skill,
+        spGradualPctMax: 2.5,
+      },
+    };
+
+    const entry = calculateEntries([dataModified], inputDataTemplate, elemBonusData)[0];
+    expect(entry.efficiency.spFullFillSec).toBe(40);
+  });
+
+  it('does not calculate SP full fill sec if the skill is SP-based', async () => {
+    const entry = calculateEntries([data], inputDataTemplate, elemBonusData)[0];
+    expect(entry.efficiency.spFullFillSec).toBe(0);
+  });
+
+  it('does not calculate efficiency if SP info will not display', async () => {
+    const inputData: InputData = {
+      ...inputDataTemplate,
+      display: {
+        ...inputDataTemplate.display,
+        spInfo: false,
+      },
+    };
+
+    const entry = calculateEntries([data], inputData, elemBonusData)[0];
+    expect(entry.efficiency.modPctPer1KSp).toBe(0);
+  });
+
+  it('does not calculate actual damage if actual damage will not display', async () => {
+    const inputData: InputData = {
+      ...inputDataTemplate,
+      display: {
+        ...inputDataTemplate.display,
+        actualDamage: false,
+      },
+    };
+
+    const entry = calculateEntries([data], inputData, elemBonusData)[0];
+    expect(entry.skillDamage.expected).toBe(0);
+  });
 });
diff --git a/src/components/elements/gameData/skillAtk/out/utils.ts b/src/components/elements/gameData/skillAtk/out/utils/entries.ts
similarity index 58%
rename from src/components/elements/gameData/skillAtk/out/utils.ts
rename to src/components/elements/gameData/skillAtk/out/utils/entries.ts
index d799755f..75392c1d 100644
--- a/src/components/elements/gameData/skillAtk/out/utils.ts
+++ b/src/components/elements/gameData/skillAtk/out/utils/entries.ts
@@ -1,8 +1,9 @@
-import {AttackingSkillData, ElementBonusData} from '../../../../../api-def/resources';
-import {calculateDamage} from '../../../../../utils/game/damage';
-import {InputData} from '../in/types';
-import {sortFunc} from '../sorter/lookup';
-import {CalculatedSkillEntry, Efficiency} from './types';
+import {AttackingSkillData, ElementBonusData} from '../../../../../../api-def/resources';
+import {calculateDamage} from '../../../../../../utils/game/damage';
+import {InputData} from '../../in/types';
+import {sortFunc} from '../../sorter/lookup';
+import {CalculatedSkillEntry, Efficiency} from '../types';
+import {calculateEfficiency} from './calc';
 
 
 export const filterSkillEntries = (inputData: InputData, atkSkillEntries: Array<AttackingSkillData>) => {
@@ -39,34 +40,29 @@ export const calculateEntries = (
   atkSkillEntries: Array<AttackingSkillData>, inputData: InputData, elemBonusData: ElementBonusData,
 ): Array<CalculatedSkillEntry> => {
   return atkSkillEntries
-    .map((entry: AttackingSkillData) => {
+    .map((skillEntry: AttackingSkillData) => {
       // Element bonus rate
       const charaElementRate = elemBonusData.getElementBonus(
-        String(entry.chara.element),
+        String(skillEntry.chara.element),
         String(inputData.target.elemCondCode),
       );
 
       // Calculate skill damage
-      const skillDamage = calculateDamage(inputData, entry, charaElementRate);
+      const skillDamage = calculateDamage(inputData, skillEntry, charaElementRate);
 
       // Calculate efficiency
-      const efficiency: Efficiency = {
-        modPctPer1KSp: (skillDamage.totalMods * 100) / (entry.skill.spMax / 1000),
-        modPctPer1KSsp: (skillDamage.totalMods * 100) / (entry.skill.ssSp / 1000),
-        secPer1KSp: Object.fromEntries(entry.skill.afflictions.map((afflictionUnit) => (
-          [afflictionUnit.statusCode, afflictionUnit.duration / (entry.skill.spMax / 1000)]
-        ))),
-        secPer1KSsp: Object.fromEntries(entry.skill.afflictions.map((afflictionUnit) => (
-          [afflictionUnit.statusCode, afflictionUnit.duration / (entry.skill.ssSp / 1000)]
-        ))),
-      };
+      const efficiency: Efficiency = inputData.display.spInfo ?
+        calculateEfficiency(skillDamage.totalMods, skillEntry) :
+        {
+          modPctPer1KSp: 0,
+          modPctPer1KSsp: 0,
+          secPer1KSp: {},
+          secPer1KSsp: {},
+          spFullFillSec: 0,
+        };
 
-      return {
-        skillDamage,
-        skillEntry: entry,
-        efficiency,
-      };
+      return {skillDamage, skillEntry, efficiency};
     })
-    .filter((calcData) => calcData.skillDamage.expected > 0)
+    .filter((calcData) => calcData.skillDamage.totalMods > 0)
     .sort(sortFunc[inputData.sortBy]);
 };
diff --git a/src/components/elements/gameData/skillAtk/preset/main.test.tsx b/src/components/elements/gameData/skillAtk/preset/main.test.tsx
index b030cddf..f4defc66 100644
--- a/src/components/elements/gameData/skillAtk/preset/main.test.tsx
+++ b/src/components/elements/gameData/skillAtk/preset/main.test.tsx
@@ -6,6 +6,8 @@ import userEvent from '@testing-library/user-event';
 import {renderReact} from '../../../../../../test/render/main';
 import {ApiResponseCode} from '../../../../../api-def/api';
 import {ApiRequestSender} from '../../../../../utils/services/api/requestSender';
+import {PRESET_QUERY_NAME} from '../hooks/preset';
+import {generateInputData} from '../in/utils/inputData';
 import {AttackingSkillPreset} from './main';
 
 
@@ -14,6 +16,9 @@ describe('ATK skill input preset manager', () => {
 
   beforeEach(() => {
     window.location.href = 'http://localhost:3000';
+    // @ts-ignore
+    // noinspection JSConstantReassignment
+    navigator.clipboard = {writeText: jest.fn().mockResolvedValue(void 0)};
     fnMakePreset = jest.spyOn(ApiRequestSender, 'setPresetAtkSkill').mockResolvedValue({
       code: ApiResponseCode.SUCCESS,
       success: true,
@@ -22,8 +27,9 @@ describe('ATK skill input preset manager', () => {
   });
 
   it('makes a preset on clicking share', async () => {
+    const inputData = generateInputData();
     renderReact(
-      () => <AttackingSkillPreset isEnabled/>,
+      () => <AttackingSkillPreset inputData={inputData} isEnabled/>,
       {hasSession: true},
     );
 
@@ -31,5 +37,72 @@ describe('ATK skill input preset manager', () => {
     userEvent.click(shareButton);
 
     await waitFor(() => expect(fnMakePreset).toHaveBeenCalled());
+    expect(fnMakePreset.mock.calls[0][1]).toBe(inputData);
+  });
+
+  it('shows clipboard icon and link after making a preset and copied once', async () => {
+    jest.useFakeTimers();
+
+    renderReact(
+      () => <AttackingSkillPreset inputData={generateInputData()} isEnabled/>,
+      {hasSession: true},
+    );
+
+    const shareButton = screen.getByText('', {selector: 'i.bi-share-fill'});
+    userEvent.click(shareButton);
+
+    await waitFor(() => expect(fnMakePreset).toHaveBeenCalled());
+    jest.runTimersToTime(7000);
+    await waitFor(() => expect(screen.getByText('', {selector: 'i.bi-clipboard'})).toBeInTheDocument());
+    expect(screen.getByDisplayValue(`http://localhost/?${PRESET_QUERY_NAME}=presetLink`)).toBeInTheDocument();
+
+    jest.useRealTimers();
+  });
+
+  it('copies the correct link if re-copy', async () => {
+    jest.useFakeTimers();
+
+    renderReact(
+      () => <AttackingSkillPreset inputData={generateInputData()} isEnabled/>,
+      {hasSession: true},
+    );
+
+    const shareButton = screen.getByText('', {selector: 'i.bi-share-fill'});
+    userEvent.click(shareButton);
+
+    await waitFor(() => expect(fnMakePreset).toHaveBeenCalled());
+    jest.runTimersToTime(7000);
+
+    const clipboardButton = screen.getByText('', {selector: 'i.bi-clipboard'});
+    userEvent.click(clipboardButton);
+
+    const expectedLink = `http://localhost/?${PRESET_QUERY_NAME}=presetLink`;
+    await waitFor(() => expect(navigator.clipboard.writeText).toHaveBeenLastCalledWith(expectedLink));
+
+    jest.useRealTimers();
+  });
+
+  it('does not have 2 preset IDs in the new link if created twice', async () => {
+    jest.spyOn(ApiRequestSender, 'getPresetAtkSkill').mockResolvedValue({
+      code: ApiResponseCode.SUCCESS,
+      success: true,
+      preset: {a: true},
+    });
+    window.location.href = `http://localhost/?${PRESET_QUERY_NAME}=preset`;
+
+    renderReact(
+      () => <AttackingSkillPreset inputData={generateInputData()} isEnabled/>,
+      {
+        hasSession: true,
+        routerOptions: {query: {[PRESET_QUERY_NAME]: 'preset'}},
+      },
+    );
+
+    const shareButton = screen.getByText('', {selector: 'i.bi-share-fill'});
+    userEvent.click(shareButton);
+
+    await waitFor(() => expect(navigator.clipboard.writeText).toHaveBeenCalled());
+    const copiedLink = (navigator.clipboard.writeText as jest.Mock).mock.calls[0][0];
+    expect(new URL(copiedLink).searchParams.getAll(PRESET_QUERY_NAME).length).toBe(1);
   });
 });
diff --git a/src/components/elements/gameData/skillAtk/preset/main.tsx b/src/components/elements/gameData/skillAtk/preset/main.tsx
index ebc69fad..823f4001 100644
--- a/src/components/elements/gameData/skillAtk/preset/main.tsx
+++ b/src/components/elements/gameData/skillAtk/preset/main.tsx
@@ -5,10 +5,13 @@ import FormControl from 'react-bootstrap/FormControl';
 import InputGroup from 'react-bootstrap/InputGroup';
 import Spinner from 'react-bootstrap/Spinner';
 
+import {AppReactContext} from '../../../../../context/app/main';
 import {useI18n} from '../../../../../i18n/hook';
-import {CommonModal, ModalState} from '../../../common/modal';
-import {useAtkSkillInput} from '../hooks/preset';
-import {PresetStatus} from './types';
+import {ApiRequestSender} from '../../../../../utils/services/api/requestSender';
+import {CommonModal} from '../../../common/modal';
+import {PRESET_QUERY_NAME} from '../hooks/preset';
+import {InputData} from '../in/types';
+import {PresetState, PresetStatus} from './types';
 
 
 const statusButtonIcon: { [status in PresetStatus]: React.ReactElement } = {
@@ -19,69 +22,100 @@ const statusButtonIcon: { [status in PresetStatus]: React.ReactElement } = {
 };
 
 type Props = {
+  inputData: InputData,
   isEnabled: boolean,
 }
 
-export const AttackingSkillPreset = ({isEnabled}: Props) => {
+export const AttackingSkillPreset = ({inputData, isEnabled}: Props) => {
   const {t} = useI18n();
 
-  const [status, setStatus] = React.useState<PresetStatus>('notCreated');
-  const [presetLink, setPresetLink] = React.useState<string>(t((t) => t.game.skillAtk.info.preset));
-  const [modalState, setModalState] = React.useState<ModalState>({
-    show: false,
-    title: '',
-    message: '',
-  });
-  const {makePreset, makePresetLink} = useAtkSkillInput(() => {
-    setStatus('notCreated');
-    setModalState({
-      ...modalState,
-      show: true,
-      message: t((t) => t.game.skillAtk.error.presetMustLogin),
-    });
+  const context = React.useContext(AppReactContext);
+
+  const [state, setState] = React.useState<PresetState>({
+    status: 'notCreated',
+    link: t((t) => t.game.skillAtk.info.preset),
+    modal: {
+      show: false,
+      title: '',
+      message: '',
+    },
   });
 
-  const copyAndSetTimeout = (link: string) => {
-    navigator.clipboard.writeText(link).then(() => setStatus('copied'));
-    return setTimeout(() => setStatus('createdNotCopied'), 5000);
+  React.useEffect(() => {
+    setState({...state, status: 'notCreated', link: t((t) => t.game.skillAtk.info.preset)});
+  }, [inputData]);
+
+  const makePreset = () => {
+    setState({...state, status: 'creating'});
+    if (!context?.session) {
+      setState({
+        ...state,
+        status: 'notCreated',
+        modal: {
+          title: 'Error',
+          show: true,
+          message: t((t) => t.game.skillAtk.error.presetMustLogin),
+        },
+      });
+      return;
+    }
+
+    ApiRequestSender.setPresetAtkSkill(context.session.user.id.toString(), inputData)
+      .then((response) => {
+        const link = new URL(window.location.href);
+        link.searchParams.set(PRESET_QUERY_NAME, response.presetId);
+
+        copyAndSetTimeout(link.href);
+      })
+      .catch((e) => {
+        setState({
+          ...state,
+          status: 'notCreated',
+          modal: {
+            title: 'Error',
+            show: true,
+            message: e.message,
+          },
+        });
+      });
+  };
+
+  const copyAndSetTimeout = (link: string = state.link) => {
+    navigator.clipboard.writeText(link).then(() => setState({...state, status: 'copied', link}));
+    return setTimeout(() => setState({...state, status: 'createdNotCopied', link}), 5000);
   };
 
   const onClickShareButton = () => {
-    if (status === 'notCreated') {
-      setStatus('creating');
+    if (state.status === 'notCreated') {
       makePreset();
     }
-    if (status === 'createdNotCopied' && makePresetLink) {
-      const timeout: NodeJS.Timeout = copyAndSetTimeout(makePresetLink);
-      return () => clearTimeout(timeout);
+    if (state.status === 'createdNotCopied') {
+      copyAndSetTimeout();
     }
   };
 
-  if (status === 'creating' && makePresetLink) {
-    setPresetLink(makePresetLink);
-    copyAndSetTimeout(makePresetLink);
-    setStatus('copied');
-  }
-
   return (
     <>
-      <CommonModal modalState={modalState} setModalState={setModalState}/>
+      <CommonModal
+        modalState={state.modal}
+        setModalState={(modalState) => setState({...state, modal: modalState})}
+      />
       <InputGroup className="mb-2 mr-sm-2">
         <FormControl
-          className={`bg-black-32 ${status === 'copied' ? 'text-info' : 'text-light'}`} disabled
+          className={`bg-black-32 ${state.status === 'copied' ? 'text-info' : 'text-light'}`} disabled
           value={
-            status === 'copied' ?
+            state.status === 'copied' ?
               t((t) => t.game.skillAtk.info.presetExpiry) :
-              presetLink
+              state.link
           }
         />
         <InputGroup.Append>
           <Button
             className="d-flex align-items-center"
             variant="outline-light" onClick={onClickShareButton}
-            disabled={!isEnabled || status === 'creating' || status === 'copied'}
+            disabled={!isEnabled || state.status === 'creating' || state.status === 'copied'}
           >
-            {statusButtonIcon[status]}
+            {statusButtonIcon[state.status]}
           </Button>
         </InputGroup.Append>
       </InputGroup>
diff --git a/src/components/elements/gameData/skillAtk/preset/types.ts b/src/components/elements/gameData/skillAtk/preset/types.ts
index 07dcd563..260b215a 100644
--- a/src/components/elements/gameData/skillAtk/preset/types.ts
+++ b/src/components/elements/gameData/skillAtk/preset/types.ts
@@ -1,5 +1,14 @@
+import {ModalState} from '../../../common/modal';
+
+
 export type PresetStatus =
   'notCreated' |
   'creating' |
   'copied' |
   'createdNotCopied';
+
+export type PresetState = {
+  status: PresetStatus,
+  link: string,
+  modal: ModalState,
+}
diff --git a/src/components/elements/gameData/skillAtk/sorter/main.test.tsx b/src/components/elements/gameData/skillAtk/sorter/main.test.tsx
index 1cbdcfd4..36e5f9ce 100644
--- a/src/components/elements/gameData/skillAtk/sorter/main.test.tsx
+++ b/src/components/elements/gameData/skillAtk/sorter/main.test.tsx
@@ -5,6 +5,7 @@ import userEvent from '@testing-library/user-event';
 
 import {renderReact} from '../../../../../../test/render/main';
 import {translation as translationEN} from '../../../../../i18n/translations/en/translation';
+import {generateInputData} from '../in/utils/inputData';
 import {AttackingSkillSorter} from './main';
 
 
@@ -16,13 +17,23 @@ describe('ATK skill entry sorter', () => {
   });
 
   it('shows the current sort order', async () => {
-    renderReact(() => <AttackingSkillSorter onOrderPicked={onOrderPicked}/>);
-
-    expect(screen.getByText(`Order: ${translationEN.game.skillAtk.sort.damageDesc}`)).toBeInTheDocument();
+    renderReact(() => (
+      <AttackingSkillSorter
+        inputData={generateInputData({sortBy: 'sp'})}
+        onOrderPicked={onOrderPicked}
+      />
+    ));
+
+    expect(screen.getByText(`Order: ${translationEN.game.skillAtk.sort.sp}`)).toBeInTheDocument();
   });
 
   it('dispatches on order picked event', async () => {
-    renderReact(() => <AttackingSkillSorter onOrderPicked={onOrderPicked}/>);
+    renderReact(() => (
+      <AttackingSkillSorter
+        inputData={generateInputData()}
+        onOrderPicked={onOrderPicked}
+      />
+    ));
 
     const dropdownButton = screen.getByText(/Order/);
     userEvent.click(dropdownButton);
diff --git a/src/components/elements/gameData/skillAtk/sorter/main.tsx b/src/components/elements/gameData/skillAtk/sorter/main.tsx
index f45eee15..9c92cb46 100644
--- a/src/components/elements/gameData/skillAtk/sorter/main.tsx
+++ b/src/components/elements/gameData/skillAtk/sorter/main.tsx
@@ -4,28 +4,24 @@ import Dropdown from 'react-bootstrap/Dropdown';
 import DropdownButton from 'react-bootstrap/DropdownButton';
 
 import {useI18n} from '../../../../../i18n/hook';
-import {useAtkSkillInput} from '../hooks/preset';
 import {InputData, SortBy} from '../in/types';
 import {overwriteInputData} from '../in/utils/inputData';
 import {orderName} from './lookup';
 
 
 type Props = {
+  inputData: InputData,
   onOrderPicked: (newInputData: InputData) => void,
 }
 
-export const AttackingSkillSorter = ({onOrderPicked}: Props) => {
+export const AttackingSkillSorter = ({inputData, onOrderPicked}: Props) => {
   const {t} = useI18n();
 
-  const {inputData, setInputData} = useAtkSkillInput();
-
   const sortBy = t(orderName[inputData.sortBy]);
   const title = t((t) => t.game.skillAtk.sort.text, {sortBy});
 
   const onItemPicked = (sortBy: SortBy) => () => {
     const newInputData = overwriteInputData(inputData, {sortBy});
-    setInputData(newInputData);
-
     onOrderPicked(newInputData);
   };
 
diff --git a/src/components/elements/markdown/main.image.test.tsx b/src/components/elements/markdown/main.image.test.tsx
index a7d3ec6e..51b6c570 100644
--- a/src/components/elements/markdown/main.image.test.tsx
+++ b/src/components/elements/markdown/main.image.test.tsx
@@ -17,7 +17,7 @@ describe('Markdown (Image)', () => {
   });
 
   it('shows unit image as icon', async () => {
-    renderReact(() => <Markdown>{'![Alt](https://i.imgur.com/mtxtE5j.jpeg|unitIcon)'}</Markdown>);
+    renderReact(() => <Markdown>{'![Alt](https://i.imgur.com/mtxtE5j.jpeg[unitIcon])'}</Markdown>);
 
     const image = screen.getByAltText('Alt');
     expect(image).toHaveAttribute('src', 'https://i.imgur.com/mtxtE5j.jpeg');
diff --git a/src/components/elements/markdown/main.module.css b/src/components/elements/markdown/main.module.css
index c2c10b3b..550bab9a 100644
--- a/src/components/elements/markdown/main.module.css
+++ b/src/components/elements/markdown/main.module.css
@@ -100,6 +100,8 @@
 .mdBody ul,
 .mdBody dl {
   padding-inline-start: 25px;
+}
+.mdBody > ol, .mdBody > ul, .mdBody > dl {
   margin-bottom: 0.75rem;
 }
 .mdBody h1,
diff --git a/src/components/elements/markdown/main.module.css.map b/src/components/elements/markdown/main.module.css.map
index e8d0864f..bbfffff0 100644
--- a/src/components/elements/markdown/main.module.css.map
+++ b/src/components/elements/markdown/main.module.css.map
@@ -1 +1 @@
-{"version":3,"sourceRoot":"","sources":["main.module.scss","../../../../public/colors.scss"],"names":[],"mappings":"AAMA;EACE;EACA,kBCHe;EDIf;EACA;EACA;;AAEA;EAGE;EACA;EACA,eAdU;;AAgBV;EACE;EACA;;AAGF;EACE;;AAGF;AAAA;EAEE;EACA;EACA;;AAIA;EACE;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;IACE;;EAGF;IACE;IACA;IACA;;EAGF;IACE;IACA;;EAGF;IACE;;EAGF;IACE;IACA,eApEM;;EAuER;IACE;;;AAMJ;EACE,eA/EQ;;AAkFV;EACE;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAMJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA,OCvHe;EDwHf;;AAGF;EACE,OAlIW;;AAqIb;AAAA;AAAA;EAGE;EACA,eAvIU;;AA0IZ;AAAA;AAAA;EAGE;EACA;;AAGF;AAAA;AAAA;AAAA;AAAA;AAAA;EAME;EACA,eAxJU;EAyJV","file":"main.module.css"}
\ No newline at end of file
+{"version":3,"sourceRoot":"","sources":["main.module.scss","../../../../public/colors.scss"],"names":[],"mappings":"AAMA;EACE;EACA,kBCHe;EDIf;EACA;EACA;;AAEA;EAGE;EACA;EACA,eAdU;;AAgBV;EACE;EACA;;AAGF;EACE;;AAGF;AAAA;EAEE;EACA;EACA;;AAIA;EACE;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;IACE;;EAGF;IACE;IACA;IACA;;EAGF;IACE;IACA;;EAGF;IACE;;EAGF;IACE;IACA,eApEM;;EAuER;IACE;;;AAMJ;EACE,eA/EQ;;AAkFV;EACE;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAMJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA,OCvHe;EDwHf;;AAGF;EACE,OAlIW;;AAqIb;AAAA;AAAA;EAGE;;AAGF;EAGE,eA5IU;;AA+IZ;AAAA;AAAA;EAGE;EACA;;AAGF;AAAA;AAAA;AAAA;AAAA;AAAA;EAME;EACA,eA7JU;EA8JV","file":"main.module.css"}
\ No newline at end of file
diff --git a/src/components/elements/markdown/main.module.scss b/src/components/elements/markdown/main.module.scss
index e07b2f9d..dfd28b64 100644
--- a/src/components/elements/markdown/main.module.scss
+++ b/src/components/elements/markdown/main.module.scss
@@ -137,6 +137,11 @@ $item-margin: 0.75rem;
   ul,
   dl {
     padding-inline-start: 25px;
+  }
+
+  & > ol,
+  & > ul,
+  & > dl {
     margin-bottom: $item-margin;
   }
 
diff --git a/src/components/elements/markdown/transformers/image/const.ts b/src/components/elements/markdown/transformers/image/const.ts
index f673d431..f754de60 100644
--- a/src/components/elements/markdown/transformers/image/const.ts
+++ b/src/components/elements/markdown/transformers/image/const.ts
@@ -1 +1 @@
-export const IMAGE_CLASS_SPLITTER = '%7C'; // Escaped "|"
+export const IMAGE_REGEX = /(?<src>(?:(?!%5B).)+)(?:%5B(?<className>[\w ]+)%5D)?/; // %5B = [ | %5D = ]
diff --git a/src/components/elements/markdown/transformers/image/main.test.tsx b/src/components/elements/markdown/transformers/image/main.test.tsx
new file mode 100644
index 00000000..1d201987
--- /dev/null
+++ b/src/components/elements/markdown/transformers/image/main.test.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+
+import {screen} from '@testing-library/react';
+
+import {renderReact} from '../../../../../../test/render/main';
+import {ImageInHTML} from './main';
+
+
+describe('Image in HTML', () => {
+  it('renders image with specified class names', async () => {
+    renderReact(() => <ImageInHTML src="https://i.imgur.com/mtxtE5j.jpeg%5BclassName%5D" alt="alt"/>);
+
+    expect(screen.getByAltText('alt')).toHaveClass('className');
+  });
+
+  it('renders image if no class name', async () => {
+    renderReact(() => <ImageInHTML src="https://i.imgur.com/mtxtE5j.jpeg" alt="alt"/>);
+
+    expect(screen.getByAltText('alt')).toHaveAttribute('src', 'https://i.imgur.com/mtxtE5j.jpeg');
+  });
+});
diff --git a/src/components/elements/markdown/transformers/image/main.tsx b/src/components/elements/markdown/transformers/image/main.tsx
index 29282a7b..f4416642 100644
--- a/src/components/elements/markdown/transformers/image/main.tsx
+++ b/src/components/elements/markdown/transformers/image/main.tsx
@@ -6,7 +6,7 @@ import {useI18n} from '../../../../../i18n/hook';
 import {GoogleAnalytics} from '../../../../../utils/services/ga';
 import {Image} from '../../../common/image';
 import {CommonModal, ModalState} from '../../../common/modal';
-import {IMAGE_CLASS_SPLITTER} from './const';
+import {IMAGE_REGEX} from './const';
 import {ImageProps} from './types';
 
 
@@ -19,7 +19,7 @@ export const ImageInHTML = ({src, alt}: ImageProps) => {
     message: '',
   });
 
-  const [actualSrc, className] = src.split(IMAGE_CLASS_SPLITTER, 2);
+  const {src: actualSrc, className} = src.match(IMAGE_REGEX)?.groups || {};
 
   const openGifModal = () => {
     GoogleAnalytics.showGif(actualSrc);
diff --git a/src/components/elements/posts/analysis/output/dragonBody.tsx b/src/components/elements/posts/analysis/output/dragonBody.tsx
index 258a5278..5c2bfbf5 100644
--- a/src/components/elements/posts/analysis/output/dragonBody.tsx
+++ b/src/components/elements/posts/analysis/output/dragonBody.tsx
@@ -17,7 +17,7 @@ export const AnalysisOutputDragonBody = ({analysis}: SectionProps<DragonAnalysis
       <AdsInPost/>
       <h3 className="mb-3">{t((t) => t.posts.analysis.notesDragon)}</h3>
       <Markdown>{analysis.notes}</Markdown>
-      <h3 className="mb-3">{t((t) => t.posts.analysis.suitable)}</h3>
+      <h3 className="my-3">{t((t) => t.posts.analysis.suitable)}</h3>
       <Markdown>{analysis.suitableCharacters}</Markdown>
     </>
   );
diff --git a/src/components/elements/posts/analysis/output/top.tsx b/src/components/elements/posts/analysis/output/top.tsx
index 0c0161f6..a512ddc2 100644
--- a/src/components/elements/posts/analysis/output/top.tsx
+++ b/src/components/elements/posts/analysis/output/top.tsx
@@ -3,6 +3,7 @@ import React from 'react';
 import {AnalysisGetResponse} from '../../../../../api-def/api';
 import {useI18n} from '../../../../../i18n/hook';
 import {AdsInPost} from '../../../common/ads/main';
+import {OverlayPopover} from '../../../common/overlay/popover';
 import {Markdown} from '../../../markdown/main';
 import {SectionProps} from './props';
 
@@ -22,7 +23,13 @@ export const SectionTop = <R extends AnalysisGetResponse>({analysis}: SectionPro
         <>
           <hr/>
           <h3 className="mb-3">
-            {t((t) => t.posts.analysis.summonResult)}
+            {t((t) => t.posts.analysis.summonResult)}&nbsp;
+            <OverlayPopover
+              title={t((t) => t.posts.analysis.summonExplanation.title)}
+              content={t((t) => t.posts.analysis.summonExplanation.description)}
+            >
+              <i className="bi bi-info-circle"/>
+            </OverlayPopover>
           </h3>
           <Markdown>{analysis.summonResult}</Markdown>
         </>
diff --git a/src/components/elements/posts/quest/form/general.tsx b/src/components/elements/posts/quest/form/general.tsx
index 7a9995f2..d6745722 100644
--- a/src/components/elements/posts/quest/form/general.tsx
+++ b/src/components/elements/posts/quest/form/general.tsx
@@ -1,21 +1,22 @@
 import React from 'react';
 
 import Col from 'react-bootstrap/Col';
-import Row from 'react-bootstrap/Row';
+import Form from 'react-bootstrap/Form';
 
 import {QuestPostPublishPayload} from '../../../../../api-def/api';
 import {useI18n} from '../../../../../i18n/hook';
 import {MarkdownInput} from '../../../markdown/input';
 import {PostFormDataProps} from '../../shared/form/types';
 
+
 export const FormGeneralInfo = <P extends QuestPostPublishPayload>({formState, setPayload}: PostFormDataProps<P>) => {
   const {t} = useI18n();
 
   const {payload} = formState;
 
   return (
-    <Row>
-      <Col className="pr-2" lg={6}>
+    <Form.Row>
+      <Col className="mb-3 mb-lg-0" lg={6}>
         <h5>{t((t) => t.posts.quest.general)}</h5>
         <MarkdownInput
           onChanged={(e) => setPayload('general', e.target.value)}
@@ -23,13 +24,13 @@ export const FormGeneralInfo = <P extends QuestPostPublishPayload>({formState, s
           required
         />
       </Col>
-      <Col className="pl-2" lg={6}>
+      <Col lg={6}>
         <h5>{t((t) => t.posts.quest.video)}</h5>
         <MarkdownInput
           onChanged={(e) => setPayload('video', e.target.value)}
           rows={5} value={payload.video}
         />
       </Col>
-    </Row>
+    </Form.Row>
   );
 };
diff --git a/src/i18n/translations/cht/translation.ts b/src/i18n/translations/cht/translation.ts
index c4ed112f..d0bb0fe5 100644
--- a/src/i18n/translations/cht/translation.ts
+++ b/src/i18n/translations/cht/translation.ts
@@ -222,6 +222,8 @@ export const translation: TranslationStruct = {
           secPer1KSsp: '異常效期 (秒) / 1K SSP',
         },
         sp: 'SP',
+        spGradualFill: '{{secs}} 秒 ({{sp}})',
+        spPctPerSec: '每秒回復 SP %',
         ssp: 'SSP',
         ssCost: 'SS Cost',
       },
@@ -389,6 +391,12 @@ export const translation: TranslationStruct = {
       suitable: '適配角色',
       summary: '懶人包',
       summonResult: '個人抽抽結果',
+      summonExplanation: {
+        title: '關於這個區塊',
+        description: '有些人可能會好奇為何這個部分會出現在評測中。' +
+          '一剛開始我在寫評測的時候,我想讓我的觀眾知道幾抽就中只是純粹好運,需要幾十、幾百,甚至天井才有是很正常的事情。' +
+          '我從來沒有想過最後我會這麼認真寫評測,而且我也一樣想讓我們觀眾們知道前面提到關於抽卡的事情,所以我決定把這個習慣保留下來。',
+      },
       tipsBuilds: '要點 & 建議配置',
       ultimate: '大招',
       videos: '相關影片',
diff --git a/src/i18n/translations/definition.ts b/src/i18n/translations/definition.ts
index 0bf8036e..b960dab4 100644
--- a/src/i18n/translations/definition.ts
+++ b/src/i18n/translations/definition.ts
@@ -214,8 +214,10 @@ export type TranslationStruct = {
       },
       spInfo: {
         efficiencyIndexes: string,
-        efficiency: {[index in keyof Efficiency]: string},
+        efficiency: {[index in keyof Omit<Efficiency, 'spFullFillSec'>]: string},
         sp: string,
+        spGradualFill: string,
+        spPctPerSec: string,
         ssp: string,
         ssCost: string,
       },
@@ -317,6 +319,10 @@ export type TranslationStruct = {
       suitable: string,
       summary: string,
       summonResult: string,
+      summonExplanation: {
+        title: string,
+        description: string,
+      },
       tipsBuilds: string,
       ultimate: string,
       videos: string,
diff --git a/src/i18n/translations/en/translation.ts b/src/i18n/translations/en/translation.ts
index 3970c81e..6cda6bbb 100644
--- a/src/i18n/translations/en/translation.ts
+++ b/src/i18n/translations/en/translation.ts
@@ -246,6 +246,8 @@ export const translation: TranslationStruct = {
           secPer1KSsp: 'Affliction (sec) / 1K SSP',
         },
         sp: 'SP',
+        spGradualFill: '{{secs}} secs ({{sp}})',
+        spPctPerSec: 'SP Regen % / sec',
         ssp: 'SSP',
         ssCost: 'SS Cost',
       },
@@ -418,6 +420,16 @@ export const translation: TranslationStruct = {
       suitable: 'Suitable Characters',
       summary: 'Summary',
       summonResult: 'My Summoning Result',
+      summonExplanation: {
+        title: 'About this section',
+        description: 'Some people may wonder why this is listed in my analysis. ' +
+          'When I started writing analysis, ' +
+          'I wanted to let people know that getting lucky is pure luck; ' +
+          'getting unlucky is just a usual thing. ' +
+          'I never thought that I would end up dedicating writing analysis like this, ' +
+          'and I still want to let people know what was mentioned about luck, ' +
+          'so I decided to leave it instead of removing it.',
+      },
       tipsBuilds: 'Tips & Builds',
       ultimate: 'Ultimate',
       videos: 'Related Videos',
diff --git a/src/i18n/translations/jp/translation.ts b/src/i18n/translations/jp/translation.ts
index f3a4eccd..55619b30 100644
--- a/src/i18n/translations/jp/translation.ts
+++ b/src/i18n/translations/jp/translation.ts
@@ -221,6 +221,8 @@ export const translation: TranslationStruct = {
           secPer1KSsp: '異常效期 (秒) / 1K SSP',
         },
         sp: 'SP',
+        spGradualFill: '{{secs}} secs ({{sp}})',
+        spPctPerSec: 'SP Regen % / sec',
         ssp: 'SSP',
         ssCost: 'SS Cost',
       },
@@ -386,6 +388,16 @@ export const translation: TranslationStruct = {
       suitable: '相性良いキャラ',
       summary: '結論',
       summonResult: '個人のガチャ結果',
+      summonExplanation: {
+        title: 'About this section',
+        description: 'Some people may wonder why this is listed in my analysis. ' +
+          'When I started writing analysis, ' +
+          'I wanted to let people know that getting lucky is pure luck; ' +
+          'getting unlucky is just a usual thing. ' +
+          'I never thought that I would end up dedicating writing analysis like this, ' +
+          'and I still want to let people know what was mentioned about luck, ' +
+          'so I decided to leave it instead of removing it.',
+      },
       tipsBuilds: 'ポイント & おすすめ装備編成',
       ultimate: '必殺技',
       videos: '関する動画',
diff --git a/src/utils/game/damage.test.ts b/src/utils/game/damage.test.ts
index baff9e9d..941bfdf8 100644
--- a/src/utils/game/damage.test.ts
+++ b/src/utils/game/damage.test.ts
@@ -50,6 +50,9 @@ describe('Damage calculation', () => {
         },
         state: ConditionCodes.TARGET_STATE_BK,
       },
+      display: {
+        actualDamage: true,
+      },
     });
 
     const attackingSkillData = {
diff --git a/src/utils/game/damage.ts b/src/utils/game/damage.ts
index 97c2322f..57645f21 100644
--- a/src/utils/game/damage.ts
+++ b/src/utils/game/damage.ts
@@ -47,6 +47,11 @@ export const calculateDamage = (
     })
     .reduce((a, b) => a + b, 0);
 
+  // Omit numeric calculations if not to display
+  if (!inputData.display.actualDamage) {
+    return {lowest: 0, expected: 0, highest: 0, totalMods};
+  }
+
   let damage = 5 / 3; // Base damage
 
   // Damage from ATK
@@ -96,10 +101,5 @@ export const calculateDamage = (
   // Special - Bog
   damage *= inputData.target.afflictionCodes.includes(ConditionCodes.TARGET_BOGGED) ? 1.5 : 1;
 
-  return {
-    lowest: damage * 0.95,
-    expected: damage,
-    highest: damage * 1.05,
-    totalMods: totalMods,
-  };
+  return {lowest: damage * 0.95, expected: damage, highest: damage * 1.05, totalMods};
 };
diff --git a/src/utils/process/text.test.ts b/src/utils/process/text.test.tsx
similarity index 79%
rename from src/utils/process/text.test.ts
rename to src/utils/process/text.test.tsx
index de071f3d..4e1558ac 100644
--- a/src/utils/process/text.test.ts
+++ b/src/utils/process/text.test.tsx
@@ -1,6 +1,12 @@
+import React from 'react';
+
+import {screen} from '@testing-library/react';
+
 import {generateGalaMymInfo} from '../../../test/data/mock/unitInfo';
+import {renderReact} from '../../../test/render/main';
 import {SupportedLanguages} from '../../api-def/api';
-import {DepotPaths} from '../../api-def/resources/paths';
+import {DepotPaths} from '../../api-def/resources';
+import {Markdown} from '../../components/elements/markdown/main';
 import {PostPath} from '../../const/path/definitions';
 import {translations} from '../../i18n/translations/main';
 import {makePostPath} from '../path/make';
@@ -12,7 +18,7 @@ describe('Process text', () => {
   const lang = SupportedLanguages.EN;
 
   const galaMymAnalysisLink = `[Gala Mym](${makePostPath(PostPath.ANALYSIS, {pid: 10550101, lang})})`;
-  const galaMymImageMd = `![Gala Mym](${DepotPaths.getCharaIconURL('100010_04_r05')}|unitIcon)`;
+  const galaMymImageMd = `![Gala Mym](${DepotPaths.getCharaIconURL('100010_04_r05')}[unitIcon])`;
   const galaMymMdTransformed = `${galaMymImageMd}${galaMymAnalysisLink}`;
 
   beforeEach(() => {
@@ -57,4 +63,14 @@ describe('Process text', () => {
 
     expect(result).toBe(text);
   });
+
+  it('renders correctly for unit icon in table cell', async () => {
+    const text = 'head | col 2\n:---: | :---:\n:Gala Mym: | Y';
+
+    const result = await processText({text, lang});
+
+    renderReact(() => <Markdown>{result}</Markdown>);
+
+    expect(screen.getByAltText('Gala Mym')).toBeInTheDocument();
+  });
 });
diff --git a/src/utils/process/transformers/quickReference.test.ts b/src/utils/process/transformers/quickReference.test.ts
index a4c6b838..15a7fe70 100644
--- a/src/utils/process/transformers/quickReference.test.ts
+++ b/src/utils/process/transformers/quickReference.test.ts
@@ -15,7 +15,7 @@ import {transformQuickReference} from './quickReference';
 const lang = SupportedLanguages.EN;
 
 const galaMymAnalysisLink = `[Gala Mym](${makePostPath(PostPath.ANALYSIS, {pid: 10550101, lang})})`;
-const galaMymImageMd = `![Gala Mym](${DepotPaths.getCharaIconURL('100010_04_r05')}|unitIcon)`;
+const galaMymImageMd = `![Gala Mym](${DepotPaths.getCharaIconURL('100010_04_r05')}[unitIcon])`;
 const galaMymMdTransformed = `${galaMymImageMd}${galaMymAnalysisLink}`;
 
 describe('Quick reference transformer (Quest/Misc/Mixed)', () => {
@@ -148,7 +148,7 @@ describe('Quick reference transformer (Analysis)', () => {
 
   it('matches greedily', async () => {
     const brunhildaExtAnalysisLink = `[BrunhildaExtended](${makePostPath(PostPath.ANALYSIS, {pid: 20050102, lang})})`;
-    const brunhildaExtImageMd = `![BrunhildaExtended](${DepotPaths.getDragonIconURL('210039_01')}|unitIcon)`;
+    const brunhildaExtImageMd = `![BrunhildaExtended](${DepotPaths.getDragonIconURL('210039_01')}[unitIcon])`;
     const brunhildaExtMdTransformed = `${brunhildaExtImageMd}${brunhildaExtAnalysisLink}`;
 
     const text = ':BrunhildaExtended:';
diff --git a/src/utils/process/transformers/quickReference.ts b/src/utils/process/transformers/quickReference.ts
index e3a180d1..d9b848cb 100644
--- a/src/utils/process/transformers/quickReference.ts
+++ b/src/utils/process/transformers/quickReference.ts
@@ -39,7 +39,7 @@ const transformAnalysis: TextTransformer = async ({text, lang}) => {
       }
 
       const postPath = makePostPath(PostPath.ANALYSIS, {pid: unitInfo.id, lang});
-      const imageMd = `![${unitName}](${getImageURL(unitInfo)}|unitIcon)`;
+      const imageMd = `![${unitName}](${getImageURL(unitInfo)}[unitIcon])`;
 
       leftRemainder = leftRemainder.replace(/:/g, '');
       rightRemainder = rightRemainder.replace(/:/g, '');
diff --git a/test/data/mock/skill.ts b/test/data/mock/skill.ts
index 764b1e6f..b3ee7b22 100644
--- a/test/data/mock/skill.ts
+++ b/test/data/mock/skill.ts
@@ -48,6 +48,7 @@ export const generateAttackingSkillEntry = (): AttackingSkillData => ({
     },
     sharable: false,
     spMax: 9999,
+    spGradualPctMax: 0,
     ssCost: 5,
     ssSp: 17777,
     hitTimingSecMax: [],
@@ -75,5 +76,6 @@ export const generateCalculatedEntry = (): CalculatedSkillEntry => ({
       3: 3.5757575757,
       4: 6.0131313131,
     },
+    spFullFillSec: 0,
   },
 });
diff --git a/test/data/resources b/test/data/resources
index 529c6514..58227f5f 160000
--- a/test/data/resources
+++ b/test/data/resources
@@ -1 +1 @@
-Subproject commit 529c65142d2d569c88671f4f44d985abd5547af2
+Subproject commit 58227f5f342107b6d99eb160a3c824bce68521ee